blob: a25e00d1e752fb4416ff9f03425856b266c5387d [file]
// 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/ast/token.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/error/listener.dart';
import 'package:analyzer/source/file_source.dart';
import 'package:analyzer/src/dart/ast/ast.dart';
import 'package:analyzer/src/dart/ast/token.dart';
import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer/src/dart/element/type_schema.dart';
import 'package:analyzer/src/diagnostic/diagnostic.dart' as diag;
import 'package:analyzer/src/test_utilities/test_library_builder.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import '../../../generated/type_system_base.dart';
main() {
defineReflectiveSuite(() {
defineReflectiveTests(GenericFunctionInferenceTest);
});
}
@reflectiveTest
class GenericFunctionInferenceTest extends AbstractTypeSystemTest {
void test_boundedByAnotherTypeParameter() {
// <TFrom, TTo extends Iterable<TFrom>>(TFrom) -> TTo
var cast = parseFunctionType(
'TTo Function<TFrom, TTo extends Iterable<TFrom>>(TFrom)',
);
_assertTypes(_inferCall(cast, [parseType('String')]), [
parseType('String'),
parseType('Iterable<String>'),
]);
}
void test_boundedByOuterClass() {
// Regression test for https://github.com/dart-lang/sdk/issues/25740.
buildTestLibrary(
classes: [
ClassSpec('class A'),
ClassSpec('class B extends A'),
ClassSpec(
'class C<T extends A>',
methods: [MethodSpec('S m<S extends T>(S _)')],
),
],
);
// class B extends A {}
var typeB = parseInterfaceType('B');
// class C<T extends A> { S m<S extends T>(S); }
// C<Object> cOfObject;
var cOfObject = parseInterfaceType('C<Object>');
// C<A> cOfA;
var cOfA = parseInterfaceType('C<A>');
// C<B> cOfB;
var cOfB = parseInterfaceType('C<B>');
// B b;
// cOfB.m(b); // infer <B>
_assertType(
_inferCall2(cOfB.getMethod('m')!.type, [typeB]),
'B Function(B)',
);
// cOfA.m(b); // infer <B>
_assertType(
_inferCall2(cOfA.getMethod('m')!.type, [typeB]),
'B Function(B)',
);
// cOfObject.m(b); // infer <B>
_assertType(
_inferCall2(cOfObject.getMethod('m')!.type, [typeB]),
'B Function(B)',
);
}
void test_boundedByOuterClassSubstituted() {
// Regression test for https://github.com/dart-lang/sdk/issues/25740.
buildTestLibrary(
classes: [
ClassSpec('class A'),
ClassSpec('class B extends A'),
ClassSpec(
'class C<T extends A>',
methods: [MethodSpec('S m<S extends Iterable<T>>(S _)')],
),
],
);
// class C<T extends A> { S m<S extends Iterable<T>>(S); }
// C<Object> cOfObject;
var cOfObject = parseInterfaceType('C<Object>');
// C<A> cOfA;
var cOfA = parseInterfaceType('C<A>');
// C<B> cOfB;
var cOfB = parseInterfaceType('C<B>');
// List<B> b;
var listOfB = parseType('List<B>');
// cOfB.m(b); // infer <B>
_assertType(
_inferCall2(cOfB.getMethod('m')!.type, [listOfB]),
'List<B> Function(List<B>)',
);
// cOfA.m(b); // infer <B>
_assertType(
_inferCall2(cOfA.getMethod('m')!.type, [listOfB]),
'List<B> Function(List<B>)',
);
// cOfObject.m(b); // infer <B>
_assertType(
_inferCall2(cOfObject.getMethod('m')!.type, [listOfB]),
'List<B> Function(List<B>)',
);
}
void test_boundedRecursively() {
buildTestLibrary(
classes: [
ClassSpec('class Cloneable<T extends Cloneable<T>>'),
ClassSpec('class B extends Cloneable<B>'),
],
);
// class Cloneable<T extends Cloneable<T>>
// class B extends A<B> {}
var typeB = parseInterfaceType('B');
// (S, S) -> S
var clone = parseFunctionType('S Function<S extends Cloneable<S>>(S, S)');
_assertTypes(_inferCall(clone, [typeB, typeB]), [typeB]);
// Something invalid...
_assertTypes(
_inferCall(clone, [
parseType('String'),
parseType('num'),
], expectError: true),
[parseInterfaceType('Cloneable<Object?>')],
);
}
void test_buildTestLibrary_topLevelFunctionHeader() {
buildTestLibrary(functions: [TopLevelFunctionSpec('T f<T>(T value)')]);
_assertType(testLibrary.topLevelFunctions.single.type, 'T Function<T>(T)');
}
void test_buildTestLibrary_topLevelFunctionHeader_namedParameters() {
buildTestLibrary(
functions: [
TopLevelFunctionSpec('void f({int optional, required int required})'),
],
);
_assertType(
testLibrary.topLevelFunctions.single.type,
'void Function({int optional, required int required})',
);
}
/// https://github.com/dart-lang/language/issues/1182#issuecomment-702272641
void test_demoteType() {
// <T>(T x) -> void
var rawType = parseFunctionType('void Function<T>(T)');
withTypeParameterScope('S', (scope) {
var S = scope.typeParameter('S');
var S_and_int = scope.parseTypeParameterType('S & int');
var inferredTypes = _inferCall(rawType, [S_and_int]);
var inferredType = inferredTypes[0] as TypeParameterTypeImpl;
expect(inferredType.element, S);
expect(inferredType.promotedBound, isNull);
});
}
void test_genericCastFunction() {
// <TFrom, TTo>(TFrom) -> TTo
var cast = parseFunctionType('TTo Function<TFrom, TTo>(TFrom)');
_assertTypes(_inferCall(cast, [parseType('int')]), [
parseType('int'),
parseType('dynamic'),
]);
}
void test_genericCastFunctionWithUpperBound() {
// <TFrom, TTo extends TFrom>(TFrom) -> TTo
var cast = parseFunctionType(
'TTo Function<TFrom, TTo extends TFrom>(TFrom)',
);
_assertTypes(_inferCall(cast, [parseType('int')]), [
parseType('int'),
parseType('int'),
]);
}
void test_parameter_contravariantUseUpperBound() {
// <T>(T x, void Function(T) y) -> T
// Generates constraints int <: T <: num.
// Since T is contravariant, choose num.
var numFunction = parseFunctionType('void Function(num)');
var function = parseFunctionType('T Function<in T>(T, void Function(T))');
_assertTypes(_inferCall(function, [parseType('int'), numFunction]), [
parseType('num'),
]);
}
void test_parameter_covariantUseLowerBound() {
// <T>(T x, void Function(T) y) -> T
// Generates constraints int <: T <: num.
// Since T is covariant, choose int.
var numFunction = parseFunctionType('void Function(num)');
var function = parseFunctionType('T Function<out T>(T, void Function(T))');
_assertTypes(_inferCall(function, [parseType('int'), numFunction]), [
parseType('int'),
]);
}
void test_parametersToFunctionParam() {
// <T>(f(T t)) -> T
var cast = parseFunctionType('T Function<T>(dynamic Function(T))');
_assertTypes(
_inferCall(cast, [parseFunctionType('dynamic Function(num)')]),
[parseType('num')],
);
}
void test_parametersUseLeastUpperBound() {
// <T>(T x, T y) -> T
var cast = parseFunctionType('T Function<T>(T, T)');
_assertTypes(_inferCall(cast, [parseType('int'), parseType('double')]), [
parseType('num'),
]);
}
void test_parameterTypeUsesUpperBound() {
// <T extends num>(T) -> dynamic
var f = parseFunctionType('dynamic Function<T extends num>(T)');
_assertTypes(_inferCall(f, [parseType('int')]), [parseType('int')]);
}
void test_returnFunctionWithGenericParameter() {
// <T>(T -> T) -> (T -> void)
var f = parseFunctionType('void Function(T) Function<T>(T Function(T))');
_assertTypes(_inferCall(f, [parseFunctionType('int Function(num)')]), [
parseType('int'),
]);
}
void test_returnFunctionWithGenericParameterAndContext() {
// <T>(T -> T) -> (T -> Null)
var f = parseFunctionType('Null Function(T) Function<T>(T Function(T))');
_assertTypes(
_inferCall(f, [], returnType: parseFunctionType('int? Function(num)')),
[parseType('num')],
);
}
void test_returnFunctionWithGenericParameterAndReturn() {
// <T>(T -> T) -> (T -> T)
var f = parseFunctionType('T Function(T) Function<T>(T Function(T))');
_assertTypes(_inferCall(f, [parseFunctionType('int Function(num)')]), [
parseType('int'),
]);
}
void test_returnFunctionWithGenericReturn() {
// <T>(T -> T) -> (() -> T)
var f = parseFunctionType('T Function() Function<T>(T Function(T))');
_assertTypes(_inferCall(f, [parseFunctionType('int Function(num)')]), [
parseType('int'),
]);
}
void test_returnTypeFromContext() {
// <T>() -> T
var f = parseFunctionType('T Function<T>()');
_assertTypes(_inferCall(f, [], returnType: parseType('String')), [
parseType('String'),
]);
}
void test_returnTypeWithBoundFromContext() {
// <T extends num>() -> T
var f = parseFunctionType('T Function<T extends num>()');
_assertTypes(_inferCall(f, [], returnType: parseType('double')), [
parseType('double'),
]);
}
void test_returnTypeWithBoundFromInvalidContext() {
// <T extends num>() -> T
var f = parseFunctionType('T Function<T extends num>()');
_assertTypes(_inferCall(f, [], returnType: parseType('String')), [
parseType('Never'),
]);
}
void test_unifyParametersToFunctionParam() {
// <T>(f(T t), g(T t)) -> T
var cast = parseFunctionType(
'T Function<T>(dynamic Function(T), dynamic Function(T))',
);
_assertTypes(
_inferCall(cast, [
parseFunctionType('dynamic Function(int)'),
parseFunctionType('dynamic Function(double)'),
]),
[parseType('Never')],
);
}
void test_unusedReturnTypeIsDynamic() {
// <T>() -> T
var f = parseFunctionType('T Function<T>()');
_assertTypes(_inferCall(f, []), [parseType('dynamic')]);
}
void test_unusedReturnTypeWithUpperBound() {
// <T extends num>() -> T
var f = parseFunctionType('T Function<T extends num>()');
_assertTypes(_inferCall(f, []), [parseType('num')]);
}
void _assertType(DartType type, String expected) {
var typeStr = type.getDisplayString();
expect(typeStr, expected);
}
void _assertTypes(List<DartType> actual, List<DartType> expected) {
var actualStr = actual.map((e) {
return e.getDisplayString();
}).toList();
var expectedStr = expected.map((e) {
return e.getDisplayString();
}).toList();
expect(actualStr, expectedStr);
}
List<DartType> _inferCall(
FunctionTypeImpl ft,
List<TypeImpl> arguments, {
TypeImpl returnType = UnknownInferredType.instance,
bool expectError = false,
}) {
var listener = RecordingDiagnosticListener();
var file = newFile('/test.dart', '');
var fileSource = FileSource(file);
var reporter = DiagnosticReporter(listener, fileSource);
var inferrer = typeSystem.setupGenericTypeInference(
typeParameters: ft.typeParameters,
declaredReturnType: ft.returnType,
contextReturnType: returnType,
diagnosticReporter: reporter,
errorEntity: NullLiteralImpl(literal: KeywordToken(Keyword.NULL, 0)),
genericMetadataIsEnabled: true,
inferenceUsingBoundsIsEnabled: true,
strictInference: false,
strictCasts: false,
typeSystemOperations: typeSystemOperations,
dataForTesting: null,
nodeForTesting: null,
);
inferrer.constrainArguments2(
parameters: ft.formalParameters,
argumentTypes: arguments,
nodeForTesting: null,
);
var typeArguments = inferrer.chooseFinalTypes();
if (expectError) {
expect(listener.diagnostics.map((e) => e.diagnosticCode).toList(), [
diag.couldNotInfer,
], reason: 'expected exactly 1 could not infer error.');
} else {
expect(
listener.diagnostics,
isEmpty,
reason: 'did not expect any errors.',
);
}
return typeArguments;
}
FunctionType _inferCall2(
FunctionTypeImpl ft,
List<TypeImpl> arguments, {
TypeImpl returnType = UnknownInferredType.instance,
bool expectError = false,
}) {
var typeArguments = _inferCall(
ft,
arguments,
returnType: returnType,
expectError: expectError,
);
return ft.instantiate(typeArguments);
}
}