blob: a057443a62209700b78a8f2a3183195be2ea12d7 [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 kernel.transformations.reify.transformation.transformer;
import '../analysis/program_analysis.dart';
import 'package:kernel/ast.dart';
import 'binding.dart' show RuntimeLibrary;
import 'builder.dart' show RuntimeTypeSupportBuilder;
import 'dart:collection' show LinkedHashMap;
import '../asts.dart';
export 'binding.dart' show RuntimeLibrary;
export 'builder.dart' show RuntimeTypeSupportBuilder;
enum RuntimeTypeStorage {
none,
inheritedField,
field,
getter,
}
class TransformationContext {
/// Describes how the runtime type is stored on the object.
RuntimeTypeStorage runtimeTypeStorage;
/// Field added to store the runtime type if [runtimeType] is
/// [RuntimeTypeStorage.field].
Field runtimeTypeField;
/// The parameter for the type information introduced to the constructor or
/// to static initializers.
VariableDeclaration parameter;
/// A ordered collection of fields together with their initializers rewritten
/// to static initializer functions that can be used in the constructor's
/// initializer list.
/// The order is important because of possible side-effects in the
/// initializers.
LinkedHashMap<Field, Procedure> initializers;
// `true` if the visitor currently is in a field initializer, a initializer
// list of a constructor, or the body of a factory method. In these cases,
// type argument access is different than in an instance context, since `this`
// is not available.
bool inInitializer = false;
String toString() => "s: ${runtimeTypeStorage} f: $runtimeTypeField,"
" p: $parameter, i: $inInitializer";
}
abstract class DebugTrace {
static const bool debugTrace = false;
static const int lineLength = 80;
TransformationContext get context;
String getNodeLevel(TreeNode node) {
String level = "";
while (node != null && node is! Library) {
level = " $level";
node = node.parent;
}
return level;
}
String shorten(String s) {
return s.length > lineLength ? s.substring(0, lineLength) : s;
}
void trace(TreeNode node) {
if (debugTrace) {
String nodeText = node.toString().replaceAll("\n", " ");
print(shorten("trace:${getNodeLevel(node)}$context"
" [${node.runtimeType}] $nodeText"));
}
}
}
/// Rewrites a tree to remove generic types and runtime type checks and replace
/// them with Dart objects.
///
/// Runtime types are stored in a field/getter called [runtimeTypeName] on the
/// object, which for parameterized classes is initialized in the constructor.
// TODO(karlklose):
// - add a scoped namer
// - rewrite types (supertypes, implemented types)
// - rewrite as
class ReifyVisitor extends Transformer with DebugTrace {
final RuntimeLibrary rtiLibrary;
final RuntimeTypeSupportBuilder builder;
final ProgramKnowledge knowledge;
ReifyVisitor(this.rtiLibrary, this.builder, this.knowledge,
[this.libraryToTransform]);
/// If not null, the transformation will only be applied to classes declared
/// in this library.
final Library libraryToTransform;
// TODO(karlklose): find a way to get rid of this state in the visitor.
TransformationContext context;
static const String genericMethodTypeParametersName = r"$typeParameters";
bool libraryShouldBeTransformed(Library library) {
return libraryToTransform == null || libraryToTransform == library;
}
bool needsTypeInformation(Class cls) {
return !isObject(cls) &&
!rtiLibrary.contains(cls) &&
libraryShouldBeTransformed(cls.enclosingLibrary);
}
bool usesTypeGetter(Class cls) {
return cls.typeParameters.isEmpty;
}
bool isObject(Class cls) {
// TODO(karlklose): use [CoreTypes].
return "$cls" == 'dart.core::Object';
}
Initializer addTypeAsArgument(initializer) {
assert(initializer is SuperInitializer ||
initializer is RedirectingInitializer);
Class cls = getEnclosingClass(initializer.target);
if (needsTypeInformation(cls) && !usesTypeGetter(cls)) {
// If the current class uses a getter for type information, we did not add
// a parameter to the constructor, but we can pass `null` as the value to
// initialize the type field, since it will be shadowed by the getter.
Expression type = (context.parameter != null)
? new VariableGet(context.parameter)
: new NullLiteral();
builder.insertAsFirstArgument(initializer.arguments, type);
}
return initializer;
}
Expression interceptInstantiation(
InvocationExpression invocation, Member target) {
Class targetClass = target.parent;
Library targetLibrary = targetClass.parent;
Library currentLibrary = getEnclosingLibrary(invocation);
if (libraryShouldBeTransformed(currentLibrary) &&
!libraryShouldBeTransformed(targetLibrary) &&
!rtiLibrary.contains(target)) {
return builder.attachTypeToConstructorInvocation(invocation, target);
}
return invocation;
}
Expression createRuntimeType(DartType type) {
if (context?.inInitializer == true) {
// In initializer context, the instance type is provided in
// `context.parameter` as there is no `this`.
return builder.createRuntimeType(type, typeContext: context.parameter);
} else {
return builder.createRuntimeType(type);
}
}
TreeNode defaultTreeNode(TreeNode node) {
trace(node);
return super.defaultTreeNode(node);
}
Expression visitStaticInvocation(StaticInvocation invocation) {
trace(invocation);
invocation.transformChildren(this);
Procedure target = invocation.target;
if (target == rtiLibrary.reifyFunction) {
/// Rewrite calls to reify(TypeLiteral) to a reified type.
TypeLiteral literal = invocation.arguments.positional.single;
return createRuntimeType(literal.type);
} else if (target.kind == ProcedureKind.Factory) {
// Intercept calls to factories of classes we do not transform
return interceptInstantiation(invocation, target);
}
addTypeArgumentToGenericInvocation(invocation);
return invocation;
}
Library visitLibrary(Library library) {
trace(library);
if (libraryShouldBeTransformed(library)) {
library.transformChildren(this);
}
return library;
}
Expression visitConstructorInvocation(ConstructorInvocation invocation) {
invocation.transformChildren(this);
return interceptInstantiation(invocation, invocation.target);
}
Member getStaticInvocationTarget(InvocationExpression invocation) {
if (invocation is ConstructorInvocation) {
return invocation.target;
} else if (invocation is StaticInvocation) {
return invocation.target;
} else {
throw "Unexpected InvocationExpression $invocation.";
}
}
bool isInstantiation(TreeNode invocation) {
return invocation is ConstructorInvocation ||
invocation is StaticInvocation &&
invocation.target.kind == ProcedureKind.Factory;
}
bool isTypeVariable(DartType type) => type is TypeParameterType;
/// Add the runtime type as an extra argument to constructor invocations.
Arguments visitArguments(Arguments arguments) {
trace(arguments);
arguments.transformChildren(this);
TreeNode parent = arguments.parent;
if (isInstantiation(parent)) {
Class targetClass = getEnclosingClass(getStaticInvocationTarget(parent));
// Do not add the extra argument if the class does not need a type member
// or if it can be implemented as a getter.
if (!needsTypeInformation(targetClass) || usesTypeGetter(targetClass)) {
return arguments;
}
List<DartType> typeArguments = arguments.types;
Expression type =
createRuntimeType(new InterfaceType(targetClass, typeArguments));
builder.insertAsFirstArgument(arguments, type);
}
return arguments;
}
Field visitField(Field field) {
trace(field);
visitDartType(field.type);
for (Expression annotation in field.annotations) {
annotation.accept(this);
}
// Do not visit initializers, they have already been transformed when the
// class was handled.
return field;
}
/// Go through all initializers of fields and record a static initializer
/// function, if necessary.
void rewriteFieldInitializers(Class cls) {
assert(context != null);
context.initializers = new LinkedHashMap<Field, Procedure>();
List<Field> fields = cls.fields;
bool initializerRewritten = false;
for (Field field in fields) {
if (!initializerRewritten && knowledge.usedParameters(field).isEmpty) {
// This field needs no static initializer.
continue;
}
Expression initializer = field.initializer;
if (initializer == null || field.isStatic) continue;
// Declare a new variable that holds the type information and can be
// used to access type variables in initializer context.
// TODO(karlklose): some fields do not need the parameter.
VariableDeclaration typeObject = new VariableDeclaration(r"$type");
context.parameter = typeObject;
context.inInitializer = true;
// Translate the initializer while keeping track of whether there was
// already an initializers that required type information in
// [typeVariableUsedInInitializer].
initializer = initializer.accept(this);
context.parameter = null;
context.inInitializer = false;
// Create a static initializer function from the translated initializer
// expression and record it.
Name name = new Name("\$init\$${field.name.name}");
Statement body = new ReturnStatement(initializer);
Procedure staticInitializer = new Procedure(
name,
ProcedureKind.Method,
new FunctionNode(body,
positionalParameters: <VariableDeclaration>[typeObject]),
isStatic: true,
fileUri: cls.fileUri);
context.initializers[field] = staticInitializer;
// Finally, remove the initializer from the field.
field.initializer = null;
}
}
bool inheritsTypeProperty(Class cls) {
assert(needsTypeInformation(cls));
Class superclass = cls.superclass;
return needsTypeInformation(superclass);
}
Class visitClass(Class cls) {
trace(cls);
if (needsTypeInformation(cls)) {
context = new TransformationContext();
List<TypeParameter> typeParameters = cls.typeParameters;
if (usesTypeGetter(cls)) {
assert(typeParameters.isEmpty);
context.runtimeTypeStorage = RuntimeTypeStorage.getter;
Member getter = builder.createGetter(rtiLibrary.runtimeTypeName,
createRuntimeType(cls.rawType), cls, rtiLibrary.typeType);
cls.addMember(getter);
} else if (!inheritsTypeProperty(cls)) {
context.runtimeTypeStorage = RuntimeTypeStorage.field;
// TODO(karlklose): should we add the field to [Object]?
context.runtimeTypeField = new Field(rtiLibrary.runtimeTypeName,
fileUri: cls.fileUri, isFinal: true, type: rtiLibrary.typeType);
cls.addMember(context.runtimeTypeField);
} else {
context.runtimeTypeStorage = RuntimeTypeStorage.inheritedField;
}
for (int i = 0; i < typeParameters.length; ++i) {
TypeParameter variable = typeParameters[i];
cls.addMember(builder.createTypeVariableGetter(cls, variable, i));
}
// Tag the class as supporting the runtime type getter.
InterfaceType interfaceTypeForSupertype =
new InterfaceType(rtiLibrary.markerClass);
cls.implementedTypes.add(new Supertype(
interfaceTypeForSupertype.classNode,
interfaceTypeForSupertype.typeArguments));
// Before transforming the parts of the class declaration, rewrite field
// initializers that use type variables (or that would be called after one
// that does) to static functions that can be used from constructors.
rewriteFieldInitializers(cls);
// Add properties for declaration tests.
for (Class test in knowledge.classTests) {
if (test == rtiLibrary.markerClass) continue;
Procedure tag = builder.createGetter(
builder.getTypeTestTagName(test),
new BoolLiteral(isSuperClass(test, cls)),
cls,
builder.coreTypes.boolClass.rawType);
cls.addMember(tag);
}
// Add a runtimeType getter.
if (!usesTypeGetter(cls) && !inheritsTypeProperty(cls)) {
cls.addMember(new Procedure(
new Name("runtimeType"),
ProcedureKind.Getter,
new FunctionNode(
new ReturnStatement(new DirectPropertyGet(
new ThisExpression(), context.runtimeTypeField)),
returnType: builder.coreTypes.typeClass.rawType),
fileUri: cls.fileUri));
}
}
cls.transformChildren(this);
// Add the static initializer functions. They have already been transformed.
if (context?.initializers != null) {
context.initializers.forEach((_, Procedure initializer) {
cls.addMember(initializer);
});
}
// TODO(karlklose): clear type arguments later, the order of class
// transformations otherwise influences the result.
// cls.typeParameters.clear();
context = null;
return cls;
}
// TODO(karlklose): replace with a structure that can answer also the question
// which tags must be overriden due to different values.
/// Returns `true` if [a] is a declaration used in a supertype of [b].
bool isSuperClass(Class a, Class b) {
if (b == null) return false;
if (a == b) return true;
if (isSuperClass(a, b.superclass)) {
return true;
}
Iterable<Class> interfaceClasses = b.implementedTypes
.map((Supertype type) => type.classNode)
.where((Class cls) => cls != rtiLibrary.markerClass);
return interfaceClasses
.any((Class declaration) => isSuperClass(a, declaration));
}
bool isConstructorOrFactory(TreeNode node) {
return isFactory(node) || node is Constructor;
}
bool isFactory(TreeNode node) {
return node is Procedure && node.kind == ProcedureKind.Factory;
}
bool needsParameterForRuntimeType(TreeNode node) {
if (!isConstructorOrFactory(node)) return false;
RuntimeTypeStorage access = context.runtimeTypeStorage;
assert(access != RuntimeTypeStorage.none);
return access == RuntimeTypeStorage.field ||
access == RuntimeTypeStorage.inheritedField;
}
FunctionNode visitFunctionNode(FunctionNode node) {
trace(node);
addTypeArgumentToGenericDeclaration(node);
// If we have a [TransformationContext] with a runtime type field and we
// translate a constructor or factory, we need a parameter that the code of
// initializers or the factory body can use to access type arguments.
// The parameter field in the context will be reset in the visit-method of
// the parent.
if (context != null && needsParameterForRuntimeType(node.parent)) {
assert(context.parameter == null);
// Create the parameter and insert it as the function's first parameter.
context.parameter = new VariableDeclaration(
rtiLibrary.runtimeTypeName.name,
type: rtiLibrary.typeType);
context.parameter.parent = node;
node.positionalParameters.insert(0, context.parameter);
node.requiredParameterCount++;
}
node.transformChildren(this);
return node;
}
SuperInitializer visitSuperInitializer(SuperInitializer initializer) {
initializer.transformChildren(this);
return addTypeAsArgument(initializer);
}
RedirectingInitializer visitRedirectingInitializer(
RedirectingInitializer initializer) {
initializer.transformChildren(this);
return addTypeAsArgument(initializer);
}
Procedure visitProcedure(Procedure procedure) {
trace(procedure);
transformList(procedure.annotations, this, procedure.parent);
// Visit the function body in a initializing context, if it is a factory.
context?.inInitializer = isFactory(procedure);
procedure.function?.accept(this);
context?.inInitializer = false;
context?.parameter = null;
return procedure;
}
Constructor visitConstructor(Constructor constructor) {
trace(constructor);
transformList(constructor.annotations, this, constructor);
if (constructor.function != null) {
constructor.function = constructor.function.accept(this);
constructor.function?.parent = constructor;
}
context?.inInitializer = true;
transformList(constructor.initializers, this, constructor);
context?.inInitializer = false;
if (context != null) {
if (context.runtimeTypeStorage == RuntimeTypeStorage.field) {
// Initialize the runtime type field with value given in the additional
// constructor parameter.
assert(context.parameter != null);
Initializer initializer = new FieldInitializer(
context.runtimeTypeField, new VariableGet(context.parameter));
initializer.parent = constructor;
constructor.initializers.insert(0, initializer);
}
if (context.initializers != null) {
// For each field that needed a static initializer function, initialize
// the field by calling the function.
List<Initializer> fieldInitializers = <Initializer>[];
context.initializers.forEach((Field field, Procedure initializer) {
assert(context.parameter != null);
Arguments argument =
new Arguments(<Expression>[new VariableGet(context.parameter)]);
fieldInitializers.add(new FieldInitializer(
field, new StaticInvocation(initializer, argument)));
});
constructor.initializers.insertAll(0, fieldInitializers);
}
context.parameter = null;
}
return constructor;
}
/// Returns `true` if the given type can be tested using type test tags.
///
/// This implies that there are no subtypes of the [type] that are not
/// transformed.
bool typeSupportsTagTest(InterfaceType type) {
return needsTypeInformation(type.classNode);
}
Expression visitIsExpression(IsExpression expression) {
trace(expression);
expression.transformChildren(this);
if (getEnclosingLibrary(expression) == rtiLibrary.interceptorsLibrary) {
// In the interceptor library we need actual is-checks at the moment.
return expression;
}
Expression target = expression.operand;
DartType type = expression.type;
if (type is InterfaceType && typeSupportsTagTest(type)) {
assert(knowledge.classTests.contains(type.classNode));
bool checkArguments =
type.typeArguments.any((DartType type) => type is! DynamicType);
Class declaration = type.classNode;
VariableDeclaration typeExpression =
new VariableDeclaration(null, initializer: createRuntimeType(type));
VariableDeclaration targetValue =
new VariableDeclaration(null, initializer: target);
Expression markerClassTest = new IsExpression(
new VariableGet(targetValue), rtiLibrary.markerClass.rawType);
Expression tagCheck = new PropertyGet(new VariableGet(targetValue),
builder.getTypeTestTagName(declaration));
Expression check = new LogicalExpression(markerClassTest, "&&", tagCheck);
if (checkArguments) {
// TODO(karlklose): support a direct argument check, we already checked
// the declaration.
Expression uninterceptedCheck = new Let(
typeExpression,
builder.createIsSubtypeOf(
new VariableGet(targetValue), new VariableGet(typeExpression),
targetHasTypeProperty: true));
check = new LogicalExpression(check, "&&", uninterceptedCheck);
}
return new Let(targetValue, check);
} else {
return builder.createIsSubtypeOf(target, createRuntimeType(type));
}
}
Expression visitListLiteral(ListLiteral node) {
trace(node);
node.transformChildren(this);
return builder.callAttachType(
node,
new InterfaceType(
builder.coreTypes.listClass, <DartType>[node.typeArgument]));
}
Expression visitMapLiteral(MapLiteral node) {
trace(node);
node.transformChildren(this);
return builder.callAttachType(
node,
new InterfaceType(builder.coreTypes.mapClass,
<DartType>[node.keyType, node.valueType]));
}
Expression visitMethodInvocation(MethodInvocation node) {
node.transformChildren(this);
addTypeArgumentToGenericInvocation(node);
return node;
}
bool isGenericMethod(FunctionNode node) {
if (node.parent is Member) {
Member member = node.parent;
if (member is Constructor ||
member is Procedure && member.kind == ProcedureKind.Factory) {
return member.enclosingClass.typeParameters.length <
node.typeParameters.length;
}
}
return node.typeParameters.isNotEmpty;
}
void addTypeArgumentToGenericInvocation(InvocationExpression expression) {
if (expression.arguments.types.length > 0) {
ListLiteral genericMethodTypeParameters = new ListLiteral(
expression.arguments.types
.map(createRuntimeType)
.toList(growable: false),
typeArgument: rtiLibrary.typeType);
expression.arguments.named.add(new NamedExpression(
genericMethodTypeParametersName, genericMethodTypeParameters)
..parent = expression.arguments);
}
}
void addTypeArgumentToGenericDeclaration(FunctionNode node) {
if (isGenericMethod(node)) {
VariableDeclaration genericMethodTypeParameters = new VariableDeclaration(
genericMethodTypeParametersName,
type: new InterfaceType(
builder.coreTypes.listClass, <DartType>[rtiLibrary.typeType]));
genericMethodTypeParameters.parent = node;
node.namedParameters.insert(0, genericMethodTypeParameters);
}
}
}