Implement runtime type equality.

Change-Id: I603b5147d7d7e6d8e7267be9c861324b575f259d
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/134041
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Paul Berry <paulberry@google.com>
This commit is contained in:
Konstantin Shcheglov 2020-01-30 22:59:43 +00:00 committed by commit-bot@chromium.org
parent 5d7f3093fd
commit d550879d9d
6 changed files with 659 additions and 0 deletions

View file

@ -0,0 +1,201 @@
// Copyright (c) 2020, 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/element/element.dart';
import 'package:analyzer/dart/element/nullability_suffix.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer/src/dart/element/type_algebra.dart';
import 'package:analyzer/src/dart/element/type_visitor.dart';
import 'package:analyzer/src/generated/type_system.dart';
class RuntimeTypeEqualityHelper {
final TypeSystemImpl _typeSystem;
RuntimeTypeEqualityHelper(TypeSystemImpl typeSystem)
: _typeSystem = typeSystem;
/// Return `true` if runtime types [T1] and [T2] are equal.
///
/// nnbd/feature-specification.md#runtime-type-equality-operator
bool equal(DartType T1, DartType T2) {
var N1 = _typeSystem.normalize(T1);
var N2 = _typeSystem.normalize(T2);
return const RuntimeTypeEqualityVisitor().visit(N1, N2);
}
}
class RuntimeTypeEqualityVisitor extends DartTypeVisitor1<bool, DartType> {
const RuntimeTypeEqualityVisitor();
@override
bool defaultDartType(DartType T1, DartType T2) {
throw UnimplementedError('(${T1.runtimeType}) $T1');
}
bool visit(DartType T1, DartType T2) {
return DartTypeVisitor1.visit(T1, this, T2);
}
@override
bool visitDynamicType(DynamicTypeImpl T1, DartType T2) {
return identical(T1, T2);
}
@override
bool visitFunctionType(FunctionType T1, DartType T2) {
if (T2 is FunctionType) {
var typeParameters = _typeParameters(T1.typeFormals, T2.typeFormals);
if (typeParameters == null) {
return false;
}
bool equal(DartType T1, DartType T2) {
T1 = typeParameters.T1_substitution.substituteType(T1);
T2 = typeParameters.T2_substitution.substituteType(T2);
return visit(T1, T2);
}
if (!equal(T1.returnType, T2.returnType)) {
return false;
}
var T1_parameters = T1.parameters;
var T2_parameters = T2.parameters;
if (T1_parameters.length != T2_parameters.length) {
return false;
}
for (var i = 0; i < T1_parameters.length; i++) {
var T1_parameter = T1_parameters[i];
var T2_parameter = T2_parameters[i];
// ignore: deprecated_member_use_from_same_package
if (T1_parameter.parameterKind != T2_parameter.parameterKind) {
return false;
}
if (T1_parameter.isNamed) {
if (T1_parameter.name != T2_parameter.name) {
return false;
}
}
if (!equal(T1_parameter.type, T2_parameter.type)) {
return false;
}
}
return true;
}
return false;
}
@override
bool visitInterfaceType(InterfaceType T1, DartType T2) {
if (T2 is InterfaceType &&
T1.element == T2.element &&
_compatibleNullability(T1, T2)) {
var T1_typeArguments = T1.typeArguments;
var T2_typeArguments = T2.typeArguments;
if (T1_typeArguments.length == T2_typeArguments.length) {
for (var i = 0; i < T1_typeArguments.length; i++) {
var T1_typeArgument = T1_typeArguments[i];
var T2_typeArgument = T2_typeArguments[i];
if (!visit(T1_typeArgument, T2_typeArgument)) {
return false;
}
}
return true;
}
}
return false;
}
@override
bool visitNeverType(NeverTypeImpl T1, DartType T2) {
// Note, that all types are normalized before this visitor.
// So, `Never?` never happens, it is already `Null`.
assert(T1.nullabilitySuffix != NullabilitySuffix.question);
return T2 is NeverTypeImpl && _compatibleNullability(T1, T2);
}
@override
bool visitTypeParameterType(TypeParameterType T1, DartType T2) {
return T2 is TypeParameterType &&
_compatibleNullability(T1, T2) &&
T1.element == T2.element;
}
@override
bool visitVoidType(VoidType T1, DartType T2) {
return identical(T1, T2);
}
bool _compatibleNullability(DartType T1, DartType T2) {
var T1_nullability = T1.nullabilitySuffix;
var T2_nullability = T2.nullabilitySuffix;
return T1_nullability == T2_nullability ||
T1_nullability == NullabilitySuffix.star &&
T2_nullability == NullabilitySuffix.none ||
T2_nullability == NullabilitySuffix.star &&
T1_nullability == NullabilitySuffix.none;
}
/// Determines if the two lists of type parameters are equal. If they are,
/// returns a [_TypeParametersResult] indicating the substitutions necessary
/// to demonstrate their equality. If they aren't, returns `null`.
_TypeParametersResult _typeParameters(
List<TypeParameterElement> T1_parameters,
List<TypeParameterElement> T2_parameters,
) {
if (T1_parameters.length != T2_parameters.length) {
return null;
}
var newParameters = <TypeParameterElementImpl>[];
var newTypes = <TypeParameterType>[];
for (var i = 0; i < T1_parameters.length; i++) {
var name = T1_parameters[i].name;
var newParameter = TypeParameterElementImpl.synthetic(name);
newParameters.add(newParameter);
var newType = newParameter.instantiate(
nullabilitySuffix: NullabilitySuffix.none,
);
newTypes.add(newType);
}
var T1_substitution = Substitution.fromPairs(T1_parameters, newTypes);
var T2_substitution = Substitution.fromPairs(T2_parameters, newTypes);
for (var i = 0; i < T1_parameters.length; i++) {
var T1_parameter = T1_parameters[i];
var T2_parameter = T2_parameters[i];
var T1_bound = T1_parameter.bound;
var T2_bound = T2_parameter.bound;
if (T1_bound == null && T2_bound == null) {
// OK, no bound.
} else if (T1_bound != null && T2_bound != null) {
T1_bound = T1_substitution.substituteType(T1_bound);
T2_bound = T2_substitution.substituteType(T2_bound);
if (!visit(T1_bound, T2_bound)) {
return null;
}
} else {
return null;
}
}
return _TypeParametersResult(T1_substitution, T2_substitution);
}
}
class _TypeParametersResult {
final Substitution T1_substitution;
final Substitution T2_substitution;
_TypeParametersResult(this.T1_substitution, this.T2_substitution);
}

