blob: a0eb64f7f94636d868570837f2fe04ed03ef4031 [file] [log] [blame]
// 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;
}
}