diff --git a/pkg/analyzer/lib/src/dart/ast/ast_factory.dart b/pkg/analyzer/lib/src/dart/ast/ast_factory.dart index 029702e5874..cc2e492a06f 100644 --- a/pkg/analyzer/lib/src/dart/ast/ast_factory.dart +++ b/pkg/analyzer/lib/src/dart/ast/ast_factory.dart @@ -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( diff --git a/pkg/analyzer/lib/src/dart/resolver/ast_rewrite.dart b/pkg/analyzer/lib/src/dart/resolver/ast_rewrite.dart index b725ead2ef4..651a9805db2 100644 --- a/pkg/analyzer/lib/src/dart/resolver/ast_rewrite.dart +++ b/pkg/analyzer/lib/src/dart/resolver/ast_rewrite.dart @@ -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; + } } diff --git a/pkg/analyzer/lib/src/dart/resolver/resolution_visitor.dart b/pkg/analyzer/lib/src/dart/resolver/resolution_visitor.dart index b367f4a2924..63ea50651d1 100644 --- a/pkg/analyzer/lib/src/dart/resolver/resolution_visitor.dart +++ b/pkg/analyzer/lib/src/dart/resolver/resolution_visitor.dart @@ -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 { LibraryElementImpl _libraryElement; final TypeProvider _typeProvider; @@ -743,6 +744,16 @@ class ResolutionVisitor extends RecursiveAstVisitor { }); } + @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; diff --git a/pkg/analyzer/test/src/dart/resolution/constructor_reference_test.dart b/pkg/analyzer/test/src/dart/resolution/constructor_reference_test.dart index 5d29577c50d..769396f4fa3 100644 --- a/pkg/analyzer/test/src/dart/resolution/constructor_reference_test.dart +++ b/pkg/analyzer/test/src/dart/resolution/constructor_reference_test.dart @@ -360,6 +360,35 @@ void bar() { ); } + test_prefixedClass_generic_targetOfFunctionCall() async { + newFile('$testPackageLibPath/a.dart', content: ''' +class A { + A(); +} +'''); + await assertNoErrorsInCode(''' +import 'a.dart' as a; +extension on Function { + void m() {} +} +void bar() { + a.A.new.m(); +} +'''); + + var classElement = + findElement.importFind('package:test/a.dart').class_('A'); + assertConstructorReference( + findNode.constructorReference('a.A.new'), + elementMatcher(classElement.unnamedConstructor, + substitution: {'T': 'int'}), + classElement, + 'A Function()', + expectedTypeNameType: 'A', + expectedPrefix: findElement.import('package:test/a.dart').prefix, + ); + } + test_prefixedClass_generic_unnamed() async { newFile('$testPackageLibPath/a.dart', content: ''' class A { diff --git a/pkg/analyzer/test/src/dart/resolution/function_reference_test.dart b/pkg/analyzer/test/src/dart/resolution/function_reference_test.dart index c39a3396ddb..3aca3956a7a 100644 --- a/pkg/analyzer/test/src/dart/resolution/function_reference_test.dart +++ b/pkg/analyzer/test/src/dart/resolution/function_reference_test.dart @@ -328,6 +328,25 @@ bar() { findNode.functionReference('foo;'), null, 'dynamic'); } + test_instanceMethod_explicitReceiver_targetOfFunctionCall() async { + await assertNoErrorsInCode(''' +extension on Function { + void m() {} +} +class A { + void foo(T a) {} +} + +bar(A a) { + a.foo.m(); +} +'''); + + var reference = findNode.functionReference('foo'); + 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 a) {} + + bar() { + foo.m(); + } +} +'''); + + var reference = findNode.functionReference('foo'); + 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 arg) {} +'''); + await assertNoErrorsInCode(''' +import 'a.dart' as a; + +extension on Function { + void m() {} +} +void bar() { + a.foo.m(); +} +'''); + + assertImportPrefix(findNode.simple('a.f'), findElement.prefix('a')); + var reference = findNode.functionReference('foo'); + assertFunctionReference( + reference, + findElement.importFind('package:test/a.dart').topFunction('foo'), + 'void Function(int)', + ); + } + test_topLevelFunction_prefix_unknownPrefix() async { await assertErrorsInCode(''' bar() { diff --git a/tests/language/constructor/reference_test.dart b/tests/language/constructor/reference_test.dart index 46892bb4f12..ddbd57d736e 100644 --- a/tests/language/constructor/reference_test.dart +++ b/tests/language/constructor/reference_test.dart @@ -105,11 +105,13 @@ main() { // [analyzer] COMPILE_TIME_ERROR.WRONG_NUMBER_OF_TYPE_ARGUMENTS_CONSTRUCTOR Foo.bar.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(); // ^ // [cfe] Getter not found: 'bar'. diff --git a/tests/language_2/constructor/reference_test.dart b/tests/language_2/constructor/reference_test.dart index ec284ab16eb..402f2669058 100644 --- a/tests/language_2/constructor/reference_test.dart +++ b/tests/language_2/constructor/reference_test.dart @@ -107,11 +107,13 @@ main() { // [analyzer] COMPILE_TIME_ERROR.WRONG_NUMBER_OF_TYPE_ARGUMENTS_CONSTRUCTOR Foo.bar.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(); // ^ // [cfe] Getter not found: 'bar'.