Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(97)

Side by Side Diff: pkg/kernel/lib/transformations/reify/transformation/transformer.dart

Issue 2697873007: Merge the work on Generic Types Reification from 'dart-lang/reify' repo (Closed)
Patch Set: Get back parameter erroneously removed by previous commit Created 3 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file.
4
5 library kernel.transformations.reify.transformation.transformer;
6
7 import '../analysis/program_analysis.dart';
8 import 'package:kernel/ast.dart';
9 import 'binding.dart' show RuntimeLibrary;
10 import 'builder.dart' show RuntimeTypeSupportBuilder;
11 import 'dart:collection' show LinkedHashMap;
12 import '../asts.dart';
13
14 export 'binding.dart' show RuntimeLibrary;
15 export 'builder.dart' show RuntimeTypeSupportBuilder;
16
17 enum RuntimeTypeStorage {
18 none,
19 inheritedField,
20 field,
21 getter,
22 }
23
24 class TransformationContext {
25 /// Describes how the runtime type is stored on the object.
26 RuntimeTypeStorage runtimeTypeStorage;
27
28 /// Field added to store the runtime type if [runtimeType] is
29 /// [RuntimeTypeStorage.field].
30 Field runtimeTypeField;
31
32 /// The parameter for the type information introduced to the constructor or
33 /// to static initializers.
34 VariableDeclaration parameter;
35
36 /// A ordered collection of fields together with their initializers rewritten
37 /// to static initializer functions that can be used in the constructor's
38 /// initializer list.
39 /// The order is important because of possible side-effects in the
40 /// initializers.
41 LinkedHashMap<Field, Procedure> initializers;
42
43 // `true` if the visitor currently is in a field initializer, a initializer
44 // list of a constructor, or the body of a factory method. In these cases,
45 // type argument access is different than in an instance context, since `this`
46 // is not available.
47 bool inInitializer = false;
48
49 String toString() => "s: ${runtimeTypeStorage} f: $runtimeTypeField,"
50 " p: $parameter, i: $inInitializer";
51 }
52
53 abstract class DebugTrace {
54 static const bool debugTrace = false;
55
56 static const int lineLength = 80;
57
58 TransformationContext get context;
59
60 String getNodeLevel(TreeNode node) {
61 String level = "";
62 while (node != null && node is! Library) {
63 level = " $level";
64 node = node.parent;
65 }
66 return level;
67 }
68
69 String shorten(String s) {
70 return s.length > lineLength ? s.substring(0, lineLength) : s;
71 }
72
73 void trace(TreeNode node) {
74 if (debugTrace) {
75 String nodeText = node.toString().replaceAll("\n", " ");
76 print(shorten("trace:${getNodeLevel(node)}$context"
77 " [${node.runtimeType}] $nodeText"));
78 }
79 }
80 }
81
82 /// Rewrites a tree to remove generic types and runtime type checks and replace
83 /// them with Dart objects.
84 ///
85 /// Runtime types are stored in a field/getter called [runtimeTypeName] on the
86 /// object, which for parameterized classes is initialized in the constructor.
87 // TODO(karlklose):
88 // - add a scoped namer
89 // - rewrite types (supertypes, implemented types)
90 // - rewrite as
91 class ReifyVisitor extends Transformer with DebugTrace {
92 final RuntimeLibrary rtiLibrary;
93 final RuntimeTypeSupportBuilder builder;
94 final ProgramKnowledge knowledge;
95
96 ReifyVisitor(this.rtiLibrary, this.builder, this.knowledge,
97 [this.libraryToTransform]);
98
99 /// If not null, the transformation will only be applied to classes declared
100 /// in this library.
101 final Library libraryToTransform;
102
103 // TODO(karlklose): find a way to get rid of this state in the visitor.
104 TransformationContext context;
105
106 bool libraryShouldBeTransformed(Library library) {
107 return libraryToTransform == null || libraryToTransform == library;
108 }
109
110 bool needsTypeInformation(Class cls) {
111 return !isObject(cls) &&
112 !rtiLibrary.contains(cls) &&
113 libraryShouldBeTransformed(cls.enclosingLibrary);
114 }
115
116 bool usesTypeGetter(Class cls) {
117 return cls.typeParameters.isEmpty;
118 }
119
120 bool isObject(Class cls) {
121 // TODO(karlklose): use [CoreTypes].
122 return "$cls" == 'dart.core::Object';
123 }
124
125 Initializer addTypeAsArgument(initializer) {
126 assert(initializer is SuperInitializer ||
127 initializer is RedirectingInitializer);
128 Class cls = getEnclosingClass(initializer.target);
129 if (needsTypeInformation(cls) && !usesTypeGetter(cls)) {
130 // If the current class uses a getter for type information, we did not add
131 // a parameter to the constructor, but we can pass `null` as the value to
132 // initialize the type field, since it will be shadowed by the getter.
133 Expression type = (context.parameter != null)
134 ? new VariableGet(context.parameter)
135 : new NullLiteral();
136 builder.insertAsFirstArgument(initializer.arguments, type);
137 }
138 return initializer;
139 }
140
141 Expression interceptInstantiation(
142 InvocationExpression invocation, Member target) {
143 Class targetClass = target.parent;
144 Library targetLibrary = targetClass.parent;
145 Library currentLibrary = getEnclosingLibrary(invocation);
146 if (libraryShouldBeTransformed(currentLibrary) &&
147 !libraryShouldBeTransformed(targetLibrary) &&
148 !rtiLibrary.contains(target)) {
149 return builder.attachTypeToConstructorInvocation(invocation, target);
150 }
151 return invocation;
152 }
153
154 Expression createRuntimeType(DartType type) {
155 if (context?.inInitializer == true) {
156 // In initializer context, the instance type is provided in
157 // `context.parameter` as there is no `this`.
158 return builder.createRuntimeType(type, typeContext: context.parameter);
159 } else {
160 return builder.createRuntimeType(type);
161 }
162 }
163
164 TreeNode defaultTreeNode(TreeNode node) {
165 trace(node);
166 return super.defaultTreeNode(node);
167 }
168
169 Expression visitStaticInvocation(StaticInvocation invocation) {
170 trace(invocation);
171
172 invocation.transformChildren(this);
173
174 Procedure target = invocation.target;
175 if (target == rtiLibrary.reifyFunction) {
176 /// Rewrite calls to reify(TypeLiteral) to a reified type.
177 TypeLiteral literal = invocation.arguments.positional.single;
178 return createRuntimeType(literal.type);
179 } else if (target.kind == ProcedureKind.Factory) {
180 // Intercept calls to factories of classes we do not transform
181 return interceptInstantiation(invocation, target);
182 }
183 return invocation;
184 }
185
186 Library visitLibrary(Library library) {
187 trace(library);
188
189 if (libraryShouldBeTransformed(library)) {
190 library.transformChildren(this);
191 }
192 return library;
193 }
194
195 Expression visitConstructorInvocation(ConstructorInvocation invocation) {
196 invocation.transformChildren(this);
197 return interceptInstantiation(invocation, invocation.target);
198 }
199
200 Member getStaticInvocationTarget(InvocationExpression invocation) {
201 if (invocation is ConstructorInvocation) {
202 return invocation.target;
203 } else if (invocation is StaticInvocation) {
204 return invocation.target;
205 } else {
206 throw "Unexpected InvocationExpression $invocation.";
207 }
208 }
209
210 bool isInstantiation(TreeNode invocation) {
211 return invocation is ConstructorInvocation ||
212 invocation is StaticInvocation &&
213 invocation.target.kind == ProcedureKind.Factory;
214 }
215
216 bool isTypeVariable(DartType type) => type is TypeParameterType;
217
218 /// Add the runtime type as an extra argument to constructor invocations.
219 Arguments visitArguments(Arguments arguments) {
220 trace(arguments);
221
222 arguments.transformChildren(this);
223 TreeNode parent = arguments.parent;
224 if (isInstantiation(parent)) {
225 Class targetClass = getEnclosingClass(getStaticInvocationTarget(parent));
226 // Do not add the extra argument if the class does not need a type member
227 // or if it can be implemented as a getter.
228 if (!needsTypeInformation(targetClass) || usesTypeGetter(targetClass)) {
229 return arguments;
230 }
231
232 List<DartType> typeArguments = arguments.types;
233
234 Expression type =
235 createRuntimeType(new InterfaceType(targetClass, typeArguments));
236
237 builder.insertAsFirstArgument(arguments, type);
238 }
239 return arguments;
240 }
241
242 Field visitField(Field field) {
243 trace(field);
244
245 visitDartType(field.type);
246 for (Expression annotation in field.annotations) {
247 annotation.accept(this);
248 }
249 // Do not visit initializers, they have already been transformed when the
250 // class was handled.
251 return field;
252 }
253
254 /// Go through all initializers of fields and record a static initializer
255 /// function, if necessary.
256 void rewriteFieldInitializers(Class cls) {
257 assert(context != null);
258 context.initializers = new LinkedHashMap<Field, Procedure>();
259 List<Field> fields = cls.fields;
260 bool initializerRewritten = false;
261 for (Field field in fields) {
262 if (!initializerRewritten && knowledge.usedParameters(field).isEmpty) {
263 // This field needs no static initializer.
264 continue;
265 }
266
267 Expression initializer = field.initializer;
268 if (initializer == null || field.isStatic) continue;
269 // Declare a new variable that holds the type information and can be
270 // used to access type variables in initializer context.
271 // TODO(karlklose): some fields do not need the parameter.
272 VariableDeclaration typeObject = new VariableDeclaration(r"$type");
273 context.parameter = typeObject;
274 context.inInitializer = true;
275 // Translate the initializer while keeping track of whether there was
276 // already an initializers that required type information in
277 // [typeVariableUsedInInitializer].
278 initializer = initializer.accept(this);
279 context.parameter = null;
280 context.inInitializer = false;
281 // Create a static initializer function from the translated initializer
282 // expression and record it.
283 Name name = new Name("\$init\$${field.name.name}");
284 Statement body = new ReturnStatement(initializer);
285 Procedure staticInitializer = new Procedure(
286 name,
287 ProcedureKind.Method,
288 new FunctionNode(body,
289 positionalParameters: <VariableDeclaration>[typeObject]),
290 isStatic: true,
291 fileUri: cls.fileUri);
292 context.initializers[field] = staticInitializer;
293 // Finally, remove the initializer from the field.
294 field.initializer = null;
295 }
296 }
297
298 bool inheritsTypeProperty(Class cls) {
299 assert(needsTypeInformation(cls));
300 Class superclass = cls.superclass;
301 return needsTypeInformation(superclass);
302 }
303
304 Class visitClass(Class cls) {
305 trace(cls);
306
307 if (needsTypeInformation(cls)) {
308 context = new TransformationContext();
309 List<TypeParameter> typeParameters = cls.typeParameters;
310 if (usesTypeGetter(cls)) {
311 assert(typeParameters.isEmpty);
312 context.runtimeTypeStorage = RuntimeTypeStorage.getter;
313 Member getter = builder.createGetter(rtiLibrary.runtimeTypeName,
314 createRuntimeType(cls.rawType), cls, rtiLibrary.typeType);
315 cls.addMember(getter);
316 } else if (!inheritsTypeProperty(cls)) {
317 context.runtimeTypeStorage = RuntimeTypeStorage.field;
318 // TODO(karlklose): should we add the field to [Object]?
319 context.runtimeTypeField = new Field(rtiLibrary.runtimeTypeName,
320 fileUri: cls.fileUri, isFinal: true, type: rtiLibrary.typeType);
321 cls.addMember(context.runtimeTypeField);
322 } else {
323 context.runtimeTypeStorage = RuntimeTypeStorage.inheritedField;
324 }
325
326 for (int i = 0; i < typeParameters.length; ++i) {
327 TypeParameter variable = typeParameters[i];
328 cls.addMember(builder.createTypeVariableGetter(cls, variable, i));
329 }
330
331 // Tag the class as supporting the runtime type getter.
332 InterfaceType interfaceTypeForSupertype =
333 new InterfaceType(rtiLibrary.markerClass);
334 cls.implementedTypes.add(new Supertype(
335 interfaceTypeForSupertype.classNode,
336 interfaceTypeForSupertype.typeArguments));
337
338 // Before transforming the parts of the class declaration, rewrite field
339 // initializers that use type variables (or that would be called after one
340 // that does) to static functions that can be used from constructors.
341 rewriteFieldInitializers(cls);
342
343 // Add properties for declaration tests.
344 for (Class test in knowledge.classTests) {
345 if (test == rtiLibrary.markerClass) continue;
346
347 Procedure tag = builder.createGetter(
348 builder.getTypeTestTagName(test),
349 new BoolLiteral(isSuperClass(test, cls)),
350 cls,
351 builder.coreTypes.boolClass.rawType);
352 cls.addMember(tag);
353 }
354
355 // Add a runtimeType getter.
356 if (!usesTypeGetter(cls) && !inheritsTypeProperty(cls)) {
357 cls.addMember(new Procedure(
358 new Name("runtimeType"),
359 ProcedureKind.Getter,
360 new FunctionNode(
361 new ReturnStatement(new DirectPropertyGet(
362 new ThisExpression(), context.runtimeTypeField)),
363 returnType: builder.coreTypes.typeClass.rawType),
364 fileUri: cls.fileUri));
365 }
366 }
367
368 cls.transformChildren(this);
369
370 // Add the static initializer functions. They have already been transformed.
371 if (context?.initializers != null) {
372 context.initializers.forEach((_, Procedure initializer) {
373 cls.addMember(initializer);
374 });
375 }
376
377 // TODO(karlklose): clear type arguments later, the order of class
378 // transformations otherwise influences the result.
379 // cls.typeParameters.clear();
380 context = null;
381 return cls;
382 }
383
384 // TODO(karlklose): replace with a structure that can answer also the question
385 // which tags must be overriden due to different values.
386 /// Returns `true` if [a] is a declaration used in a supertype of [b].
387 bool isSuperClass(Class a, Class b) {
388 if (b == null) return false;
389 if (a == b) return true;
390
391 if (isSuperClass(a, b.superclass)) {
392 return true;
393 }
394
395 Iterable<Class> interfaceClasses = b.implementedTypes
396 .map((Supertype type) => type.classNode)
397 .where((Class cls) => cls != rtiLibrary.markerClass);
398 return interfaceClasses
399 .any((Class declaration) => isSuperClass(a, declaration));
400 }
401
402 bool isConstructorOrFactory(TreeNode node) {
403 return isFactory(node) || node is Constructor;
404 }
405
406 bool isFactory(TreeNode node) {
407 return node is Procedure && node.kind == ProcedureKind.Factory;
408 }
409
410 bool needsParameterForRuntimeType(TreeNode node) {
411 if (!isConstructorOrFactory(node)) return false;
412
413 RuntimeTypeStorage access = context.runtimeTypeStorage;
414 assert(access != RuntimeTypeStorage.none);
415 return access == RuntimeTypeStorage.field ||
416 access == RuntimeTypeStorage.inheritedField;
417 }
418
419 FunctionNode visitFunctionNode(FunctionNode node) {
420 trace(node);
421
422 // If we have a [TransformationContext] with a runtime type field and we
423 // translate a constructor or factory, we need a parameter that the code of
424 // initializers or the factory body can use to access type arguments.
425 // The parameter field in the context will be reset in the visit-method of
426 // the parent.
427 if (context != null && needsParameterForRuntimeType(node.parent)) {
428 assert(context.parameter == null);
429 // Create the parameter and insert it as the function's first parameter.
430 context.parameter = new VariableDeclaration(
431 rtiLibrary.runtimeTypeName.name,
432 type: rtiLibrary.typeType);
433 context.parameter.parent = node;
434 node.positionalParameters.insert(0, context.parameter);
435 node.requiredParameterCount++;
436 }
437 node.transformChildren(this);
438 return node;
439 }
440
441 SuperInitializer visitSuperInitializer(SuperInitializer initializer) {
442 initializer.transformChildren(this);
443 return addTypeAsArgument(initializer);
444 }
445
446 RedirectingInitializer visitRedirectingInitializer(
447 RedirectingInitializer initializer) {
448 initializer.transformChildren(this);
449 return addTypeAsArgument(initializer);
450 }
451
452 Procedure visitProcedure(Procedure procedure) {
453 trace(procedure);
454
455 transformList(procedure.annotations, this, procedure.parent);
456 // Visit the function body in a initializing context, if it is a factory.
457 context?.inInitializer = isFactory(procedure);
458 procedure.function?.accept(this);
459 context?.inInitializer = false;
460
461 context?.parameter = null;
462 return procedure;
463 }
464
465 Constructor visitConstructor(Constructor constructor) {
466 trace(constructor);
467
468 transformList(constructor.annotations, this, constructor);
469 if (constructor.function != null) {
470 constructor.function = constructor.function.accept(this);
471 constructor.function?.parent = constructor;
472 }
473
474 context?.inInitializer = true;
475 transformList(constructor.initializers, this, constructor);
476 context?.inInitializer = false;
477
478 if (context != null) {
479 if (context.runtimeTypeStorage == RuntimeTypeStorage.field) {
480 // Initialize the runtime type field with value given in the additional
481 // constructor parameter.
482 assert(context.parameter != null);
483 Initializer initializer = new FieldInitializer(
484 context.runtimeTypeField, new VariableGet(context.parameter));
485 initializer.parent = constructor;
486 constructor.initializers.insert(0, initializer);
487 }
488 if (context.initializers != null) {
489 // For each field that needed a static initializer function, initialize
490 // the field by calling the function.
491 List<Initializer> fieldInitializers = <Initializer>[];
492 context.initializers.forEach((Field field, Procedure initializer) {
493 assert(context.parameter != null);
494 Arguments argument =
495 new Arguments(<Expression>[new VariableGet(context.parameter)]);
496 fieldInitializers.add(new FieldInitializer(
497 field, new StaticInvocation(initializer, argument)));
498 });
499 constructor.initializers.insertAll(0, fieldInitializers);
500 }
501 context.parameter = null;
502 }
503
504 return constructor;
505 }
506
507 /// Returns `true` if the given type can be tested using type test tags.
508 ///
509 /// This implies that there are no subtypes of the [type] that are not
510 /// transformed.
511 bool typeSupportsTagTest(InterfaceType type) {
512 return needsTypeInformation(type.classNode);
513 }
514
515 Expression visitIsExpression(IsExpression expression) {
516 trace(expression);
517
518 expression.transformChildren(this);
519
520 if (getEnclosingLibrary(expression) == rtiLibrary.interceptorsLibrary) {
521 // In the interceptor library we need actual is-checks at the moment.
522 return expression;
523 }
524
525 Expression target = expression.operand;
526 DartType type = expression.type;
527
528 if (type is InterfaceType && typeSupportsTagTest(type)) {
529 assert(knowledge.classTests.contains(type.classNode));
530 bool checkArguments =
531 type.typeArguments.any((DartType type) => type is! DynamicType);
532 Class declaration = type.classNode;
533 VariableDeclaration typeExpression =
534 new VariableDeclaration(null, initializer: createRuntimeType(type));
535 VariableDeclaration targetValue =
536 new VariableDeclaration(null, initializer: target);
537 Expression markerClassTest = new IsExpression(
538 new VariableGet(targetValue), rtiLibrary.markerClass.rawType);
539 Expression tagCheck = new PropertyGet(new VariableGet(targetValue),
540 builder.getTypeTestTagName(declaration));
541 Expression check = new LogicalExpression(markerClassTest, "&&", tagCheck);
542 if (checkArguments) {
543 // TODO(karlklose): support a direct argument check, we already checked
544 // the declaration.
545 Expression uninterceptedCheck = new Let(
546 typeExpression,
547 builder.createIsSubtypeOf(
548 new VariableGet(targetValue), new VariableGet(typeExpression),
549 targetHasTypeProperty: true));
550 check = new LogicalExpression(check, "&&", uninterceptedCheck);
551 }
552 return new Let(targetValue, check);
553 } else {
554 return builder.createIsSubtypeOf(target, createRuntimeType(type));
555 }
556 }
557
558 Expression visitListLiteral(ListLiteral node) {
559 trace(node);
560 node.transformChildren(this);
561 return builder.callAttachType(
562 node,
563 new InterfaceType(
564 builder.coreTypes.listClass, <DartType>[node.typeArgument]));
565 }
566
567 Expression visitMapLiteral(MapLiteral node) {
568 trace(node);
569 node.transformChildren(this);
570 return builder.callAttachType(
571 node,
572 new InterfaceType(builder.coreTypes.mapClass,
573 <DartType>[node.keyType, node.valueType]));
574 }
575 }
OLDNEW
« no previous file with comments | « pkg/kernel/lib/transformations/reify/transformation/remove_generics.dart ('k') | pkg/kernel/runtime/reify/README.md » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698