blob: f99b5a05cc176189aa9809cbff13c924e9e6c535 [file] [log] [blame]
// Copyright (c) 2019, 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/nullability_suffix.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/dart/element/type_provider.dart';
import 'package:analyzer/dart/element/type_visitor.dart';
import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer/src/dart/element/type_system.dart';
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(NormalizeTypeTest);
});
}
@reflectiveTest
class NormalizeTypeTest with ElementsTypesMixin {
@override
late final TypeProvider typeProvider;
late final 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_functionType_parameter() {
_check(
functionTypeNone(
returnType: voidNone,
parameters: [
requiredParameter(type: futureOrNone(objectNone)),
],
),
functionTypeNone(
returnType: voidNone,
parameters: [
requiredParameter(type: objectNone),
],
),
);
_check(
functionTypeNone(
returnType: voidNone,
parameters: [
namedParameter(name: 'a', type: futureOrNone(objectNone)),
],
),
functionTypeNone(
returnType: voidNone,
parameters: [
namedParameter(name: 'a', type: objectNone),
],
),
);
_check(
functionTypeNone(
returnType: voidNone,
parameters: [
namedRequiredParameter(name: 'a', type: futureOrNone(objectNone)),
],
),
functionTypeNone(
returnType: voidNone,
parameters: [
namedRequiredParameter(name: 'a', type: objectNone),
],
),
);
_check(
functionTypeNone(
returnType: voidNone,
parameters: [
positionalParameter(type: futureOrNone(objectNone)),
],
),
functionTypeNone(
returnType: voidNone,
parameters: [
positionalParameter(type: objectNone),
],
),
);
}
test_functionType_parameter_covariant() {
_check(
functionTypeNone(
returnType: voidNone,
parameters: [
requiredParameter(type: futureOrNone(objectNone), isCovariant: true),
],
),
functionTypeNone(
returnType: voidNone,
parameters: [
requiredParameter(type: objectNone, isCovariant: true),
],
),
);
}
test_functionType_parameter_typeParameter() {
TypeParameterElement T;
TypeParameterElement T2;
T = typeParameter('T', bound: neverNone);
T2 = typeParameter('T2', bound: neverNone);
_check(
functionTypeNone(
returnType: voidNone,
typeFormals: [T],
parameters: [
requiredParameter(type: typeParameterTypeNone(T)),
],
),
functionTypeNone(
returnType: voidNone,
typeFormals: [T2],
parameters: [
requiredParameter(type: neverNone),
],
),
);
T = typeParameter('T', bound: iterableNone(futureOrNone(dynamicNone)));
T2 = typeParameter('T2', bound: iterableNone(dynamicNone));
_check(
functionTypeNone(
returnType: voidNone,
typeFormals: [T],
parameters: [
requiredParameter(type: typeParameterTypeNone(T)),
],
),
functionTypeNone(
returnType: voidNone,
typeFormals: [T2],
parameters: [
requiredParameter(type: typeParameterTypeNone(T2)),
],
),
);
}
test_functionType_returnType() {
_check(
functionTypeNone(
returnType: futureOrNone(objectNone),
),
functionTypeNone(
returnType: objectNone,
),
);
_check(
functionTypeNone(
returnType: intNone,
),
functionTypeNone(
returnType: intNone,
),
);
}
test_functionType_typeParameter_bound_normalized() {
_check(
functionTypeNone(
returnType: voidNone,
typeFormals: [
typeParameter('T', bound: futureOrNone(objectNone)),
],
),
functionTypeNone(
returnType: voidNone,
typeFormals: [
typeParameter('T', bound: objectNone),
],
),
);
}
test_functionType_typeParameter_bound_unchanged() {
_check(
functionTypeNone(
returnType: intNone,
typeFormals: [
typeParameter('T', bound: numNone),
],
),
functionTypeNone(
returnType: intNone,
typeFormals: [
typeParameter('T', bound: numNone),
],
),
);
}
test_functionType_typeParameter_fresh() {
var T = typeParameter('T');
var T2 = typeParameter('T');
_check(
functionTypeNone(
returnType: typeParameterTypeNone(T),
typeFormals: [T],
parameters: [
requiredParameter(
type: typeParameterTypeNone(T),
),
],
),
functionTypeNone(
returnType: typeParameterTypeNone(T2),
typeFormals: [T2],
parameters: [
requiredParameter(
type: typeParameterTypeNone(T2),
),
],
),
);
}
test_functionType_typeParameter_fresh_bound() {
var T = typeParameter('T');
var S = typeParameter('S', bound: typeParameterTypeNone(T));
var T2 = typeParameter('T');
var S2 = typeParameter('S', bound: typeParameterTypeNone(T2));
_check(
functionTypeNone(
returnType: typeParameterTypeNone(T),
typeFormals: [T, S],
parameters: [
requiredParameter(
type: typeParameterTypeNone(T),
),
requiredParameter(
type: typeParameterTypeNone(S),
),
],
),
functionTypeNone(
returnType: typeParameterTypeNone(T2),
typeFormals: [T2, S2],
parameters: [
requiredParameter(
type: typeParameterTypeNone(T2),
),
requiredParameter(
type: typeParameterTypeNone(S2),
),
],
),
);
}
/// NORM(FutureOr<T>)
/// * let S be NORM(T)
test_futureOr() {
void check(DartType T, DartType expected) {
var input = futureOrNone(T);
_check(input, expected);
}
// * if S is a top type then S
check(dynamicNone, dynamicNone);
check(voidNone, voidNone);
check(objectQuestion, objectQuestion);
// * if S is Object then S
check(objectNone, objectNone);
// * if S is Object* then S
check(objectStar, objectStar);
// * if S is Never then Future<Never>
check(neverNone, futureNone(neverNone));
// * if S is Null then Future<Null>?
check(nullNone, futureQuestion(nullNone));
// * else FutureOr<S>
check(intNone, futureOrNone(intNone));
}
test_interfaceType() {
_check(listNone(intNone), listNone(intNone));
_check(
listNone(
futureOrNone(objectNone),
),
listNone(objectNone),
);
}
test_primitive() {
_check(dynamicNone, dynamicNone);
_check(neverNone, neverNone);
_check(voidNone, voidNone);
_check(intNone, intNone);
}
/// NORM(T?)
/// * let S be NORM(T)
test_question() {
void check(DartType T, DartType expected) {
_assertNullabilityQuestion(T);
_check(T, expected);
}
// * if S is a top type then S
check(futureOrQuestion(dynamicNone), dynamicNone);
check(futureOrQuestion(voidNone), voidNone);
check(futureOrQuestion(objectQuestion), objectQuestion);
// * if S is Never then Null
check(neverQuestion, nullNone);
// * if S is Never* then Null
// Analyzer: impossible, we have only one suffix
// * if S is Null then Null
check(nullQuestion, nullNone);
// * if S is FutureOr<R> and R is nullable then S
check(futureOrQuestion(intQuestion), futureOrNone(intQuestion));
// * if S is FutureOr<R>* and R is nullable then FutureOr<R>
// Analyzer: impossible, we have only one suffix
// * if S is R? then R?
// * if S is R* then R?
// * else S?
check(intQuestion, intQuestion);
check(objectQuestion, objectQuestion);
check(futureOrQuestion(objectNone), objectQuestion);
check(futureOrQuestion(objectStar), objectStar);
}
/// NORM(T*)
/// * let S be NORM(T)
test_star() {
void check(DartType T, DartType expected) {
_assertNullabilityStar(T);
_check(T, expected);
}
// * if S is a top type then S
check(futureOrStar(dynamicNone), dynamicNone);
check(futureOrStar(voidNone), voidNone);
check(futureOrStar(objectQuestion), objectQuestion);
// * if S is Null then Null
check(nullStar, nullNone);
// * if S is R? then R?
check(futureOrStar(nullNone), futureQuestion(nullNone));
// * if S is R* then R*
// * else S*
check(intStar, intStar);
}
/// NORM(X & T)
/// * let S be NORM(T)
test_typeParameter_bound() {
TypeParameterElement T;
// * if S is Never then Never
T = typeParameter('T', bound: neverNone);
_check(typeParameterTypeNone(T), neverNone);
// * else X
T = typeParameter('T');
_check(typeParameterTypeNone(T), typeParameterTypeNone(T));
// * else X
T = typeParameter('T', bound: futureOrNone(objectNone));
_check(typeParameterTypeNone(T), typeParameterTypeNone(T));
}
test_typeParameter_bound_recursive() {
var T = typeParameter('T');
T.bound = iterableNone(typeParameterTypeNone(T));
_check(typeParameterTypeNone(T), typeParameterTypeNone(T));
}
test_typeParameter_promoted() {
var T = typeParameter('T');
// * if S is Never then Never
_check(
promotedTypeParameterTypeNone(T, neverNone),
neverNone,
);
// * if S is a top type then X
_check(
promotedTypeParameterTypeNone(T, objectQuestion),
typeParameterTypeNone(T),
);
_check(
promotedTypeParameterTypeNone(T, futureOrQuestion(objectNone)),
typeParameterTypeNone(T),
);
// * if S is X then X
_check(
promotedTypeParameterTypeNone(T, typeParameterTypeNone(T)),
typeParameterTypeNone(T),
);
// * if S is Object and NORM(B) is Object where B is the bound of X then X
T = typeParameter('T', bound: objectNone);
_check(
promotedTypeParameterTypeNone(T, futureOrNone(objectNone)),
typeParameterTypeNone(T),
);
// else X & S
T = typeParameter('T');
_check(
promotedTypeParameterType(
element: T,
nullabilitySuffix: NullabilitySuffix.none,
promotedBound: futureOrNone(neverNone),
),
promotedTypeParameterType(
element: T,
nullabilitySuffix: NullabilitySuffix.none,
promotedBound: futureNone(neverNone),
),
);
}
void _assertNullability(DartType type, NullabilitySuffix expected) {
if (type.nullabilitySuffix != expected) {
fail('Expected $expected in ' + _typeString(type));
}
}
void _assertNullabilityQuestion(DartType type) {
_assertNullability(type, NullabilitySuffix.question);
}
void _assertNullabilityStar(DartType type) {
_assertNullability(type, NullabilitySuffix.star);
}
void _check(DartType T, DartType expected) {
var expectedStr = _typeString(expected);
var result = typeSystem.normalize(T);
var resultStr = _typeString(result);
expect(result, expected, reason: '''
expected: $expectedStr
actual: $resultStr
''');
_checkFormalParametersIsCovariant(result, expected);
}
void _checkFormalParametersIsCovariant(DartType T1, DartType T2) {
if (T1 is FunctionType && T2 is FunctionType) {
var parameters1 = T1.parameters;
var parameters2 = T2.parameters;
expect(parameters1, hasLength(parameters2.length));
for (var i = 0; i < parameters1.length; i++) {
var parameter1 = parameters1[i];
var parameter2 = parameters2[i];
if (parameter1.isCovariant != parameter2.isCovariant) {
fail('''
parameter1: $parameter1, isCovariant: ${parameter1.isCovariant}
parameter2: $parameter2, isCovariant: ${parameter2.isCovariant}
T1: ${_typeString(T1 as TypeImpl)}
T2: ${_typeString(T2 as TypeImpl)}
''');
}
_checkFormalParametersIsCovariant(parameter1.type, parameter2.type);
}
}
}
String _typeParametersStr(DartType type) {
var typeStr = '';
var typeParameterCollector = _TypeParameterCollector();
type.accept(typeParameterCollector);
for (var typeParameter in typeParameterCollector.typeParameters) {
typeStr += ', $typeParameter';
}
return typeStr;
}
String _typeString(DartType type) {
return type.getDisplayString(withNullability: true) +
_typeParametersStr(type);
}
}
class _TypeParameterCollector extends TypeVisitor<void> {
final Set<String> typeParameters = {};
/// We don't need to print bounds for these type parameters, because
/// they are already included into the function type itself, and cannot
/// be promoted.
final Set<TypeParameterElement> functionTypeParameters = {};
@override
void visitDynamicType(DynamicType type) {}
@override
void visitFunctionType(FunctionType type) {
functionTypeParameters.addAll(type.typeFormals);
for (var typeParameter in type.typeFormals) {
var bound = typeParameter.bound;
if (bound != null) {
bound.accept(this);
}
}
for (var parameter in type.parameters) {
parameter.type.accept(this);
}
type.returnType.accept(this);
}
@override
void visitInterfaceType(InterfaceType type) {
for (var typeArgument in type.typeArguments) {
typeArgument.accept(this);
}
}
@override
void visitNeverType(NeverType type) {}
@override
void visitTypeParameterType(TypeParameterType type) {
if (!functionTypeParameters.contains(type.element)) {
var bound = type.element.bound;
var promotedBound = (type as TypeParameterTypeImpl).promotedBound;
if (bound == null && promotedBound == null) {
return;
}
var str = '';
if (bound != null) {
var boundStr = bound.getDisplayString(withNullability: true);
str += '${type.element.name} extends ' + boundStr;
}
if (promotedBound != null) {
var promotedBoundStr = promotedBound.getDisplayString(
withNullability: true,
);
if (str.isNotEmpty) {
str += ', ';
}
str += '${type.element.name} & ' + promotedBoundStr;
}
typeParameters.add(str);
}
}
@override
void visitVoidType(VoidType type) {}
}