analyzer: Support type instantiation of 'call' tearoff of function

Fixes https://github.com/dart-lang/sdk/issues/47212

Change-Id: Id4d5fc47e2e3ae1ef9949a698ba22c697f9e4b14
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/213539
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Samuel Rawlins <srawlins@google.com>
This commit is contained in:
Sam Rawlins 2021-09-16 19:45:45 +00:00 committed by commit-bot@chromium.org
parent d30a43d464
commit 797bfe5bb0
2 changed files with 180 additions and 15 deletions

View file

@ -77,6 +77,31 @@ class FunctionReferenceResolver {
}
}
/// Checks for a type instantiation of a `dynamic`-typed expression.
///
/// Returns `true` if an error was reported, and resolution can stop.
bool _checkDynamicTypeInstantiation(FunctionReferenceImpl node,
PrefixedIdentifierImpl function, Element prefixElement) {
DartType? prefixType;
if (prefixElement is VariableElement) {
prefixType = prefixElement.type;
} else if (prefixElement is PropertyAccessorElement) {
prefixType = prefixElement.returnType;
}
function.prefix.staticType = prefixType;
if (prefixType != null && prefixType.isDynamic) {
_errorReporter.reportErrorForNode(
CompileTimeErrorCode.GENERIC_METHOD_TYPE_INSTANTIATION_ON_DYNAMIC,
function,
[],
);
node.staticType = DynamicTypeImpl.instance;
return true;
}
return false;
}
List<DartType> _checkTypeArguments(
TypeArgumentList typeArgumentList,
String? name,
@ -271,6 +296,36 @@ class FunctionReferenceResolver {
_resolve(node: node, rawType: member.type, name: propertyName.name);
}
/// Resolve a possible function tearoff of a [FunctionElement] receiver.
///
/// There are three possible valid cases: tearing off the `call` method of a
/// function element, tearing off an extension element declared on [Function],
/// and tearing off an extension element declared on a function type.
ExecutableElement? _resolveFunctionElementFunction(
Expression receiver,
SimpleIdentifier methodName,
FunctionElement receiverElement,
) {
if (methodName.name == FunctionElement.CALL_METHOD_NAME) {
return receiverElement;
}
var methodElement = _resolver.typePropertyResolver
.resolve(
receiver: receiver,
receiverType: receiverElement.type,
name: methodName.name,
propertyErrorEntity: methodName,
nameErrorEntity: methodName,
)
.getter;
if (methodElement != null && methodElement.isStatic) {
_reportInvalidAccessToStaticMember(methodName, methodElement,
implicitReceiver: false);
}
return methodElement;
}
void _resolvePrefixedIdentifierFunction(
FunctionReferenceImpl node, PrefixedIdentifierImpl function) {
var prefixElement = function.prefix.scopeLookupResult!.getter;
@ -287,14 +342,15 @@ class FunctionReferenceResolver {
}
function.prefix.staticElement = prefixElement;
var functionName = function.identifier.name;
if (prefixElement is PrefixElement) {
var functionName = function.identifier.name;
var functionElement = prefixElement.scope.lookup(functionName).getter;
if (functionElement == null) {
_errorReporter.reportErrorForNode(
CompileTimeErrorCode.UNDEFINED_PREFIXED_NAME,
function.identifier,
[function.identifier.name, function.prefix.name],
[functionName, function.prefix.name],
);
function.staticType = DynamicTypeImpl.instance;
node.staticType = DynamicTypeImpl.instance;
@ -306,21 +362,17 @@ class FunctionReferenceResolver {
}
}
DartType? prefixType;
if (prefixElement is VariableElement) {
prefixType = prefixElement.type;
} else if (prefixElement is PropertyAccessorElement) {
prefixType = prefixElement.returnType;
if (_checkDynamicTypeInstantiation(node, function, prefixElement)) {
return;
}
function.prefix.staticType = prefixType;
if (prefixType != null && prefixType.isDynamic) {
_errorReporter.reportErrorForNode(
CompileTimeErrorCode.GENERIC_METHOD_TYPE_INSTANTIATION_ON_DYNAMIC,
function,
[],
if (prefixElement is FunctionElement &&
functionName == FunctionElement.CALL_METHOD_NAME) {
_resolve(
node: node,
rawType: prefixElement.type,
name: functionName,
);
node.staticType = DynamicTypeImpl.instance;
return;
}
@ -337,7 +389,7 @@ class FunctionReferenceResolver {
_resolve(
node: node,
rawType: methodElement.type,
name: function.identifier.name,
name: functionName,
);
return;
}
@ -686,6 +738,8 @@ class FunctionReferenceResolver {
}
if (receiverElement is ClassElement) {
return _resolveStaticElement(receiverElement, name);
} else if (receiverElement is FunctionElement) {
return _resolveFunctionElementFunction(receiver, name, receiverElement);
} else if (receiverElement is TypeAliasElement) {
var aliasedType = receiverElement.aliasedType;
if (aliasedType is InterfaceType) {

View file

@ -334,6 +334,102 @@ extension E on A {
reference, findElement.method('foo'), 'void Function(int)');
}
test_function_call() async {
await assertNoErrorsInCode('''
void foo<T>(T a) {}
void bar() {
foo.call<int>;
}
''');
assertFunctionReference(findNode.functionReference('foo.call<int>;'), null,
'void Function(int)');
}
test_function_call_tooFewTypeArgs() async {
await assertErrorsInCode('''
void foo<T, U>(T a, U b) {}
void bar() {
foo.call<int>;
}
''', [
error(
CompileTimeErrorCode.WRONG_NUMBER_OF_TYPE_ARGUMENTS_FUNCTION, 52, 5),
]);
assertFunctionReference(findNode.functionReference('foo.call<int>;'), null,
'void Function(dynamic, dynamic)');
}
test_function_call_tooManyTypeArgs() async {
await assertErrorsInCode('''
void foo(String a) {}
void bar() {
foo.call<int>;
}
''', [
error(
CompileTimeErrorCode.WRONG_NUMBER_OF_TYPE_ARGUMENTS_FUNCTION, 46, 5),
]);
assertFunctionReference(findNode.functionReference('foo.call<int>;'), null,
'void Function(String)');
}
test_function_call_typeArgNotMatchingBound() async {
await assertNoErrorsInCode('''
void foo<T extends num>(T a) {}
void bar() {
foo.call<String>;
}
''');
assertFunctionReference(findNode.functionReference('foo.call<String>;'),
null, 'void Function(String)');
}
test_function_extensionOnFunction() async {
// TODO(srawlins): Test extension on function type, like
// `extension on void Function<T>(T)`.
await assertNoErrorsInCode('''
void foo<T>(T a) {}
void bar() {
foo.m<int>;
}
extension on Function {
void m<T>(T t) {}
}
''');
assertFunctionReference(findNode.functionReference('foo.m<int>;'),
findElement.method('m'), 'void Function(int)');
}
test_function_extensionOnFunction_static() async {
await assertErrorsInCode('''
void foo<T>(T a) {}
void bar() {
foo.m<int>;
}
extension on Function {
static void m<T>(T t) {}
}
''', [
error(CompileTimeErrorCode.INSTANCE_ACCESS_TO_STATIC_MEMBER, 40, 1),
]);
assertFunctionReference(findNode.functionReference('foo.m<int>;'),
findElement.method('m'), 'void Function(int)');
}
test_instanceGetter_explicitReceiver() async {
await assertErrorsInCode('''
class A {
@ -1040,6 +1136,21 @@ bar() {
findNode.functionReference('foo<int>;'), null, 'dynamic');
}
test_topLevelFunction_targetOfCall() async {
await assertNoErrorsInCode('''
void foo<T>(T a) {}
void bar() {
foo<int>.call;
}
''');
assertFunctionReference(findNode.functionReference('foo<int>.call;'),
findElement.topFunction('foo'), 'void Function(int)');
assertSimpleIdentifier(findNode.simple('call;'),
element: null, type: 'void Function(int)');
}
test_topLevelFunction_targetOfFunctionCall() async {
await assertNoErrorsInCode('''
void foo<T>(T arg) {}