| // Copyright (c) 2016, 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:analyzer/dart/ast/ast.dart'; |
| import 'package:analyzer/dart/element/element.dart'; |
| import 'package:analyzer/dart/element/type.dart' |
| show DartType, InterfaceType, FunctionType; |
| import 'package:analyzer/dart/element/type.dart'; |
| import 'package:analyzer/src/dart/element/type.dart'; |
| import 'package:analyzer/src/generated/constant.dart' |
| show DartObject, DartObjectImpl; |
| import 'package:analyzer/src/generated/constant.dart'; |
| import 'package:analyzer/src/generated/type_system.dart' show Dart2TypeSystem; |
| |
| class Tuple2<T0, T1> { |
| final T0 e0; |
| final T1 e1; |
| Tuple2(this.e0, this.e1); |
| } |
| |
| /// Instantiates [t] with dynamic bounds. |
| /// |
| /// Note: this should only be used when the resulting type is later replaced, |
| /// such as a "deferred supertype" (used to break circularity between the |
| /// supertype's type arguments and the class definition). Otherwise |
| /// [instantiateElementTypeToBounds] should be used instead. |
| InterfaceType fillDynamicTypeArgsForClass(InterfaceType t) { |
| if (t.typeArguments.isNotEmpty) { |
| var rawT = t.element.type; |
| var dyn = List.filled(rawT.typeArguments.length, DynamicTypeImpl.instance); |
| return rawT.substitute2(dyn, rawT.typeArguments); |
| } |
| return t; |
| } |
| |
| /// Instantiates the [element] type to bounds. |
| /// |
| /// Conceptually this is similar to [Dart2TypeSystem.instantiateToBounds] on |
| /// `element.type`, but unfortunately typedef elements do not return a |
| /// meaningful type, so we need to work around that. |
| DartType instantiateElementTypeToBounds( |
| Dart2TypeSystem rules, TypeDefiningElement element) { |
| Element e = element; |
| if (e is TypeParameterizedElement) { |
| // TODO(jmesserly): we can't use `instantiateToBounds` because typedefs do |
| // not include their type parameters, for example: |
| // |
| // typedef void void Func<T>(T x); // Dart 1 syntax. |
| // typedef void GenericFunc<T> = S Func<S>(T x); // Dart 2 syntax. |
| // |
| // There is no way to get a type that has `<T>` as a type formal from the |
| // element (without constructing it ourselves). |
| // |
| // Futhermore, the second line is represented by a GenericTypeAliasElement, |
| // and its type getter does not even include its own type formals `<S>`. |
| // That has to be worked around using `.function.type`. |
| var type = e is GenericTypeAliasElement ? e.function.type : e.type; |
| var bounds = rules.instantiateTypeFormalsToBounds(e.typeParameters); |
| if (bounds == null) return type; |
| return type.substitute2( |
| bounds, TypeParameterTypeImpl.getTypes(e.typeParameters)); |
| } |
| return element.type; |
| } |
| |
| /// Given an [element] and a [test] function, returns the first matching |
| /// constant valued metadata annotation on the element. |
| /// |
| /// If the element is a synthetic getter/setter (Analyzer creates these for |
| /// fields), then this will use the corresponding real element, which will have |
| /// the metadata annotations. |
| /// |
| /// 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 |
| /// |
| DartObject findAnnotation(Element element, bool test(DartObjectImpl value)) { |
| if (element == null) return null; |
| var accessor = element; |
| if (accessor is PropertyAccessorElement && accessor.isSynthetic) { |
| // Look for metadata on the real element, not the synthetic one. |
| element = accessor.variable; |
| } |
| for (var metadata in element.metadata) { |
| var value = metadata.computeConstantValue(); |
| if (value is DartObjectImpl && test(value)) return value; |
| } |
| return null; |
| } |
| |
| /// Searches all supertype, in order of most derived members, to see if any |
| /// [match] a condition. If so, returns the first match, otherwise returns null. |
| InterfaceType findSupertype(InterfaceType type, bool match(InterfaceType t)) { |
| for (var m in type.mixins.reversed) { |
| if (match(m)) return m; |
| } |
| var s = type.superclass; |
| if (s == null) return null; |
| |
| if (match(s)) return type; |
| return findSupertype(s, match); |
| } |
| |
| //TODO(leafp): Is this really necessary? In theory I think |
| // the static type should always be filled in for resolved |
| // ASTs. This may be a vestigial workaround. |
| DartType getStaticType(Expression e) => |
| e.staticType ?? DynamicTypeImpl.instance; |
| |
| // TODO(leafp) Factor this out or use an existing library |
| /// Similar to [SimpleIdentifier] inGetterContext, inSetterContext, and |
| /// inDeclarationContext, this method returns true if [node] is used in an |
| /// invocation context such as a MethodInvocation. |
| bool inInvocationContext(Expression node) { |
| if (node == null) return false; |
| var parent = node.parent; |
| while (parent is ParenthesizedExpression) { |
| node = parent; |
| parent = node.parent; |
| } |
| return parent is InvocationExpression && identical(node, parent.function) || |
| parent is MethodInvocation && |
| parent.methodName.name == 'call' && |
| identical(node, parent.target); |
| } |
| |
| bool isInlineJS(Element e) { |
| if (e != null && e.name == 'JS' && e is FunctionElement) { |
| var uri = e.librarySource.uri; |
| return uri.scheme == 'dart' && uri.path == '_foreign_helper'; |
| } |
| return false; |
| } |
| |
| bool isLibraryPrefix(Expression node) => |
| node is SimpleIdentifier && node.staticElement is PrefixElement; |
| |
| ExecutableElement getFunctionBodyElement(FunctionBody body) { |
| var f = body.parent; |
| if (f is FunctionExpression) { |
| return f.declaredElement; |
| } else if (f is MethodDeclaration) { |
| return f.declaredElement; |
| } else { |
| return (f as ConstructorDeclaration).declaredElement; |
| } |
| } |
| |
| /// Returns the value of the `name` field from the [match]ing annotation on |
| /// [element], or `null` if we didn't find a valid match or it was not a string. |
| /// |
| /// For example, consider this code: |
| /// |
| /// class MyAnnotation { |
| /// final String name; |
| /// // ... |
| /// const MyAnnotation(this.name/*, ... other params ... */); |
| /// } |
| /// |
| /// @MyAnnotation('FooBar') |
| /// main() { ... } |
| /// |
| /// If we match the annotation for the `@MyAnnotation('FooBar')` this will |
| /// return the string 'FooBar'. |
| String getAnnotationName(Element element, bool match(DartObjectImpl value)) => |
| findAnnotation(element, match)?.getField('name')?.toStringValue(); |
| |
| List<ClassElement> getSuperclasses(ClassElement cls) { |
| var result = <ClassElement>[]; |
| var visited = HashSet<ClassElement>(); |
| while (cls != null && visited.add(cls)) { |
| for (var mixinType in cls.mixins.reversed) { |
| var mixin = mixinType.element; |
| if (mixin != null) result.add(mixin); |
| } |
| var supertype = cls.supertype; |
| // Object or mixin declaration. |
| if (supertype == null) break; |
| |
| cls = supertype.element; |
| result.add(cls); |
| } |
| return result; |
| } |
| |
| List<ClassElement> getImmediateSuperclasses(ClassElement c) { |
| var result = <ClassElement>[]; |
| for (var m in c.mixins.reversed) { |
| result.add(m.element); |
| } |
| var s = c.supertype; |
| if (s != null) result.add(s.element); |
| return result; |
| } |
| |
| /// Returns true if the library [l] is dart:_runtime. |
| // TODO(jmesserly): unlike other methods in this file, this one wouldn't be |
| // suitable for upstream to Analyzer, as it's DDC specific. |
| bool isSdkInternalRuntime(LibraryElement l) { |
| var uri = l.source.uri; |
| return uri.scheme == 'dart' && uri.path == '_runtime'; |
| } |
| |
| /// Return `true` if the given [classElement] has a noSuchMethod() method |
| /// distinct from the one declared in class Object, as per the Dart Language |
| /// Specification (section 10.4). |
| // TODO(jmesserly): this was taken from error_verifier.dart |
| bool hasNoSuchMethod(ClassElement classElement) { |
| // TODO(jmesserly): this is slow in Analyzer. It's a linear scan through all |
| // methods, up through the class hierarchy. |
| var method = classElement.lookUpMethod( |
| FunctionElement.NO_SUCH_METHOD_METHOD_NAME, classElement.library); |
| var definingClass = method?.enclosingElement; |
| return definingClass != null && !definingClass.type.isObject; |
| } |
| |
| /// 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(ClassElement c) { |
| return c.isMixinApplication && c.supertype.isObject && c.mixins.length == 1; |
| } |
| |
| bool isCallableClass(ClassElement c) { |
| // See if we have a "call" with a statically known function type: |
| // |
| // - if it's a method, then it does because all methods do, |
| // - if it's a getter, check the return type. |
| // |
| // Other cases like a getter returning dynamic/Object/Function will be |
| // handled at runtime by the dynamic call mechanism. So we only |
| // concern ourselves with statically known function types. |
| // |
| // We can ignore `noSuchMethod` because: |
| // * `dynamic d; d();` without a declared `call` method is handled by dcall. |
| // * for `class C implements Callable { noSuchMethod(i) { ... } }` we find |
| // the `call` method on the `Callable` interface. |
| var callMethod = c.type.lookUpInheritedGetterOrMethod('call'); |
| return callMethod is PropertyAccessorElement |
| ? callMethod.returnType is FunctionType |
| : callMethod != null; |
| } |
| |
| /// Returns true if [x] and [y] are equal, in other words, `x <: y` and `y <: x` |
| /// and they have equivalent display form when printed. |
| // |
| // TODO(jmesserly): this exists to work around broken FunctionTypeImpl.== in |
| // Analyzer. It has two bugs: |
| // - typeArguments are considered, even though this has no semantic effect. |
| // For example: `int -> int` that resulted from `(<T>(T) -> T)<int>` will not |
| // equal another `int -> int`, even though they are the same type. |
| // - named arguments are incorrectly treated as ordered, see |
| // https://github.com/dart-lang/sdk/issues/26126. |
| bool typesAreEqual(DartType x, DartType y) { |
| if (identical(x, y)) return true; |
| if (x is FunctionType) { |
| if (y is FunctionType) { |
| if (x.typeFormals.length != y.typeFormals.length) { |
| return false; |
| } |
| // `<T>T -> T` should be equal to `<U>U -> U` |
| // To test this, we instantiate both types with the same (unique) type |
| // variables, and see if the result is equal. |
| if (x.typeFormals.isNotEmpty) { |
| var fresh = FunctionTypeImpl.relateTypeFormals( |
| x, y, (t, s, _, __) => typesAreEqual(t, s)); |
| if (fresh == null) { |
| return false; |
| } |
| return typesAreEqual(x.instantiate(fresh), y.instantiate(fresh)); |
| } |
| |
| return typesAreEqual(x.returnType, y.returnType) && |
| _argumentsAreEqual(x.normalParameterTypes, y.normalParameterTypes) && |
| _argumentsAreEqual( |
| x.optionalParameterTypes, y.optionalParameterTypes) && |
| _namedArgumentsAreEqual(x.namedParameterTypes, y.namedParameterTypes); |
| } else { |
| return false; |
| } |
| } |
| if (x is InterfaceType) { |
| return y is InterfaceType && |
| x.element == y.element && |
| _argumentsAreEqual(x.typeArguments, y.typeArguments); |
| } |
| return x == y; |
| } |
| |
| bool _argumentsAreEqual(List<DartType> first, List<DartType> second) { |
| if (first.length != second.length) return false; |
| for (int i = 0; i < first.length; i++) { |
| if (!typesAreEqual(first[i], second[i])) return false; |
| } |
| return true; |
| } |
| |
| bool _namedArgumentsAreEqual( |
| Map<String, DartType> xArgs, Map<String, DartType> yArgs) { |
| if (yArgs.length != xArgs.length) return false; |
| for (var name in xArgs.keys) { |
| var x = xArgs[name]; |
| var y = yArgs[name]; |
| if (y == null || !typesAreEqual(x, y)) return false; |
| } |
| return true; |
| } |
| |
| /// Returns a valid hashCode for [t] for use with [typesAreEqual]. |
| int typeHashCode(DartType t) { |
| // TODO(jmesserly): this is from Analyzer; it's not a great hash function. |
| // We should at least fix how this combines hashes. |
| if (t is FunctionType) { |
| if (t.typeFormals.isNotEmpty) { |
| // Instantiate the generic function type, so we hash equivalent |
| // generic function types to the same value. For example, `<X>(X) -> X` |
| // and `<Y>(Y) -> Y` shouold have the same hash. |
| // |
| // TODO(jmesserly): it would be better to instantiate these with unique |
| // types for each position, so we can distinguish cases like these: |
| // |
| // <A, B>(A) -> B |
| // <C, D>(D) -> C // reversed |
| // <E, F>(E) -> void // uses `void` |
| // |
| // Currently all of those will have the same hash code. |
| // |
| // The choice of `void` is rather arbitrary. Of the types we can easily |
| // obtain from Analyzer, it should collide a bit less than something like |
| // `dynamic`. |
| int code = t.typeFormals.length; |
| code = (code << 1) + |
| typeHashCode(t.instantiate( |
| List.filled(t.typeFormals.length, VoidTypeImpl.instance))); |
| return code; |
| } |
| int code = typeHashCode(t.returnType); |
| for (var p in t.normalParameterTypes) { |
| code = (code << 1) + typeHashCode(p); |
| } |
| for (var p in t.optionalParameterTypes) { |
| code = (code << 1) + typeHashCode(p); |
| } |
| for (var p in t.namedParameterTypes.values) { |
| code ^= typeHashCode(p); // xor because named parameters are unordered. |
| } |
| return code; |
| } |
| return t.hashCode; |
| } |
| |
| Uri uriForCompilationUnit(CompilationUnitElement unit) { |
| if (unit.source.isInSystemLibrary) { |
| return unit.source.uri; |
| } |
| // TODO(jmesserly): this needs serious cleanup. |
| // There does appear to be something strange going on with Analyzer |
| // URIs if we try and use them directly on Windows. |
| // See also compiler.dart placeSourceMap, which could use cleanup too. |
| var sourcePath = unit.source.fullName; |
| return sourcePath.startsWith('package:') |
| ? Uri.parse(sourcePath) |
| // TODO(jmesserly): shouldn't this be path.toUri? |
| : Uri.file(sourcePath); |
| } |
| |
| /// Returns true iff this factory constructor just throws [UnsupportedError]/ |
| /// |
| /// `dart:html` has many of these. |
| bool isUnsupportedFactoryConstructor(ConstructorDeclaration node) { |
| var ctorBody = node.body; |
| var element = node.declaredElement; |
| if (element.isPrivate && |
| element.librarySource.isInSystemLibrary && |
| ctorBody is BlockFunctionBody) { |
| var statements = ctorBody.block.statements; |
| if (statements.length == 1) { |
| var statement = statements[0]; |
| if (statement is ExpressionStatement) { |
| var expr = statement.expression; |
| if (expr is ThrowExpression && |
| expr.expression is InstanceCreationExpression) { |
| if (expr.expression.staticType.name == 'UnsupportedError') { |
| // HTML adds a lot of private constructors that are unreachable. |
| // Skip these. |
| return true; |
| } |
| } |
| } |
| } |
| } |
| return false; |
| } |
| |
| bool isBuiltinAnnotation( |
| DartObjectImpl value, String libraryName, String annotationName) { |
| var e = value?.type?.element; |
| if (e?.name != annotationName) return false; |
| var uri = e.source.uri; |
| var path = uri.pathSegments[0]; |
| return uri.scheme == 'dart' && path == libraryName; |
| } |
| |
| /// Returns the integer value for [node] as a [BigInt]. |
| /// |
| /// `node.value` should not be used directly as it depends on platform integers |
| /// and may be `null` for some valid integer literals (in either an `int` or a |
| /// `double` context) |
| BigInt getLiteralBigIntValue(IntegerLiteral node) { |
| // TODO(jmesserly): workaround for #34360: Analyzer tree does not store |
| // the BigInt or double value, so we need to re-parse it from the token. |
| return BigInt.parse(node.literal.lexeme); |
| } |