View file

@ -62,3 +62,76 @@ class DartTypeVisitor<R> {
throw UnimplementedError('(${type.runtimeType}) $type');
}
}
class DartTypeVisitor1<R, T> {
const DartTypeVisitor1();
R defaultDartType(DartType type, T arg) => null;
R visitDynamicType(DynamicTypeImpl type, T arg) {
return defaultDartType(type, arg);
}
R visitFunctionType(FunctionType type, T arg) {
return defaultDartType(type, arg);
}
R visitFunctionTypeBuilder(FunctionTypeBuilder type, T arg) {
return defaultDartType(type, arg);
}
R visitInterfaceType(InterfaceType type, T arg) {
return defaultDartType(type, arg);
}
R visitNamedTypeBuilder(NamedTypeBuilder type, T arg) {
return defaultDartType(type, arg);
}
R visitNeverType(NeverTypeImpl type, T arg) {
return defaultDartType(type, arg);
}
R visitTypeParameterType(TypeParameterType type, T arg) {
return defaultDartType(type, arg);
}
R visitUnknownInferredType(UnknownInferredType type, T arg) {
return defaultDartType(type, arg);
}
R visitVoidType(VoidType type, T arg) {
return defaultDartType(type, arg);
}
static R visit<R, T>(DartType type, DartTypeVisitor1<R, T> visitor, T arg) {
if (type is NeverTypeImpl) {
return visitor.visitNeverType(type, arg);
}
if (type is DynamicTypeImpl) {
return visitor.visitDynamicType(type, arg);
}
if (type is FunctionType) {
return visitor.visitFunctionType(type, arg);
}
if (type is FunctionTypeBuilder) {
return visitor.visitFunctionTypeBuilder(type, arg);
}
if (type is InterfaceType) {
return visitor.visitInterfaceType(type, arg);
}
if (type is NamedTypeBuilder) {
return visitor.visitNamedTypeBuilder(type, arg);
}
if (type is TypeParameterType) {
return visitor.visitTypeParameterType(type, arg);
}
if (type is UnknownInferredType) {
return visitor.visitUnknownInferredType(type, arg);
}
if (type is VoidType) {
return visitor.visitVoidType(type, arg);
}
throw UnimplementedError('(${type.runtimeType}) $type');
}
}

