| // Copyright (c) 2017, 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. |
| |
| import 'dart:collection'; |
| import 'package:kernel/core_types.dart'; |
| import 'package:kernel/kernel.dart'; |
| |
| Constructor unnamedConstructor(Class c) => |
| c.constructors.firstWhere((c) => c.name.name == '', orElse: () => null); |
| |
| /// Returns the enclosing library for reference [node]. |
| Library getLibrary(NamedNode node) { |
| for (TreeNode n = node; n != null; n = n.parent) { |
| if (n is Library) return n; |
| } |
| return null; |
| } |
| |
| final Pattern _syntheticTypeCharacters = new RegExp('[&^#.]'); |
| |
| String _escapeIdentifier(String identifier) { |
| // Remove the special characters used to encode mixin application class names |
| // which are legal in Kernel, but not in JavaScript. |
| return identifier?.replaceAll(_syntheticTypeCharacters, r'$'); |
| } |
| |
| /// Returns the escaped name for class [node]. |
| /// |
| /// The caller of this function has to make sure that this name is unique in |
| /// the current scope. |
| /// |
| /// In the current encoding, generic classes are generated in a function scope |
| /// which avoids name clashes of the escaped class name. |
| String getLocalClassName(Class node) => _escapeIdentifier(node.name); |
| |
| /// Returns the escaped name for the type parameter [node]. |
| /// |
| /// In the current encoding, generic classes are generated in a function scope |
| /// which avoids name clashes of the escaped parameter name. |
| String getTypeParameterName(TypeParameter node) => _escapeIdentifier(node.name); |
| |
| String getTopLevelName(NamedNode n) { |
| if (n is Procedure) return n.name.name; |
| if (n is Class) return n.name; |
| if (n is Typedef) return n.name; |
| if (n is Field) return n.name.name; |
| return n.canonicalName?.name; |
| } |
| |
| /// Given an annotated [node] and a [test] function, returns the first matching |
| /// constant valued annotation. |
| /// |
| /// For example if we had the ClassDeclaration node for `FontElement`: |
| /// |
| /// @js.JS('HTMLFontElement') |
| /// @deprecated |
| /// class FontElement { ... } |
| /// |
| /// We could match `@deprecated` with a test function like: |
| /// |
| /// (v) => v.type.name == 'Deprecated' && v.type.element.library.isDartCore |
| /// |
| Expression findAnnotation(TreeNode node, bool test(Expression value)) { |
| List<Expression> annotations; |
| if (node is Class) { |
| annotations = node.annotations; |
| } else if (node is Typedef) { |
| annotations = node.annotations; |
| } else if (node is Procedure) { |
| annotations = node.annotations; |
| } else if (node is Member) { |
| annotations = node.annotations; |
| } else if (node is Library) { |
| annotations = node.annotations; |
| } else { |
| return null; |
| } |
| return annotations.firstWhere(test, orElse: () => null); |
| } |
| |
| bool isBuiltinAnnotation( |
| Expression value, String libraryName, String expectedName) { |
| if (value is ConstructorInvocation) { |
| var c = value.target.enclosingClass; |
| return c.name == expectedName && |
| c.enclosingLibrary.importUri.toString() == libraryName; |
| } |
| return false; |
| } |
| |
| /// If [node] has annotation matching [test] and the first argument is a |
| /// string, this returns the string value. |
| /// |
| /// Calls [findAnnotation] followed by [getNameFromAnnotation]. |
| String getAnnotationName(NamedNode node, bool test(Expression value)) { |
| return getNameFromAnnotation(findAnnotation(node, test)); |
| } |
| |
| /// If [node] is an annotation and the first argument is a string, this returns |
| /// the string value. |
| /// |
| /// For example |
| /// |
| /// class MyAnnotation { |
| /// final String name; |
| /// // ... |
| /// const MyAnnotation(this.name/*, ... other params ... */); |
| /// } |
| /// |
| /// @MyAnnotation('FooBar') |
| /// main() { ... } |
| /// |
| /// Given teh node for `@MyAnnotation('FooBar')` this will return `'FooBar'`. |
| String getNameFromAnnotation(ConstructorInvocation node) { |
| if (node != null) { |
| Expression first; |
| if (node.arguments.named.isNotEmpty) { |
| first = node.arguments.named |
| .firstWhere((n) => n.name == 'name', orElse: () => null) |
| ?.value; |
| } |
| if (node.arguments.positional.isNotEmpty) { |
| first ??= node.arguments.positional[0]; |
| } |
| if (first != null) { |
| first = _followConstFields(first); |
| if (first is StringLiteral) return first.value; |
| } |
| } |
| return null; |
| } |
| |
| Expression _followConstFields(Expression expr) { |
| if (expr is StaticGet) { |
| var target = expr.target; |
| if (target is Field) { |
| return _followConstFields(target.initializer); |
| } |
| } |
| return expr; |
| } |
| |
| /// Finds constant expressions as defined in Dart language spec 4th ed, |
| /// 16.1 Constants |
| class ConstantVisitor extends ExpressionVisitor<bool> { |
| final CoreTypes coreTypes; |
| ConstantVisitor(this.coreTypes); |
| |
| bool isConstant(Expression e) => e.accept(this) as bool; |
| |
| defaultExpression(node) => false; |
| defaultBasicLiteral(node) => true; |
| visitTypeLiteral(node) => true; // TODO(jmesserly): deferred libraries? |
| visitSymbolLiteral(node) => true; |
| visitListLiteral(node) => node.isConst; |
| visitMapLiteral(node) => node.isConst; |
| visitStaticInvocation(node) { |
| return node.isConst || |
| node.target == coreTypes.identicalProcedure && |
| node.arguments.positional.every(isConstant); |
| } |
| |
| visitDirectMethodInvocation(node) { |
| return node.receiver is BasicLiteral && |
| isOperatorMethodName(node.name.name) && |
| node.arguments.positional.every((p) => p is BasicLiteral); |
| } |
| |
| visitMethodInvocation(node) { |
| return node.receiver is BasicLiteral && |
| isOperatorMethodName(node.name.name) && |
| node.arguments.positional.every((p) => p is BasicLiteral); |
| } |
| |
| visitConstructorInvocation(node) => node.isConst; |
| visitStringConcatenation(node) => |
| node.expressions.every((e) => e is BasicLiteral); |
| visitStaticGet(node) { |
| var target = node.target; |
| return target is Procedure || target is Field && target.isConst; |
| } |
| |
| visitVariableGet(node) => node.variable.isConst; |
| visitNot(node) { |
| var operand = node.operand; |
| return operand is BoolLiteral || |
| operand is DirectMethodInvocation && |
| visitDirectMethodInvocation(operand) || |
| operand is MethodInvocation && visitMethodInvocation(operand); |
| } |
| |
| visitLogicalExpression(node) => |
| node.left is BoolLiteral && node.right is BoolLiteral; |
| visitConditionalExpression(node) => |
| node.condition is BoolLiteral && |
| node.then is BoolLiteral && |
| node.otherwise is BoolLiteral; |
| |
| visitLet(Let node) { |
| var init = node.variable.initializer; |
| return (init == null || isConstant(init)) && isConstant(node.body); |
| } |
| } |
| |
| /// Returns true if [name] is an operator method that is available on primitive |
| /// types (`int`, `double`, `num`, `String`, `bool`). |
| /// |
| /// This does not include logical operators that cannot be user-defined |
| /// (`!`, `&&` and `||`). |
| bool isOperatorMethodName(String name) { |
| switch (name) { |
| case '==': |
| case '~': |
| case '^': |
| case '|': |
| case '&': |
| case '>>': |
| case '<<': |
| case '+': |
| case 'unary-': |
| case '-': |
| case '*': |
| case '/': |
| case '~/': |
| case '>': |
| case '<': |
| case '>=': |
| case '<=': |
| case '%': |
| return true; |
| } |
| return false; |
| } |
| |
| /// Returns true if this class is of the form: |
| /// `class C = Object with M [implements I1, I2 ...];` |
| /// |
| /// A mixin alias class is a mixin application, that can also be itself used as |
| /// a mixin. |
| bool isMixinAliasClass(Class c) => |
| c.isMixinApplication && c.superclass.superclass == null; |
| |
| List<Class> getSuperclasses(Class c) { |
| var result = <Class>[]; |
| var visited = new HashSet<Class>(); |
| while (c != null && visited.add(c)) { |
| for (var m = c.mixedInClass; m != null; m = m.mixedInClass) { |
| result.add(m); |
| } |
| var superclass = c.superclass; |
| if (superclass == null) break; |
| result.add(superclass); |
| c = superclass; |
| } |
| return result; |
| } |
| |
| List<Class> getImmediateSuperclasses(Class c) { |
| var result = <Class>[]; |
| var m = c.mixedInClass; |
| if (m != null) result.add(m); |
| var s = c.superclass; |
| if (s != null) result.add(s); |
| return result; |
| } |
| |
| Expression getInvocationReceiver(InvocationExpression node) => |
| node is MethodInvocation |
| ? node.receiver |
| : node is DirectMethodInvocation ? node.receiver : null; |
| |
| bool isInlineJS(Member e) => |
| e is Procedure && |
| e.name.name == 'JS' && |
| e.enclosingLibrary.importUri.toString() == 'dart:_foreign_helper'; |
| |
| // Check whether we have any covariant parameters. |
| // Usually we don't, so we can use the same type. |
| bool isCovariant(VariableDeclaration p) => |
| p.isCovariant || p.isGenericCovariantImpl; |
| |
| /// Returns true iff this factory constructor just throws [UnsupportedError]/ |
| /// |
| /// `dart:html` has many of these. |
| bool isUnsupportedFactoryConstructor(Procedure node) { |
| if (node.name.isPrivate && node.enclosingLibrary.importUri.scheme == 'dart') { |
| var body = node.function.body; |
| if (body is Block) { |
| var statements = body.statements; |
| if (statements.length == 1) { |
| var statement = statements[0]; |
| if (statement is ExpressionStatement) { |
| var expr = statement.expression; |
| if (expr is Throw) { |
| var error = expr.expression; |
| if (error is ConstructorInvocation && |
| error.target.enclosingClass.name == 'UnsupportedError') { |
| // HTML adds a lot of private constructors that are unreachable. |
| // Skip these. |
| return true; |
| } |
| } |
| } |
| } |
| } |
| } |
| return false; |
| } |
| |
| /// Returns the redirecting factory constructors for the enclosing class, |
| /// if the field [f] is storing that information, otherwise returns `null`. |
| Iterable<Member> getRedirectingFactories(Field f) { |
| // TODO(jmesserly): this relies on implementation details in Kernel |
| if (f.name.name == "_redirecting#") { |
| assert(f.isStatic); |
| var list = f.initializer as ListLiteral; |
| return list.expressions.map((e) => (e as StaticGet).target); |
| } |
| return null; |
| } |
| |
| /// Gets the real supertype of [c] and the list of [mixins] in reverse |
| /// application order (mixins will appear before ones they override). |
| /// |
| /// This is used to ignore synthetic mixin application classes. |
| /// |
| // TODO(jmesserly): consider replacing this with Kernel's mixin unrolling once |
| // we don't have the Analyzer backend to maintain. |
| Class getSuperclassAndMixins(Class c, List<Class> mixins) { |
| assert(mixins.isEmpty); |
| |
| var mixedInClass = c.mixedInClass; |
| if (mixedInClass != null) mixins.add(mixedInClass); |
| |
| var sc = c.superclass; |
| for (; sc.isSyntheticMixinImplementation; sc = sc.superclass) { |
| mixins.add(sc.mixedInClass); |
| } |
| return sc; |
| } |