| OLD | NEW |
| 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | 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 | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 /// Records accesses to Dart program declarations and generates code that will | 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`. | 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 | 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 | 8 /// program, and then uses [SmokeCodeGenerator] to produce the code needed by |
| 9 /// the smoke system. | 9 /// the smoke system. |
| 10 /// | 10 /// |
| (...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 69 | 69 |
| 70 /// Adds any declaration and superclass information that is needed to answer a | 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 | 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 | 72 /// setters if [includeAccessors] is true. If [results] is not null, it will |
| 73 /// be filled up with the members that match the query. | 73 /// be filled up with the members that match the query. |
| 74 void runQuery(ClassElement type, QueryOptions options, | 74 void runQuery(ClassElement type, QueryOptions options, |
| 75 {bool includeAccessors: true, List results}) { | 75 {bool includeAccessors: true, List results}) { |
| 76 if (type.type.isObject) return; // We don't include Object in query results. | 76 if (type.type.isObject) return; // We don't include Object in query results. |
| 77 var id = _typeFor(type); | 77 var id = _typeFor(type); |
| 78 var parent = type.supertype != null ? type.supertype.element : null; | 78 var parent = type.supertype != null ? type.supertype.element : null; |
| 79 if (options.includeInherited && parent != null && | 79 if (options.includeInherited && |
| 80 parent != null && |
| 80 parent != options.includeUpTo) { | 81 parent != options.includeUpTo) { |
| 81 lookupParent(type); | 82 lookupParent(type); |
| 82 runQuery(parent, options, includeAccessors: includeAccessors); | 83 runQuery(parent, options, includeAccessors: includeAccessors); |
| 83 var parentId = _typeFor(parent); | 84 var parentId = _typeFor(parent); |
| 84 for (var m in type.mixins) { | 85 for (var m in type.mixins) { |
| 85 var mixinClass = m.element; | 86 var mixinClass = m.element; |
| 86 var mixinId = _mixins[parentId][mixinClass]; | 87 var mixinId = _mixins[parentId][mixinClass]; |
| 87 _runQueryInternal( | 88 _runQueryInternal( |
| 88 mixinClass, mixinId, options, includeAccessors, results); | 89 mixinClass, mixinId, options, includeAccessors, results); |
| 89 parentId = mixinId; | 90 parentId = mixinId; |
| 90 } | 91 } |
| 91 } | 92 } |
| 92 _runQueryInternal(type, id, options, includeAccessors, results); | 93 _runQueryInternal(type, id, options, includeAccessors, results); |
| 93 } | 94 } |
| 94 | 95 |
| 95 /// Helper for [runQuery]. This runs the query only on a specific [type], | 96 /// Helper for [runQuery]. This runs the query only on a specific [type], |
| 96 /// which could be a class or a mixin labeled by [id]. | 97 /// which could be a class or a mixin labeled by [id]. |
| 97 // TODO(sigmund): currently we materialize mixins in smoke/static.dart, | 98 // TODO(sigmund): currently we materialize mixins in smoke/static.dart, |
| 98 // we should consider to include the mixin declaration information directly, | 99 // we should consider to include the mixin declaration information directly, |
| 99 // and remove the duplication we have for mixins today. | 100 // and remove the duplication we have for mixins today. |
| 100 void _runQueryInternal(ClassElement type, TypeIdentifier id, | 101 void _runQueryInternal(ClassElement type, TypeIdentifier id, |
| 101 QueryOptions options, bool includeAccessors, List results) { | 102 QueryOptions options, bool includeAccessors, List results) { |
| 102 | |
| 103 skipBecauseOfAnnotations(Element e) { | 103 skipBecauseOfAnnotations(Element e) { |
| 104 if (options.withAnnotations == null) return false; | 104 if (options.withAnnotations == null) return false; |
| 105 return !_matchesAnnotation(e.metadata, options.withAnnotations); | 105 return !_matchesAnnotation(e.metadata, options.withAnnotations); |
| 106 } | 106 } |
| 107 | 107 |
| 108 if (options.includeFields) { | 108 if (options.includeFields) { |
| 109 for (var f in type.fields) { | 109 for (var f in type.fields) { |
| 110 if (f.isStatic) continue; | 110 if (f.isStatic) continue; |
| 111 if (f.isSynthetic) continue; // exclude getters | 111 if (f.isSynthetic) continue; // exclude getters |
| 112 if (options.excludeFinal && f.isFinal) continue; | 112 if (options.excludeFinal && f.isFinal) continue; |
| 113 var name = f.displayName; | 113 var name = f.displayName; |
| 114 if (options.matches != null && !options.matches(name)) continue; | 114 if (options.matches != null && !options.matches(name)) continue; |
| 115 if (skipBecauseOfAnnotations(f)) continue; | 115 if (skipBecauseOfAnnotations(f)) continue; |
| 116 if (results != null) results.add(f); | 116 if (results != null) results.add(f); |
| 117 generator.addDeclaration(id, name, _typeFor(f.type.element), | 117 generator.addDeclaration(id, name, _typeFor(f.type.element), |
| 118 isField: true, isFinal: f.isFinal, | 118 isField: true, |
| 119 isFinal: f.isFinal, |
| 119 annotations: _copyAnnotations(f)); | 120 annotations: _copyAnnotations(f)); |
| 120 if (includeAccessors) _addAccessors(name, !f.isFinal); | 121 if (includeAccessors) _addAccessors(name, !f.isFinal); |
| 121 } | 122 } |
| 122 } | 123 } |
| 123 | 124 |
| 124 if (options.includeProperties) { | 125 if (options.includeProperties) { |
| 125 for (var a in type.accessors) { | 126 for (var a in type.accessors) { |
| 126 if (a is! PropertyAccessorElement) continue; | 127 if (a is! PropertyAccessorElement) continue; |
| 127 if (a.isStatic || !a.isGetter) continue; | 128 if (a.isStatic || !a.isGetter) continue; |
| 128 var v = a.variable; | 129 var v = a.variable; |
| 129 if (v is FieldElement && !v.isSynthetic) continue; // exclude fields | 130 if (v is FieldElement && !v.isSynthetic) continue; // exclude fields |
| 130 if (options.excludeFinal && v.isFinal) continue; | 131 if (options.excludeFinal && v.isFinal) continue; |
| 131 var name = v.displayName; | 132 var name = v.displayName; |
| 132 if (options.matches != null && !options.matches(name)) continue; | 133 if (options.matches != null && !options.matches(name)) continue; |
| 133 if (skipBecauseOfAnnotations(a)) continue; | 134 if (skipBecauseOfAnnotations(a)) continue; |
| 134 if (results != null) results.add(a); | 135 if (results != null) results.add(a); |
| 135 generator.addDeclaration(id, name, _typeFor(a.type.returnType.element), | 136 generator.addDeclaration(id, name, _typeFor(a.type.returnType.element), |
| 136 isProperty: true, isFinal: v.isFinal, | 137 isProperty: true, |
| 138 isFinal: v.isFinal, |
| 137 annotations: _copyAnnotations(a)); | 139 annotations: _copyAnnotations(a)); |
| 138 if (includeAccessors) _addAccessors(name, !v.isFinal); | 140 if (includeAccessors) _addAccessors(name, !v.isFinal); |
| 139 } | 141 } |
| 140 } | 142 } |
| 141 | 143 |
| 142 if (options.includeMethods) { | 144 if (options.includeMethods) { |
| 143 for (var m in type.methods) { | 145 for (var m in type.methods) { |
| 144 if (m.isStatic) continue; | 146 if (m.isStatic) continue; |
| 145 var name = m.displayName; | 147 var name = m.displayName; |
| 146 if (options.matches != null && !options.matches(name)) continue; | 148 if (options.matches != null && !options.matches(name)) continue; |
| 147 if (skipBecauseOfAnnotations(m)) continue; | 149 if (skipBecauseOfAnnotations(m)) continue; |
| 148 if (results != null) results.add(m); | 150 if (results != null) results.add(m); |
| 149 generator.addDeclaration(id, name, | 151 generator.addDeclaration( |
| 150 new TypeIdentifier('dart:core', 'Function'), isMethod: true, | 152 id, name, new TypeIdentifier('dart:core', 'Function'), |
| 151 annotations: _copyAnnotations(m)); | 153 isMethod: true, annotations: _copyAnnotations(m)); |
| 152 if (includeAccessors) _addAccessors(name, false); | 154 if (includeAccessors) _addAccessors(name, false); |
| 153 } | 155 } |
| 154 } | 156 } |
| 155 } | 157 } |
| 156 | 158 |
| 157 /// Adds the declaration of [name] if it was found in [type]. If [recursive] | 159 /// Adds the declaration of [name] if it was found in [type]. If [recursive] |
| 158 /// is true, then we continue looking up [name] in the parent classes until we | 160 /// is true, then we continue looking up [name] in the parent classes until we |
| 159 /// find it or we reach [includeUpTo] or Object. Returns whether the | 161 /// find it or we reach [includeUpTo] or Object. Returns whether the |
| 160 /// declaration was found. When a declaration is found, add also a symbol, | 162 /// declaration was found. When a declaration is found, add also a symbol, |
| 161 /// getter, and setter if [includeAccessors] is true. | 163 /// getter, and setter if [includeAccessors] is true. |
| 162 bool lookupMember(ClassElement type, String name, {bool recursive: false, | 164 bool lookupMember(ClassElement type, String name, {bool recursive: false, |
| 163 bool includeAccessors: true, ClassElement includeUpTo}) => | 165 bool includeAccessors: true, ClassElement includeUpTo}) => |
| 164 _lookupMemberInternal(type, _typeFor(type), name, recursive, | 166 _lookupMemberInternal( |
| 165 includeAccessors, includeUpTo); | 167 type, _typeFor(type), name, recursive, includeAccessors, includeUpTo); |
| 166 | 168 |
| 167 /// Helper for [lookupMember] that walks up the type hierarchy including mixin | 169 /// Helper for [lookupMember] that walks up the type hierarchy including mixin |
| 168 /// classes. | 170 /// classes. |
| 169 bool _lookupMemberInternal(ClassElement type, TypeIdentifier id, String name, | 171 bool _lookupMemberInternal(ClassElement type, TypeIdentifier id, String name, |
| 170 bool recursive, bool includeAccessors, ClassElement includeUpTo) { | 172 bool recursive, bool includeAccessors, ClassElement includeUpTo) { |
| 171 // Exclude members from [Object]. | 173 // Exclude members from [Object]. |
| 172 if (type.type.isObject) return false; | 174 if (type.type.isObject) return false; |
| 173 generator.addEmptyDeclaration(id); | 175 generator.addEmptyDeclaration(id); |
| 174 for (var f in type.fields) { | 176 for (var f in type.fields) { |
| 175 if (f.displayName != name) continue; | 177 if (f.displayName != name) continue; |
| 176 if (f.isSynthetic) continue; // exclude getters | 178 if (f.isSynthetic) continue; // exclude getters |
| 177 generator.addDeclaration(id, name, | 179 generator.addDeclaration(id, name, _typeFor(f.type.element), |
| 178 _typeFor(f.type.element), isField: true, isFinal: f.isFinal, | 180 isField: true, |
| 179 isStatic: f.isStatic, annotations: _copyAnnotations(f)); | 181 isFinal: f.isFinal, |
| 182 isStatic: f.isStatic, |
| 183 annotations: _copyAnnotations(f)); |
| 180 if (includeAccessors && !f.isStatic) _addAccessors(name, !f.isFinal); | 184 if (includeAccessors && !f.isStatic) _addAccessors(name, !f.isFinal); |
| 181 return true; | 185 return true; |
| 182 } | 186 } |
| 183 | 187 |
| 184 for (var a in type.accessors) { | 188 for (var a in type.accessors) { |
| 185 if (a is! PropertyAccessorElement) continue; | 189 if (a is! PropertyAccessorElement) continue; |
| 186 // TODO(sigmund): support setters without getters. | 190 // TODO(sigmund): support setters without getters. |
| 187 if (!a.isGetter) continue; | 191 if (!a.isGetter) continue; |
| 188 if (a.displayName != name) continue; | 192 if (a.displayName != name) continue; |
| 189 var v = a.variable; | 193 var v = a.variable; |
| 190 if (v is FieldElement && !v.isSynthetic) continue; // exclude fields | 194 if (v is FieldElement && !v.isSynthetic) continue; // exclude fields |
| 191 generator.addDeclaration(id, name, | 195 generator.addDeclaration(id, name, _typeFor(a.type.returnType.element), |
| 192 _typeFor(a.type.returnType.element), isProperty: true, | 196 isProperty: true, |
| 193 isFinal: v.isFinal, isStatic: a.isStatic, | 197 isFinal: v.isFinal, |
| 198 isStatic: a.isStatic, |
| 194 annotations: _copyAnnotations(a)); | 199 annotations: _copyAnnotations(a)); |
| 195 if (includeAccessors && !v.isStatic) _addAccessors(name, !v.isFinal); | 200 if (includeAccessors && !v.isStatic) _addAccessors(name, !v.isFinal); |
| 196 return true; | 201 return true; |
| 197 } | 202 } |
| 198 | 203 |
| 199 for (var m in type.methods) { | 204 for (var m in type.methods) { |
| 200 if (m.displayName != name) continue; | 205 if (m.displayName != name) continue; |
| 201 generator.addDeclaration(id, name, | 206 generator.addDeclaration( |
| 202 new TypeIdentifier('dart:core', 'Function'), isMethod: true, | 207 id, name, new TypeIdentifier('dart:core', 'Function'), |
| 203 isStatic: m.isStatic, annotations: _copyAnnotations(m)); | 208 isMethod: true, |
| 209 isStatic: m.isStatic, |
| 210 annotations: _copyAnnotations(m)); |
| 204 if (includeAccessors) { | 211 if (includeAccessors) { |
| 205 if (m.isStatic) { | 212 if (m.isStatic) { |
| 206 generator.addStaticMethod(id, name); | 213 generator.addStaticMethod(id, name); |
| 207 generator.addSymbol(name); | 214 generator.addSymbol(name); |
| 208 } else { | 215 } else { |
| 209 _addAccessors(name, false); | 216 _addAccessors(name, false); |
| 210 } | 217 } |
| 211 } | 218 } |
| 212 return true; | 219 return true; |
| 213 } | 220 } |
| 214 | 221 |
| 215 if (recursive) { | 222 if (recursive) { |
| 216 lookupParent(type); | 223 lookupParent(type); |
| 217 var parent = type.supertype != null ? type.supertype.element : null; | 224 var parent = type.supertype != null ? type.supertype.element : null; |
| 218 if (parent == null || parent == includeUpTo) return false; | 225 if (parent == null || parent == includeUpTo) return false; |
| 219 var parentId = _typeFor(parent); | 226 var parentId = _typeFor(parent); |
| 220 for (var m in type.mixins) { | 227 for (var m in type.mixins) { |
| 221 var mixinClass = m.element; | 228 var mixinClass = m.element; |
| 222 var mixinId = _mixins[parentId][mixinClass]; | 229 var mixinId = _mixins[parentId][mixinClass]; |
| 223 if (_lookupMemberInternal(mixinClass, mixinId, name, false, | 230 if (_lookupMemberInternal( |
| 224 includeAccessors, includeUpTo)) { | 231 mixinClass, mixinId, name, false, includeAccessors, includeUpTo)) { |
| 225 return true; | 232 return true; |
| 226 } | 233 } |
| 227 parentId = mixinId; | 234 parentId = mixinId; |
| 228 } | 235 } |
| 229 return _lookupMemberInternal(parent, parentId, name, true, | 236 return _lookupMemberInternal( |
| 230 includeAccessors, includeUpTo); | 237 parent, parentId, name, true, includeAccessors, includeUpTo); |
| 231 } | 238 } |
| 232 return false; | 239 return false; |
| 233 } | 240 } |
| 234 | 241 |
| 235 /// Add information so smoke can invoke the static method [type].[name]. | 242 /// Add information so smoke can invoke the static method [type].[name]. |
| 236 void addStaticMethod(ClassElement type, String name) { | 243 void addStaticMethod(ClassElement type, String name) { |
| 237 generator.addStaticMethod(_typeFor(type), name); | 244 generator.addStaticMethod(_typeFor(type), name); |
| 238 } | 245 } |
| 239 | 246 |
| 240 /// Adds [name] as a symbol, a getter, and optionally a setter in [generator]. | 247 /// Adds [name] as a symbol, a getter, and optionally a setter in [generator]. |
| (...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 284 | 291 |
| 285 throw new UnsupportedError('unsupported annotation $annotation'); | 292 throw new UnsupportedError('unsupported annotation $annotation'); |
| 286 } | 293 } |
| 287 | 294 |
| 288 /// Converts [expression] into a [ConstExpression]. | 295 /// Converts [expression] into a [ConstExpression]. |
| 289 ConstExpression _convertExpression(Expression expression) { | 296 ConstExpression _convertExpression(Expression expression) { |
| 290 if (expression is StringLiteral) { | 297 if (expression is StringLiteral) { |
| 291 return new ConstExpression.string(expression.stringValue); | 298 return new ConstExpression.string(expression.stringValue); |
| 292 } | 299 } |
| 293 | 300 |
| 294 if (expression is BooleanLiteral || expression is DoubleLiteral || | 301 if (expression is BooleanLiteral || |
| 295 expression is IntegerLiteral || expression is NullLiteral) { | 302 expression is DoubleLiteral || |
| 303 expression is IntegerLiteral || |
| 304 expression is NullLiteral) { |
| 296 return new CodeAsConstExpression("${(expression as dynamic).value}"); | 305 return new CodeAsConstExpression("${(expression as dynamic).value}"); |
| 297 } | 306 } |
| 298 | 307 |
| 299 if (expression is Identifier) { | 308 if (expression is Identifier) { |
| 300 var element = expression.bestElement; | 309 var element = expression.bestElement; |
| 301 if (element == null || !element.isPublic) { | 310 if (element == null || !element.isPublic) { |
| 302 throw new UnsupportedError('private constants are not supported'); | 311 throw new UnsupportedError('private constants are not supported'); |
| 303 } | 312 } |
| 304 | 313 |
| 305 var url = importUrlFor(element.library); | 314 var url = importUrlFor(element.library); |
| (...skipping 15 matching lines...) Expand all Loading... |
| 321 throw new UnimplementedError('expression convertion not implemented in ' | 330 throw new UnimplementedError('expression convertion not implemented in ' |
| 322 'smoke.codegen.recorder (${expression.runtimeType} $expression)'); | 331 'smoke.codegen.recorder (${expression.runtimeType} $expression)'); |
| 323 } | 332 } |
| 324 } | 333 } |
| 325 | 334 |
| 326 /// Returns whether [metadata] contains any annotation that is either equal to | 335 /// Returns whether [metadata] contains any annotation that is either equal to |
| 327 /// an annotation in [queryAnnotations] or whose type is a subclass of a type | 336 /// an annotation in [queryAnnotations] or whose type is a subclass of a type |
| 328 /// listed in [queryAnnotations]. This is equivalent to the check done in | 337 /// listed in [queryAnnotations]. This is equivalent to the check done in |
| 329 /// `src/common.dart#matchesAnnotation`, except that this is applied to | 338 /// `src/common.dart#matchesAnnotation`, except that this is applied to |
| 330 /// static metadata as it was provided by the analyzer. | 339 /// static metadata as it was provided by the analyzer. |
| 331 bool _matchesAnnotation(Iterable<ElementAnnotation> metadata, | 340 bool _matchesAnnotation( |
| 332 Iterable<Element> queryAnnotations) { | 341 Iterable<ElementAnnotation> metadata, Iterable<Element> queryAnnotations) { |
| 333 for (var meta in metadata) { | 342 for (var meta in metadata) { |
| 334 var element = meta.element; | 343 var element = meta.element; |
| 335 var exp; | 344 var exp; |
| 336 var type; | 345 var type; |
| 337 if (element is PropertyAccessorElement) { | 346 if (element is PropertyAccessorElement) { |
| 338 exp = element.variable; | 347 exp = element.variable; |
| 339 type = exp.evaluationResult.value.type; | 348 type = exp.evaluationResult.value.type; |
| 340 } else if (element is ConstructorElement) { | 349 } else if (element is ConstructorElement) { |
| 341 exp = element; | 350 exp = element; |
| 342 type = element.enclosingElement.type; | 351 type = element.enclosingElement.type; |
| (...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 379 | 388 |
| 380 /// If [withAnnotation] is not null, then it should be a list of types, so | 389 /// If [withAnnotation] is not null, then it should be a list of types, so |
| 381 /// only symbols that are annotated with instances of those types are | 390 /// only symbols that are annotated with instances of those types are |
| 382 /// included. | 391 /// included. |
| 383 final List<Element> withAnnotations; | 392 final List<Element> withAnnotations; |
| 384 | 393 |
| 385 /// If [matches] is not null, then only those fields, properties, or methods | 394 /// If [matches] is not null, then only those fields, properties, or methods |
| 386 /// that match will be included. | 395 /// that match will be included. |
| 387 final NameMatcher matches; | 396 final NameMatcher matches; |
| 388 | 397 |
| 389 const QueryOptions({ | 398 const QueryOptions({this.includeFields: true, this.includeProperties: true, |
| 390 this.includeFields: true, | 399 this.includeInherited: true, this.includeUpTo: null, |
| 391 this.includeProperties: true, | 400 this.excludeFinal: false, this.includeMethods: false, |
| 392 this.includeInherited: true, | 401 this.withAnnotations: null, this.matches: null}); |
| 393 this.includeUpTo: null, | |
| 394 this.excludeFinal: false, | |
| 395 this.includeMethods: false, | |
| 396 this.withAnnotations: null, | |
| 397 this.matches: null}); | |
| 398 } | 402 } |
| 399 | 403 |
| 400 /// Predicate that tells whether [name] should be included in query results. | 404 /// Predicate that tells whether [name] should be included in query results. |
| 401 typedef bool NameMatcher(String name); | 405 typedef bool NameMatcher(String name); |
| OLD | NEW |