View file

@ -18,6 +18,7 @@ import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/dart/element/member.dart' show TypeParameterMember;
import 'package:analyzer/src/dart/element/normalize.dart';
import 'package:analyzer/src/dart/element/nullability_eliminator.dart';
import 'package:analyzer/src/dart/element/runtime_type_equality.dart';
import 'package:analyzer/src/dart/element/top_merge.dart';
import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer/src/dart/element/type_algebra.dart';
@ -1520,6 +1521,13 @@ class Dart2TypeSystem extends TypeSystem {
return NormalizeHelper(this).normalize(T);
}
/// Return `true` if runtime types [T1] and [T2] are equal.
///
/// nnbd/feature-specification.md#runtime-type-equality-operator
bool runtimeTypesEqual(DartType T1, DartType T2) {
return RuntimeTypeEqualityHelper(this).equal(T1, T2);
}
@override
DartType refineBinaryExpressionType(DartType leftType, TokenType operator,
DartType rightType, DartType currentType) {

View file

@ -14,6 +14,21 @@ import 'package:analyzer/src/generated/utilities_dart.dart';
import 'package:meta/meta.dart';
mixin ElementsTypesMixin {
InterfaceType get boolNone {
var element = typeProvider.boolElement;
return interfaceTypeNone(element);
}
InterfaceType get boolQuestion {
var element = typeProvider.boolElement;
return interfaceTypeQuestion(element);
}
InterfaceType get boolStar {
var element = typeProvider.boolElement;
return interfaceTypeStar(element);
}
InterfaceType get doubleNone {
var element = typeProvider.doubleType.element;
return interfaceTypeNone(element);

View file

@ -0,0 +1,360 @@
// Copyright (c) 2020, 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/analysis/features.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/dart/element/type_provider.dart';
import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer/src/generated/resolver.dart' show TypeSystemImpl;
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import '../../../generated/elements_types_mixin.dart';
import '../../../generated/test_analysis_context.dart';
main() {
defineReflectiveSuite(() {
defineReflectiveTests(RuntimeTypeEqualityTypeTest);
});
}
@reflectiveTest
class RuntimeTypeEqualityTypeTest with ElementsTypesMixin {
@override
TypeProvider typeProvider;
TypeSystemImpl typeSystem;
FeatureSet get testFeatureSet {
return FeatureSet.forTesting(
additionalFeatures: [Feature.non_nullable],
);
}
void setUp() {
var analysisContext = TestAnalysisContext(
featureSet: testFeatureSet,
);
typeProvider = analysisContext.typeProviderNonNullableByDefault;
typeSystem = analysisContext.typeSystemNonNullableByDefault;
}
test_dynamic() {
_equal(dynamicNone, dynamicNone);
_notEqual(dynamicNone, voidNone);
_notEqual(dynamicNone, intNone);
_notEqual(dynamicNone, neverNone);
_notEqual(dynamicNone, neverQuestion);
_notEqual(dynamicNone, neverStar);
}
test_functionType_parameters() {
void check(
ParameterElement T1_parameter,
ParameterElement T2_parameter,
bool expected,
) {
var T1 = functionTypeNone(
returnType: voidNone,
parameters: [T1_parameter],
);
var T2 = functionTypeNone(
returnType: voidNone,
parameters: [T2_parameter],
);
_check(T1, T2, expected);
}
{
void checkRequiredParameter(
DartType T1_type,
DartType T2_type,
bool expected,
) {
check(
requiredParameter(type: T1_type),
requiredParameter(type: T2_type),
expected,
);
}
checkRequiredParameter(intNone, intNone, true);
checkRequiredParameter(intNone, intQuestion, false);
checkRequiredParameter(intNone, intStar, true);
checkRequiredParameter(intQuestion, intNone, false);
checkRequiredParameter(intQuestion, intQuestion, true);
checkRequiredParameter(intQuestion, intStar, false);
checkRequiredParameter(intStar, intNone, true);
checkRequiredParameter(intStar, intQuestion, false);
checkRequiredParameter(intStar, intStar, true);
check(
requiredParameter(type: intNone, name: 'a'),
requiredParameter(type: intNone, name: 'b'),
true,
);
check(
requiredParameter(type: intNone),
positionalParameter(type: intNone),
false,
);
check(
requiredParameter(type: intNone),
namedParameter(type: intNone, name: 'a'),
false,
);
check(
requiredParameter(type: intNone),
namedRequiredParameter(type: intNone, name: 'a'),
false,
);
}
{
check(
namedParameter(type: intNone, name: 'a'),
namedParameter(type: intNone, name: 'a'),
true,
);
check(
namedParameter(type: intNone, name: 'a'),
namedParameter(type: boolNone, name: 'a'),
false,
);
check(
namedParameter(type: intNone, name: 'a'),
namedParameter(type: intNone, name: 'b'),
false,
);
check(
namedParameter(type: intNone, name: 'a'),
namedRequiredParameter(type: intNone, name: 'a'),
false,
);
}
{
check(
namedRequiredParameter(type: intNone, name: 'a'),
namedRequiredParameter(type: intNone, name: 'a'),
true,
);
check(
namedRequiredParameter(type: intNone, name: 'a'),
namedRequiredParameter(type: boolNone, name: 'a'),
false,
);
check(
namedRequiredParameter(type: intNone, name: 'a'),
namedRequiredParameter(type: intNone, name: 'b'),
false,
);
check(
namedRequiredParameter(type: intNone, name: 'a'),
namedParameter(type: intNone, name: 'a'),
false,
);
}
}
test_functionType_returnType() {
void check(
DartType T1_returnType,
DartType T2_returnType,
bool expected,
) {
var T1 = functionTypeNone(
returnType: T1_returnType,
);
var T2 = functionTypeNone(
returnType: T2_returnType,
);
_check(T1, T2, expected);
}
check(intNone, intNone, true);
check(intNone, intQuestion, false);
check(intNone, intStar, true);
}
test_functionType_typeParameters() {
{
var T1_T = typeParameter('T', bound: numNone);
_check(
functionTypeNone(
typeFormals: [T1_T],
returnType: voidNone,
),
functionTypeNone(
returnType: voidNone,
),
false,
);
}
{
var T1_T = typeParameter('T', bound: numNone);
var T2_U = typeParameter('U');
_check(
functionTypeNone(
typeFormals: [T1_T],
returnType: voidNone,
),
functionTypeNone(
typeFormals: [T2_U],
returnType: voidNone,
),
false,
);
}
{
var T1_T = typeParameter('T');
var T2_U = typeParameter('U');
_check(
functionTypeNone(
typeFormals: [T1_T],
returnType: typeParameterTypeNone(T1_T),
parameters: [
requiredParameter(
type: typeParameterTypeNone(T1_T),
)
],
),
functionTypeNone(
typeFormals: [T2_U],
returnType: typeParameterTypeNone(T2_U),
parameters: [
requiredParameter(
type: typeParameterTypeNone(T2_U),
)
],
),
true,
);
}
}
test_interfaceType() {
_notEqual(intNone, boolNone);
_equal(intNone, intNone);
_notEqual(intNone, intQuestion);
_equal(intNone, intStar);
_notEqual(intQuestion, intNone);
_equal(intQuestion, intQuestion);
_notEqual(intQuestion, intStar);
_equal(intStar, intNone);
_notEqual(intStar, intQuestion);
_equal(intStar, intStar);
}
test_interfaceType_typeArguments() {
void _equal(DartType T1, DartType T2) {
this._equal(listNone(T1), listNone(T2));
}
void _notEqual(DartType T1, DartType T2) {
this._notEqual(listNone(T1), listNone(T2));
}
_notEqual(intNone, boolNone);
_equal(intNone, intNone);
_notEqual(intNone, intQuestion);
_equal(intNone, intStar);
_notEqual(intQuestion, intNone);
_equal(intQuestion, intQuestion);
_notEqual(intQuestion, intStar);
_equal(intStar, intNone);
_notEqual(intStar, intQuestion);
_equal(intStar, intStar);
}
test_never() {
_equal(neverNone, neverNone);
_notEqual(neverNone, neverQuestion);
_equal(neverNone, neverStar);
_notEqual(neverNone, intNone);
_notEqual(neverQuestion, neverNone);
_equal(neverQuestion, neverQuestion);
_notEqual(neverQuestion, neverStar);
_notEqual(neverQuestion, intNone);
_equal(neverQuestion, nullNone);
_equal(neverStar, neverNone);
_notEqual(neverStar, neverQuestion);
_equal(neverStar, neverStar);
_notEqual(neverStar, intNone);
}
test_norm() {
_equal(futureOrNone(objectNone), objectNone);
_equal(futureOrNone(neverNone), futureNone(neverNone));
_equal(neverQuestion, nullNone);
}
test_void() {
_equal(voidNone, voidNone);
_notEqual(voidNone, dynamicNone);
_notEqual(voidNone, intNone);
_notEqual(voidNone, neverNone);
_notEqual(voidNone, neverQuestion);
_notEqual(voidNone, neverStar);
}
void _check(DartType T1, DartType T2, bool expected) {
bool result;
result = typeSystem.runtimeTypesEqual(T1, T2);
if (result != expected) {
fail('''
Expected ${expected ? 'equal' : 'not equal'}.
T1: ${_typeString(T1)}
T2: ${_typeString(T2)}
''');
}
result = typeSystem.runtimeTypesEqual(T2, T1);
if (result != expected) {
fail('''
Expected ${expected ? 'equal' : 'not equal'}.
T1: ${_typeString(T1)}
T2: ${_typeString(T2)}
''');
}
}
void _equal(DartType T1, DartType T2) {
_check(T1, T2, true);
}
void _notEqual(DartType T1, DartType T2) {
_check(T1, T2, false);
}
String _typeString(TypeImpl type) {
if (type == null) return null;
return type.getDisplayString(withNullability: true);
}
}

View file

@ -11,6 +11,7 @@ import 'least_upper_bound_helper_test.dart' as least_upper_bound_helper;
import 'normalize_type_test.dart' as normalize_type;
import 'nullability_eliminator_test.dart' as nullability_eliminator;
import 'nullable_test.dart' as nullable;
import 'runtime_type_equality_test.dart' as runtime_type_equality;
import 'subtype_test.dart' as subtype;
import 'top_merge_test.dart' as top_merge;
import 'type_algebra_test.dart' as type_algebra;
@ -27,6 +28,7 @@ main() {
normalize_type.main();
nullability_eliminator.main();
nullable.main();
runtime_type_equality.main();
subtype.main();
top_merge.main();
type_algebra.main();