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

Side by Side Diff: pkg/smoke/lib/codegen/recorder.dart

Issue 204143002: Changes in smoke in preparation of polymer's codegen: (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 6 years, 9 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 | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2014, 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 /// Records accesses to Dart program declarations and generates code that will
6 /// allow to do the same accesses at runtime using `package:smoke/static.dart`.
7 /// Internally, this library relies on the `analyzer` to extract data from the
8 /// program, and then uses [SmokeCodeGenerator] to produce the code needed by
9 /// the smoke system.
10 ///
11 /// This library only uses the analyzer to consume data previously produced by
12 /// running the resolver. This library does not provide any hooks to integrate
13 /// running the analyzer itself. See `package:code_transformers` to integrate
14 /// the analyzer into pub transformers.
15 library smoke.codegen.recorder;
16
17 import 'package:analyzer/src/generated/element.dart';
18 import 'package:analyzer/src/generated/ast.dart';
19 import 'generator.dart';
20
21 typedef String ImportUrlResolver(LibraryElement lib);
22
23 /// A recorder that tracks how elements are accessed in order to generate code
24 /// that replicates those accesses with the smoke runtime.
25 class Recorder {
26 /// Underlying code generator.
27 SmokeCodeGenerator generator;
28
29 /// Function that provides the import url for a library element. This may
30 /// internally use the resolver to resolve the import url.
31 ImportUrlResolver importUrlFor;
32
33 Recorder(this.generator, this.importUrlFor);
34
35 /// Stores mixins that have been recorded and associates a type identifier
36 /// with them. Mixins don't have an associated identifier in the code, so we
37 /// generate a unique identifier for them and use it throughout the code.
38 Map<TypeIdentifier, Map<ClassElement, TypeIdentifier>> _mixins = {};
39
40 /// Adds the superclass information of [type] (including intermediate mixins).
41 /// This will not generate data for direct subtypes of Object, as that is
42 /// considered redundant information.
43 void lookupParent(ClassElement type) {
44 var parent = type.supertype;
45 var mixins = type.mixins;
46 if (parent == null && mixins.isEmpty) return; // type is Object
47 var baseType = parent.element;
48 var baseId = _typeFor(baseType);
49 if (mixins.isNotEmpty) {
50 _mixins.putIfAbsent(baseId, () => {});
51 for (var m in mixins) {
52 var mixinType = m.element;
53 var mixinId = _mixins[baseId].putIfAbsent(mixinType, () {
54 var comment = '${baseId.name} & ${mixinType.name}';
55 return generator.createMixinType(comment);
56 });
57 if (!baseType.type.isObject) generator.addParent(mixinId, baseId);
58 baseType = mixinType;
59 baseId = mixinId;
60 _mixins.putIfAbsent(mixinId, () => {});
61 }
62 }
63 if (!baseType.type.isObject) generator.addParent(_typeFor(type), baseId);
64 }
65
66 TypeIdentifier _typeFor(Element type) => new TypeIdentifier(
67 type.library == null ? 'dart:core' : importUrlFor(type.library),
68 type.displayName);
69
70 /// Adds any declaration and superclass information that is needed to answer a
71 /// query on [type] that matches [options]. Also adds symbols, getters, and
72 /// setters if [includeAccessors] is true.
73 void runQuery(ClassElement type, QueryOptions options,
74 {bool includeAccessors: true}) {
75 if (type.type.isObject) return; // We don't include Object in query results.
76 var id = _typeFor(type);
77 var parent = type.supertype != null ? type.supertype.element : null;
78 if (options.includeInherited && parent != null &&
79 parent != options.includeUpTo) {
80 lookupParent(type);
81 runQuery(parent, options, includeAccessors: includeAccessors);
82 var parentId = _typeFor(parent);
83 for (var m in type.mixins) {
84 var mixinClass = m.element;
85 var mixinId = _mixins[parentId][mixinClass];
86 _runQueryInternal(mixinClass, mixinId, options, includeAccessors);
87 parentId = mixinId;
88 }
89 }
90 _runQueryInternal(type, id, options, includeAccessors);
91 }
92
93 /// Helper for [runQuery]. This runs the query only on a specific [type],
94 /// which could be a class or a mixin labeled by [id].
95 // TODO(sigmund): currently we materialize mixins in smoke/static.dart,
96 // we should consider to include the mixin declaration information directly,
97 // and remove the duplication we have for mixins today.
98 void _runQueryInternal(ClassElement type, TypeIdentifier id,
99 QueryOptions options, bool includeAccessors) {
100
101 skipBecauseOfAnnotations(Element e) {
102 if (options.withAnnotations == null) return false;
103 return !_matchesAnnotation(e.metadata, options.withAnnotations);
104 }
105
106 if (options.includeFields) {
107 for (var f in type.fields) {
108 if (f.isStatic) continue;
109 if (f.isSynthetic) continue; // exclude getters
110 if (options.excludeFinal && f.isFinal) continue;
111 var name = f.displayName;
112 if (options.matches != null && !options.matches(name)) continue;
113 if (skipBecauseOfAnnotations(f)) continue;
114 generator.addDeclaration(id, name, _typeFor(f.type.element),
115 isField: true, isFinal: f.isFinal,
116 annotations: _copyAnnotations(f));
117 if (includeAccessors) _addAccessors(name, !f.isFinal);
118 }
119 }
120
121 if (options.includeProperties) {
122 for (var a in type.accessors) {
123 if (a is! PropertyAccessorElement) continue;
124 if (a.isStatic || !a.isGetter) continue;
125 var v = a.variable;
126 if (v is FieldElement && !v.isSynthetic) continue; // exclude fields
127 if (options.excludeFinal && v.isFinal) continue;
128 var name = v.displayName;
129 if (options.matches != null && !options.matches(name)) continue;
130 if (skipBecauseOfAnnotations(a)) continue;
131 generator.addDeclaration(id, name, _typeFor(a.type.returnType.element),
132 isProperty: true, isFinal: v.isFinal,
133 annotations: _copyAnnotations(a));
134 if (includeAccessors) _addAccessors(name, !v.isFinal);
135 }
136 }
137
138 if (options.includeMethods) {
139 for (var m in type.methods) {
140 if (m.isStatic) continue;
141 var name = m.displayName;
142 if (options.matches != null && !options.matches(name)) continue;
143 if (skipBecauseOfAnnotations(m)) continue;
144 generator.addDeclaration(id, name,
145 new TypeIdentifier('dart:core', 'Function'), isMethod: true,
146 annotations: _copyAnnotations(m));
147 if (includeAccessors) _addAccessors(name, false);
148 }
149 }
150 }
151
152 /// Adds the declaration of [name] if it was found in [type]. If [recursive]
153 /// is true, then we continue looking up [name] in the parent classes until we
154 /// find it or we reach Object. Returns whether the declaration was found.
155 /// When a declaration is found, add also a symbol, getter, and setter if
156 /// [includeAccessors] is true.
157 bool lookupMember(ClassElement type, String name, {bool recursive: false,
158 bool includeAccessors: true}) =>
159 _lookupMemberInternal(type, _typeFor(type), name, recursive,
160 includeAccessors);
161
162 /// Helper for [lookupMember] that walks up the type hierarchy including mixin
163 /// classes.
164 bool _lookupMemberInternal(ClassElement type, TypeIdentifier id, String name,
165 bool recursive, bool includeAccessors) {
166 // Exclude members from [Object].
167 if (type.type.isObject) return false;
168 generator.addEmptyDeclaration(id);
169 for (var f in type.fields) {
170 if (f.displayName != name) continue;
171 if (f.isSynthetic) continue; // exclude getters
172 generator.addDeclaration(id, name,
173 _typeFor(f.type.element), isField: true, isFinal: f.isFinal,
174 isStatic: f.isStatic, annotations: _copyAnnotations(f));
175 if (includeAccessors) _addAccessors(name, !f.isFinal);
176 return true;
177 }
178
179 for (var a in type.accessors) {
180 if (a is! PropertyAccessorElement) continue;
181 // TODO(sigmund): support setters without getters.
182 if (!a.isGetter) continue;
183 if (a.displayName != name) continue;
184 var v = a.variable;
185 if (v is FieldElement && !v.isSynthetic) continue; // exclude fields
186 generator.addDeclaration(id, name,
187 _typeFor(a.type.returnType.element), isProperty: true,
188 isFinal: v.isFinal, isStatic: a.isStatic,
189 annotations: _copyAnnotations(a));
190 if (includeAccessors) _addAccessors(name, !v.isFinal);
191 return true;
192 }
193
194 for (var m in type.methods) {
195 if (m.displayName != name) continue;
196 generator.addDeclaration(id, name,
197 new TypeIdentifier('dart:core', 'Function'), isMethod: true,
198 isStatic: m.isStatic, annotations: _copyAnnotations(m));
199 if (includeAccessors) _addAccessors(name, false);
200 return true;
201 }
202
203 if (recursive) {
204 lookupParent(type);
205 var parent = type.supertype != null ? type.supertype.element : null;
206 if (parent == null) return false;
207 var parentId = _typeFor(parent);
208 for (var m in type.mixins) {
209 var mixinClass = m.element;
210 var mixinId = _mixins[parentId][mixinClass];
211 if (_lookupMemberInternal(mixinClass, mixinId, name, false,
212 includeAccessors)) {
213 return true;
214 }
215 parentId = mixinId;
216 }
217 return _lookupMemberInternal(parent, parentId, name, true,
218 includeAccessors);
219 }
220 return false;
221 }
222
223
224 /// Adds [name] as a symbol, a getter, and optionally a setter in [generator].
225 _addAccessors(String name, bool includeSetter) {
226 generator.addSymbol(name);
227 generator.addGetter(name);
228 if (includeSetter) generator.addSetter(name);
229 }
230
231 /// Copy metadata associated with the declaration of [target].
232 List<ConstExpression> _copyAnnotations(Element target) {
233 var node = target.node;
234 // [node] is the initialization expression, we walk up to get to the actual
235 // member declaration where the metadata is attached to.
236 while (node is! ClassMember) node = node.parent;
237 return node.metadata.map(_convertAnnotation).toList();
238 }
239
240 /// Converts annotations into [ConstExpression]s supported by the codegen
241 /// library.
242 ConstExpression _convertAnnotation(Annotation annotation) {
243 var element = annotation.element;
244 if (element is ConstructorElement) {
245 if (!element.name.isEmpty) {
246 throw new UnimplementedError(
247 'named constructors are not implemented in smoke.codegen.recorder');
248 }
249
250 var positionalArgs = [];
251 for (var arg in annotation.arguments.arguments) {
252 if (arg is NamedExpression) {
253 throw new UnimplementedError(
254 'named arguments in constructors are not implemented in '
255 'smoke.codegen.recorder');
256 }
257 positionalArgs.add(_convertExpression(arg));
258 }
259
260 return new ConstructorExpression(importUrlFor(element.library),
261 element.enclosingElement.name, positionalArgs, const {});
262 }
263
264 if (element is PropertyAccessorElement) {
265 return new TopLevelIdentifier(
266 importUrlFor(element.library), element.name);
267 }
268
269 throw new UnsupportedError('unsupported annotation $annotation');
270 }
271
272 /// Converts [expression] into a [ConstExpression].
273 ConstExpression _convertExpression(Expression expression) {
274 if (expression is StringLiteral) {
275 return new ConstExpression.string(expression.stringValue);
276 }
277
278 if (expression is BooleanLiteral || expression is DoubleLiteral ||
279 expression is IntegerLiteral || expression is NullLiteral) {
280 return new CodeAsConstExpression("${(expression as dynamic).value}");
281 }
282
283 if (expression is Identifier) {
284 var element = expression.bestElement;
285 if (element == null || !element.isPublic) {
286 throw new UnsupportedError('private constants are not supported');
287 }
288
289 var url = importUrlFor(element.library);
290 if (element is ClassElement) {
291 return new TopLevelIdentifier(url, element.name);
292 }
293
294 if (element is PropertyAccessorElement) {
295 var variable = element.variable;
296 if (variable is FieldElement) {
297 var cls = variable.enclosingElement;
298 return new TopLevelIdentifier(url, '${cls.name}.${variable.name}');
299 } else if (variable is TopLevelVariableElement) {
300 return new TopLevelIdentifier(url, variable.name);
301 }
302 }
303 }
304
305 throw new UnimplementedError('expression convertion not implemented in '
306 'smoke.codegen.recorder (${expression.runtimeType} $expression)');
307 }
308 }
309
310 /// Returns whether [metadata] contains any annotation that is either equal to
311 /// an annotation in [queryAnnotations] or whose type is a subclass of a type
312 /// listed in [queryAnnotations]. This is equivalent to the check done in
313 /// `src/common.dart#matchesAnnotation`, except that this is applied to
314 /// static metadata as it was provided by the analyzer.
315 bool _matchesAnnotation(Iterable<ElementAnnotation> metadata,
316 Iterable<Element> queryAnnotations) {
317 for (var meta in metadata) {
318 var element = meta.element;
319 var exp;
320 var type;
321 if (element is PropertyAccessorElement) {
322 exp = element.variable;
323 type = exp.evaluationResult.value.type;
324 } else if (element is ConstructorElement) {
325 exp = element;
326 type = element.enclosingElement.type;
327 } else {
328 throw new UnimplementedError('Unsupported annotation: ${meta}');
329 }
330 for (var queryMeta in queryAnnotations) {
331 if (exp == queryMeta) return true;
332 if (queryMeta is ClassElement && type.isSubtypeOf(queryMeta.type)) {
333 return true;
334 }
335 }
336 }
337 return false;
338 }
339
340 /// Options equivalent to `smoke.dart#QueryOptions`, except that type
341 /// information and annotations are denoted by resolver's elements.
342 class QueryOptions {
343 /// Whether to include fields (default is true).
344 final bool includeFields;
345
346 /// Whether to include getters and setters (default is true). Note that to
347 /// include fields you also need to enable [includeFields].
348 final bool includeProperties;
349
350 /// Whether to include symbols from the given type and its superclasses
351 /// (except [Object]).
352 final bool includeInherited;
353
354 /// If [includeInherited], walk up the type hierarchy up to this type
355 /// (defaults to [Object]).
356 final ClassElement includeUpTo;
357
358 /// Whether to include final fields and getter-only properties.
359 final bool excludeFinal;
360
361 /// Whether to include methods (default is false).
362 final bool includeMethods;
363
364 /// If [withAnnotation] is not null, then it should be a list of types, so
365 /// only symbols that are annotated with instances of those types are
366 /// included.
367 final List<Element> withAnnotations;
368
369 /// If [matches] is not null, then only those fields, properties, or methods
370 /// that match will be included.
371 final NameMatcher matches;
372
373 const QueryOptions({
374 this.includeFields: true,
375 this.includeProperties: true,
376 this.includeInherited: true,
377 this.includeUpTo: null,
378 this.excludeFinal: false,
379 this.includeMethods: false,
380 this.withAnnotations: null,
381 this.matches: null});
382 }
383
384 /// Predicate that tells whether [name] should be included in query results.
385 typedef bool NameMatcher(String name);
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698