blob: d6596974c32009ac00854324848b9f95d7d7a285 [file] [log] [blame]
// Copyright (c) 2019, 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.
import 'package:_fe_analyzer_shared/src/scanner/token.dart' show Token;
import 'package:kernel/ast.dart';
import 'package:kernel/core_types.dart';
import '../constant_context.dart' show ConstantContext;
import '../dill/dill_member_builder.dart';
import '../kernel/body_builder.dart' show BodyBuilder;
import '../kernel/class_hierarchy_builder.dart' show ClassMember;
import '../kernel/constructor_tearoff_lowering.dart';
import '../kernel/expression_generator_helper.dart'
show ExpressionGeneratorHelper;
import '../kernel/kernel_builder.dart'
show isRedirectingGenerativeConstructorImplementation;
import '../kernel/kernel_target.dart' show ClonedFunctionNode;
import '../loader.dart' show Loader;
import '../messages.dart'
show
Message,
messageMoreThanOneSuperInitializer,
messageRedirectingConstructorWithAnotherInitializer,
messageRedirectingConstructorWithMultipleRedirectInitializers,
messageRedirectingConstructorWithSuperInitializer,
messageSuperInitializerNotLast,
noLength;
import '../source/source_class_builder.dart';
import '../source/source_library_builder.dart' show SourceLibraryBuilder;
import '../type_inference/type_schema.dart';
import '../util/helpers.dart' show DelayedActionPerformer;
import 'builder.dart';
import 'field_builder.dart';
import 'formal_parameter_builder.dart';
import 'function_builder.dart';
import 'member_builder.dart';
import 'metadata_builder.dart';
import 'type_builder.dart';
import 'type_variable_builder.dart';
abstract class ConstructorBuilder implements FunctionBuilder {
abstract Token? beginInitializers;
@override
ConstructorBuilder? get actualOrigin;
ConstructorBuilder? get patchForTesting;
Constructor get actualConstructor;
@override
ConstructorBuilder get origin;
bool get isRedirectingGenerativeConstructor;
/// The [Constructor] built by this builder.
Constructor get constructor;
void injectInvalidInitializer(Message message, int charOffset, int length,
ExpressionGeneratorHelper helper);
void addInitializer(
Initializer initializer, ExpressionGeneratorHelper helper);
void prepareInitializers();
/// Infers the types of any untyped initializing formals.
void inferFormalTypes();
/// Registers field as being initialized by this constructor.
///
/// The field can be initialized either via an initializing formal or via an
/// entry in the constructor initializer list.
void registerInitializedField(FieldBuilder fieldBuilder);
/// Returns the fields registered as initialized by this constructor.
///
/// Returns the set of fields previously registered via
/// [registerInitializedField] and passes on the ownership of the collection
/// to the caller.
Set<FieldBuilder>? takeInitializedFields();
}
class ConstructorBuilderImpl extends FunctionBuilderImpl
implements ConstructorBuilder {
final Constructor _constructor;
final Procedure? _constructorTearOff;
Set<FieldBuilder>? _initializedFields;
final int charOpenParenOffset;
bool hasMovedSuperInitializer = false;
SuperInitializer? superInitializer;
RedirectingInitializer? redirectingInitializer;
@override
Token? beginInitializers;
@override
ConstructorBuilder? actualOrigin;
@override
Constructor get actualConstructor => _constructor;
ConstructorBuilderImpl(
List<MetadataBuilder>? metadata,
int modifiers,
TypeBuilder? returnType,
String name,
List<TypeVariableBuilder>? typeVariables,
List<FormalParameterBuilder>? formals,
SourceLibraryBuilder compilationUnit,
int startCharOffset,
int charOffset,
this.charOpenParenOffset,
int charEndOffset,
Member? referenceFrom,
{String? nativeMethodName,
required bool forAbstractClassOrEnum})
: _constructor = new Constructor(new FunctionNode(null),
name: new Name(name, compilationUnit.library),
fileUri: compilationUnit.fileUri,
reference: referenceFrom?.reference)
..startFileOffset = startCharOffset
..fileOffset = charOffset
..fileEndOffset = charEndOffset
..isNonNullableByDefault = compilationUnit.isNonNullableByDefault,
_constructorTearOff = createConstructorTearOffProcedure(
name, compilationUnit, charOffset,
forAbstractClassOrEnum: forAbstractClassOrEnum),
super(metadata, modifiers, returnType, name, typeVariables, formals,
compilationUnit, charOffset, nativeMethodName);
SourceLibraryBuilder get library => super.library as SourceLibraryBuilder;
@override
Member? get readTarget => _constructorTearOff ?? _constructor;
@override
Member? get writeTarget => null;
@override
Member get invokeTarget => constructor;
FunctionNode get function => _constructor.function;
@override
Iterable<Member> get exportedMembers => [constructor];
@override
ConstructorBuilder get origin => actualOrigin ?? this;
@override
ConstructorBuilder? get patchForTesting =>
dataForTesting?.patchForTesting as ConstructorBuilder?;
@override
bool get isDeclarationInstanceMember => false;
@override
bool get isClassInstanceMember => false;
@override
bool get isConstructor => true;
@override
AsyncMarker get asyncModifier => AsyncMarker.Sync;
@override
ProcedureKind? get kind => null;
@override
bool get isRedirectingGenerativeConstructor {
return isRedirectingGenerativeConstructorImplementation(_constructor);
}
@override
void buildMembers(
SourceLibraryBuilder library, void Function(Member, BuiltMemberKind) f) {
Member member = build(library);
f(member, BuiltMemberKind.Constructor);
if (_constructorTearOff != null) {
f(_constructorTearOff!, BuiltMemberKind.Method);
}
}
bool _hasBeenBuilt = false;
@override
Constructor build(SourceLibraryBuilder libraryBuilder) {
if (!_hasBeenBuilt) {
buildFunction(libraryBuilder);
_constructor.function.fileOffset = charOpenParenOffset;
_constructor.function.fileEndOffset = _constructor.fileEndOffset;
_constructor.function.typeParameters = const <TypeParameter>[];
_constructor.isConst = isConst;
_constructor.isExternal = isExternal;
updatePrivateMemberName(_constructor, libraryBuilder);
if (_constructorTearOff != null) {
buildConstructorTearOffProcedure(_constructorTearOff!, _constructor,
classBuilder!.cls, libraryBuilder);
}
_hasBeenBuilt = true;
}
if (formals != null) {
bool needsInference = false;
for (FormalParameterBuilder formal in formals!) {
if (formal.type == null && formal.isInitializingFormal) {
formal.variable!.type = const UnknownType();
needsInference = true;
}
}
if (needsInference) {
assert(
library == libraryBuilder,
"Unexpected library builder ${libraryBuilder} for"
" constructor $this in ${library}.");
libraryBuilder.loader.typeInferenceEngine.toBeInferred[_constructor] =
this;
}
}
return _constructor;
}
@override
void inferFormalTypes() {
if (formals != null) {
for (FormalParameterBuilder formal in formals!) {
if (formal.type == null && formal.isInitializingFormal) {
formal.finalizeInitializingFormal(classBuilder!);
}
}
}
}
@override
void buildOutlineExpressions(
SourceLibraryBuilder library,
CoreTypes coreTypes,
List<DelayedActionPerformer> delayedActionPerformers) {
super.buildOutlineExpressions(library, coreTypes, delayedActionPerformers);
// For modular compilation purposes we need to include initializers
// for const constructors into the outline.
if (isConst && beginInitializers != null) {
BodyBuilder bodyBuilder = library.loader
.createBodyBuilderForOutlineExpression(
library, classBuilder!, this, classBuilder!.scope, fileUri);
bodyBuilder.constantContext = ConstantContext.required;
bodyBuilder.parseInitializers(beginInitializers!);
bodyBuilder.resolveRedirectingFactoryTargets();
}
if (_constructorTearOff != null) {
buildConstructorTearOffOutline(
_constructorTearOff!, constructor, classBuilder!.cls);
}
beginInitializers = null;
}
@override
void buildFunction(SourceLibraryBuilder library) {
// According to the specification §9.3 the return type of a constructor
// function is its enclosing class.
super.buildFunction(library);
Class enclosingClass = classBuilder!.cls;
List<DartType> typeParameterTypes = <DartType>[];
for (int i = 0; i < enclosingClass.typeParameters.length; i++) {
TypeParameter typeParameter = enclosingClass.typeParameters[i];
typeParameterTypes.add(
new TypeParameterType.withDefaultNullabilityForLibrary(
typeParameter, library.library));
}
function.returnType = new InterfaceType(
enclosingClass, library.nonNullable, typeParameterTypes);
}
@override
Constructor get constructor => isPatch ? origin.constructor : _constructor;
@override
Member get member => constructor;
@override
void injectInvalidInitializer(Message message, int charOffset, int length,
ExpressionGeneratorHelper helper) {
List<Initializer> initializers = _constructor.initializers;
Initializer lastInitializer = initializers.removeLast();
assert(lastInitializer == superInitializer ||
lastInitializer == redirectingInitializer);
Initializer error = helper.buildInvalidInitializer(
helper.buildProblem(message, charOffset, length));
initializers.add(error..parent = _constructor);
initializers.add(lastInitializer);
}
@override
void addInitializer(
Initializer initializer, ExpressionGeneratorHelper helper) {
List<Initializer> initializers = _constructor.initializers;
if (initializer is SuperInitializer) {
if (superInitializer != null) {
injectInvalidInitializer(messageMoreThanOneSuperInitializer,
initializer.fileOffset, "super".length, helper);
} else if (redirectingInitializer != null) {
injectInvalidInitializer(
messageRedirectingConstructorWithSuperInitializer,
initializer.fileOffset,
"super".length,
helper);
} else {
initializers.add(initializer..parent = _constructor);
superInitializer = initializer;
}
} else if (initializer is RedirectingInitializer) {
if (superInitializer != null) {
// Point to the existing super initializer.
injectInvalidInitializer(
messageRedirectingConstructorWithSuperInitializer,
superInitializer!.fileOffset,
"super".length,
helper);
} else if (redirectingInitializer != null) {
injectInvalidInitializer(
messageRedirectingConstructorWithMultipleRedirectInitializers,
initializer.fileOffset,
noLength,
helper);
} else if (initializers.isNotEmpty) {
// Error on all previous ones.
for (int i = 0; i < initializers.length; i++) {
Initializer initializer = initializers[i];
int length = noLength;
if (initializer is AssertInitializer) length = "assert".length;
Initializer error = helper.buildInvalidInitializer(
helper.buildProblem(
messageRedirectingConstructorWithAnotherInitializer,
initializer.fileOffset,
length));
error.parent = _constructor;
initializers[i] = error;
}
initializers.add(initializer..parent = _constructor);
redirectingInitializer = initializer;
} else {
initializers.add(initializer..parent = _constructor);
redirectingInitializer = initializer;
}
} else if (redirectingInitializer != null) {
int length = noLength;
if (initializer is AssertInitializer) length = "assert".length;
injectInvalidInitializer(
messageRedirectingConstructorWithAnotherInitializer,
initializer.fileOffset,
length,
helper);
} else if (superInitializer != null) {
injectInvalidInitializer(messageSuperInitializerNotLast,
initializer.fileOffset, noLength, helper);
} else {
initializers.add(initializer..parent = _constructor);
}
}
@override
VariableDeclaration? getTearOffParameter(int index) {
if (_constructorTearOff != null) {
if (index < _constructorTearOff!.function.positionalParameters.length) {
return _constructorTearOff!.function.positionalParameters[index];
} else {
index -= _constructorTearOff!.function.positionalParameters.length;
if (index < _constructorTearOff!.function.namedParameters.length) {
return _constructorTearOff!.function.namedParameters[index];
}
}
}
return null;
}
@override
int finishPatch() {
if (!isPatch) return 0;
// TODO(ahe): restore file-offset once we track both origin and patch file
// URIs. See https://github.com/dart-lang/sdk/issues/31579
origin.constructor.fileUri = fileUri;
origin.constructor.startFileOffset = _constructor.startFileOffset;
origin.constructor.fileOffset = _constructor.fileOffset;
origin.constructor.fileEndOffset = _constructor.fileEndOffset;
origin.constructor.annotations
.forEach((m) => m.fileOffset = _constructor.fileOffset);
origin.constructor.isExternal = _constructor.isExternal;
origin.constructor.function = _constructor.function;
origin.constructor.function.parent = origin.constructor;
origin.constructor.initializers = _constructor.initializers;
setParents(origin.constructor.initializers, origin.constructor);
return 1;
}
@override
void becomeNative(Loader loader) {
_constructor.isExternal = true;
super.becomeNative(loader);
}
@override
void applyPatch(Builder patch) {
if (patch is ConstructorBuilderImpl) {
if (checkPatch(patch)) {
patch.actualOrigin = this;
dataForTesting?.patchForTesting = patch;
}
} else {
reportPatchMismatch(patch);
}
}
@override
void prepareInitializers() {
// For const constructors we parse initializers already at the outlining
// stage, there is no easy way to make body building stage skip initializer
// parsing, so we simply clear parsed initializers and rebuild them
// again.
// For when doing an experimental incremental compilation they are also
// potentially done more than once (because it rebuilds the bodies of an old
// compile), and so we also clear them.
// Note: this method clears both initializers from the target Kernel node
// and internal state associated with parsing initializers.
_constructor.initializers.length = 0;
redirectingInitializer = null;
superInitializer = null;
hasMovedSuperInitializer = false;
}
@override
List<ClassMember> get localMembers =>
throw new UnsupportedError('${runtimeType}.localMembers');
@override
List<ClassMember> get localSetters =>
throw new UnsupportedError('${runtimeType}.localSetters');
@override
void registerInitializedField(FieldBuilder fieldBuilder) {
(_initializedFields ??= {}).add(fieldBuilder);
}
@override
Set<FieldBuilder>? takeInitializedFields() {
Set<FieldBuilder>? result = _initializedFields;
_initializedFields = null;
return result;
}
}
class SyntheticConstructorBuilder extends DillConstructorBuilder {
MemberBuilderImpl? _origin;
ClonedFunctionNode? _clonedFunctionNode;
SyntheticConstructorBuilder(SourceClassBuilder parent,
Constructor constructor, Procedure? constructorTearOff,
{MemberBuilderImpl? origin, ClonedFunctionNode? clonedFunctionNode})
: _origin = origin,
_clonedFunctionNode = clonedFunctionNode,
super(constructor, constructorTearOff, parent);
@override
void buildOutlineExpressions(
SourceLibraryBuilder libraryBuilder,
CoreTypes coreTypes,
List<DelayedActionPerformer> delayedActionPerformers) {
if (_origin != null) {
// Ensure that default value expressions have been created for [_origin].
_origin!.buildOutlineExpressions(
libraryBuilder, coreTypes, delayedActionPerformers);
_clonedFunctionNode!.cloneDefaultValues();
_clonedFunctionNode = null;
_origin = null;
}
}
}