blob: 5b9f2a0f0e3d83c32e05912aa8618e247e40161e [file] [log] [blame]
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
part of 'fragment.dart';
class FieldFragment
with FieldDeclarationMixin
implements Fragment, FieldDeclaration, Inferable, InferredTypeListener {
@override
final String name;
@override
final Uri fileUri;
@override
final int nameOffset;
final int endOffset;
Token? _initializerToken;
Token? _constInitializerToken;
@override
final List<MetadataBuilder>? metadata;
@override
final TypeBuilder type;
final bool isTopLevel;
final Modifiers modifiers;
// TODO(johnniwinther): Create separate fragment for primary constructor
// fields.
final bool isPrimaryConstructorField;
SourcePropertyBuilder? _builder;
late final _FieldEncoding _encoding;
FieldFragment(
{required this.name,
required this.fileUri,
required this.nameOffset,
required this.endOffset,
required Token? initializerToken,
required Token? constInitializerToken,
required this.metadata,
required this.type,
required this.isTopLevel,
required this.modifiers,
required this.isPrimaryConstructorField})
: _initializerToken = initializerToken,
_constInitializerToken = constInitializerToken;
@override
bool get hasSetter {
if (modifiers.isConst) {
return false;
} else if (modifiers.isFinal) {
if (modifiers.isLate) {
return !modifiers.hasInitializer;
} else {
return false;
}
} else {
return true;
}
}
/// Returns the token for the initializer of this field, if any.
///
/// This can only be called once and will hand over the responsibility of
/// the token to the caller.
Token? get initializerToken {
Token? result = _initializerToken;
// Ensure that we don't hold onto the token.
_initializerToken = null;
return result;
}
/// Returns the token for the initializer of this field, if any. This is the
/// same as [initializerToken] but is used to signal that the initializer
/// needs to be computed for outline expressions.
///
/// This can only be called once and will hand over the responsibility of
/// the token to the caller.
Token? get constInitializerToken {
Token? result = _constInitializerToken;
// Ensure that we don't hold onto the token.
_constInitializerToken = null;
return result;
}
@override
SourcePropertyBuilder get builder {
assert(_builder != null, "Builder has not been computed for $this.");
return _builder!;
}
void set builder(SourcePropertyBuilder value) {
assert(_builder == null, "Builder has already been computed for $this.");
_builder = value;
SourceLibraryBuilder libraryBuilder = builder.libraryBuilder;
bool isAbstract = modifiers.isAbstract;
bool isExternal = modifiers.isExternal;
bool isInstanceMember = builder.isDeclarationInstanceMember;
bool isExtensionMember = builder.isExtensionMember;
bool isExtensionTypeMember = builder.isExtensionTypeMember;
// If in mixed mode, late lowerings cannot use `null` as a sentinel on
// non-nullable fields since they can be assigned from legacy code.
late_lowering.IsSetStrategy isSetStrategy =
late_lowering.computeIsSetStrategy(libraryBuilder);
if (isAbstract || isExternal) {
_encoding = new AbstractOrExternalFieldEncoding(this,
isExtensionInstanceMember: isExtensionMember && isInstanceMember,
isExtensionTypeInstanceMember:
isExtensionTypeMember && isInstanceMember,
isAbstract: isAbstract,
isExternal: isExternal);
} else if (isExtensionTypeMember && isInstanceMember) {
if (isPrimaryConstructorField) {
_encoding = new RepresentationFieldEncoding(this);
} else {
// Field on a extension type. Encode as abstract.
// TODO(johnniwinther): Should we have an erroneous flag on such
// members?
_encoding = new AbstractOrExternalFieldEncoding(this,
isExtensionInstanceMember: isExtensionMember && isInstanceMember,
isExtensionTypeInstanceMember:
isExtensionTypeMember && isInstanceMember,
isAbstract: true,
isExternal: false,
isForcedExtension: true);
}
} else if (isLate &&
libraryBuilder.loader.target.backendTarget.isLateFieldLoweringEnabled(
hasInitializer: hasInitializer,
isFinal: isFinal,
isStatic: !isInstanceMember)) {
if (hasInitializer) {
if (isFinal) {
_encoding = new LateFinalFieldWithInitializerEncoding(this,
isSetStrategy: isSetStrategy);
} else {
_encoding = new LateFieldWithInitializerEncoding(this,
isSetStrategy: isSetStrategy);
}
} else {
if (isFinal) {
_encoding = new LateFinalFieldWithoutInitializerEncoding(this,
isSetStrategy: isSetStrategy);
} else {
_encoding = new LateFieldWithoutInitializerEncoding(this,
isSetStrategy: isSetStrategy);
}
}
} else if (libraryBuilder
.loader.target.backendTarget.useStaticFieldLowering &&
!isInstanceMember &&
!modifiers.isConst &&
hasInitializer) {
if (isFinal) {
_encoding = new LateFinalFieldWithInitializerEncoding(this,
isSetStrategy: isSetStrategy);
} else {
_encoding = new LateFieldWithInitializerEncoding(this,
isSetStrategy: isSetStrategy);
}
} else {
_encoding = new RegularFieldEncoding(this, isEnumElement: false);
}
type.registerInferredTypeListener(this);
Token? token = initializerToken;
if (type is InferableTypeBuilder) {
if (!modifiers.hasInitializer && _isStatic) {
// A static field without type and initializer will always be inferred
// to have type `dynamic`.
type.registerInferredType(const DynamicType());
} else {
// A field with no type and initializer or an instance field without
// type and initializer need to have the type inferred.
_encoding.type = new InferredType(
libraryBuilder: libraryBuilder,
typeBuilder: type,
inferType: inferType,
computeType: _computeInferredType,
fileUri: fileUri,
name: name,
nameOffset: nameOffset,
nameLength: name.length,
token: token);
type.registerInferable(this);
}
}
}
DartType _computeInferredType(
ClassHierarchyBase classHierarchy, Token? token) {
DartType? inferredType;
SourceLibraryBuilder libraryBuilder = builder.libraryBuilder;
DeclarationBuilder? declarationBuilder = builder.declarationBuilder;
if (token != null) {
InterfaceType? enclosingClassThisType = declarationBuilder
is SourceClassBuilder
? libraryBuilder.loader.typeInferenceEngine.coreTypes
.thisInterfaceType(
declarationBuilder.cls, libraryBuilder.library.nonNullable)
: null;
TypeInferrer typeInferrer =
libraryBuilder.loader.typeInferenceEngine.createTopLevelTypeInferrer(
fileUri,
enclosingClassThisType,
libraryBuilder,
builder
.dataForTesting
// Coverage-ignore(suite): Not run.
?.inferenceData);
BodyBuilderContext bodyBuilderContext = createBodyBuilderContext();
BodyBuilder bodyBuilder = libraryBuilder.loader.createBodyBuilderForField(
libraryBuilder,
bodyBuilderContext,
declarationBuilder?.scope ?? libraryBuilder.scope,
typeInferrer,
fileUri);
bodyBuilder.constantContext =
modifiers.isConst ? ConstantContext.inferred : ConstantContext.none;
bodyBuilder.inFieldInitializer = true;
bodyBuilder.inLateFieldInitializer = modifiers.isLate;
Expression initializer = bodyBuilder.parseFieldInitializer(token);
inferredType =
typeInferrer.inferImplicitFieldType(bodyBuilder, initializer);
} else {
inferredType = const DynamicType();
}
return inferredType;
}
@override
bool get isEnumElement => false;
BodyBuilderContext createBodyBuilderContext() {
return new _FieldFragmentBodyBuilderContext(
this, builder.libraryBuilder, builder.declarationBuilder,
isDeclarationInstanceMember: builder.isDeclarationInstanceMember);
}
/// Registers that a `super` call has occurred in the initializer of this
/// field.
void registerSuperCall() {
_encoding.registerSuperCall();
}
@override
void buildOutlineNode(SourceLibraryBuilder libraryBuilder,
NameScheme nameScheme, BuildNodesCallback f, FieldReference references,
{required List<TypeParameter>? classTypeParameters}) {
_encoding.buildOutlineNode(libraryBuilder, nameScheme, references,
isAbstractOrExternal: modifiers.isAbstract || modifiers.isExternal,
classTypeParameters: classTypeParameters);
if (type is! InferableTypeBuilder) {
fieldType = type.build(libraryBuilder, TypeUse.fieldType);
}
_encoding.registerMembers(f);
}
@override
Iterable<Reference> getExportedMemberReferences(FieldReference references) {
return [
references.getterReference!,
if (hasSetter) references.setterReference!
];
}
shared.Expression? _initializerExpression;
@override
// Coverage-ignore(suite): Not run.
shared.Expression? get initializerExpression => _initializerExpression;
@override
void buildOutlineExpressions(
ClassHierarchy classHierarchy,
SourceLibraryBuilder libraryBuilder,
DeclarationBuilder? declarationBuilder,
LookupScope parentScope,
List<Annotatable> annotatables,
{required bool isClassInstanceMember,
required bool createFileUriExpression}) {
BodyBuilderContext bodyBuilderContext = createBodyBuilderContext();
for (Annotatable annotatable in annotatables) {
_buildMetadataForOutlineExpressions(libraryBuilder, parentScope,
bodyBuilderContext, annotatable, metadata,
fileUri: fileUri, createFileUriExpression: createFileUriExpression);
}
// For modular compilation we need to include initializers of all const
// fields and all non-static final fields in classes with const constructors
// into the outline.
Token? token = constInitializerToken;
if ((modifiers.isConst ||
(isFinal &&
isClassInstanceMember &&
(declarationBuilder as SourceClassBuilder)
.declaresConstConstructor)) &&
token != null) {
LookupScope scope = declarationBuilder?.scope ?? libraryBuilder.scope;
BodyBuilder bodyBuilder = libraryBuilder.loader
.createBodyBuilderForOutlineExpression(
libraryBuilder, createBodyBuilderContext(), scope, fileUri);
bodyBuilder.constantContext = modifiers.isConst
? ConstantContext.inferred
: ConstantContext.required;
Expression initializer = bodyBuilder.typeInferrer
.inferFieldInitializer(
bodyBuilder, fieldType, bodyBuilder.parseFieldInitializer(token))
.expression;
buildBody(classHierarchy.coreTypes, initializer);
bodyBuilder.performBacklogComputations();
if (computeSharedExpressionForTesting) {
// Coverage-ignore-block(suite): Not run.
_initializerExpression = parseFieldInitializer(libraryBuilder.loader,
token, libraryBuilder.importUri, fileUri, scope);
}
}
}
@override
void checkTypes(SourceLibraryBuilder libraryBuilder,
TypeEnvironment typeEnvironment, SourcePropertyBuilder? setterBuilder,
{required bool isAbstract, required bool isExternal}) {
libraryBuilder.checkTypesInField(typeEnvironment,
isInstanceMember: builder.isDeclarationInstanceMember,
isLate: isLate,
isExternal: isExternal,
hasInitializer: hasInitializer,
fieldType: fieldType,
name: name,
nameLength: name.length,
nameOffset: nameOffset,
fileUri: fileUri);
}
@override
void ensureTypes(
ClassMembersBuilder membersBuilder,
Set<ClassMember>? getterOverrideDependencies,
Set<ClassMember>? setterOverrideDependencies) {
if (getterOverrideDependencies != null ||
setterOverrideDependencies != null) {
membersBuilder.inferFieldType(
builder.declarationBuilder as SourceClassBuilder,
type,
[...?getterOverrideDependencies, ...?setterOverrideDependencies],
name: name,
fileUri: fileUri,
nameOffset: nameOffset,
nameLength: name.length,
isAssignable: hasSetter);
} else {
type.build(builder.libraryBuilder, TypeUse.fieldType,
hierarchy: membersBuilder.hierarchyBuilder);
}
}
@override
void checkVariance(
SourceClassBuilder sourceClassBuilder, TypeEnvironment typeEnvironment) {
sourceClassBuilder.checkVarianceInField(typeEnvironment,
fieldType: fieldType,
isInstanceMember: !_isStatic,
hasSetter: hasSetter,
isCovariantByDeclaration: modifiers.isCovariant,
fileUri: fileUri,
fileOffset: nameOffset);
}
@override
int computeDefaultTypes(ComputeDefaultTypeContext context) {
if (type is! OmittedTypeBuilder) {
context.reportInboundReferenceIssuesForType(type);
context.recursivelyReportGenericFunctionTypesAsBoundsForType(type);
}
return 0;
}
@override
Member get readTarget => _encoding.readTarget;
@override
Member? get writeTarget => _encoding.writeTarget;
/// Whether the body of this field has been built.
///
/// Constant fields have their initializer built in the outline so we avoid
/// building them twice as part of the non-outline build.
bool hasBodyBeenBuilt = false;
/// Builds the body of this field using [initializer] as the initializer
/// expression.
void buildBody(CoreTypes coreTypes, Expression? initializer) {
assert(!hasBodyBeenBuilt, "Body has already been built for $this.");
hasBodyBeenBuilt = true;
if (!modifiers.hasInitializer &&
initializer != null &&
initializer is! NullLiteral &&
// Coverage-ignore(suite): Not run.
!modifiers.isConst &&
// Coverage-ignore(suite): Not run.
!modifiers.isFinal) {
internalProblem(
messageInternalProblemAlreadyInitialized, nameOffset, fileUri);
}
_encoding.createBodies(coreTypes, initializer);
}
@override
DartType get fieldType => _encoding.type;
@override
// Coverage-ignore(suite): Not run.
DartType get _fieldTypeInternal => _encoding.type;
@override
void set _fieldTypeInternal(DartType value) {
_encoding.type = value;
}
@override
void _setCovariantByClassInternal() {
_encoding.setCovariantByClass();
}
@override
Initializer buildErroneousInitializer(Expression effect, Expression value,
{required int fileOffset}) {
return _encoding.buildErroneousInitializer(effect, value,
fileOffset: fileOffset);
}
@override
void buildImplicitDefaultValue() {
_encoding.buildImplicitDefaultValue();
}
@override
Initializer buildImplicitInitializer() {
return _encoding.buildImplicitInitializer();
}
@override
List<Initializer> buildInitializer(int fileOffset, Expression value,
{required bool isSynthetic}) {
return _encoding.createInitializer(fileOffset, value,
isSynthetic: isSynthetic);
}
@override
bool get hasInitializer => modifiers.hasInitializer;
@override
bool get isExtensionTypeDeclaredInstanceField =>
builder.isExtensionTypeInstanceMember && !isPrimaryConstructorField;
@override
bool get isFinal => modifiers.isFinal;
@override
bool get isConst => modifiers.isConst;
@override
bool get isLate => modifiers.isLate;
bool get _isStatic =>
modifiers.isStatic || builder.declarationBuilder == null;
@override
String toString() => '$runtimeType($name,$fileUri,$nameOffset)';
@override
List<ClassMember> get localMembers => _encoding.localMembers;
@override
List<ClassMember> get localSetters => _encoding.localSetters;
}