| // 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 'package:analyzer/dart/ast/ast.dart'; |
| import 'package:analyzer/dart/element/element.dart'; |
| import 'package:analyzer/dart/element/type.dart'; |
| import 'package:analyzer/dart/element/type_system.dart'; |
| import 'package:analyzer/src/dart/element/member.dart'; // ignore: implementation_imports |
| import 'package:meta/meta.dart'; |
| |
| typedef AstNodePredicate = bool Function(AstNode node); |
| |
| class DartTypeUtilities { |
| static bool extendsClass(DartType type, String className, String library) => |
| _extendsClass(type, <ClassElement>{}, className, library); |
| |
| static bool _extendsClass(DartType type, Set<ClassElement> seenTypes, |
| String className, String library) => |
| type is InterfaceType && |
| seenTypes.add(type.element) && |
| (isClass(type, className, library) || |
| _extendsClass(type.superclass, seenTypes, className, library)); |
| |
| static Element getCanonicalElement(Element element) { |
| if (element is PropertyAccessorElement) { |
| final variable = element.variable; |
| if (variable is FieldMember) { |
| // A field element defined in a parameterized type where the values of |
| // the type parameters are known. |
| // |
| // This concept should be invisible when comparing FieldElements, but a |
| // bug in the analyzer causes FieldElements to not evaluate as |
| // equivalent to equivalent FieldMembers. See |
| // https://github.com/dart-lang/sdk/issues/35343. |
| return variable.declaration; |
| } else { |
| return variable; |
| } |
| } else { |
| return element; |
| } |
| } |
| |
| static Element getCanonicalElementFromIdentifier(AstNode rawNode) { |
| if (rawNode is Expression) { |
| final node = rawNode.unParenthesized; |
| if (node is Identifier) { |
| return getCanonicalElement(node.staticElement); |
| } else if (node is PropertyAccess) { |
| return getCanonicalElement(node.propertyName.staticElement); |
| } |
| } |
| return null; |
| } |
| |
| /// Return whether the canonical elements of two elements are equal. |
| static bool canonicalElementsAreEqual(Element element1, Element element2) => |
| getCanonicalElement(element1) == getCanonicalElement(element2); |
| |
| /// Returns whether the canonical elements from two nodes are equal. |
| /// |
| /// As in, [getCanonicalElementFromIdentifier], the two nodes must be |
| /// [Expression]s in order to be compared (otherwise `false` is returned). |
| /// |
| /// The two nodes must both be a [SimpleIdentifier], [PrefixedIdentifier], or |
| /// [PropertyAccess] (otherwise `false` is returned). |
| /// |
| /// If the two nodes are PrefixedIdentifiers, or PropertyAccess nodes, then |
| /// `true` is returned only if their canonical elements are equal, in |
| /// addition to their prefixes' and targets' (respectfully) canonical |
| /// elements. |
| /// |
| /// There is an inherent assumption about pure getters. For example: |
| /// |
| /// A a1 = ... |
| /// A a2 = ... |
| /// a1.b.c; // statement 1 |
| /// a2.b.c; // statement 2 |
| /// a1.b.c; // statement 3 |
| /// |
| /// The canonical elements from statements 1 and 2 are different, because a1 |
| /// is not the same element as a2. The canonical elements from statements 1 |
| /// and 3 are considered to be equal, even though `A.b` may have side effects |
| /// which alter the returned value. |
| static bool canonicalElementsFromIdentifiersAreEqual( |
| Expression rawExpression1, Expression rawExpression2) { |
| if (rawExpression1 == null || rawExpression2 == null) return false; |
| |
| final expression1 = rawExpression1.unParenthesized; |
| final expression2 = rawExpression2.unParenthesized; |
| |
| if (expression1 is SimpleIdentifier) { |
| return expression2 is SimpleIdentifier && |
| canonicalElementsAreEqual( |
| expression1.staticElement, expression2.staticElement); |
| } |
| |
| if (expression1 is PrefixedIdentifier) { |
| return expression2 is PrefixedIdentifier && |
| canonicalElementsAreEqual(expression1.prefix.staticElement, |
| expression2.prefix.staticElement) && |
| canonicalElementsAreEqual( |
| expression1.staticElement, expression2.staticElement); |
| } |
| |
| if (expression1 is PropertyAccess && expression2 is PropertyAccess) { |
| final target1 = expression1.target; |
| final target2 = expression2.target; |
| return canonicalElementsFromIdentifiersAreEqual(target1, target2) && |
| canonicalElementsAreEqual(expression1.propertyName.staticElement, |
| expression2.propertyName.staticElement); |
| } |
| |
| return false; |
| } |
| |
| static Iterable<InterfaceType> getImplementedInterfaces(InterfaceType type) { |
| void recursiveCall(InterfaceType type, Set<ClassElement> alreadyVisited, |
| List<InterfaceType> interfaceTypes) { |
| if (type == null || !alreadyVisited.add(type.element)) { |
| return; |
| } |
| interfaceTypes.add(type); |
| recursiveCall(type.superclass, alreadyVisited, interfaceTypes); |
| for (final interface in type.interfaces) { |
| recursiveCall(interface, alreadyVisited, interfaceTypes); |
| } |
| for (final mixin in type.mixins) { |
| recursiveCall(mixin, alreadyVisited, interfaceTypes); |
| } |
| } |
| |
| final interfaceTypes = <InterfaceType>[]; |
| recursiveCall(type, <ClassElement>{}, interfaceTypes); |
| return interfaceTypes; |
| } |
| |
| static Statement getLastStatementInBlock(Block node) { |
| if (node.statements.isEmpty) { |
| return null; |
| } |
| final lastStatement = node.statements.last; |
| if (lastStatement is Block) { |
| return getLastStatementInBlock(lastStatement); |
| } |
| return lastStatement; |
| } |
| |
| static bool hasInheritedMethod(MethodDeclaration node) => |
| lookUpInheritedMethod(node) != null; |
| |
| static bool implementsAnyInterface( |
| DartType type, Iterable<InterfaceTypeDefinition> definitions) { |
| if (type is! InterfaceType) { |
| return false; |
| } |
| final interfaceType = type as InterfaceType; |
| bool predicate(InterfaceType i) => |
| definitions.any((d) => isInterface(i, d.name, d.library)); |
| final element = interfaceType.element; |
| return predicate(interfaceType) || |
| !element.isSynthetic && element.allSupertypes.any(predicate); |
| } |
| |
| static bool implementsInterface( |
| DartType type, String interface, String library) { |
| if (type is! InterfaceType) { |
| return false; |
| } |
| final interfaceType = type as InterfaceType; |
| bool predicate(InterfaceType i) => isInterface(i, interface, library); |
| final element = interfaceType.element; |
| return predicate(interfaceType) || |
| !element.isSynthetic && element.allSupertypes.any(predicate); |
| } |
| |
| // todo (pq): unify and `isInterface` into a shared method: `isInterfaceType` |
| static bool isClass(DartType type, String className, String library) => |
| type is InterfaceType && |
| type.element.name == className && |
| type.element.library?.name == library; |
| |
| static bool isConstructorElement(ConstructorElement element, |
| {@required String uriStr, |
| @required String className, |
| @required String constructorName}) => |
| element != null && |
| element.library?.name == uriStr && |
| element.enclosingElement.name == className && |
| element.name == constructorName; |
| |
| static bool isInterface( |
| InterfaceType type, String interface, String library) => |
| type.element.name == interface && type.element.library.name == library; |
| |
| static bool isNullLiteral(Expression expression) => |
| expression?.unParenthesized is NullLiteral; |
| |
| static PropertyAccessorElement lookUpGetter(MethodDeclaration node) { |
| final parent = node.declaredElement.enclosingElement; |
| if (parent is ClassElement) { |
| return parent.lookUpGetter(node.name.name, node.declaredElement.library); |
| } |
| if (parent is ExtensionElement) { |
| return parent.getGetter(node.name.name); |
| } |
| return null; |
| } |
| |
| static PropertyAccessorElement lookUpInheritedConcreteGetter( |
| MethodDeclaration node) { |
| final parent = node.declaredElement.enclosingElement; |
| if (parent is ClassElement) { |
| return parent.lookUpInheritedConcreteGetter( |
| node.name.name, node.declaredElement.library); |
| } |
| // Extensions don't inherit. |
| return null; |
| } |
| |
| static MethodElement lookUpInheritedConcreteMethod(MethodDeclaration node) { |
| final parent = node.declaredElement.enclosingElement; |
| if (parent is ClassElement) { |
| return parent.lookUpInheritedConcreteMethod( |
| node.name.name, node.declaredElement.library); |
| } |
| // Extensions don't inherit. |
| return null; |
| } |
| |
| static PropertyAccessorElement lookUpInheritedConcreteSetter( |
| MethodDeclaration node) { |
| final parent = node.declaredElement.enclosingElement; |
| if (parent is ClassElement) { |
| return parent.lookUpInheritedConcreteSetter( |
| node.name.name, node.declaredElement.library); |
| } |
| // Extensions don't inherit. |
| return null; |
| } |
| |
| static MethodElement lookUpInheritedMethod(MethodDeclaration node) { |
| final parent = node.declaredElement.enclosingElement; |
| return parent is ClassElement |
| ? parent.lookUpInheritedMethod( |
| node.name.name, node.declaredElement.library) |
| : null; |
| } |
| |
| static PropertyAccessorElement lookUpSetter(MethodDeclaration node) { |
| final parent = node.declaredElement.enclosingElement; |
| if (parent is ClassElement) { |
| return parent.lookUpSetter(node.name.name, node.declaredElement.library); |
| } |
| if (parent is ExtensionElement) { |
| return parent.getSetter(node.name.name); |
| } |
| return null; |
| } |
| |
| static bool matchesArgumentsWithParameters( |
| NodeList<Expression> arguments, NodeList<FormalParameter> parameters) { |
| final namedParameters = <String, Element>{}; |
| final namedArguments = <String, Element>{}; |
| final positionalParameters = <Element>[]; |
| final positionalArguments = <Element>[]; |
| for (final parameter in parameters) { |
| if (parameter.isNamed) { |
| namedParameters[parameter.identifier.name] = |
| parameter.identifier.staticElement; |
| } else { |
| positionalParameters.add(parameter.identifier.staticElement); |
| } |
| } |
| for (final argument in arguments) { |
| if (argument is NamedExpression) { |
| final element = DartTypeUtilities.getCanonicalElementFromIdentifier( |
| argument.expression); |
| if (element == null) { |
| return false; |
| } |
| namedArguments[argument.name.label.name] = element; |
| } else { |
| final element = |
| DartTypeUtilities.getCanonicalElementFromIdentifier(argument); |
| if (element == null) { |
| return false; |
| } |
| positionalArguments.add(element); |
| } |
| } |
| if (positionalParameters.length != positionalArguments.length || |
| namedParameters.keys.length != namedArguments.keys.length) { |
| return false; |
| } |
| for (var i = 0; i < positionalArguments.length; i++) { |
| if (positionalArguments[i] != positionalParameters[i]) { |
| return false; |
| } |
| } |
| |
| for (final key in namedParameters.keys) { |
| if (namedParameters[key] != namedArguments[key]) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| static bool overridesMethod(MethodDeclaration node) { |
| final parent = node.parent; |
| if (parent is! ClassOrMixinDeclaration) { |
| return false; |
| } |
| final name = node.declaredElement.name; |
| final clazz = parent as ClassOrMixinDeclaration; |
| final classElement = clazz.declaredElement; |
| final library = classElement.library; |
| return classElement.allSupertypes |
| .map(node.isGetter |
| ? (InterfaceType t) => t.lookUpGetter2 |
| : node.isSetter |
| ? (InterfaceType t) => t.lookUpSetter2 |
| : (InterfaceType t) => t.lookUpMethod2) |
| .any((lookUp) => lookUp(name, library) != null); |
| } |
| |
| /// Builds the list resulting from traversing the node in DFS and does not |
| /// include the node itself, it excludes the nodes for which the exclusion |
| /// predicate returns true, if not provided, all is included. |
| static Iterable<AstNode> traverseNodesInDFS(AstNode node, |
| {AstNodePredicate excludeCriteria}) { |
| final nodes = <AstNode>{}; |
| void recursiveCall(node) { |
| if (node is AstNode && |
| (excludeCriteria == null || !excludeCriteria(node))) { |
| nodes.add(node); |
| node.childEntities.forEach(recursiveCall); |
| } |
| } |
| |
| node.childEntities.forEach(recursiveCall); |
| return nodes; |
| } |
| |
| /// Return whether [leftType] and [rightType] are _definitely_ unrelated. |
| /// |
| /// For the purposes of this function, here are some "relation" rules: |
| /// * `dynamic` and `Null` are considered related to any other type. |
| /// * Two equal types are considered related, e.g. classes `int` and `int`, |
| /// classes `List<String>` and `List<String>`, |
| /// classes `List<T>` and `List<T>`, and type variables `A` and `A`. |
| /// * Two types such that one is a subtype of the other, such as classes |
| /// `List<dynamic>` and `Iterable<dynamic>`, and type variables `A` and `B` |
| /// where `A extends B`. |
| /// * Two types, each representing a class: |
| /// * are related if they represent the same class, modulo type arguments, |
| /// and each of their pair-wise type arguments are related, e.g. |
| /// `List<dynamic>` and `List<int>`, and `Future<T>` and `Future<S>` where |
| /// `S extends T`. |
| /// * are unrelated if [leftType]'s supertype is [Object]. |
| /// * are related if their supertypes are equal, e.g. `List<dynamic>` and |
| /// `Set<dynamic>`. |
| /// * Two types, each representing a type variable, are related if their |
| /// bounds are related. |
| /// * Otherwise, the types are related. |
| // TODO(srawlins): typedefs and functions in general. |
| static bool unrelatedTypes( |
| TypeSystem typeSystem, DartType leftType, DartType rightType) { |
| // If we don't have enough information, or can't really compare the types, |
| // return false as they _might_ be related. |
| if (leftType == null || |
| leftType.isBottom || |
| leftType.isDynamic || |
| rightType == null || |
| rightType.isBottom || |
| rightType.isDynamic) { |
| return false; |
| } |
| if (leftType == rightType || |
| typeSystem.isSubtypeOf(leftType, rightType) || |
| typeSystem.isSubtypeOf(rightType, leftType)) { |
| return false; |
| } |
| if (leftType is InterfaceType && rightType is InterfaceType) { |
| // In this case, [leftElement] and [rightElement] each represent |
| // the same class, like `int`, or `Iterable<String>`. |
| var leftElement = leftType.element; |
| var rightElement = rightType.element; |
| if (leftElement == rightElement) { |
| // In this case, [leftElement] and [rightElement] represent the same |
| // class, modulo generics, e.g. `List<int>` and `List<dynamic>`. Now we |
| // need to check type arguments. |
| var leftTypeArguments = leftType.typeArguments; |
| var rightTypeArguments = rightType.typeArguments; |
| if (leftTypeArguments.length != rightTypeArguments.length) { |
| // I cannot think of how we would enter this block, but it guards |
| // against RangeError below. |
| return false; |
| } |
| for (var i = 0; i < leftTypeArguments.length; i++) { |
| // If any of the pair-wise type arguments are unrelated, then |
| // [leftType] and [rightType] are unrelated. |
| if (unrelatedTypes( |
| typeSystem, leftTypeArguments[i], rightTypeArguments[i])) { |
| return true; |
| } |
| } |
| // Otherwise, they might be related. |
| return false; |
| } else { |
| return leftElement.supertype?.isObject == true || |
| leftElement.supertype != rightElement.supertype; |
| } |
| } else if (leftType is TypeParameterType && |
| rightType is TypeParameterType) { |
| return unrelatedTypes( |
| typeSystem, leftType.element.bound, rightType.element.bound); |
| } else if (leftType is FunctionType) { |
| if (_isFunctionTypeUnrelatedToType(leftType, rightType)) { |
| return true; |
| } |
| } else if (rightType is FunctionType) { |
| if (_isFunctionTypeUnrelatedToType(rightType, leftType)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| static bool _isFunctionTypeUnrelatedToType( |
| FunctionType type1, DartType type2) { |
| if (type2 is FunctionType) { |
| return false; |
| } |
| final element2 = type2.element; |
| if (element2 is ClassElement && |
| element2.lookUpConcreteMethod('call', element2.library) != null) { |
| return false; |
| } |
| return true; |
| } |
| } |
| |
| class InterfaceTypeDefinition { |
| final String name; |
| final String library; |
| |
| InterfaceTypeDefinition(this.name, this.library); |
| |
| @override |
| int get hashCode => name.hashCode ^ library.hashCode; |
| |
| @override |
| bool operator ==(Object other) { |
| if (identical(this, other)) { |
| return true; |
| } |
| return other is InterfaceTypeDefinition && |
| name == other.name && |
| library == other.library; |
| } |
| } |