blob: 91c2de640e7c7361e2a6fb633f030482cb89c71c [file] [log] [blame]
// Copyright (c) 2025, 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/identifiers.dart';
import '../../base/problems.dart' show unexpected, unhandled;
import '../../base/scope.dart';
import '../../builder/builder.dart';
import '../../builder/constructor_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/member_builder.dart';
import '../../builder/omitted_type_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 '../../fragment/fragment.dart';
import '../../kernel/body_builder_context.dart';
import '../../kernel/constructor_tearoff_lowering.dart';
import '../../kernel/kernel_helper.dart';
import '../../source/name_scheme.dart';
import '../../source/redirecting_factory_body.dart';
import '../../source/source_factory_builder.dart';
import '../../source/source_function_builder.dart';
import '../../source/source_library_builder.dart' show SourceLibraryBuilder;
import '../../source/source_loader.dart'
show CompilationPhaseForProblemReporting, SourceLoader;
import '../../source/source_member_builder.dart';
import '../../source/source_type_parameter_builder.dart';
import '../../type_inference/inference_helper.dart';
import '../../type_inference/type_inferrer.dart';
import '../../type_inference/type_schema.dart';
class FactoryEncoding implements InferredTypeListener {
late final Procedure _procedure;
late final Procedure? _tearOff;
final FactoryFragment _fragment;
AsyncMarker _asyncModifier;
final List<SourceNominalParameterBuilder>? typeParameters;
final TypeBuilder returnType;
DelayedDefaultValueCloner? _delayedDefaultValueCloner;
List<DartType>? _redirectionTypeArguments;
FreshTypeParameters? _tearOffTypeParameters;
final ConstructorReferenceBuilder? _redirectionTarget;
FactoryEncoding(
this._fragment, {
required this.typeParameters,
required this.returnType,
required ConstructorReferenceBuilder? redirectionTarget,
}) : _redirectionTarget = redirectionTarget,
_asyncModifier = redirectionTarget != null
? AsyncMarker.Sync
: _fragment.asyncModifier;
void createNode({
required String name,
required SourceLibraryBuilder libraryBuilder,
required NameScheme nameScheme,
required Reference? procedureReference,
required Reference? tearOffReference,
}) {
_procedure = new Procedure(
dummyName,
nameScheme.isExtensionTypeMember
? ProcedureKind.Method
: ProcedureKind.Factory,
new FunctionNode(null)
..asyncMarker = _asyncModifier
..dartAsyncMarker = _asyncModifier,
fileUri: _fragment.fileUri,
reference: procedureReference)
..fileStartOffset = _fragment.startOffset
..fileOffset = _fragment.fullNameOffset
..fileEndOffset = _fragment.endOffset
..isExtensionTypeMember = nameScheme.isExtensionTypeMember;
nameScheme
.getConstructorMemberName(name, isTearOff: false)
.attachMember(_procedure);
_tearOff = createFactoryTearOffProcedure(
nameScheme.getConstructorMemberName(name, isTearOff: true),
libraryBuilder,
_fragment.fileUri,
_fragment.fullNameOffset,
tearOffReference,
forceCreateLowering: nameScheme.isExtensionTypeMember)
?..isExtensionTypeMember = nameScheme.isExtensionTypeMember;
returnType.registerInferredTypeListener(this);
}
Procedure get procedure => _procedure;
Procedure? get tearOff => _tearOff;
@override
// Coverage-ignore(suite): Not run.
void onInferredType(DartType type) {
_procedure.function.returnType = type;
}
void set asyncModifier(AsyncMarker newModifier) {
_asyncModifier = newModifier;
_procedure.function.asyncMarker = _asyncModifier;
_procedure.function.dartAsyncMarker = _asyncModifier;
}
List<DartType>? get redirectionTypeArguments {
assert(_redirectionTarget != null);
return _redirectionTypeArguments;
}
void set redirectionTypeArguments(List<DartType>? value) {
assert(_redirectionTarget != null);
_redirectionTypeArguments = value;
}
void buildOutlineNodes(
{required SourceLibraryBuilder libraryBuilder,
required SourceFactoryBuilder factoryBuilder,
required BuildNodesCallback f,
required bool isConst}) {
_procedure.function.asyncMarker = _asyncModifier;
if (_redirectionTarget == null &&
!_fragment.modifiers.isAbstract &&
!_fragment.modifiers.isExternal) {
_procedure.function.body = new EmptyStatement()
..parent = _procedure.function;
}
buildTypeParametersAndFormals(
libraryBuilder, _procedure.function, typeParameters, _fragment.formals,
classTypeParameters: null, supportsTypeParameters: true);
if (returnType is! InferableTypeBuilder) {
_procedure.function.returnType =
returnType.build(libraryBuilder, TypeUse.returnType);
}
_procedure.function.fileOffset = _fragment.formalsOffset;
_procedure.function.fileEndOffset = _procedure.fileEndOffset;
_procedure.isAbstract = _fragment.modifiers.isAbstract;
_procedure.isExternal = _fragment.modifiers.isExternal;
// TODO(johnniwinther): DDC platform currently relies on the ability to
// patch a const constructor with a non-const patch. Remove this and enforce
// equal constness on origin and patch.
_procedure.isConst = isConst;
_procedure.isStatic = _fragment.modifiers.isStatic;
if (_redirectionTarget != null) {
if (_redirectionTarget.typeArguments != null) {
redirectionTypeArguments = new List<DartType>.generate(
_redirectionTarget.typeArguments!.length,
(int i) => _redirectionTarget.typeArguments![i]
.build(libraryBuilder, TypeUse.redirectionTypeArgument),
growable: false);
}
if (_tearOff != null) {
_tearOffTypeParameters =
buildRedirectingFactoryTearOffProcedureParameters(
tearOff: _tearOff,
implementationConstructor: _procedure,
libraryBuilder: libraryBuilder);
}
} else {
if (_tearOff != null) {
_delayedDefaultValueCloner = buildConstructorTearOffProcedure(
tearOff: _tearOff,
declarationConstructor: _procedure,
implementationConstructor: _procedure,
libraryBuilder: libraryBuilder);
}
}
f(
member: _procedure,
tearOff: _tearOff,
kind: factoryBuilder.isExtensionTypeMember
? (_redirectionTarget != null
? BuiltMemberKind.ExtensionTypeRedirectingFactory
: BuiltMemberKind.ExtensionTypeFactory)
: (_redirectionTarget != null
? BuiltMemberKind.RedirectingFactory
: BuiltMemberKind.Factory));
}
void buildOutlineExpressions(
{required List<DelayedDefaultValueCloner> delayedDefaultValueCloners}) {
if (_delayedDefaultValueCloner != null) {
delayedDefaultValueCloners.add(_delayedDefaultValueCloner!);
}
}
void inferRedirectionTarget(
{required SourceLibraryBuilder libraryBuilder,
required DeclarationBuilder declarationBuilder,
required BodyBuilderContext bodyBuilderContext,
required ClassHierarchy classHierarchy,
required List<DelayedDefaultValueCloner> delayedDefaultValueCloners}) {
if (_redirectionTarget == null) {
return;
}
RedirectingFactoryTarget? redirectingFactoryTarget =
_procedure.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(
_fragment.fileUri,
declarationBuilder.thisType,
libraryBuilder,
_fragment.typeParameterScope,
null);
InferenceHelper helper = libraryBuilder.loader
.createBodyBuilderForOutlineExpression(libraryBuilder,
bodyBuilderContext, _fragment.enclosingScope, _fragment.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",
_fragment.fullNameOffset, _fragment.fileUri);
}
typeArguments = inferrer.inferRedirectingFactoryTypeArguments(
helper,
_procedure.function.returnType,
_procedure.function,
_fragment.fullNameOffset,
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);
}
_procedure.function.body = createRedirectingFactoryBody(
target, typeArguments, _procedure.function);
_procedure.function.body!.parent = _procedure.function;
_procedure.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 (_tearOff != null) {
delayedDefaultValueCloners.add(buildRedirectingFactoryTearOffBody(
_tearOff,
target!,
typeArguments,
_tearOffTypeParameters!,
libraryBuilder));
}
delayedDefaultValueCloners.add(new DelayedDefaultValueCloner(
target!, _procedure,
libraryBuilder: libraryBuilder, identicalSignatures: false));
}
}
void resolveRedirectingFactory(
{required SourceLibraryBuilder libraryBuilder}) {
ConstructorReferenceBuilder? redirectionTarget = _redirectionTarget;
if (redirectionTarget != null) {
// Compute the immediate redirection target, not the effective.
List<TypeBuilder>? typeArguments = redirectionTarget.typeArguments;
Builder? target = redirectionTarget.target;
if (typeArguments != null && target is MemberBuilder) {
TypeName redirectionTargetName = redirectionTarget.typeName;
if (redirectionTargetName.qualifier == null) {
// Do nothing. This is the case of an identifier followed by
// type arguments, such as the following:
// B<T>
// B<T>.named
} else {
if (target.name.isEmpty) {
// Do nothing. This is the case of a qualified
// non-constructor prefix (for example, with a library
// qualifier) followed by type arguments, such as the
// following:
// lib.B<T>
} else if (target.name != redirectionTargetName.name) {
// Do nothing. This is the case of a qualified
// non-constructor prefix followed by type arguments followed
// by a constructor name, such as the following:
// lib.B<T>.named
} else {
// TODO(cstefantsova,johnniwinther): Handle this in case in
// ConstructorReferenceBuilder.resolveIn and unify with other
// cases of handling of type arguments after constructor
// names.
libraryBuilder.addProblem(
messageConstructorWithTypeArguments,
redirectionTargetName.nameOffset,
redirectionTargetName.nameLength,
_fragment.fileUri);
}
}
}
Builder? targetBuilder = redirectionTarget.target;
Member? targetNode;
if (targetBuilder is FunctionBuilder) {
targetNode = targetBuilder.invokeTarget!;
} else if (targetBuilder is DillMemberBuilder) {
// Coverage-ignore-block(suite): Not run.
targetNode = targetBuilder.invokeTarget!;
} else if (targetBuilder is AmbiguousBuilder) {
_addProblemForRedirectingFactory(
libraryBuilder: libraryBuilder,
message: templateDuplicatedDeclarationUse
.withArguments(redirectionTarget.fullNameForErrors),
fileOffset: redirectionTarget.charOffset,
length: noLength,
fileUri: redirectionTarget.fileUri);
} else {
_addProblemForRedirectingFactory(
libraryBuilder: libraryBuilder,
message: templateRedirectionTargetNotFound
.withArguments(redirectionTarget.fullNameForErrors),
fileOffset: redirectionTarget.charOffset,
length: noLength,
fileUri: redirectionTarget.fileUri);
}
if (targetNode != null &&
targetNode is Constructor &&
targetNode.enclosingClass.isAbstract) {
_addProblemForRedirectingFactory(
libraryBuilder: libraryBuilder,
message: templateAbstractRedirectedClassInstantiation
.withArguments(redirectionTarget.fullNameForErrors),
fileOffset: redirectionTarget.charOffset,
length: noLength,
fileUri: redirectionTarget.fileUri);
targetNode = null;
}
if (targetNode != null &&
targetNode is Constructor &&
targetNode.enclosingClass.isEnum) {
_addProblemForRedirectingFactory(
libraryBuilder: libraryBuilder,
message: messageEnumFactoryRedirectsToConstructor,
fileOffset: redirectionTarget.charOffset,
length: noLength,
fileUri: redirectionTarget.fileUri);
targetNode = null;
}
if (targetNode != null) {
List<DartType>? typeArguments = redirectionTypeArguments;
if (typeArguments == null) {
int typeArgumentCount;
if (targetBuilder!.isExtensionTypeMember) {
ExtensionTypeDeclarationBuilder extensionTypeDeclarationBuilder =
targetBuilder.parent as ExtensionTypeDeclarationBuilder;
typeArgumentCount =
extensionTypeDeclarationBuilder.typeParametersCount;
} else {
typeArgumentCount =
targetNode.enclosingClass!.typeParameters.length;
}
typeArguments =
new List<DartType>.filled(typeArgumentCount, const UnknownType());
}
_setRedirectingFactoryBody(
libraryBuilder: libraryBuilder,
target: targetNode,
typeArguments: typeArguments);
}
}
}
void _setRedirectingFactoryBody(
{required SourceLibraryBuilder libraryBuilder,
required Member target,
required List<DartType> typeArguments}) {
if (_procedure.function.body != null) {
unexpected("null", "${_procedure.function.body.runtimeType}",
_fragment.fullNameOffset, _fragment.fileUri);
}
// Ensure that constant factories only have constant targets/bodies.
if (_fragment.modifiers.isConst && !target.isConst) {
// Coverage-ignore-block(suite): Not run.
libraryBuilder.addProblem(messageConstFactoryRedirectionToNonConst,
_fragment.fullNameOffset, noLength, _fragment.fileUri);
}
_procedure.function.body =
createRedirectingFactoryBody(target, typeArguments, _procedure.function)
..parent = _procedure.function;
_procedure.function.redirectingFactoryTarget =
new RedirectingFactoryTarget(target, typeArguments);
}
void _addProblemForRedirectingFactory(
{required SourceLibraryBuilder libraryBuilder,
required Message message,
required int fileOffset,
required int length,
required Uri fileUri}) {
libraryBuilder.addProblem(message, fileOffset, length, fileUri);
String text = libraryBuilder.loader.target.context
.format(
message.withLocation(fileUri, fileOffset, length), Severity.error)
.plain;
_setRedirectingFactoryError(message: text);
}
void _setRedirectingFactoryError({required String message}) {
assert(_redirectionTarget != null);
setBody(createRedirectingFactoryErrorBody(message));
_procedure.function.redirectingFactoryTarget =
new RedirectingFactoryTarget.error(message);
if (_tearOff != null) {
_tearOff.function.body = createRedirectingFactoryErrorBody(message)
..parent = _tearOff.function;
}
}
/// Checks this factory builder if it is for a redirecting factory.
void checkRedirectingFactory(
{required SourceLibraryBuilder libraryBuilder,
required SourceFactoryBuilder factoryBuilder,
required TypeEnvironment typeEnvironment}) {
assert(_redirectionTarget != null);
// Check that factory declaration is not cyclic.
if (_isCyclicRedirectingFactory(factoryBuilder)) {
_addProblemForRedirectingFactory(
libraryBuilder: libraryBuilder,
message: templateCyclicRedirectingFactoryConstructors
.withArguments("${factoryBuilder.declarationBuilder.name}"
"${_fragment.name == '' ? '' : '.${_fragment.name}'}"),
fileOffset: _fragment.fullNameOffset,
length: noLength,
fileUri: _fragment.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 =
_procedure.function.computeThisFunctionType(Nullability.nonNullable);
FunctionType? redirecteeType = _computeRedirecteeType(
libraryBuilder: libraryBuilder, typeEnvironment: typeEnvironment);
Map<TypeParameter, DartType> substitutionMap = {};
for (int i = 0; i < factoryType.typeParameters.length; i++) {
TypeParameter functionTypeParameter =
_procedure.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 SourceFactoryBuilder &&
redirectionTargetBuilder.redirectionTarget != null) {
redirectionTargetBuilder.checkRedirectingFactories(typeEnvironment);
String? errorMessage = redirectionTargetBuilder
.function.redirectingFactoryTarget?.errorMessage;
if (errorMessage != null) {
_setRedirectingFactoryError(message: errorMessage);
}
}
Builder? redirectionTargetParent = _redirectionTarget.target?.parent;
bool redirectingTargetParentIsEnum = redirectionTargetParent is ClassBuilder
? redirectionTargetParent.isEnum
: false;
if (!((factoryBuilder.classBuilder?.cls.isEnum ?? false) &&
(_redirectionTarget.target is ConstructorBuilder) &&
redirectingTargetParentIsEnum)) {
// Check whether [redirecteeType] <: [factoryType].
FunctionType factoryTypeWithoutTypeParameters =
factoryType.withoutTypeParameters;
if (!typeEnvironment.isSubtypeOf(
redirecteeType,
factoryTypeWithoutTypeParameters,
SubtypeCheckMode.withNullabilities)) {
_addProblemForRedirectingFactory(
libraryBuilder: libraryBuilder,
message: templateIncompatibleRedirecteeFunctionType.withArguments(
redirecteeType, factoryTypeWithoutTypeParameters),
fileOffset: _redirectionTarget.charOffset,
length: noLength,
fileUri: _redirectionTarget.fileUri);
}
} else {
// Redirection to generative enum constructors is forbidden.
assert(libraryBuilder.loader.assertProblemReportedElsewhere(
"RedirectingFactoryBuilder._checkRedirectingFactory: "
"Redirection to generative enum constructor.",
expectedPhase: CompilationPhaseForProblemReporting.bodyBuilding));
}
}
// Computes the function type of a given redirection target. Returns [null] if
// the type of the target could not be computed.
FunctionType? _computeRedirecteeType(
{required SourceLibraryBuilder libraryBuilder,
required TypeEnvironment typeEnvironment}) {
assert(_redirectionTarget != null);
ConstructorReferenceBuilder redirectionTarget = _redirectionTarget!;
Builder? targetBuilder = redirectionTarget.target;
FunctionNode targetNode;
if (targetBuilder == null) return null;
if (targetBuilder is FunctionBuilder) {
targetNode = targetBuilder.function;
} else if (targetBuilder is DillExtensionTypeFactoryBuilder) {
// Coverage-ignore-block(suite): Not run.
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",
_fragment.fullNameOffset, _fragment.fileUri);
}
List<DartType>? typeArguments =
_procedure.function.redirectingFactoryTarget!.typeArguments;
FunctionType targetFunctionType =
targetNode.computeFunctionType(Nullability.nonNullable);
if (typeArguments != null &&
targetFunctionType.typeParameters.length != typeArguments.length) {
_addProblemForRedirectingFactory(
libraryBuilder: libraryBuilder,
message: templateTypeArgumentMismatch
.withArguments(targetFunctionType.typeParameters.length),
fileOffset: redirectionTarget.charOffset,
length: noLength,
fileUri: 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.
_addProblemForRedirectingFactory(
libraryBuilder: libraryBuilder,
message: templateRedirectingFactoryIncompatibleTypeArgument
.withArguments(typeArgument, typeParameterBound),
fileOffset: redirectionTarget.charOffset,
length: noLength,
fileUri: redirectionTarget.fileUri);
hasProblem = true;
} else {
if (!typeEnvironment.isSubtypeOf(typeArgument, typeParameterBound,
SubtypeCheckMode.withNullabilities)) {
_addProblemForRedirectingFactory(
libraryBuilder: libraryBuilder,
message: templateRedirectingFactoryIncompatibleTypeArgument
.withArguments(typeArgument, typeParameterBound),
fileOffset: redirectionTarget.charOffset,
length: noLength,
fileUri: 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;
}
static bool _isCyclicRedirectingFactory(SourceFactoryBuilder factory) {
assert(factory.redirectionTarget != null);
// 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! SourceFactoryBuilder || hare.redirectionTarget == null) {
return false;
}
hare = hare.redirectionTarget!.target;
if (hare == factory) {
return true;
}
if (hare is! SourceFactoryBuilder || hare.redirectionTarget == null) {
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 SourceFactoryBuilder).redirectionTarget!.target;
}
// Cycle found, but original factory doesn't belong to a cycle.
return false;
}
void setBody(Statement value) {
_procedure.function.body = value..parent = _procedure.function;
}
void becomeNative(SourceLoader loader) {
_procedure.isExternal = true;
}
// Coverage-ignore(suite): Not run.
bool get isNative => _fragment.nativeMethodName != null;
FunctionNode get function => _procedure.function;
FormalParameterBuilder? getFormal(Identifier identifier) {
if (_fragment.formals != null) {
for (FormalParameterBuilder formal in _fragment.formals!) {
if (formal.isWildcard &&
identifier.name == '_' &&
formal.fileOffset == identifier.nameOffset) {
return formal;
}
if (formal.name == identifier.name &&
formal.fileOffset == identifier.nameOffset) {
return formal;
}
}
// Coverage-ignore(suite): Not run.
// If we have any formals we should find the one we're looking for.
assert(false, "$identifier not found in ${_fragment.formals}");
}
return null;
}
VariableDeclaration? getTearOffParameter(int index) {
if (_tearOff != null) {
if (index < _tearOff.function.positionalParameters.length) {
return _tearOff.function.positionalParameters[index];
} else {
index -= _tearOff.function.positionalParameters.length;
if (index < _tearOff.function.namedParameters.length) {
return _tearOff.function.namedParameters[index];
}
}
}
return null;
}
}