blob: 48e4fb1d5cfd7d5dda30cab12517306384160877 [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(ExtensionMethodsWithoutNullSafetyTest);
});
}
@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();
}
''');
var node = findNode.methodInvocation('other.foo()');
if (result.libraryElement.isNonNullableByDefault) {
assertResolvedNodeText(node, r'''
MethodInvocation
target: SimpleIdentifier
token: other
staticElement: other@75
staticType: List<T>
operator: .
methodName: SimpleIdentifier
token: foo
staticElement: MethodMember
base: self::@extension::E::@method::foo
substitution: {T: T}
staticType: List<T> Function()
argumentList: ArgumentList
leftParenthesis: (
rightParenthesis: )
staticInvokeType: List<T> Function()
staticType: List<T>
''');
} else {
assertResolvedNodeText(node, r'''
MethodInvocation
target: SimpleIdentifier
token: other
staticElement: other@75
staticType: List<T*>*
operator: .
methodName: SimpleIdentifier
token: foo
staticElement: MethodMember
base: self::@extension::E::@method::foo
substitution: {T: T*}
staticType: List<T*>* Function()*
argumentList: ArgumentList
leftParenthesis: (
rightParenthesis: )
staticInvokeType: List<T*>* Function()*
staticType: 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;
}
''');
assertAssignment(
findNode.assignment('foo ='),
readElement: null,
readType: null,
writeElement: elementMatcher(
findElement.setter('foo', of: 'E'),
substitution: {'T': 'int'},
),
writeType: 'int',
operatorElement: null,
type: '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();
}
''');
var node = findNode.methodInvocation('test();');
if (result.libraryElement.isNonNullableByDefault) {
assertResolvedNodeText(node, r'''
MethodInvocation
target: SimpleIdentifier
token: x
staticElement: x@87
staticType: S
operator: .
methodName: SimpleIdentifier
token: test
staticElement: MethodMember
base: self::@extension::Test::@method::test
substitution: {T: S}
staticType: S Function(S) Function()
argumentList: ArgumentList
leftParenthesis: (
rightParenthesis: )
staticInvokeType: S Function(S) Function()
staticType: S Function(S)
''');
} else {
assertResolvedNodeText(node, r'''
MethodInvocation
target: SimpleIdentifier
token: x
staticElement: x@87
staticType: S*
operator: .
methodName: SimpleIdentifier
token: test
staticElement: MethodMember
base: self::@extension::Test::@method::test
substitution: {T: num*}
staticType: num* Function(num*)* Function()*
argumentList: ArgumentList
leftParenthesis: (
rightParenthesis: )
staticInvokeType: num* Function(num*)* Function()*
staticType: 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) {
assertAssignment(
findNode.assignment('(x).test'),
readElement: null,
readType: null,
writeElement: elementMatcher(
findElement.setter('test'),
substitution: {'T': 'S'},
),
writeType: 'S',
operatorElement: null,
type: 'S',
);
assertTypeArgumentTypes(
findNode.methodInvocation('g()'),
['S'],
);
} else {
assertAssignment(
findNode.assignment('(x).test'),
readElement: null,
readType: null,
writeElement: elementMatcher(
findElement.setter('test'),
substitution: {'T': 'num'},
),
writeType: 'num',
operatorElement: null,
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'));
assertElementTypes(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'));
assertElementTypes(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'));
assertElementTypes(override.typeArgumentTypes, ['num']);
assertType(override.extendedType, 'A<num>');
assertAssignment(
findNode.assignment('foo ='),
readElement: null,
readType: null,
writeElement: elementMatcher(
findElement.setter('foo', of: 'E'),
substitution: {'T': 'num'},
),
writeType: 'num',
operatorElement: null,
type: 'double',
);
}
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)');
assertElementTypes(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'));
assertElementTypes(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'));
assertElementTypes(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'));
assertElementTypes(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'));
assertElementTypes(override.typeArgumentTypes, ['int']);
assertType(override.extendedType, 'A<int>');
assertAssignment(
findNode.assignment('foo ='),
readElement: null,
readType: null,
writeElement: elementMatcher(
findElement.setter('foo', of: 'E'),
substitution: {'T': 'int'},
),
writeType: 'int',
operatorElement: null,
type: 'int',
);
}
}
@reflectiveTest
class ExtensionMethodsWithoutNullSafetyTest extends PubPackageResolutionTest
with WithoutNullSafetyMixin, ExtensionMethodsTestCases {}