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

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

Issue 2989763002: Update charted to 0.4.8 and roll (Closed)
Patch Set: Removed Cutch from list of reviewers Created 3 years, 4 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
« no previous file with comments | « packages/smoke/lib/codegen/generator.dart ('k') | packages/smoke/lib/mirrors.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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. If [results] is not null, it will
73 /// be filled up with the members that match the query.
74 void runQuery(ClassElement type, QueryOptions options,
75 {bool includeAccessors: true, List results}) {
76 if (type.type.isObject) return; // We don't include Object in query results.
77 var id = _typeFor(type);
78 var parent = type.supertype != null ? type.supertype.element : null;
79 if (options.includeInherited &&
80 parent != null &&
81 parent != options.includeUpTo) {
82 lookupParent(type);
83 runQuery(parent, options, includeAccessors: includeAccessors);
84 var parentId = _typeFor(parent);
85 for (var m in type.mixins) {
86 var mixinClass = m.element;
87 var mixinId = _mixins[parentId][mixinClass];
88 _runQueryInternal(
89 mixinClass, mixinId, options, includeAccessors, results);
90 parentId = mixinId;
91 }
92 }
93 _runQueryInternal(type, id, options, includeAccessors, results);
94 }
95
96 /// Helper for [runQuery]. This runs the query only on a specific [type],
97 /// which could be a class or a mixin labeled by [id].
98 // TODO(sigmund): currently we materialize mixins in smoke/static.dart,
99 // we should consider to include the mixin declaration information directly,
100 // and remove the duplication we have for mixins today.
101 void _runQueryInternal(ClassElement type, TypeIdentifier id,
102 QueryOptions options, bool includeAccessors, List results) {
103 skipBecauseOfAnnotations(Element e) {
104 if (options.withAnnotations == null) return false;
105 return !_matchesAnnotation(e.metadata, options.withAnnotations);
106 }
107
108 if (options.includeFields) {
109 for (var f in type.fields) {
110 if (f.isStatic) continue;
111 if (f.isSynthetic) continue; // exclude getters
112 if (options.excludeFinal && f.isFinal) continue;
113 var name = f.displayName;
114 if (options.matches != null && !options.matches(name)) continue;
115 if (skipBecauseOfAnnotations(f)) continue;
116 if (results != null) results.add(f);
117 generator.addDeclaration(id, name, _typeFor(f.type.element),
118 isField: true,
119 isFinal: f.isFinal,
120 annotations: _copyAnnotations(f));
121 if (includeAccessors) _addAccessors(name, !f.isFinal);
122 }
123 }
124
125 if (options.includeProperties) {
126 for (var a in type.accessors) {
127 if (a is! PropertyAccessorElement) continue;
128 if (a.isStatic || !a.isGetter) continue;
129 var v = a.variable;
130 if (v is FieldElement && !v.isSynthetic) continue; // exclude fields
131 if (options.excludeFinal && v.isFinal) continue;
132 var name = v.displayName;
133 if (options.matches != null && !options.matches(name)) continue;
134 if (skipBecauseOfAnnotations(a)) continue;
135 if (results != null) results.add(a);
136 generator.addDeclaration(id, name, _typeFor(a.type.returnType.element),
137 isProperty: true,
138 isFinal: v.isFinal,
139 annotations: _copyAnnotations(a));
140 if (includeAccessors) _addAccessors(name, !v.isFinal);
141 }
142 }
143
144 if (options.includeMethods) {
145 for (var m in type.methods) {
146 if (m.isStatic) continue;
147 var name = m.displayName;
148 if (options.matches != null && !options.matches(name)) continue;
149 if (skipBecauseOfAnnotations(m)) continue;
150 if (results != null) results.add(m);
151 generator.addDeclaration(
152 id, name, new TypeIdentifier('dart:core', 'Function'),
153 isMethod: true, annotations: _copyAnnotations(m));
154 if (includeAccessors) _addAccessors(name, false);
155 }
156 }
157 }
158
159 /// Adds the declaration of [name] if it was found in [type]. If [recursive]
160 /// is true, then we continue looking up [name] in the parent classes until we
161 /// find it or we reach [includeUpTo] or Object. Returns whether the
162 /// declaration was found. When a declaration is found, add also a symbol,
163 /// getter, and setter if [includeAccessors] is true.
164 bool lookupMember(ClassElement type, String name, {bool recursive: false,
165 bool includeAccessors: true, ClassElement includeUpTo}) =>
166 _lookupMemberInternal(
167 type, _typeFor(type), name, recursive, includeAccessors, includeUpTo);
168
169 /// Helper for [lookupMember] that walks up the type hierarchy including mixin
170 /// classes.
171 bool _lookupMemberInternal(ClassElement type, TypeIdentifier id, String name,
172 bool recursive, bool includeAccessors, ClassElement includeUpTo) {
173 // Exclude members from [Object].
174 if (type.type.isObject) return false;
175 generator.addEmptyDeclaration(id);
176 for (var f in type.fields) {
177 if (f.displayName != name) continue;
178 if (f.isSynthetic) continue; // exclude getters
179 generator.addDeclaration(id, name, _typeFor(f.type.element),
180 isField: true,
181 isFinal: f.isFinal,
182 isStatic: f.isStatic,
183 annotations: _copyAnnotations(f));
184 if (includeAccessors && !f.isStatic) _addAccessors(name, !f.isFinal);
185 return true;
186 }
187
188 for (var a in type.accessors) {
189 if (a is! PropertyAccessorElement) continue;
190 // TODO(sigmund): support setters without getters.
191 if (!a.isGetter) continue;
192 if (a.displayName != name) continue;
193 var v = a.variable;
194 if (v is FieldElement && !v.isSynthetic) continue; // exclude fields
195 generator.addDeclaration(id, name, _typeFor(a.type.returnType.element),
196 isProperty: true,
197 isFinal: v.isFinal,
198 isStatic: a.isStatic,
199 annotations: _copyAnnotations(a));
200 if (includeAccessors && !v.isStatic) _addAccessors(name, !v.isFinal);
201 return true;
202 }
203
204 for (var m in type.methods) {
205 if (m.displayName != name) continue;
206 generator.addDeclaration(
207 id, name, new TypeIdentifier('dart:core', 'Function'),
208 isMethod: true,
209 isStatic: m.isStatic,
210 annotations: _copyAnnotations(m));
211 if (includeAccessors) {
212 if (m.isStatic) {
213 generator.addStaticMethod(id, name);
214 generator.addSymbol(name);
215 } else {
216 _addAccessors(name, false);
217 }
218 }
219 return true;
220 }
221
222 if (recursive) {
223 lookupParent(type);
224 var parent = type.supertype != null ? type.supertype.element : null;
225 if (parent == null || parent == includeUpTo) return false;
226 var parentId = _typeFor(parent);
227 for (var m in type.mixins) {
228 var mixinClass = m.element;
229 var mixinId = _mixins[parentId][mixinClass];
230 if (_lookupMemberInternal(
231 mixinClass, mixinId, name, false, includeAccessors, includeUpTo)) {
232 return true;
233 }
234 parentId = mixinId;
235 }
236 return _lookupMemberInternal(
237 parent, parentId, name, true, includeAccessors, includeUpTo);
238 }
239 return false;
240 }
241
242 /// Add information so smoke can invoke the static method [type].[name].
243 void addStaticMethod(ClassElement type, String name) {
244 generator.addStaticMethod(_typeFor(type), name);
245 }
246
247 /// Adds [name] as a symbol, a getter, and optionally a setter in [generator].
248 _addAccessors(String name, bool includeSetter) {
249 generator.addSymbol(name);
250 generator.addGetter(name);
251 if (includeSetter) generator.addSetter(name);
252 }
253
254 /// Copy metadata associated with the declaration of [target].
255 List<ConstExpression> _copyAnnotations(Element target) {
256 var node = target.computeNode();
257 // [node] is the initialization expression, we walk up to get to the actual
258 // member declaration where the metadata is attached to.
259 while (node is! ClassMember) node = node.parent;
260 return node.metadata.map(_convertAnnotation).toList();
261 }
262
263 /// Converts annotations into [ConstExpression]s supported by the codegen
264 /// library.
265 ConstExpression _convertAnnotation(Annotation annotation) {
266 var element = annotation.element;
267 if (element is ConstructorElement) {
268 if (!element.name.isEmpty) {
269 throw new UnimplementedError(
270 'named constructors are not implemented in smoke.codegen.recorder');
271 }
272
273 var positionalArgs = [];
274 var namedArgs = {};
275 for (var arg in annotation.arguments.arguments) {
276 if (arg is NamedExpression) {
277 namedArgs[arg.name.label.name] = _convertExpression(arg.expression);
278 } else {
279 positionalArgs.add(_convertExpression(arg));
280 }
281 }
282
283 return new ConstructorExpression(importUrlFor(element.library),
284 element.enclosingElement.name, positionalArgs, namedArgs);
285 }
286
287 if (element is PropertyAccessorElement) {
288 return new TopLevelIdentifier(
289 importUrlFor(element.library), element.name);
290 }
291
292 throw new UnsupportedError('unsupported annotation $annotation');
293 }
294
295 /// Converts [expression] into a [ConstExpression].
296 ConstExpression _convertExpression(Expression expression) {
297 if (expression is StringLiteral) {
298 return new ConstExpression.string(expression.stringValue);
299 }
300
301 if (expression is BooleanLiteral ||
302 expression is DoubleLiteral ||
303 expression is IntegerLiteral ||
304 expression is NullLiteral) {
305 return new CodeAsConstExpression("${(expression as dynamic).value}");
306 }
307
308 if (expression is Identifier) {
309 var element = expression.bestElement;
310 if (element == null || !element.isPublic) {
311 throw new UnsupportedError('private constants are not supported');
312 }
313
314 var url = importUrlFor(element.library);
315 if (element is ClassElement) {
316 return new TopLevelIdentifier(url, element.name);
317 }
318
319 if (element is PropertyAccessorElement) {
320 var variable = element.variable;
321 if (variable is FieldElement) {
322 var cls = variable.enclosingElement;
323 return new TopLevelIdentifier(url, '${cls.name}.${variable.name}');
324 } else if (variable is TopLevelVariableElement) {
325 return new TopLevelIdentifier(url, variable.name);
326 }
327 }
328 }
329
330 throw new UnimplementedError('expression convertion not implemented in '
331 'smoke.codegen.recorder (${expression.runtimeType} $expression)');
332 }
333 }
334
335 /// Returns whether [metadata] contains any annotation that is either equal to
336 /// an annotation in [queryAnnotations] or whose type is a subclass of a type
337 /// listed in [queryAnnotations]. This is equivalent to the check done in
338 /// `src/common.dart#matchesAnnotation`, except that this is applied to
339 /// static metadata as it was provided by the analyzer.
340 bool _matchesAnnotation(
341 Iterable<ElementAnnotation> metadata, Iterable<Element> queryAnnotations) {
342 for (var meta in metadata) {
343 var element = meta.element;
344 var exp;
345 var type;
346 if (element is PropertyAccessorElement) {
347 exp = element.variable;
348 type = exp.evaluationResult.value.type;
349 } else if (element is ConstructorElement) {
350 exp = element;
351 type = element.enclosingElement.type;
352 } else {
353 throw new UnimplementedError('Unsupported annotation: ${meta}');
354 }
355 for (var queryMeta in queryAnnotations) {
356 if (exp == queryMeta) return true;
357 if (queryMeta is ClassElement && type.isSubtypeOf(queryMeta.type)) {
358 return true;
359 }
360 }
361 }
362 return false;
363 }
364
365 /// Options equivalent to `smoke.dart#QueryOptions`, except that type
366 /// information and annotations are denoted by resolver's elements.
367 class QueryOptions {
368 /// Whether to include fields (default is true).
369 final bool includeFields;
370
371 /// Whether to include getters and setters (default is true). Note that to
372 /// include fields you also need to enable [includeFields].
373 final bool includeProperties;
374
375 /// Whether to include symbols from the given type and its superclasses
376 /// (except [Object]).
377 final bool includeInherited;
378
379 /// If [includeInherited], walk up the type hierarchy up to this type
380 /// (defaults to [Object]).
381 final ClassElement includeUpTo;
382
383 /// Whether to include final fields and getter-only properties.
384 final bool excludeFinal;
385
386 /// Whether to include methods (default is false).
387 final bool includeMethods;
388
389 /// If [withAnnotation] is not null, then it should be a list of types, so
390 /// only symbols that are annotated with instances of those types are
391 /// included.
392 final List<Element> withAnnotations;
393
394 /// If [matches] is not null, then only those fields, properties, or methods
395 /// that match will be included.
396 final NameMatcher matches;
397
398 const QueryOptions({this.includeFields: true, this.includeProperties: true,
399 this.includeInherited: true, this.includeUpTo: null,
400 this.excludeFinal: false, this.includeMethods: false,
401 this.withAnnotations: null, this.matches: null});
402 }
403
404 /// Predicate that tells whether [name] should be included in query results.
405 typedef bool NameMatcher(String name);
OLDNEW
« no previous file with comments | « packages/smoke/lib/codegen/generator.dart ('k') | packages/smoke/lib/mirrors.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698