blob: 43c92f86633c4151ecdd90915dc3ef5ac4f46e34 [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/src/error/codes.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import '../context_collection_resolution.dart';
import '../resolution.dart';
main() {
defineReflectiveSuite(() {
defineReflectiveTests(ExtensionMethodsTest);
defineReflectiveTests(ExtensionMethodsWithNullSafetyTest);
});
}
@reflectiveTest
class ExtensionMethodsTest extends PubPackageResolutionTest
with ExtensionMethodsTestCases {}
mixin ExtensionMethodsTestCases on ResolutionTest {
test_implicit_getter() async {
await assertNoErrorsInCode('''
class A<T> {}
extension E<T> on A<T> {
List<T> get foo => <T>[];
}
void f(A<int> a) {
a.foo;
}
''');
var prefixedIdentifier = findNode.prefixed('.foo');
assertMember(
prefixedIdentifier,
findElement.getter('foo', of: 'E'),
{'T': 'int'},
);
assertType(prefixedIdentifier, 'List<int>');
}
test_implicit_method() async {
await assertNoErrorsInCode('''
class A<T> {}
extension E<T> on A<T> {
Map<T, U> foo<U>(U u) => <T, U>{};
}
void f(A<int> a) {
a.foo(1.0);
}
''');
// TODO(scheglov) We need to instantiate "foo" fully.
var invocation = findNode.methodInvocation('foo(1.0)');
assertMember(
invocation,
findElement.method('foo', of: 'E'),
{'T': 'int'},
);
// assertMember(
// invocation,
// findElement.method('foo', of: 'E'),
// {'T': 'int', 'U': 'double'},
// );
assertInvokeType(invocation, 'Map<int, double> Function(double)');
assertType(invocation, 'Map<int, double>');
}
test_implicit_method_internal() async {
await assertNoErrorsInCode(r'''
extension E<T> on List<T> {
List<T> foo() => this;
List<T> bar(List<T> other) => other.foo();
}
''');
assertMethodInvocation2(
findNode.methodInvocation('other.foo()'),
element: elementMatcher(
findElement.method('foo'),
substitution: {'T': 'T'},
),
typeArgumentTypes: [],
invokeType: 'List<T> Function()',
type: 'List<T>',
);
}
test_implicit_method_onTypeParameter() async {
await assertNoErrorsInCode('''
extension E<T> on T {
Map<T, U> foo<U>(U value) => <T, U>{};
}
void f(String a) {
a.foo(0);
}
''');
// TODO(scheglov) We need to instantiate "foo" fully.
var invocation = findNode.methodInvocation('foo(0)');
assertMember(
invocation,
findElement.method('foo', of: 'E'),
{'T': 'String'},
);
// assertMember(
// invocation,
// findElement.method('foo', of: 'E'),
// {'T': 'int', 'U': 'double'},
// );
assertInvokeType(invocation, 'Map<String, int> Function(int)');
assertType(invocation, 'Map<String, int>');
}
test_implicit_method_tearOff() async {
await assertNoErrorsInCode('''
class A<T> {}
extension E<T> on A<T> {
Map<T, U> foo<U>(U u) => <T, U>{};
}
void f(A<int> a) {
a.foo;
}
''');
var prefixedIdentifier = findNode.prefixed('foo;');
assertMember(
prefixedIdentifier,
findElement.method('foo', of: 'E'),
{'T': 'int'},
);
assertType(prefixedIdentifier, 'Map<int, U> Function<U>(U)');
}
test_implicit_setter() async {
await assertNoErrorsInCode('''
class A<T> {}
extension E<T> on A<T> {
set foo(T value) {}
}
void f(A<int> a) {
a.foo = 0;
}
''');
var propertyAccess = findNode.prefixed('.foo =');
assertMember(
propertyAccess,
findElement.setter('foo', of: 'E'),
{'T': 'int'},
);
}
test_implicit_targetTypeParameter_hasBound_methodInvocation() async {
await assertNoErrorsInCode('''
extension Test<T> on T {
T Function(T) test() => throw 0;
}
void f<S extends num>(S x) {
x.test();
}
''');
if (result.libraryElement.isNonNullableByDefault) {
assertMethodInvocation2(
findNode.methodInvocation('test();'),
element: elementMatcher(
findElement.method('test'),
substitution: {'T': 'S'},
),
typeArgumentTypes: [],
invokeType: 'S Function(S) Function()',
type: 'S Function(S)',
);
} else {
assertMethodInvocation2(
findNode.methodInvocation('test();'),
element: elementMatcher(
findElement.method('test'),
substitution: {'T': 'num'},
),
typeArgumentTypes: [],
invokeType: 'num Function(num) Function()',
type: 'num Function(num)',
);
}
}
test_implicit_targetTypeParameter_hasBound_propertyAccess_getter() async {
await assertNoErrorsInCode('''
extension Test<T> on T {
T Function(T) get test => throw 0;
}
void f<S extends num>(S x) {
(x).test;
}
''');
if (result.libraryElement.isNonNullableByDefault) {
assertPropertyAccess2(
findNode.propertyAccess('.test'),
element: elementMatcher(
findElement.getter('test'),
substitution: {'T': 'S'},
),
type: 'S Function(S)',
);
} else {
assertPropertyAccess2(
findNode.propertyAccess('.test'),
element: elementMatcher(
findElement.getter('test'),
substitution: {'T': 'num'},
),
type: 'num Function(num)',
);
}
}
test_implicit_targetTypeParameter_hasBound_propertyAccess_setter() async {
await assertNoErrorsInCode('''
extension Test<T> on T {
void set test(T _) {}
}
T g<T>() => throw 0;
void f<S extends num>(S x) {
(x).test = g();
}
''');
if (result.libraryElement.isNonNullableByDefault) {
assertPropertyAccess2(
findNode.propertyAccess('.test'),
element: elementMatcher(
findElement.setter('test'),
substitution: {'T': 'S'},
),
type: 'S',
);
assertTypeArgumentTypes(
findNode.methodInvocation('g()'),
['S'],
);
} else {
assertPropertyAccess2(
findNode.propertyAccess('.test'),
element: elementMatcher(
findElement.setter('test'),
substitution: {'T': 'num'},
),
type: 'num',
);
assertTypeArgumentTypes(
findNode.methodInvocation('g()'),
['num'],
);
}
}
test_override_downward_hasTypeArguments() async {
await assertNoErrorsInCode('''
extension E<T> on Set<T> {
void foo() {}
}
main() {
E<int>({}).foo();
}
''');
var literal = findNode.setOrMapLiteral('{}).');
assertType(literal, 'Set<int>');
}
test_override_downward_hasTypeArguments_wrongNumber() async {
await assertErrorsInCode('''
extension E<T> on Set<T> {
void foo() {}
}
main() {
E<int, bool>({}).foo();
}
''', [
error(CompileTimeErrorCode.WRONG_NUMBER_OF_TYPE_ARGUMENTS_EXTENSION, 58,
11),
]);
var literal = findNode.setOrMapLiteral('{}).');
assertType(literal, 'Set<dynamic>');
}
test_override_downward_noTypeArguments() async {
await assertNoErrorsInCode('''
extension E<T> on Set<T> {
void foo() {}
}
main() {
E({}).foo();
}
''');
var literal = findNode.setOrMapLiteral('{}).');
assertType(literal, 'Set<dynamic>');
}
test_override_hasTypeArguments_getter() async {
await assertNoErrorsInCode('''
class A<T> {}
extension E<T> on A<T> {
List<T> get foo => <T>[];
}
void f(A<int> a) {
E<num>(a).foo;
}
''');
var override = findNode.extensionOverride('E<num>(a)');
assertElement(override, findElement.extension_('E'));
assertElementTypeStrings(override.typeArgumentTypes, ['num']);
assertType(override.extendedType, 'A<num>');
var propertyAccess = findNode.propertyAccess('.foo');
assertMember(
propertyAccess,
findElement.getter('foo', of: 'E'),
{'T': 'num'},
);
assertType(propertyAccess, 'List<num>');
}
test_override_hasTypeArguments_method() async {
await assertNoErrorsInCode('''
class A<T> {}
extension E<T> on A<T> {
Map<T, U> foo<U>(U u) => <T, U>{};
}
void f(A<int> a) {
E<num>(a).foo(1.0);
}
''');
var override = findNode.extensionOverride('E<num>(a)');
assertElement(override, findElement.extension_('E'));
assertElementTypeStrings(override.typeArgumentTypes, ['num']);
assertType(override.extendedType, 'A<num>');
// TODO(scheglov) We need to instantiate "foo" fully.
var invocation = findNode.methodInvocation('foo(1.0)');
assertMember(
invocation,
findElement.method('foo', of: 'E'),
{'T': 'num'},
);
// assertMember(
// invocation,
// findElement.method('foo', of: 'E'),
// {'T': 'int', 'U': 'double'},
// );
assertInvokeType(invocation, 'Map<num, double> Function(double)');
}
test_override_hasTypeArguments_method_tearOff() async {
await assertNoErrorsInCode('''
class A<T> {}
extension E<T> on A<T> {
Map<T, U> foo<U>(U u) => <T, U>{};
}
void f(A<int> a) {
E<num>(a).foo;
}
''');
var propertyAccess = findNode.propertyAccess('foo;');
assertMember(
propertyAccess,
findElement.method('foo', of: 'E'),
{'T': 'num'},
);
assertType(propertyAccess, 'Map<num, U> Function<U>(U)');
}
test_override_hasTypeArguments_setter() async {
await assertNoErrorsInCode('''
class A<T> {}
extension E<T> on A<T> {
set foo(T value) {}
}
void f(A<int> a) {
E<num>(a).foo = 1.2;
}
''');
var override = findNode.extensionOverride('E<num>(a)');
assertElement(override, findElement.extension_('E'));
assertElementTypeStrings(override.typeArgumentTypes, ['num']);
assertType(override.extendedType, 'A<num>');
var propertyAccess = findNode.propertyAccess('.foo =');
assertMember(
propertyAccess,
findElement.setter('foo', of: 'E'),
{'T': 'num'},
);
}
test_override_inferTypeArguments_error_couldNotInfer() async {
await assertErrorsInCode('''
extension E<T extends num> on T {
void foo() {}
}
f(String s) {
E(s).foo();
}
''', [
error(CompileTimeErrorCode.COULD_NOT_INFER, 69, 1),
]);
var override = findNode.extensionOverride('E(s)');
assertElementTypeStrings(override.typeArgumentTypes, ['String']);
assertType(override.extendedType, 'String');
}
test_override_inferTypeArguments_getter() async {
await assertNoErrorsInCode('''
class A<T> {}
extension E<T> on A<T> {
List<T> get foo => <T>[];
}
void f(A<int> a) {
E(a).foo;
}
''');
var override = findNode.extensionOverride('E(a)');
assertElement(override, findElement.extension_('E'));
assertElementTypeStrings(override.typeArgumentTypes, ['int']);
assertType(override.extendedType, 'A<int>');
var propertyAccess = findNode.propertyAccess('.foo');
assertMember(
propertyAccess,
findElement.getter('foo', of: 'E'),
{'T': 'int'},
);
assertType(propertyAccess, 'List<int>');
}
test_override_inferTypeArguments_method() async {
await assertNoErrorsInCode('''
class A<T> {}
extension E<T> on A<T> {
Map<T, U> foo<U>(U u) => <T, U>{};
}
void f(A<int> a) {
E(a).foo(1.0);
}
''');
var override = findNode.extensionOverride('E(a)');
assertElement(override, findElement.extension_('E'));
assertElementTypeStrings(override.typeArgumentTypes, ['int']);
assertType(override.extendedType, 'A<int>');
// TODO(scheglov) We need to instantiate "foo" fully.
var invocation = findNode.methodInvocation('foo(1.0)');
assertMember(
invocation,
findElement.method('foo', of: 'E'),
{'T': 'int'},
);
// assertMember(
// invocation,
// findElement.method('foo', of: 'E'),
// {'T': 'int', 'U': 'double'},
// );
assertInvokeType(invocation, 'Map<int, double> Function(double)');
}
test_override_inferTypeArguments_method_tearOff() async {
await assertNoErrorsInCode('''
class A<T> {}
extension E<T> on A<T> {
Map<T, U> foo<U>(U u) => <T, U>{};
}
void f(A<int> a) {
E(a).foo;
}
''');
var override = findNode.extensionOverride('E(a)');
assertElement(override, findElement.extension_('E'));
assertElementTypeStrings(override.typeArgumentTypes, ['int']);
assertType(override.extendedType, 'A<int>');
var propertyAccess = findNode.propertyAccess('foo;');
assertMember(
propertyAccess,
findElement.method('foo', of: 'E'),
{'T': 'int'},
);
assertType(propertyAccess, 'Map<int, U> Function<U>(U)');
}
test_override_inferTypeArguments_setter() async {
await assertNoErrorsInCode('''
class A<T> {}
extension E<T> on A<T> {
set foo(T value) {}
}
void f(A<int> a) {
E(a).foo = 0;
}
''');
var override = findNode.extensionOverride('E(a)');
assertElement(override, findElement.extension_('E'));
assertElementTypeStrings(override.typeArgumentTypes, ['int']);
assertType(override.extendedType, 'A<int>');
var propertyAccess = findNode.propertyAccess('.foo =');
assertMember(
propertyAccess,
findElement.setter('foo', of: 'E'),
{'T': 'int'},
);
}
}
@reflectiveTest
class ExtensionMethodsWithNullSafetyTest extends PubPackageResolutionTest
with WithNullSafetyMixin, ExtensionMethodsTestCases {}