Index: pkg/kernel/lib/transformations/reify/transformation/transformer.dart |
diff --git a/pkg/kernel/lib/transformations/reify/transformation/transformer.dart b/pkg/kernel/lib/transformations/reify/transformation/transformer.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..867248510fc833bc62779501b704cf075f34d517 |
--- /dev/null |
+++ b/pkg/kernel/lib/transformations/reify/transformation/transformer.dart |
@@ -0,0 +1,575 @@ |
+// 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; |
+ |
+ 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); |
+ } |
+ 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); |
+ |
+ // 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])); |
+ } |
+} |