Resolve more cases of .call; correct many cases of PropertyAccess receivers

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

Much of this change is about decision-making based on the _type_ of the
function in a FunctionReference, or the receiver of that function, in the
case of prefixed identifiers or property access.

Previously, there was a rule that only direct references to functions
(top-level, local, and method) could be torn off and type-instantiated.
That rule has been reversed (#1812), so a lot of unraveling has to be
done here.

`e.call<...>` is now legal for _any_ generic function-typed expression
`e`, which certainly may be an expression without a staticElement.
Additionally, `e<...>` is now legal for _any_ generic function-typed
expression `e`.

Because we no longer resolve PropertyAccess piecemeal, we no longer
report UNDEFINED_IDENTIFIER or UNDEFINED_PREFIXED_ELEMENT etc before
deciding to check if a tearoff is being made on a function-typed
element. The unfortunate side-effect here is some redundant
error-reporting. :( I've left TODOs.

Change-Id: I62106332e39d528cbd7cdfa5ec831dc56b394b52
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/213800
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Samuel Rawlins <srawlins@google.com>
This commit is contained in:
Sam Rawlins 2021-09-20 20:17:35 +00:00 committed by commit-bot@chromium.org
parent 5c83d863ef
commit f12333dfa0
2 changed files with 202 additions and 94 deletions

View file

@ -89,7 +89,6 @@ class FunctionReferenceResolver {
prefixType = prefixElement.returnType;
}
function.prefix.staticType = prefixType;
if (prefixType != null && prefixType.isDynamic) {
_errorReporter.reportErrorForNode(
CompileTimeErrorCode.GENERIC_METHOD_TYPE_INSTANTIATION_ON_DYNAMIC,
@ -301,19 +300,15 @@ class FunctionReferenceResolver {
/// 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(
Element? _resolveFunctionTypeFunction(
Expression receiver,
SimpleIdentifier methodName,
FunctionElement receiverElement,
FunctionType receiverType,
) {
if (methodName.name == FunctionElement.CALL_METHOD_NAME) {
return receiverElement;
}
var methodElement = _resolver.typePropertyResolver
.resolve(
receiver: receiver,
receiverType: receiverElement.type,
receiverType: receiverType,
name: methodName.name,
propertyErrorEntity: methodName,
nameErrorEntity: methodName,
@ -342,6 +337,7 @@ class FunctionReferenceResolver {
}
function.prefix.staticElement = prefixElement;
function.prefix.staticType = prefixElement.referenceType;
var functionName = function.identifier.name;
if (prefixElement is PrefixElement) {
@ -376,28 +372,22 @@ class FunctionReferenceResolver {
return;
}
var methodElement = _resolveTypeProperty(
var functionType = _resolveTypeProperty(
receiver: function.prefix,
receiverElement: prefixElement,
name: function.identifier,
nameErrorEntity: function,
);
if (methodElement is MethodElement) {
function.identifier.staticElement = methodElement;
function.staticType = methodElement.type;
_resolve(
node: node,
rawType: methodElement.type,
name: functionName,
);
return;
}
if (methodElement is PropertyAccessorElement) {
function.accept(_resolver);
_resolveDisallowedExpression(node, methodElement.returnType);
return;
if (functionType != null) {
if (functionType is FunctionType) {
function.staticType = functionType;
_resolve(
node: node,
rawType: functionType,
name: functionName,
);
return;
}
}
function.accept(_resolver);
@ -425,39 +415,12 @@ class FunctionReferenceResolver {
node.staticType = DynamicTypeImpl.instance;
return;
}
} else if (target is PrefixedIdentifierImpl) {
var prefixElement = target.prefix.scopeLookupResult!.getter;
if (prefixElement is PrefixElement) {
var prefixName = target.identifier.name;
var targetElement = prefixElement.scope.lookup(prefixName).getter;
var methodElement = _resolveTypeProperty(
receiver: target,
receiverElement: targetElement,
name: function.propertyName,
nameErrorEntity: function,
);
if (methodElement == null) {
// The target is known, but the method is not; [UNDEFINED_GETTER] is
// reported elsewhere.
node.staticType = DynamicTypeImpl.instance;
return;
} else {
_resolveReceiverPrefix(node, prefixElement, target, methodElement);
return;
}
} else {
// The prefix is unknown; [UNDEFINED_IDENTIFIER] is reported elsewhere.
node.staticType = DynamicTypeImpl.instance;
return;
}
} else if (target is ExtensionOverrideImpl) {
_resolveExtensionOverride(node, function, target);
return;
} else {
targetType = target.typeOrThrow;
if (targetType.isDynamic) {
var targetType = target.staticType;
if (targetType != null && targetType.isDynamic) {
_errorReporter.reportErrorForNode(
CompileTimeErrorCode.GENERIC_METHOD_TYPE_INSTANTIATION_ON_DYNAMIC,
node,
@ -466,6 +429,29 @@ class FunctionReferenceResolver {
node.staticType = DynamicTypeImpl.instance;
return;
}
var functionType = _resolveTypeProperty(
receiver: target,
name: function.propertyName,
nameErrorEntity: function,
);
if (functionType == null) {
// The target is known, but the method is not; [UNDEFINED_GETTER] is
// reported elsewhere.
node.staticType = DynamicTypeImpl.instance;
return;
} else {
if (functionType is FunctionType) {
function.staticType = functionType;
_resolve(
node: node,
rawType: functionType,
name: function.propertyName.name,
);
}
return;
}
}
var propertyElement = _resolver.typePropertyResolver
@ -719,47 +705,52 @@ class FunctionReferenceResolver {
NodeReplacer.replace(node, typeLiteral);
}
/// Resolves [name] as a property on [receiver] (with element
/// [receiverElement]).
/// Resolves [name] as a property on [receiver].
///
/// Returns `null` if [receiverElement] is `null`, a [TypeParameterElement],
/// or a [TypeAliasElement] for a non-interface type.
ExecutableElement? _resolveTypeProperty({
/// Returns `null` if [receiver]'s type is `null`, a [TypeParameterType],
/// or a type alias for a non-interface type.
DartType? _resolveTypeProperty({
required Expression receiver,
required Element? receiverElement,
required SimpleIdentifier name,
required SimpleIdentifierImpl name,
required SyntacticEntity nameErrorEntity,
}) {
if (receiverElement == null) {
return null;
}
if (receiverElement is TypeParameterElement) {
return null;
}
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) {
return _resolveStaticElement(aliasedType.element, name);
} else {
return null;
if (receiver is Identifier) {
var receiverElement = receiver.staticElement;
if (receiverElement is ClassElement) {
var element = _resolveStaticElement(receiverElement, name);
name.staticElement = element;
// TODO(srawlins): Should this use referenceType? E.g. if `element`
// is a function-typed static getter.
return element?.type;
} else if (receiverElement is TypeAliasElement) {
var aliasedType = receiverElement.aliasedType;
if (aliasedType is InterfaceType) {
var element = _resolveStaticElement(aliasedType.element, name);
name.staticElement = element;
// TODO(srawlins): Should this use referenceType? E.g. if `element`
// is a function-typed static getter.
return element?.type;
} else {
return null;
}
}
}
DartType receiverType;
if (receiverElement is VariableElement) {
receiverType = receiverElement.type;
} else if (receiverElement is PropertyAccessorElement) {
receiverType = receiverElement.returnType;
} else {
assert(false,
'Unexpected receiverElement type: ${receiverElement.runtimeType}');
var receiverType = receiver.staticType;
if (receiverType == null) {
return null;
} else if (receiverType is TypeParameterType) {
return null;
} else if (receiverType is FunctionType) {
if (name.name == FunctionElement.CALL_METHOD_NAME) {
return receiverType;
}
var element = _resolveFunctionTypeFunction(receiver, name, receiverType);
name.staticElement = element;
return element?.referenceType;
}
var methodElement = _resolver.typePropertyResolver
var element = _resolver.typePropertyResolver
.resolve(
receiver: receiver,
receiverType: receiverType,
@ -768,10 +759,35 @@ class FunctionReferenceResolver {
nameErrorEntity: nameErrorEntity,
)
.getter;
if (methodElement != null && methodElement.isStatic) {
_reportInvalidAccessToStaticMember(name, methodElement,
name.staticElement = element;
if (element != null && element.isStatic) {
_reportInvalidAccessToStaticMember(name, element,
implicitReceiver: false);
}
return methodElement;
return element?.referenceType;
}
}
extension on Element {
/// Returns the 'type' of `this`, when accessed as a "reference", not
/// immediately followed by parentheses and arguments.
///
/// For all elements that don't have a type (for example, [ExportElement]),
/// `null` is returned. For [PropertyAccessorElement], the return value is
/// returned. For all other elements, their `type` property is returned.
DartType? get referenceType {
if (this is ConstructorElement) {
return (this as ConstructorElement).type;
} else if (this is FunctionElement) {
return (this as FunctionElement).type;
} else if (this is PropertyAccessorElement) {
return (this as PropertyAccessorElement).returnType;
} else if (this is MethodElement) {
return (this as MethodElement).type;
} else if (this is VariableElement) {
return (this as VariableElement).type;
} else {
return null;
}
}
}

View file

@ -129,6 +129,10 @@ bar() {
a.b.foo<int>;
}
''', [
// TODO(srawlins): Get the information to [FunctionReferenceResolve] that
// [PropertyElementResolver] encountered an error, to avoid double reporting.
error(CompileTimeErrorCode.GENERIC_METHOD_TYPE_INSTANTIATION_ON_DYNAMIC,
10, 12),
error(CompileTimeErrorCode.UNDEFINED_IDENTIFIER, 10, 1),
]);
@ -431,7 +435,7 @@ extension on Function {
}
test_instanceGetter_explicitReceiver() async {
await assertErrorsInCode('''
await assertNoErrorsInCode('''
class A {
late void Function<T>(T) foo;
}
@ -439,10 +443,7 @@ class A {
bar(A a) {
a.foo<int>;
}
''', [
error(
CompileTimeErrorCode.DISALLOWED_TYPE_INSTANTIATION_EXPRESSION, 58, 5),
]);
''');
assertFunctionReference(findNode.functionReference('foo<int>;'),
findElement.getter('foo'), 'void Function(int)');
@ -480,6 +481,42 @@ class A {
reference, findElement.method('foo'), 'void Function(int)');
}
test_instanceMethod_call() async {
await assertNoErrorsInCode('''
class C {
void foo<T>(T a) {}
void bar() {
foo.call<int>;
}
}
''');
var reference = findNode.functionReference('foo.call<int>;');
// TODO(srawlins): PropertyElementResolver does not return an element for
// `.call`. If we want `findElement.method('foo')` here, we must change the
// policy over there.
assertFunctionReference(reference, null, 'void Function(int)');
}
test_instanceMethod_explicitReceiver_call() async {
await assertNoErrorsInCode('''
class C {
void foo<T>(T a) {}
}
void bar(C c) {
c.foo.call<int>;
}
''');
var reference = findNode.functionReference('foo.call<int>;');
// TODO(srawlins): PropertyElementResolver does not return an element for
// `.call`. If we want `findElement.method('foo')` here, we must change the
// policy over there.
assertFunctionReference(reference, null, 'void Function(int)');
}
test_instanceMethod_explicitReceiver_field() async {
await assertNoErrorsInCode('''
class A {
@ -515,6 +552,22 @@ void f(A? a, A b) {
findElement.method('foo'), 'void Function(int)');
}
test_instanceMethod_explicitReceiver_receiverIsNotIdentifier_call() async {
await assertNoErrorsInCode('''
extension on List<Object?> {
void foo<T>(T a) {}
}
var a = [].foo.call<int>;
''');
var reference = findNode.functionReference('foo.call<int>;');
// TODO(srawlins): PropertyElementResolver does not return an element for
// `.call`. If we want `findElement.method('foo')` here, we must change the
// policy over there.
assertFunctionReference(reference, null, 'void Function(int)');
}
test_instanceMethod_explicitReceiver_super() async {
await assertNoErrorsInCode('''
class A {
@ -785,6 +838,43 @@ void bar(void Function<T>(T a) foo) {
reference, findElement.parameter('foo'), 'void Function(int)');
}
test_localVariable_call() async {
await assertNoErrorsInCode('''
void foo<T>(T a) {}
void bar() {
var fn = foo;
fn.call<int>;
}
''');
var reference = findNode.functionReference('fn.call<int>;');
// TODO(srawlins): PropertyElementResolver does not return an element for
// `.call`. If we want `findElement.method('foo')` here, we must change the
// policy over there.
assertFunctionReference(reference, null, 'void Function(int)');
}
test_localVariable_call_tooManyTypeArgs() async {
await assertErrorsInCode('''
void foo<T>(T a) {}
void bar() {
void Function(int) fn = foo;
fn.call<int>;
}
''', [
error(
CompileTimeErrorCode.WRONG_NUMBER_OF_TYPE_ARGUMENTS_FUNCTION, 74, 5),
]);
var reference = findNode.functionReference('fn.call<int>;');
// TODO(srawlins): PropertyElementResolver does not return an element for
// `.call`. If we want `findElement.method('fn')` here, we must change the
// policy over there.
assertFunctionReference(reference, null, 'void Function(int)');
}
test_localVariable_typeVariable_boundToFunction() async {
await assertErrorsInCode('''
void bar<T extends Function>(T foo) {
@ -1176,6 +1266,8 @@ bar() {
prefix.a.foo<int>;
}
''', [
error(CompileTimeErrorCode.GENERIC_METHOD_TYPE_INSTANTIATION_ON_DYNAMIC,
38, 17),
error(CompileTimeErrorCode.UNDEFINED_PREFIXED_NAME, 45, 1),
]);