blob: c5b76c73b1eeaab8bc11135ed034607614a79f52 [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/src/dart/element/type.dart'; // ignore: implementation_imports
import '../analyzer.dart';
import '../ast.dart';
import '../extensions.dart';
typedef AstNodePredicate = bool Function(AstNode node);
bool argumentsMatchParameters(
NodeList<Expression> arguments, NodeList<FormalParameter> parameters) {
var namedParameters = <String, Element?>{};
var namedArguments = <String, Element>{};
var positionalParameters = <Element?>[];
var positionalArguments = <Element>[];
for (var parameter in parameters) {
var identifier = parameter.name;
if (identifier != null) {
if (parameter.isNamed) {
namedParameters[identifier.lexeme] = parameter.declaredElement;
} else {
positionalParameters.add(parameter.declaredElement);
}
}
}
for (var argument in arguments) {
if (argument is NamedExpression) {
var element = argument.expression.canonicalElement;
if (element == null) {
return false;
}
namedArguments[argument.name.label.name] = element;
} else {
var element = argument.canonicalElement;
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 (var key in namedParameters.keys) {
if (namedParameters[key] != namedArguments[key]) {
return false;
}
}
return true;
}
/// Returns whether the canonical elements of [element1] and [element2] are
/// equal.
bool canonicalElementsAreEqual(Element? element1, Element? element2) =>
element1?.canonicalElement == element2?.canonicalElement;
/// Returns whether the canonical elements from two nodes are equal.
///
/// As in, [NullableAstNodeExtension.canonicalElement], 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.
bool canonicalElementsFromIdentifiersAreEqual(
Expression? rawExpression1, Expression? rawExpression2) {
if (rawExpression1 == null || rawExpression2 == null) return false;
var expression1 = rawExpression1.unParenthesized;
var expression2 = rawExpression2.unParenthesized;
if (expression1 is SimpleIdentifier) {
return expression2 is SimpleIdentifier &&
canonicalElementsAreEqual(getWriteOrReadElement(expression1),
getWriteOrReadElement(expression2));
}
if (expression1 is PrefixedIdentifier) {
return expression2 is PrefixedIdentifier &&
canonicalElementsAreEqual(expression1.prefix.staticElement,
expression2.prefix.staticElement) &&
canonicalElementsAreEqual(getWriteOrReadElement(expression1.identifier),
getWriteOrReadElement(expression2.identifier));
}
if (expression1 is PropertyAccess && expression2 is PropertyAccess) {
var target1 = expression1.target;
var target2 = expression2.target;
return canonicalElementsFromIdentifiersAreEqual(target1, target2) &&
canonicalElementsAreEqual(
getWriteOrReadElement(expression1.propertyName),
getWriteOrReadElement(expression2.propertyName));
}
return false;
}
/// Returns 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 types which are equal modulo nullability are considered related,
/// e.g. `int` and `int`, `String` and `String?`, `List<String>` and
/// `List<String>`, `List<T>` and `List<T>`, and type variables `A` and `A`.
/// * Two types such that one is a subtype of the other, modulo nullability,
/// such as `List<dynamic>` and `Iterable<dynamic>`, and type variables `A`
/// and `B` where `A extends B`, are considered related.
/// * Two interface types:
/// * are related if they represent the same class, modulo type arguments,
/// modulo nullability, 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 type variables are related if their bounds are related.
/// * Otherwise, the types are related.
// TODO(srawlins): typedefs and functions in general.
bool typesAreUnrelated(
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;
}
var promotedLeftType = typeSystem.promoteToNonNull(leftType);
var promotedRightType = typeSystem.promoteToNonNull(rightType);
if (promotedLeftType == promotedRightType ||
typeSystem.isSubtypeOf(promotedLeftType, promotedRightType) ||
typeSystem.isSubtypeOf(promotedRightType, promotedLeftType)) {
return false;
}
if (promotedLeftType is InterfaceType && promotedRightType is InterfaceType) {
// In this case, [leftElement] and [rightElement] each represent
// the same class, like `int`, or `Iterable<String>`.
var leftElement = promotedLeftType.element2;
var rightElement = promotedRightType.element2;
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 = promotedLeftType.typeArguments;
var rightTypeArguments = promotedRightType.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 (typesAreUnrelated(
typeSystem, leftTypeArguments[i], rightTypeArguments[i])) {
return true;
}
}
// Otherwise, they might be related.
return false;
} else {
return (leftElement.supertype?.isDartCoreObject ?? false) ||
leftElement.supertype != rightElement.supertype;
}
} else if (promotedLeftType is TypeParameterType &&
promotedRightType is TypeParameterType) {
return typesAreUnrelated(typeSystem, promotedLeftType.element2.bound,
promotedRightType.element2.bound);
} else if (promotedLeftType is FunctionType) {
if (_isFunctionTypeUnrelatedToType(promotedLeftType, promotedRightType)) {
return true;
}
} else if (promotedRightType is FunctionType) {
if (_isFunctionTypeUnrelatedToType(promotedRightType, promotedLeftType)) {
return true;
}
}
return false;
}
bool _isFunctionTypeUnrelatedToType(FunctionType type1, DartType type2) {
if (type2 is FunctionType) {
return false;
}
if (type2 is InterfaceType) {
var element2 = type2.element2;
if (element2 is ClassElement &&
element2.lookUpConcreteMethod('call', element2.library) != null) {
return false;
}
}
return true;
}
class DartTypeUtilities {
@Deprecated('Replace with `type.extendsClass`')
static bool extendsClass(
DartType? type, String? className, String? library) =>
type.extendsClass(className, library!);
@Deprecated('Replace with `rawNode.canonicalElement`')
static Element? getCanonicalElementFromIdentifier(AstNode? rawNode) =>
rawNode.canonicalElement;
@Deprecated('Replace with `type.implementsInterface`')
static bool implementsInterface(
DartType? type, String interface, String library) =>
type.implementsInterface(interface, library);
// todo(pq): remove and replace w/ an extension (pending internal migration)
@Deprecated('Slated for removal')
static bool isClass(DartType? type, String? className, String? library) =>
type is InterfaceType &&
type.element2.name == className &&
type.element2.library.name == library;
@Deprecated('Replace with `expression.isNullLiteral`')
static bool isNullLiteral(Expression? expression) => expression.isNullLiteral;
@Deprecated('Use `argumentsMatchParameters`')
static bool matchesArgumentsWithParameters(NodeList<Expression> arguments,
NodeList<FormalParameter> parameters) =>
argumentsMatchParameters(arguments, parameters);
@Deprecated('Replace with `node.traverseNodesInDFS`')
static Iterable<AstNode> traverseNodesInDFS(AstNode node,
{AstNodePredicate? excludeCriteria}) =>
node.traverseNodesInDFS(excludeCriteria: excludeCriteria);
}
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;
}
}
extension DartTypeExtensions on DartType {
/// Returns the type which should be used when conducting "interface checks"
/// on `this`.
///
/// If `this` is a type variable, then the type-for-interface-check of its
/// promoted bound or bound is returned. Otherwise, `this` is returned.
// TODO(srawlins): Move to extensions.dart.
DartType get typeForInterfaceCheck {
if (this is TypeParameterType) {
if (this is TypeParameterTypeImpl) {
var promotedType = (this as TypeParameterTypeImpl).promotedBound;
if (promotedType != null) {
return promotedType.typeForInterfaceCheck;
}
}
return (this as TypeParameterType).bound.typeForInterfaceCheck;
} else {
return this;
}
}
}