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

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

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

Powered by Google App Engine
This is Rietveld 408576698