analyzer: Rewrite function tearoff followed by a method call

Today, the parser reads `a<...>.b()` as a constructor call with
explicit type arguments for the class. This could be a method call
on a function tearoff (however unlikely).

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

Bug: https://github.com/dart-lang/sdk/issues/46020
Change-Id: I16681171a7d99adf3fd080a235fcedc46c5fb1ef
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/210780
Commit-Queue: Samuel Rawlins <srawlins@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
Sam Rawlins 2021-08-23 18:10:47 +00:00 committed by commit-bot@chromium.org
parent 7399e4e631
commit f274c79333
7 changed files with 205 additions and 5 deletions

View file

@ -640,7 +640,7 @@ class AstFactoryImpl extends AstFactory {
FunctionReferenceImpl functionReference(
{required Expression function, TypeArgumentList? typeArguments}) =>
FunctionReferenceImpl(function as ExpressionImpl,
typeArguments: typeArguments as TypeArgumentListImpl);
typeArguments: typeArguments as TypeArgumentListImpl?);
@override
FunctionTypeAliasImpl functionTypeAlias(

View file

@ -12,13 +12,76 @@ import 'package:analyzer/src/dart/ast/ast_factory.dart';
import 'package:analyzer/src/dart/ast/utilities.dart';
import 'package:analyzer/src/error/codes.dart';
/// Helper for [MethodInvocation]s into [InstanceCreationExpression] to support
/// the optional `new` and `const` feature, or [ExtensionOverride].
/// Handles possible rewrites of AST.
///
/// When code is initially parsed, many assumptions are made which may be
/// incorrect given newer Dart syntax. For example, `new a.b()` is parsed as an
/// [InstanceCreationExpression], but `a.b()` (without `new`) is parsed as a
/// [MethodInvocation]. The public methods of this class carry out the minimal
/// amount of resolution in order to determine whether a node (and its
/// descendants) should be replaced by another, and perform such replacements.
///
/// The public methods of this class form a complete accounting of possible
/// node replacements.
class AstRewriter {
final ErrorReporter _errorReporter;
AstRewriter(this._errorReporter);
/// Possibly rewrites [node] as a [MethodInvocation] with a
/// [FunctionReference] target.
///
/// Code such as `a<...>.b(...);` (or with a prefix such as `p.a<...>.b(...)`)
/// is parsed as an [ExpressionStatement] with an [InstanceCreationExpression]
/// with `a<...>.b` as the [ConstructorName] (which has 'type' of `a<...>`
/// and 'name' of `b`). The [InstanceCreationExpression] is rewritten as a
/// [MethodInvocation] if `a` resolves to a function.
AstNode instanceCreationExpression(
Scope nameScope, InstanceCreationExpression node) {
if (node.keyword != null) {
// Either `new` or `const` has been specified.
return node;
}
var typeName = node.constructorName.type.name;
if (typeName is SimpleIdentifier) {
var element = nameScope.lookup(typeName.name).getter;
if (element is MethodElement) {
return _toMethodInvocationOfFunctionReference(
node: node, function: typeName);
}
} else if (typeName is PrefixedIdentifier) {
var prefixElement = nameScope.lookup(typeName.prefix.name).getter;
if (prefixElement is PrefixElement) {
var prefixedName = typeName.identifier.name;
var element = prefixElement.scope.lookup(prefixedName).getter;
if (element is FunctionElement) {
return _toMethodInvocationOfFunctionReference(
node: node, function: typeName);
}
// If `element` is a [ClassElement], or a [TypeAliasElement] aliasing
// an interface type, then this indeed looks like a constructor call; do
// not rewrite `node`.
// If `element` is a [TypeAliasElement] aliasing a function type, then
// this looks like an attempt type instantiate a function type alias
// (which is not a feature), and then call a method on the resulting
// [Type] object; no not rewrite `node`.
// If `typeName.identifier` cannot be resolved, do not rewrite `node`.
return node;
} else {
// In the case that `prefixElement` is not a [PrefixElement], then
// `typeName`, as a [PrefixedIdentifier], cannot refer to a class or an
// aliased type; rewrite `node` as a [MethodInvocation].
return _toMethodInvocationOfFunctionReference(
node: node, function: typeName);
}
}
return node;
}
/// Possibly rewrites [node] as an [ExtensionOverride] or as an
/// [InstanceCreationExpression].
AstNode methodInvocation(Scope nameScope, MethodInvocation node) {
@ -143,6 +206,11 @@ class AstRewriter {
}
/// Possibly rewrites [node] as a [ConstructorReference].
///
/// Code such as `List.filled;` is parsed as (an [ExpressionStatement] with) a
/// [PrefixedIdentifier] with 'prefix' of `List` and 'identifier' of `filled`.
/// The [PrefixedIdentifier] may need to be rewritten as a
/// [ConstructorReference].
AstNode prefixedIdentifier(Scope nameScope, PrefixedIdentifier node) {
if (node.parent is Annotation) {
// An annotations which is a const constructor invocation can initially be
@ -181,6 +249,13 @@ class AstRewriter {
return node;
}
/// Possibly rewrites [node] as a [ConstructorReference].
///
/// Code such as `async.Future.value;` is parsed as (an [ExpressionStatement]
/// with) a [PropertyAccess] with a 'target' of [PrefixedIdentifier] (with
/// 'prefix' of `List` and 'identifier' of `filled`) and a 'propertyName' of
/// `value`. The [PropertyAccess] may need to be rewritten as a
/// [ConstructorReference].
AstNode propertyAccess(Scope nameScope, PropertyAccess node) {
if (node.isCascaded) {
// For example, `List..filled`: this is a property access on an instance
@ -397,4 +472,23 @@ class AstRewriter {
NodeReplacer.replace(node, instanceCreationExpression);
return instanceCreationExpression;
}
MethodInvocation _toMethodInvocationOfFunctionReference({
required InstanceCreationExpression node,
required Identifier function,
}) {
var functionReference = astFactory.functionReference(
function: function,
typeArguments: node.constructorName.type.typeArguments,
);
var methodInvocation = astFactory.methodInvocation(
functionReference,
node.constructorName.period,
node.constructorName.name!,
null,
node.argumentList,
);
NodeReplacer.replace(node, methodInvocation);
return methodInvocation;
}
}

View file

@ -55,6 +55,7 @@ class ElementHolder {
/// 2. Create and set new elements for local declarations.
/// 3. Resolve all [TypeName]s - set elements and types.
/// 4. Resolve all [GenericFunctionType]s - set their types.
/// 5. Rewrite AST where resolution provides a more accurate understanding.
class ResolutionVisitor extends RecursiveAstVisitor<void> {
LibraryElementImpl _libraryElement;
final TypeProvider _typeProvider;
@ -743,6 +744,16 @@ class ResolutionVisitor extends RecursiveAstVisitor<void> {
});
}
@override
void visitInstanceCreationExpression(InstanceCreationExpression node) {
var newNode = _astRewriter.instanceCreationExpression(_nameScope, node);
if (newNode != node) {
return newNode.accept(this);
}
super.visitInstanceCreationExpression(node);
}
@override
void visitLabeledStatement(LabeledStatement node) {
bool onSwitchStatement = node.statement is SwitchStatement;

View file

@ -360,6 +360,35 @@ void bar() {
);
}
test_prefixedClass_generic_targetOfFunctionCall() async {
newFile('$testPackageLibPath/a.dart', content: '''
class A<T> {
A();
}
''');
await assertNoErrorsInCode('''
import 'a.dart' as a;
extension on Function {
void m() {}
}
void bar() {
a.A<int>.new.m();
}
''');
var classElement =
findElement.importFind('package:test/a.dart').class_('A');
assertConstructorReference(
findNode.constructorReference('a.A<int>.new'),
elementMatcher(classElement.unnamedConstructor,
substitution: {'T': 'int'}),
classElement,
'A<int> Function()',
expectedTypeNameType: 'A<int>',
expectedPrefix: findElement.import('package:test/a.dart').prefix,
);
}
test_prefixedClass_generic_unnamed() async {
newFile('$testPackageLibPath/a.dart', content: '''
class A<T> {

View file

@ -328,6 +328,25 @@ bar() {
findNode.functionReference('foo<int>;'), null, 'dynamic');
}
test_instanceMethod_explicitReceiver_targetOfFunctionCall() async {
await assertNoErrorsInCode('''
extension on Function {
void m() {}
}
class A {
void foo<T>(T a) {}
}
bar(A a) {
a.foo<int>.m();
}
''');
var reference = findNode.functionReference('foo<int>');
assertFunctionReference(
reference, findElement.method('foo'), 'void Function(int)');
}
test_instanceMethod_explicitReceiver_this() async {
await assertNoErrorsInCode('''
class A {
@ -470,6 +489,25 @@ class B extends A {
reference, findElement.method('foo'), 'void Function(int)');
}
test_instanceMethod_targetOfFunctionCall() async {
await assertNoErrorsInCode('''
extension on Function {
void m() {}
}
class A {
void foo<T>(T a) {}
bar() {
foo<int>.m();
}
}
''');
var reference = findNode.functionReference('foo<int>');
assertFunctionReference(
reference, findElement.method('foo'), 'void Function(int)');
}
test_instanceMethod_unknown() async {
await assertErrorsInCode('''
class A {
@ -792,6 +830,30 @@ void bar() {
);
}
test_topLevelFunction_importPrefix_asTargetOfFunctionCall() async {
newFile('$testPackageLibPath/a.dart', content: '''
void foo<T>(T arg) {}
''');
await assertNoErrorsInCode('''
import 'a.dart' as a;
extension on Function {
void m() {}
}
void bar() {
a.foo<int>.m();
}
''');
assertImportPrefix(findNode.simple('a.f'), findElement.prefix('a'));
var reference = findNode.functionReference('foo<int>');
assertFunctionReference(
reference,
findElement.importFind('package:test/a.dart').topFunction('foo'),
'void Function(int)',
);
}
test_topLevelFunction_prefix_unknownPrefix() async {
await assertErrorsInCode('''
bar() {

View file

@ -105,11 +105,13 @@ main() {
// [analyzer] COMPILE_TIME_ERROR.WRONG_NUMBER_OF_TYPE_ARGUMENTS_CONSTRUCTOR
Foo.bar<int>.baz();
//^^^^^^^
// [analyzer] COMPILE_TIME_ERROR.CREATION_WITH_NON_TYPE
// [analyzer] COMPILE_TIME_ERROR.DISALLOWED_TYPE_INSTANTIATION_EXPRESSION
// ^
// [cfe] A constructor invocation can't have type arguments after the constructor name.
// ^
// [cfe] Method not found: 'Foo.bar.baz'.
// ^^^
// [analyzer] COMPILE_TIME_ERROR.UNDEFINED_METHOD
Foo.bar.baz<int>();
// ^
// [cfe] Getter not found: 'bar'.

View file

@ -107,11 +107,13 @@ main() {
// [analyzer] COMPILE_TIME_ERROR.WRONG_NUMBER_OF_TYPE_ARGUMENTS_CONSTRUCTOR
Foo.bar<int>.baz();
//^^^^^^^
// [analyzer] COMPILE_TIME_ERROR.CREATION_WITH_NON_TYPE
// [analyzer] COMPILE_TIME_ERROR.DISALLOWED_TYPE_INSTANTIATION_EXPRESSION
// ^
// [cfe] A constructor invocation can't have type arguments after the constructor name.
// ^
// [cfe] Method not found: 'Foo.bar.baz'.
// ^^^
// [analyzer] COMPILE_TIME_ERROR.UNDEFINED_METHOD
Foo.bar.baz<int>();
// ^
// [cfe] Getter not found: 'bar'.