blob: 6675afe0e1af25e129bb2caf267c629e8689ee8c [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:kernel/ast.dart';
import 'package:kernel/class_hierarchy.dart';
import 'package:kernel/type_algebra.dart';
import 'package:kernel/type_environment.dart';
import '../base/messages.dart'
show
messageConstFactoryRedirectionToNonConst,
noLength,
templateCyclicRedirectingFactoryConstructors,
templateIncompatibleRedirecteeFunctionType,
templateRedirectingFactoryIncompatibleTypeArgument,
templateTypeArgumentMismatch;
import '../base/modifiers.dart';
import '../base/name_space.dart';
import '../base/problems.dart' show unexpected, unhandled;
import '../base/scope.dart';
import '../builder/builder.dart';
import '../builder/constructor_reference_builder.dart';
import '../builder/declaration_builders.dart';
import '../builder/formal_parameter_builder.dart';
import '../builder/function_builder.dart';
import '../builder/metadata_builder.dart';
import '../builder/type_builder.dart';
import '../codes/cfe_codes.dart';
import '../dill/dill_extension_type_member_builder.dart';
import '../dill/dill_member_builder.dart';
import '../kernel/body_builder_context.dart';
import '../kernel/constructor_tearoff_lowering.dart';
import '../kernel/hierarchy/class_member.dart';
import '../kernel/kernel_helper.dart';
import '../kernel/type_algorithms.dart';
import '../type_inference/inference_helper.dart';
import '../type_inference/type_inferrer.dart';
import '../type_inference/type_schema.dart';
import 'name_scheme.dart';
import 'redirecting_factory_body.dart';
import 'source_class_builder.dart';
import 'source_function_builder.dart';
import 'source_library_builder.dart' show SourceLibraryBuilder;
import 'source_loader.dart'
show CompilationPhaseForProblemReporting, SourceLoader;
import 'source_member_builder.dart';
class SourceFactoryBuilder extends SourceFunctionBuilderImpl {
@override
final SourceLibraryBuilder libraryBuilder;
@override
final DeclarationBuilder declarationBuilder;
final int formalsOffset;
AsyncMarker actualAsyncModifier = AsyncMarker.Sync;
@override
final bool isExtensionInstanceMember = false;
@override
final TypeBuilder returnType;
late final Procedure _procedureInternal;
late final Procedure? _factoryTearOff;
SourceFactoryBuilder? actualOrigin;
List<SourceFactoryBuilder>? _augmentations;
final MemberName _memberName;
DelayedDefaultValueCloner? _delayedDefaultValueCloner;
final int nameOffset;
@override
final Uri fileUri;
SourceFactoryBuilder(
{required List<MetadataBuilder>? metadata,
required Modifiers modifiers,
required this.returnType,
required String name,
required List<NominalParameterBuilder>? typeParameters,
required List<FormalParameterBuilder>? formals,
required this.libraryBuilder,
required this.declarationBuilder,
required this.fileUri,
required int startOffset,
required this.nameOffset,
required this.formalsOffset,
required int endOffset,
required Reference? procedureReference,
required Reference? tearOffReference,
required AsyncMarker asyncModifier,
required NameScheme nameScheme,
String? nativeMethodName})
: _memberName = nameScheme.getDeclaredName(name),
super(metadata, modifiers, name, typeParameters, formals,
nativeMethodName) {
_procedureInternal = new Procedure(
dummyName,
nameScheme.isExtensionTypeMember
? ProcedureKind.Method
: ProcedureKind.Factory,
new FunctionNode(null),
fileUri: fileUri,
reference: procedureReference)
..fileStartOffset = startOffset
..fileOffset = nameOffset
..fileEndOffset = endOffset
..isExtensionTypeMember = nameScheme.isExtensionTypeMember;
nameScheme
.getConstructorMemberName(name, isTearOff: false)
.attachMember(_procedureInternal);
_factoryTearOff = createFactoryTearOffProcedure(
nameScheme.getConstructorMemberName(name, isTearOff: true),
libraryBuilder,
fileUri,
nameOffset,
tearOffReference,
forceCreateLowering: nameScheme.isExtensionTypeMember)
?..isExtensionTypeMember = nameScheme.isExtensionTypeMember;
this.asyncModifier = asyncModifier;
}
@override
int get fileOffset => nameOffset;
@override
Builder get parent => declarationBuilder;
@override
// Coverage-ignore(suite): Not run.
Name get memberName => _memberName.name;
// Coverage-ignore(suite): Not run.
List<SourceFactoryBuilder>? get augmentationsForTesting => _augmentations;
@override
AsyncMarker get asyncModifier => actualAsyncModifier;
@override
Statement? get body {
if (bodyInternal == null && !isAbstract && !isExternal) {
bodyInternal = new EmptyStatement();
}
return bodyInternal;
}
void set asyncModifier(AsyncMarker newModifier) {
actualAsyncModifier = newModifier;
function.asyncMarker = actualAsyncModifier;
function.dartAsyncMarker = actualAsyncModifier;
}
@override
SourceFactoryBuilder get origin => actualOrigin ?? this;
@override
// Coverage-ignore(suite): Not run.
bool get isRegularMethod => false;
@override
bool get isGetter => false;
@override
bool get isSetter => false;
@override
// Coverage-ignore(suite): Not run.
bool get isOperator => false;
@override
bool get isFactory => true;
@override
// Coverage-ignore(suite): Not run.
bool get isProperty => false;
@override
// Coverage-ignore(suite): Not run.
bool get isFinal => false;
@override
// Coverage-ignore(suite): Not run.
bool get isSynthesized => false;
@override
// Coverage-ignore(suite): Not run.
bool get isEnumElement => false;
Procedure get _procedure =>
isAugmenting ? origin._procedure : _procedureInternal;
@override
FunctionNode get function => _procedureInternal.function;
@override
Member? get readTarget => origin._factoryTearOff ?? _procedure;
@override
// Coverage-ignore(suite): Not run.
Reference? get readTargetReference =>
(origin._factoryTearOff ?? _procedure).reference;
@override
// Coverage-ignore(suite): Not run.
Member? get writeTarget => null;
@override
// Coverage-ignore(suite): Not run.
Reference? get writeTargetReference => null;
@override
Member? get invokeTarget => _procedure;
@override
// Coverage-ignore(suite): Not run.
Reference? get invokeTargetReference => _procedure.reference;
@override
// Coverage-ignore(suite): Not run.
Iterable<Reference> get exportedMemberReferences => [_procedure.reference];
@override
void buildOutlineNodes(BuildNodesCallback f) {
_build();
f(
member: _procedureInternal,
tearOff: _factoryTearOff,
kind: isExtensionTypeMember
? BuiltMemberKind.ExtensionTypeFactory
: BuiltMemberKind.Factory);
}
void _build() {
buildFunction();
_procedureInternal.function.fileOffset = formalsOffset;
_procedureInternal.function.fileEndOffset =
_procedureInternal.fileEndOffset;
_procedureInternal.isAbstract = isAbstract;
_procedureInternal.isExternal = isExternal;
_procedureInternal.isConst = isConst;
_procedureInternal.isStatic = isStatic;
if (_factoryTearOff != null) {
_delayedDefaultValueCloner = buildConstructorTearOffProcedure(
tearOff: _factoryTearOff,
declarationConstructor: _procedure,
implementationConstructor: _procedureInternal,
libraryBuilder: libraryBuilder);
}
}
bool _hasBuiltOutlines = false;
@override
void buildOutlineExpressions(ClassHierarchy classHierarchy,
List<DelayedDefaultValueCloner> delayedDefaultValueCloners) {
if (_hasBuiltOutlines) return;
inferFormals(formals, classHierarchy);
if (_delayedDefaultValueCloner != null) {
delayedDefaultValueCloners.add(_delayedDefaultValueCloner!);
}
super.buildOutlineExpressions(classHierarchy, delayedDefaultValueCloners);
_hasBuiltOutlines = true;
}
@override
VariableDeclaration? getTearOffParameter(int index) {
if (_factoryTearOff != null) {
if (index < _factoryTearOff.function.positionalParameters.length) {
return _factoryTearOff.function.positionalParameters[index];
} else {
index -= _factoryTearOff.function.positionalParameters.length;
if (index < _factoryTearOff.function.namedParameters.length) {
return _factoryTearOff.function.namedParameters[index];
}
}
}
return null;
}
@override
// Coverage-ignore(suite): Not run.
List<ClassMember> get localMembers =>
throw new UnsupportedError('${runtimeType}.localMembers');
@override
// Coverage-ignore(suite): Not run.
List<ClassMember> get localSetters =>
throw new UnsupportedError('${runtimeType}.localSetters');
@override
void becomeNative(SourceLoader loader) {
_procedureInternal.isExternal = true;
super.becomeNative(loader);
}
void setRedirectingFactoryBody(Member target, List<DartType> typeArguments) {
if (bodyInternal != null) {
unexpected("null", "${bodyInternal.runtimeType}", fileOffset, fileUri);
}
bodyInternal =
createRedirectingFactoryBody(target, typeArguments, function);
_procedureInternal.function.body = bodyInternal;
_procedureInternal.function.redirectingFactoryTarget =
new RedirectingFactoryTarget(target, typeArguments);
bodyInternal?.parent = function;
if (isAugmenting) {
// Coverage-ignore-block(suite): Not run.
actualOrigin!.setRedirectingFactoryBody(target, typeArguments);
}
}
@override
void applyAugmentation(Builder augmentation) {
if (augmentation is SourceFactoryBuilder) {
if (checkAugmentation(
augmentationLibraryBuilder: augmentation.libraryBuilder,
origin: this,
augmentation: augmentation)) {
augmentation.actualOrigin = this;
(_augmentations ??= []).add(augmentation);
}
} else {
// Coverage-ignore-block(suite): Not run.
reportAugmentationMismatch(
originLibraryBuilder: libraryBuilder,
origin: this,
augmentation: augmentation);
}
}
void _finishAugmentation() {
finishProcedureAugmentation(origin._procedure, _procedureInternal);
if (_factoryTearOff != null) {
finishProcedureAugmentation(origin._factoryTearOff!, _factoryTearOff);
}
}
@override
int buildBodyNodes(BuildNodesCallback f) {
if (!isAugmenting) return 0;
_finishAugmentation();
return 1;
}
@override
int computeDefaultTypes(ComputeDefaultTypeContext context,
{required bool inErrorRecovery}) {
int count = context.computeDefaultTypesForVariables(typeParameters,
// Type parameters are inherited from the enclosing declaration, so if
// it has issues, so do the constructors.
inErrorRecovery: inErrorRecovery);
context.reportGenericFunctionTypesForFormals(formals);
return count;
}
@override
// Coverage-ignore(suite): Not run.
void checkVariance(
SourceClassBuilder sourceClassBuilder, TypeEnvironment typeEnvironment) {}
@override
void checkTypes(SourceLibraryBuilder library, NameSpace nameSpace,
TypeEnvironment typeEnvironment) {
library.checkInitializersInFormals(formals, typeEnvironment,
isAbstract: isAbstract, isExternal: isExternal);
List<SourceFactoryBuilder>? augmentations = _augmentations;
if (augmentations != null) {
for (SourceFactoryBuilder augmentation in augmentations) {
augmentation.checkTypes(library, nameSpace, typeEnvironment);
}
}
}
/// Checks the redirecting factories of this factory builder and its
/// augmentations.
void checkRedirectingFactories(TypeEnvironment typeEnvironment) {
_checkRedirectingFactory(typeEnvironment);
List<SourceFactoryBuilder>? augmentations = _augmentations;
if (augmentations != null) {
for (SourceFactoryBuilder augmentation in augmentations) {
augmentation._checkRedirectingFactory(typeEnvironment);
}
}
}
/// Checks this factory builder if it is for a redirecting factory.
void _checkRedirectingFactory(TypeEnvironment typeEnvironment) {}
@override
BodyBuilderContext createBodyBuilderContext() {
return new FactoryBodyBuilderContext(this, _procedure);
}
@override
// Coverage-ignore(suite): Not run.
String get fullNameForErrors {
return "${declarationBuilder.name}"
"${name.isEmpty ? '' : '.$name'}";
}
// TODO(johnniwinther): Add annotations to tear-offs.
@override
Iterable<Annotatable> get annotatables => [_procedure];
@override
bool get isAugmented {
if (isAugmenting) {
return origin._augmentations!.last != this;
} else {
return _augmentations != null;
}
}
}
class RedirectingFactoryBuilder extends SourceFactoryBuilder {
final ConstructorReferenceBuilder redirectionTarget;
List<DartType>? typeArguments;
FreshTypeParameters? _tearOffTypeParameters;
bool _hasBeenCheckedAsRedirectingFactory = false;
RedirectingFactoryBuilder(
{required List<MetadataBuilder>? metadata,
required Modifiers modifiers,
required TypeBuilder returnType,
required String name,
required List<NominalParameterBuilder>? typeParameters,
required List<FormalParameterBuilder>? formals,
required SourceLibraryBuilder libraryBuilder,
required DeclarationBuilder declarationBuilder,
required Uri fileUri,
required int startOffset,
required int nameOffset,
required int formalsOffset,
required int endOffset,
required Reference? procedureReference,
required Reference? tearOffReference,
required NameScheme nameScheme,
required String? nativeMethodName,
required this.redirectionTarget})
: super(
metadata: metadata,
modifiers: modifiers,
returnType: returnType,
name: name,
typeParameters: typeParameters,
formals: formals,
libraryBuilder: libraryBuilder,
declarationBuilder: declarationBuilder,
fileUri: fileUri,
startOffset: startOffset,
nameOffset: nameOffset,
formalsOffset: formalsOffset,
endOffset: endOffset,
procedureReference: procedureReference,
tearOffReference: tearOffReference,
asyncModifier: AsyncMarker.Sync,
nameScheme: nameScheme,
nativeMethodName: nativeMethodName);
@override
Statement? get body => bodyInternal;
@override
void setRedirectingFactoryBody(Member target, List<DartType> typeArguments) {
if (bodyInternal != null) {
unexpected("null", "${bodyInternal.runtimeType}", fileOffset, fileUri);
}
// Ensure that constant factories only have constant targets/bodies.
if (isConst && !target.isConst) {
// Coverage-ignore-block(suite): Not run.
libraryBuilder.addProblem(messageConstFactoryRedirectionToNonConst,
fileOffset, noLength, fileUri);
}
bodyInternal =
createRedirectingFactoryBody(target, typeArguments, function);
_procedureInternal.function.body = bodyInternal;
_procedureInternal.function.redirectingFactoryTarget =
new RedirectingFactoryTarget(target, typeArguments);
bodyInternal?.parent = function;
if (isAugmenting) {
if (function.typeParameters.isNotEmpty) {
Map<TypeParameter, DartType> substitution = <TypeParameter, DartType>{};
for (int i = 0; i < function.typeParameters.length; i++) {
substitution[function.typeParameters[i]] =
new TypeParameterType.withDefaultNullability(
actualOrigin!.function.typeParameters[i]);
}
typeArguments = new List<DartType>.generate(typeArguments.length,
(int i) => substitute(typeArguments[i], substitution),
growable: false);
}
actualOrigin!.setRedirectingFactoryBody(target, typeArguments);
}
}
void setRedirectingFactoryError(String message) {
body = createRedirectingFactoryErrorBody(message);
_procedure.function.redirectingFactoryTarget =
new RedirectingFactoryTarget.error(message);
if (_factoryTearOff != null) {
_factoryTearOff.function.body = createRedirectingFactoryErrorBody(message)
..parent = _factoryTearOff.function;
}
}
@override
void buildOutlineNodes(BuildNodesCallback f) {
_build();
f(
member: _procedureInternal,
tearOff: _factoryTearOff,
kind: isExtensionTypeMember
? BuiltMemberKind.ExtensionTypeRedirectingFactory
: BuiltMemberKind.RedirectingFactory);
}
@override
void _build() {
buildFunction();
_procedureInternal.function.fileOffset = formalsOffset;
_procedureInternal.function.fileEndOffset =
_procedureInternal.fileEndOffset;
_procedureInternal.isAbstract = isAbstract;
_procedureInternal.isExternal = isExternal;
_procedureInternal.isConst = isConst;
_procedureInternal.isStatic = isStatic;
if (redirectionTarget.typeArguments != null) {
typeArguments = new List<DartType>.generate(
redirectionTarget.typeArguments!.length,
(int i) => redirectionTarget.typeArguments![i]
.build(libraryBuilder, TypeUse.redirectionTypeArgument),
growable: false);
}
if (_factoryTearOff != null) {
_tearOffTypeParameters =
buildRedirectingFactoryTearOffProcedureParameters(
tearOff: _factoryTearOff,
implementationConstructor: _procedureInternal,
libraryBuilder: libraryBuilder);
}
}
@override
bool _hasBuiltOutlines = false;
@override
void buildOutlineExpressions(ClassHierarchy classHierarchy,
List<DelayedDefaultValueCloner> delayedDefaultValueCloners) {
if (_hasBuiltOutlines) return;
inferFormals(formals, classHierarchy);
if (isConst && isAugmenting) {
origin.buildOutlineExpressions(
classHierarchy, delayedDefaultValueCloners);
}
super.buildOutlineExpressions(classHierarchy, delayedDefaultValueCloners);
RedirectingFactoryTarget? redirectingFactoryTarget =
_procedureInternal.function.redirectingFactoryTarget;
if (redirectingFactoryTarget == null) {
// The error is reported elsewhere.
return;
}
List<DartType>? typeArguments = redirectingFactoryTarget.typeArguments;
Member? target = redirectingFactoryTarget.target;
if (typeArguments != null && typeArguments.any((t) => t is UnknownType)) {
TypeInferrer inferrer = libraryBuilder.loader.typeInferenceEngine
.createLocalTypeInferrer(
fileUri, declarationBuilder.thisType, libraryBuilder, null);
InferenceHelper helper = libraryBuilder.loader
.createBodyBuilderForOutlineExpression(libraryBuilder,
createBodyBuilderContext(), declarationBuilder.scope, fileUri);
Builder? targetBuilder = redirectionTarget.target;
if (targetBuilder is SourceMemberBuilder) {
// Ensure that target has been built.
targetBuilder.buildOutlineExpressions(
classHierarchy, delayedDefaultValueCloners);
}
if (targetBuilder is FunctionBuilder) {
target = targetBuilder.invokeTarget!;
}
// Coverage-ignore(suite): Not run.
else if (targetBuilder is DillMemberBuilder) {
target = targetBuilder.invokeTarget!;
} else {
unhandled("${targetBuilder.runtimeType}", "buildOutlineExpressions",
fileOffset, fileUri);
}
typeArguments = inferrer.inferRedirectingFactoryTypeArguments(
helper,
_procedureInternal.function.returnType,
_procedure.function,
fileOffset,
target,
target.function!.computeFunctionType(Nullability.nonNullable));
if (typeArguments == null) {
assert(libraryBuilder.loader.assertProblemReportedElsewhere(
"RedirectingFactoryTarget.buildOutlineExpressions",
expectedPhase: CompilationPhaseForProblemReporting.outline));
// Use 'dynamic' for recovery.
typeArguments = new List<DartType>.filled(
declarationBuilder.typeParametersCount, const DynamicType(),
growable: true);
}
_procedureInternal.function.body =
createRedirectingFactoryBody(target, typeArguments, function);
assert(function == _procedureInternal.function);
_procedureInternal.function.body!.parent = function;
_procedureInternal.function.redirectingFactoryTarget =
new RedirectingFactoryTarget(target, typeArguments);
}
Set<Procedure> seenTargets = {};
while (target is Procedure && target.isRedirectingFactory) {
if (!seenTargets.add(target)) {
// Cyclic dependency.
target = null;
break;
}
RedirectingFactoryTarget redirectingFactoryTarget =
target.function.redirectingFactoryTarget!;
if (typeArguments != null) {
Substitution substitution = Substitution.fromPairs(
target.function.typeParameters, typeArguments);
typeArguments = redirectingFactoryTarget.typeArguments
?.map(substitution.substituteType)
.toList();
} else {
// Coverage-ignore-block(suite): Not run.
typeArguments = redirectingFactoryTarget.typeArguments;
}
target = redirectingFactoryTarget.target;
}
if (target is Constructor ||
target is Procedure &&
(target.isFactory || target.isExtensionTypeMember)) {
// Coverage-ignore(suite): Not run.
typeArguments ??= [];
if (_factoryTearOff != null) {
delayedDefaultValueCloners.add(buildRedirectingFactoryTearOffBody(
_factoryTearOff,
target!,
typeArguments,
_tearOffTypeParameters!,
libraryBuilder));
}
delayedDefaultValueCloners.add(new DelayedDefaultValueCloner(
target!, _procedure,
libraryBuilder: libraryBuilder, identicalSignatures: false));
}
if (isConst && isAugmenting) {
_finishAugmentation();
}
_hasBuiltOutlines = true;
}
@override
void _finishAugmentation() {
super._finishAugmentation();
SourceFactoryBuilder redirectingOrigin = origin;
if (redirectingOrigin is RedirectingFactoryBuilder) {
// Coverage-ignore-block(suite): Not run.
redirectingOrigin.typeArguments = typeArguments;
}
}
List<DartType>? getTypeArguments() {
return _procedure.function.redirectingFactoryTarget!.typeArguments;
}
@override
// Coverage-ignore(suite): Not run.
void checkVariance(
SourceClassBuilder sourceClassBuilder, TypeEnvironment typeEnvironment) {}
@override
void checkTypes(SourceLibraryBuilder library, NameSpace nameSpace,
TypeEnvironment typeEnvironment) {
// Default values are not required on redirecting factory constructors so
// we don't call [checkInitializersInFormals].
}
// Computes the function type of a given redirection target. Returns [null] if
// the type of the target could not be computed.
FunctionType? _computeRedirecteeType(
RedirectingFactoryBuilder factory, TypeEnvironment typeEnvironment) {
ConstructorReferenceBuilder redirectionTarget = factory.redirectionTarget;
Builder? targetBuilder = redirectionTarget.target;
FunctionNode targetNode;
if (targetBuilder == null) return null;
if (targetBuilder is FunctionBuilder) {
targetNode = targetBuilder.function;
} else if (targetBuilder is DillExtensionTypeFactoryBuilder) {
targetNode = targetBuilder.member.function!;
} else if (targetBuilder is AmbiguousBuilder) {
// Multiple definitions with the same name: An error has already been
// issued.
// TODO(http://dartbug.com/35294): Unfortunate error; see also
// https://dart-review.googlesource.com/c/sdk/+/85390/.
return null;
} else {
unhandled("${targetBuilder.runtimeType}", "computeRedirecteeType",
fileOffset, fileUri);
}
List<DartType>? typeArguments = factory.getTypeArguments();
FunctionType targetFunctionType =
targetNode.computeFunctionType(Nullability.nonNullable);
if (typeArguments != null &&
targetFunctionType.typeParameters.length != typeArguments.length) {
libraryBuilder.addProblemForRedirectingFactory(
factory,
templateTypeArgumentMismatch
.withArguments(targetFunctionType.typeParameters.length),
redirectionTarget.charOffset,
noLength,
redirectionTarget.fileUri);
return null;
}
// Compute the substitution of the target class type parameters if
// [redirectionTarget] has any type arguments.
FunctionTypeInstantiator? instantiator;
bool hasProblem = false;
if (typeArguments != null && typeArguments.length > 0) {
instantiator = new FunctionTypeInstantiator.fromIterables(
targetFunctionType.typeParameters, typeArguments);
for (int i = 0; i < targetFunctionType.typeParameters.length; i++) {
StructuralParameter typeParameter =
targetFunctionType.typeParameters[i];
DartType typeParameterBound =
instantiator.substitute(typeParameter.bound);
DartType typeArgument = typeArguments[i];
// Check whether the [typeArgument] respects the bounds of
// [typeParameter].
if (!typeEnvironment.isSubtypeOf(typeArgument, typeParameterBound,
SubtypeCheckMode.ignoringNullabilities)) {
// Coverage-ignore-block(suite): Not run.
libraryBuilder.addProblemForRedirectingFactory(
factory,
templateRedirectingFactoryIncompatibleTypeArgument.withArguments(
typeArgument, typeParameterBound),
redirectionTarget.charOffset,
noLength,
redirectionTarget.fileUri);
hasProblem = true;
} else {
if (!typeEnvironment.isSubtypeOf(typeArgument, typeParameterBound,
SubtypeCheckMode.withNullabilities)) {
libraryBuilder.addProblemForRedirectingFactory(
factory,
templateRedirectingFactoryIncompatibleTypeArgument
.withArguments(typeArgument, typeParameterBound),
redirectionTarget.charOffset,
noLength,
redirectionTarget.fileUri);
hasProblem = true;
}
}
}
} else if (typeArguments == null &&
targetFunctionType.typeParameters.length > 0) {
// TODO(hillerstrom): In this case, we need to perform type inference on
// the redirectee to obtain actual type arguments which would allow the
// following program to type check:
//
// class A<T> {
// factory A() = B;
// }
// class B<T> implements A<T> {
// B();
// }
//
return null;
}
// Substitute if necessary.
targetFunctionType = instantiator == null
? targetFunctionType
: (instantiator.substitute(targetFunctionType.withoutTypeParameters)
as FunctionType);
return hasProblem ? null : targetFunctionType;
}
bool _isCyclicRedirectingFactory(RedirectingFactoryBuilder factory) {
// We use the [tortoise and hare algorithm]
// (https://en.wikipedia.org/wiki/Cycle_detection#Tortoise_and_hare) to
// handle cycles.
Builder? tortoise = factory;
Builder? hare = factory.redirectionTarget.target;
if (hare == factory) {
return true;
}
while (tortoise != hare) {
// Hare moves 2 steps forward.
if (hare is! RedirectingFactoryBuilder) {
return false;
}
hare = hare.redirectionTarget.target;
if (hare == factory) {
return true;
}
if (hare is! RedirectingFactoryBuilder) {
return false;
}
hare = hare.redirectionTarget.target;
if (hare == factory) {
return true;
}
// Tortoise moves one step forward. No need to test type of tortoise
// as it follows hare which already checked types.
tortoise =
(tortoise as RedirectingFactoryBuilder).redirectionTarget.target;
}
// Cycle found, but original factory doesn't belong to a cycle.
return false;
}
@override
void _checkRedirectingFactory(TypeEnvironment typeEnvironment) {
if (_hasBeenCheckedAsRedirectingFactory) return;
_hasBeenCheckedAsRedirectingFactory = true;
// Check that factory declaration is not cyclic.
if (_isCyclicRedirectingFactory(this)) {
libraryBuilder.addProblemForRedirectingFactory(
this,
templateCyclicRedirectingFactoryConstructors
.withArguments("${declarationBuilder.name}"
"${name == '' ? '' : '.${name}'}"),
fileOffset,
noLength,
fileUri);
return;
}
// The factory type cannot contain any type parameters other than those of
// its enclosing class, because constructors cannot specify type parameters
// of their own.
FunctionType factoryType =
function.computeThisFunctionType(Nullability.nonNullable);
if (isAugmenting) {
// The redirection target type uses the origin type parameters so we must
// substitute augmentation type parameters before checking subtyping.
if (function.typeParameters.isNotEmpty) {
Map<TypeParameter, DartType> substitution = <TypeParameter, DartType>{};
for (int i = 0; i < function.typeParameters.length; i++) {
substitution[function.typeParameters[i]] =
new TypeParameterType.withDefaultNullability(
actualOrigin!.function.typeParameters[i]);
}
factoryType = substitute(factoryType, substitution) as FunctionType;
}
}
FunctionType? redirecteeType =
_computeRedirecteeType(this, typeEnvironment);
Map<TypeParameter, DartType> substitutionMap = {};
for (int i = 0; i < factoryType.typeParameters.length; i++) {
TypeParameter functionTypeParameter = origin.function.typeParameters[i];
substitutionMap[functionTypeParameter] =
new StructuralParameterType.withDefaultNullability(
factoryType.typeParameters[i]);
}
redirecteeType = redirecteeType != null
? substitute(redirecteeType, substitutionMap) as FunctionType
: null;
// TODO(hillerstrom): It would be preferable to know whether a failure
// happened during [_computeRedirecteeType].
if (redirecteeType == null) {
return;
}
Builder? redirectionTargetBuilder = redirectionTarget.target;
if (redirectionTargetBuilder is RedirectingFactoryBuilder) {
redirectionTargetBuilder._checkRedirectingFactory(typeEnvironment);
String? errorMessage = redirectionTargetBuilder
.function.redirectingFactoryTarget?.errorMessage;
if (errorMessage != null) {
setRedirectingFactoryError(errorMessage);
}
}
Builder? redirectionTargetParent = redirectionTarget.target?.parent;
bool redirectingTargetParentIsEnum = redirectionTargetParent is ClassBuilder
? redirectionTargetParent.isEnum
: false;
if (!((classBuilder?.cls.isEnum ?? false) &&
(redirectionTarget.target?.isConstructor ?? false) &&
redirectingTargetParentIsEnum)) {
// Check whether [redirecteeType] <: [factoryType].
FunctionType factoryTypeWithoutTypeParameters =
factoryType.withoutTypeParameters;
if (!typeEnvironment.isSubtypeOf(
redirecteeType,
factoryTypeWithoutTypeParameters,
SubtypeCheckMode.withNullabilities)) {
libraryBuilder.addProblemForRedirectingFactory(
this,
templateIncompatibleRedirecteeFunctionType.withArguments(
redirecteeType, factoryTypeWithoutTypeParameters),
redirectionTarget.charOffset,
noLength,
redirectionTarget.fileUri);
}
} else {
// Redirection to generative enum constructors is forbidden.
assert(libraryBuilder.loader.assertProblemReportedElsewhere(
"RedirectingFactoryBuilder._checkRedirectingFactory: "
"Redirection to generative enum constructor.",
expectedPhase: CompilationPhaseForProblemReporting.bodyBuilding));
}
}
@override
BodyBuilderContext createBodyBuilderContext() {
return new RedirectingFactoryBodyBuilderContext(this, _procedure);
}
}