blob: ff2b557240f3e90191b7ca814630debaff4a199a [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'
show
DartType,
Expression,
ExpressionStatement,
FunctionNode,
InvalidExpression,
Let,
Member,
NullLiteral,
Procedure,
StaticGet,
StringLiteral,
TypeParameterType,
VariableDeclaration;
import 'package:kernel/type_algebra.dart' show Substitution;
import 'body_builder.dart' show EnsureLoaded;
const String letName = "#redirecting_factory";
class RedirectingFactoryBody extends ExpressionStatement {
RedirectingFactoryBody.internal(Expression value,
[List<DartType> typeArguments])
: super(new Let(new VariableDeclaration(letName, initializer: value),
encodeTypeArguments(typeArguments)));
RedirectingFactoryBody(Member target, [List<DartType> typeArguments])
: this.internal(new StaticGet(target), typeArguments);
RedirectingFactoryBody.unresolved(String name)
: this.internal(new StringLiteral(name));
Member get target {
dynamic value = getValue(expression);
return value is StaticGet ? value.target : null;
}
String get unresolvedName {
dynamic value = getValue(expression);
return value is StringLiteral ? value.value : null;
}
bool get isUnresolved => unresolvedName != null;
List<DartType> get typeArguments {
if (expression is Let) {
Let bodyExpression = expression;
if (bodyExpression.variable.name == letName) {
return decodeTypeArguments(bodyExpression.body);
}
}
return null;
}
static getValue(Expression expression) {
if (expression is Let) {
VariableDeclaration variable = expression.variable;
if (variable.name == letName) {
return variable.initializer;
}
}
return null;
}
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;
ExpressionStatement statement = function.body;
List<DartType> typeArguments;
if (statement.expression is Let) {
Let expression = statement.expression;
typeArguments = decodeTypeArguments(expression.body);
}
function.body = new RedirectingFactoryBody.internal(
getValue(statement.expression), typeArguments)
..parent = function;
}
static Expression encodeTypeArguments(List<DartType> typeArguments) {
String varNamePrefix = "#typeArg";
Expression result = new InvalidExpression(null);
if (typeArguments == null) {
return result;
}
for (int i = typeArguments.length - 1; i >= 0; i--) {
result = new Let(
new VariableDeclaration("$varNamePrefix$i",
type: typeArguments[i], initializer: new NullLiteral()),
result);
}
return result;
}
static List<DartType> decodeTypeArguments(Expression encoded) {
if (encoded is InvalidExpression) {
return null;
}
List<DartType> result = <DartType>[];
while (encoded is Let) {
Let head = encoded;
result.add(head.variable.type);
encoded = head.body;
}
return result;
}
}
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 : null;
}
class RedirectionTarget {
final Member target;
final List<DartType> typeArguments;
RedirectionTarget(this.target, this.typeArguments);
}
RedirectionTarget getRedirectionTarget(Procedure member, EnsureLoaded helper) {
List<DartType> typeArguments = <DartType>[]..length =
member.function.typeParameters.length;
for (int i = 0; i < typeArguments.length; i++) {
typeArguments[i] = new TypeParameterType(member.function.typeParameters[i]);
}
// We use the [tortoise and hare algorithm]
// (https://en.wikipedia.org/wiki/Cycle_detection#Tortoise_and_hare) to
// handle cycles.
Member tortoise = member;
RedirectingFactoryBody tortoiseBody = getRedirectingFactoryBody(tortoise);
Member hare = tortoiseBody?.target;
helper.ensureLoaded(hare);
RedirectingFactoryBody hareBody = getRedirectingFactoryBody(hare);
while (tortoise != hare) {
if (tortoiseBody?.isUnresolved ?? true) {
return new RedirectionTarget(tortoise, typeArguments);
}
Member nextTortoise = tortoiseBody.target;
helper.ensureLoaded(nextTortoise);
List<DartType> nextTypeArguments = tortoiseBody.typeArguments;
if (nextTypeArguments == null) {
nextTypeArguments = <DartType>[];
}
Substitution sub =
Substitution.fromPairs(tortoise.function.typeParameters, typeArguments);
typeArguments = <DartType>[]..length = nextTypeArguments.length;
for (int i = 0; i < typeArguments.length; i++) {
typeArguments[i] = sub.substituteType(nextTypeArguments[i]);
}
tortoise = nextTortoise;
tortoiseBody = getRedirectingFactoryBody(tortoise);
helper.ensureLoaded(hareBody?.target);
hare = getRedirectingFactoryBody(hareBody?.target)?.target;
helper.ensureLoaded(hare);
hareBody = getRedirectingFactoryBody(hare);
}
return null;
}