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

Unified 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 side-by-side diff with in-line comments
Download patch
Index: pkg/smoke/lib/codegen/recorder.dart
diff --git a/pkg/smoke/lib/codegen/recorder.dart b/pkg/smoke/lib/codegen/recorder.dart
new file mode 100644
index 0000000000000000000000000000000000000000..be4c96b9080e2ca7c6aec70935818faeda761c19
--- /dev/null
+++ b/pkg/smoke/lib/codegen/recorder.dart
@@ -0,0 +1,349 @@
+// Copyright (c) 2014, 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.
+
+/// Records accesses to Dart program declarations and generates code that will
+/// allow to do the same accesses at runtime using `package:smoke/static.dart`.
+/// Internally, this library relies on the `analyzer` to extract data from the
+/// program, and then uses [SmokeCodeGenerator] to produce the code needed by
+/// the smoke system.
+///
+/// This library only uses the analyzer to consume data previously produced by
+/// running the resolver. This library does not provide any hooks to integrate
+/// running the analyzer itself. See `package:code_transformers` to integrate
+/// the analyzer into pub transformers.
+library smoke.codegen.recorder;
+
+import 'package:analyzer/src/generated/element.dart';
+import 'package:analyzer/src/generated/ast.dart';
+import 'generator.dart';
+
+typedef String ImportUrlResolver(LibraryElement lib);
+
+/// A recorder that tracks how elements are accessed in order to generate code
+/// that replicates those accesses with the smoke runtime.
+class Recorder {
+ /// Underlying code generator.
+ SmokeCodeGenerator generator;
+
+ /// Function that provides the import url for a library element. This may
+ /// internally use the resolver to resolve the import url.
+ ImportUrlResolver importUrlFor;
+
+ Recorder(this.generator, this.importUrlFor);
+
+ /// Stores mixins that have been recorded and associates a type identifier
+ /// with them. Mixins don't have an associated identifier in the code, so we
+ /// generate a unique identifier for them and use it throughout the code.
+ Map<TypeIdentifier, Map<ClassElement, TypeIdentifier>> _mixins = {};
+
+ /// Adds the superclass information of [type] (including intermediate mixins).
+ /// This will not generate data for direct subtypes of Object, as that is
+ /// considered redundant information.
+ void lookupParent(ClassElement type) {
+ var parent = type.supertype;
+ var mixins = type.mixins;
+ if (parent == null && mixins.isEmpty) return; // type is Object
+ var baseType = parent.element;
+ var baseId = _typeFor(baseType);
+ if (mixins.isNotEmpty) {
+ _mixins.putIfAbsent(baseId, () => {});
+ for (var m in mixins) {
+ var mixinType = m.element;
+ var mixinId = _mixins[baseId].putIfAbsent(mixinType, () {
+ var comment = '${baseId.name} & ${mixinType.name}';
+ return generator.createMixinType(comment);
+ });
+ if (!baseType.type.isObject) generator.addParent(mixinId, baseId);
+ baseType = mixinType;
+ baseId = mixinId;
+ _mixins.putIfAbsent(mixinId, () => {});
+ }
+ }
+ if (!baseType.type.isObject) generator.addParent(_typeFor(type), baseId);
+ }
+
+ TypeIdentifier _typeFor(ClassElement type) =>
+ new TypeIdentifier(importUrlFor(type.library), type.displayName);
+
+ /// Adds any declaration and superclass information that is needed to answer a
+ /// query on [type] that matches [options].
+ void runQuery(ClassElement type, QueryOptions options) {
+ if (type.type.isObject) return; // We don't include Object in query results.
+ var id = _typeFor(type);
+ var parent = type.supertype != null ? type.supertype.element : null;
+ if (options.includeInherited && parent != null &&
+ parent != options.includeUpTo) {
+ lookupParent(type);
+ runQuery(parent, options);
+ var parentId = _typeFor(parent);
+ for (var m in type.mixins) {
+ var mixinClass = m.element;
+ var mixinId = _mixins[parentId][mixinClass];
+ _runQueryInternal(mixinClass, mixinId, options);
+ parentId = mixinId;
+ }
+ }
+ _runQueryInternal(type, id, options);
+ }
+
+ /// Helper for [runQuery]. This runs the query only on a specific [type],
+ /// which could be a class or a mixin labeled by [id].
+ // TODO(sigmund): currently we materialize mixins in smoke/static.dart,
+ // we should consider to include the mixin declaration information directly,
+ // and remove the duplication we have for mixins today.
+ void _runQueryInternal(ClassElement type, TypeIdentifier id,
+ QueryOptions options) {
+
+ skipBecauseOfAnnotations(Element e) {
+ if (options.withAnnotations == null) return false;
+ return !_matchesAnnotation(e.metadata, options.withAnnotations);
+ }
+
+ if (options.includeFields) {
+ for (var f in type.fields) {
+ if (f.isStatic) continue;
+ if (f.isSynthetic) continue; // exclude getters
+ if (options.excludeFinal && f.isFinal) continue;
+ if (skipBecauseOfAnnotations(f)) continue;
+ generator.addDeclaration(id, f.displayName, _typeFor(f.type.element),
+ isField: true, isFinal: f.isFinal,
+ annotations: _copyAnnotations(f));
+ }
+ }
+
+ if (options.includeProperties) {
+ for (var a in type.accessors) {
+ if (a is! PropertyAccessorElement) continue;
+ if (a.isStatic || !a.isGetter) continue;
+ var v = a.variable;
+ if (v is FieldElement && !v.isSynthetic) continue; // exclude fields
+ if (options.excludeFinal && v.isFinal) continue;
+ if (skipBecauseOfAnnotations(v)) continue;
+ generator.addDeclaration(id, v.displayName, _typeFor(v.type.element),
+ isProperty: true, isFinal: v.isFinal,
+ annotations: _copyAnnotations(a));
+ }
+ }
+
+ if (options.includeMethods) {
+ for (var m in type.methods) {
+ if (m.isStatic) continue;
+ if (skipBecauseOfAnnotations(m)) continue;
+ generator.addDeclaration(id, m.displayName,
+ new TypeIdentifier('dart:core', 'Function'), isMethod: true,
+ annotations: _copyAnnotations(m));
+ }
+ }
+ }
+
+ /// Adds the declaration of [name] if it was found in [type]. If [recursive]
+ /// is true, then we continue looking up [name] in the parent classes until we
+ /// find it or we reach Object.
+ void lookupMember(ClassElement type, String name, {bool recursive: false}) {
+ _lookupMemberInternal(type, _typeFor(type), name, recursive);
+ }
+
+ /// Helper for [lookupMember] that walks up the type hierarchy including mixin
+ /// classes.
+ bool _lookupMemberInternal(ClassElement type, TypeIdentifier id, String name,
+ bool recursive) {
+ // Exclude members from [Object].
+ if (type.type.isObject) return false;
+ generator.addEmptyDeclaration(id);
+ for (var f in type.fields) {
+ if (f.displayName != name) continue;
+ if (f.isSynthetic) continue; // exclude getters
+ generator.addDeclaration(id, f.displayName,
+ _typeFor(f.type.element), isField: true, isFinal: f.isFinal,
+ isStatic: f.isStatic, annotations: _copyAnnotations(f));
+ return true;
+ }
+
+ for (var a in type.accessors) {
+ if (a is! PropertyAccessorElement) continue;
+ // TODO(sigmund): support setters without getters.
+ if (!a.isGetter) continue;
+ if (a.displayName != name) continue;
+ var v = a.variable;
+ if (v is FieldElement && !v.isSynthetic) continue; // exclude fields
+ generator.addDeclaration(id, v.displayName,
+ _typeFor(v.type.element), isProperty: true, isFinal: v.isFinal,
+ isStatic: v.isStatic, annotations: _copyAnnotations(a));
+ return true;
+ }
+
+ for (var m in type.methods) {
+ if (m.displayName != name) continue;
+ generator.addDeclaration(id, m.displayName,
+ new TypeIdentifier('dart:core', 'Function'), isMethod: true,
+ isStatic: m.isStatic, annotations: _copyAnnotations(m));
+ return true;
+ }
+
+ if (recursive) {
+ lookupParent(type);
+ var parent = type.supertype != null ? type.supertype.element : null;
+ if (parent == null) return false;
+ var parentId = _typeFor(parent);
+ for (var m in type.mixins) {
+ var mixinClass = m.element;
+ var mixinId = _mixins[parentId][mixinClass];
+ if (_lookupMemberInternal(mixinClass, mixinId, name, false)) {
+ return true;
+ }
+ parentId = mixinId;
+ }
+ return _lookupMemberInternal(parent, parentId, name, true);
+ }
+ return false;
+ }
+
+
+ /// Copy metadata associated with the declaration of [target].
+ List<ConstExpression> _copyAnnotations(Element target) {
+ var node = target.node;
+ // [node] is the initialization expression, we walk up to get to the actual
+ // member declaration where the metadata is attached to.
+ while (node is! ClassMember) node = node.parent;
+ return node.metadata.map(_convertAnnotation).toList();
+ }
+
+ /// Converts annotations into [ConstExpression]s supported by the codegen
+ /// library.
+ ConstExpression _convertAnnotation(Annotation annotation) {
+ var element = annotation.element;
+ if (element is ConstructorElement) {
+ if (!element.name.isEmpty) {
+ throw new UnimplementedError(
+ 'named constructors are not implemented in smoke.codegen.recorder');
+ }
+
+ var positionalArgs = [];
+ for (var arg in annotation.arguments.arguments) {
+ if (arg is NamedExpression) {
+ throw new UnimplementedError(
+ 'named arguments in constructors are not implemented in '
+ 'smoke.codegen.recorder');
+ }
+ positionalArgs.add(_convertExpression(arg));
+ }
+
+ return new ConstructorExpression(importUrlFor(element.library),
+ element.enclosingElement.name, positionalArgs, const {});
+ }
+
+ if (element is PropertyAccessorElement) {
+ return new TopLevelIdentifier(
+ importUrlFor(element.library), element.name);
+ }
+
+ throw new UnsupportedError('unsupported annotation $annotation');
+ }
+
+ /// Converts [expression] into a [ConstExpression].
+ ConstExpression _convertExpression(Expression expression) {
+ if (expression is StringLiteral) {
+ return new ConstExpression.string(expression.stringValue);
+ }
+
+ if (expression is BooleanLiteral || expression is DoubleLiteral ||
+ expression is IntegerLiteral || expression is NullLiteral) {
+ return new CodeAsConstExpression("${(expression as dynamic).value}");
+ }
+
+ if (expression is Identifier) {
+ var element = expression.bestElement;
+ if (element == null || !element.isPublic) {
+ throw new UnsupportedError('private constants are not supported');
+ }
+
+ var url = importUrlFor(element.library);
+ if (element is ClassElement) {
+ return new TopLevelIdentifier(url, element.name);
+ }
+
+ if (element is PropertyAccessorElement) {
+ var variable = element.variable;
+ if (variable is FieldElement) {
+ var cls = variable.enclosingElement;
+ return new TopLevelIdentifier(url, '${cls.name}.${variable.name}');
+ } else if (variable is TopLevelVariableElement) {
+ return new TopLevelIdentifier(url, variable.name);
+ }
+ }
+ }
+
+ throw new UnimplementedError('expression convertion not implemented in '
+ 'smoke.codegen.recorder (${expression.runtimeType} $expression)');
+ }
+}
+
+/// Returns whether [metadata] contains any annotation that is either equal to
+/// an annotation in [queryAnnotations] or whose type is a subclass of a type
+/// listed in [queryAnnotations]. This is equivalent to the check done in
+/// `src/common.dart#matchesAnnotation`, except that this is applied to
+/// static metadata as it was provided by the analyzer.
+bool _matchesAnnotation(Iterable<ElementAnnotation> metadata,
+ Iterable<Element> queryAnnotations) {
+ for (var meta in metadata) {
+ var element = meta.element;
+ var exp;
+ var type;
+ if (element is PropertyAccessorElement) {
+ exp = element.variable;
+ type = exp.evaluationResult.value.type;
+ } else if (element is ConstructorElement) {
+ exp = element;
+ type = element.enclosingElement.type;
+ } else {
+ throw new UnimplementedError('Unsupported annotation: ${meta}');
+ }
+ for (var queryMeta in queryAnnotations) {
+ if (exp == queryMeta) return true;
+ if (queryMeta is ClassElement && type.isSubtypeOf(queryMeta.type)) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+/// Options equivalent to `smoke.dart#QueryOptions`, except that type
+/// information and annotations are denoted by resolver's elements.
+class QueryOptions {
+ /// Whether to include fields (default is true).
+ final bool includeFields;
+
+ /// Whether to include getters and setters (default is true). Note that to
+ /// include fields you also need to enable [includeFields].
+ final bool includeProperties;
+
+ /// Whether to include symbols from the given type and its superclasses
+ /// (except [Object]).
+ final bool includeInherited;
+
+ /// If [includeInherited], walk up the type hierarchy up to this type
+ /// (defaults to [Object]).
+ final ClassElement includeUpTo;
+
+ /// Whether to include final fields and getter-only properties.
+ final bool excludeFinal;
+
+ /// Whether to include methods (default is false).
+ final bool includeMethods;
+
+ /// If [withAnnotation] is not null, then it should be a list of types, so
+ /// only symbols that are annotated with instances of those types are
+ /// included.
+ final List<Element> withAnnotations;
+
+ const QueryOptions({
+ this.includeFields: true,
+ this.includeProperties: true,
+ this.includeInherited: true,
+ this.includeUpTo: null,
+ this.excludeFinal: false,
+ this.includeMethods: false,
+ this.withAnnotations: null});
+}

Powered by Google App Engine
This is Rietveld 408576698