blob: c36340902b5440ce32b0d50cdce9c659d4240be1 [file] [log] [blame]
// Copyright (c) 2016, 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.
library fasta.redirecting_factory_body;
import 'package:kernel/ast.dart';
import 'package:kernel/type_algebra.dart' show Substitution;
import 'body_builder.dart' show EnsureLoaded;
/// Name used for a static field holding redirecting factory information.
const String redirectingName = "_redirecting#";
/// Returns `true` if [member] is synthesized field holding the names of
/// redirecting factories declared in the same class.
///
/// This field should be special-cased by backends.
bool isRedirectingFactoryField(Member member) {
return member is Field &&
member.isStatic &&
member.name.text == redirectingName;
}
/// Returns the redirecting factory constructors for the enclosing class from
/// [field].
///
/// `isRedirectingFactoryField(field)` is assumed to be true.
Iterable<Procedure> getRedirectingFactories(Field field) {
assert(isRedirectingFactoryField(field));
List<Procedure> redirectingFactories = [];
ListLiteral initializer = field.initializer as ListLiteral;
for (Expression expression in initializer.expressions) {
Procedure target;
if (expression is ConstantExpression) {
ConstructorTearOffConstant constant =
expression.constant as ConstructorTearOffConstant;
target = constant.target as Procedure;
} else {
ConstructorTearOff get = expression as ConstructorTearOff;
target = get.target as Procedure;
}
redirectingFactories.add(target);
}
return redirectingFactories;
}
/// Name used for a synthesized let variable used to encode redirecting factory
/// information in a factory method body.
const String letName = "#redirecting_factory";
/// Name used for a synthesized let variable used to encode type arguments to
/// the redirection target in a factory method body.
const String varNamePrefix = "#typeArg";
class RedirectingFactoryBody extends ReturnStatement {
RedirectingFactoryBody._internal(Expression value) : super(value);
RedirectingFactoryBody(
Member target, List<DartType> typeArguments, FunctionNode function)
: this._internal(_makeForwardingCall(target, typeArguments, function));
RedirectingFactoryBody.error(String errorMessage)
: this._internal(new InvalidExpression(errorMessage));
Member? get target {
final Expression? value = this.expression;
if (value is StaticInvocation) {
return value.target;
} else if (value is ConstructorInvocation) {
return value.target;
}
return null;
}
String? get errorMessage {
final Expression? value = this.expression;
return value is InvalidExpression ? value.message : null;
}
bool get isError => errorMessage != null;
List<DartType>? get typeArguments {
final Expression? value = this.expression;
if (value is InvocationExpression) {
return value.arguments.types;
}
return null;
}
static Expression _makeForwardingCall(
Member target, List<DartType> typeArguments, FunctionNode function) {
final List<Expression> positional = function.positionalParameters
.map((v) => new VariableGet(v)..fileOffset = v.fileOffset)
.toList();
final List<NamedExpression> named = function.namedParameters
.map((v) => new NamedExpression(
v.name!, new VariableGet(v)..fileOffset = v.fileOffset)
..fileOffset = v.fileOffset)
.toList();
final Arguments args =
new Arguments(positional, named: named, types: typeArguments);
if (target is Procedure) {
return new StaticInvocation(target, args)
..fileOffset = function.fileOffset;
} else if (target is Constructor) {
return new ConstructorInvocation(target, args)
..fileOffset = function.fileOffset;
} else {
throw 'Unexpected target for redirecting factory:'
' ${target.runtimeType} $target';
}
}
static void restoreFromDill(Procedure factory) {
// This is a hack / work around for storing redirecting constructors in
// dill files. See `ClassBuilder.addRedirectingConstructor` in
// [kernel_class_builder.dart](kernel_class_builder.dart).
FunctionNode function = factory.function;
Expression value = (function.body as ReturnStatement).expression!;
function.body = new RedirectingFactoryBody._internal(value)
..parent = function;
}
static bool hasRedirectingFactoryBodyShape(Procedure factory) {
final FunctionNode function = factory.function;
final Statement? body = function.body;
if (body is! ReturnStatement) return false;
final Expression? value = body.expression;
if (body is InvalidExpression) {
return true;
} else if (value is StaticInvocation || value is ConstructorInvocation) {
// Verify that invocation forwards all arguments.
final Arguments args = (value as InvocationExpression).arguments;
if (args.positional.length != function.positionalParameters.length) {
return false;
}
int i = 0;
for (Expression arg in args.positional) {
if (arg is! VariableGet) {
return false;
}
if (arg.variable != function.positionalParameters[i]) {
return false;
}
++i;
}
if (args.named.length != function.namedParameters.length) {
return false;
}
i = 0;
for (NamedExpression arg in args.named) {
final Expression value = arg.value;
if (value is! VariableGet) {
return false;
}
final VariableDeclaration param = function.namedParameters[i];
if (value.variable != param) {
return false;
}
if (arg.name != param.name) {
return false;
}
++i;
}
return true;
} else {
return false;
}
}
@override
String toString() {
return "RedirectingFactoryBody(${toStringInternal()})";
}
@override
String toStringInternal() {
return "";
}
}
bool isRedirectingFactory(Member? member, {EnsureLoaded? helper}) {
assert(helper == null || helper.isLoaded(member));
return member is Procedure && member.function.body is RedirectingFactoryBody;
}
RedirectingFactoryBody? getRedirectingFactoryBody(Member? member) {
return isRedirectingFactory(member)
? member!.function!.body as RedirectingFactoryBody
: null;
}
class RedirectionTarget {
final Member target;
final List<DartType> typeArguments;
RedirectionTarget(this.target, this.typeArguments);
}
RedirectionTarget getRedirectionTarget(Procedure factory, EnsureLoaded helper) {
List<DartType> typeArguments = new List<DartType>.generate(
factory.function.typeParameters.length, (int i) {
return new TypeParameterType.withDefaultNullabilityForLibrary(
factory.function.typeParameters[i], factory.enclosingLibrary);
}, growable: true);
// Cyclic factories are detected earlier, so we're guaranteed to
// reach either a non-redirecting factory or an error eventually.
Member target = factory;
for (;;) {
RedirectingFactoryBody? body = getRedirectingFactoryBody(target);
if (body == null || body.isError) {
return new RedirectionTarget(target, typeArguments);
}
Member nextMember = body.target!;
helper.ensureLoaded(nextMember);
List<DartType>? nextTypeArguments = body.typeArguments;
if (nextTypeArguments != null) {
Substitution sub = Substitution.fromPairs(
target.function!.typeParameters, typeArguments);
typeArguments =
new List<DartType>.generate(nextTypeArguments.length, (int i) {
return sub.substituteType(nextTypeArguments[i]);
}, growable: true);
} else {
typeArguments = <DartType>[];
}
target = nextMember;
}
}