From e995cb5f7cd67d39c1ee4bdbe95c8241db36725f Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Thu, 9 Sep 2021 07:04:34 +0000 Subject: [PATCH] analyzer: parse prefixed class constructor tearoff with no type args Change-Id: I5f19dd9592f83820100e0ac498522245cac16dc4 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/212700 Reviewed-by: Brian Wilkerson Reviewed-by: Konstantin Shcheglov Commit-Queue: Samuel Rawlins --- .../lib/src/dart/resolver/ast_rewrite.dart | 50 ++++++--- .../constructor_reference_test.dart | 102 ++++++++++++++++++ 2 files changed, 138 insertions(+), 14 deletions(-) diff --git a/pkg/analyzer/lib/src/dart/resolver/ast_rewrite.dart b/pkg/analyzer/lib/src/dart/resolver/ast_rewrite.dart index caf21ee3b16..513220ed520 100644 --- a/pkg/analyzer/lib/src/dart/resolver/ast_rewrite.dart +++ b/pkg/analyzer/lib/src/dart/resolver/ast_rewrite.dart @@ -266,21 +266,31 @@ class AstRewriter { return node; } var receiver = node.target!; - if (receiver is! FunctionReference) { - return node; - } var propertyName = node.propertyName; if (propertyName.isSynthetic) { // This isn't a constructor reference. return node; } - // A [ConstructorReference] with explicit type arguments is initially parsed - // as a [PropertyAccess] with a [FunctionReference] target; for example: - // `List.filled` or `core.List.filled`. - var receiverIdentifier = receiver.function; - if (receiverIdentifier is! Identifier) { - // If [receiverIdentifier] is not an Identifier then [node] is not a - // ConstructorReference. + + Identifier receiverIdentifier; + TypeArgumentList? typeArguments; + if (receiver is PrefixedIdentifier) { + receiverIdentifier = receiver; + } else if (receiver is FunctionReference) { + // A [ConstructorReference] with explicit type arguments is initially + // parsed as a [PropertyAccess] with a [FunctionReference] target; for + // example: `List.filled` or `core.List.filled`. + var function = receiver.function; + if (function is! Identifier) { + // If [receiverIdentifier] is not an Identifier then [node] is not a + // ConstructorReference. + return node; + } + receiverIdentifier = function; + typeArguments = receiver.typeArguments; + } else { + // If the receiver is not (initially) a prefixed identifier or a function + // reference, then [node] is not a constructor reference. return node; } @@ -310,7 +320,7 @@ class AstRewriter { return _toConstructorReference_propertyAccess( node: node, receiver: receiverIdentifier, - typeArguments: receiver.typeArguments!, + typeArguments: typeArguments, classElement: element, ); } else if (element is TypeAliasElement) { @@ -323,7 +333,7 @@ class AstRewriter { return _toConstructorReference_propertyAccess( node: node, receiver: receiverIdentifier, - typeArguments: receiver.typeArguments!, + typeArguments: typeArguments, classElement: aliasedType.element, ); } @@ -393,12 +403,24 @@ class AstRewriter { return constructorReference; } - ConstructorReference _toConstructorReference_propertyAccess({ + AstNode _toConstructorReference_propertyAccess({ required PropertyAccess node, required Identifier receiver, - required TypeArgumentList typeArguments, + required TypeArgumentList? typeArguments, required ClassElement classElement, }) { + var name = node.propertyName.name; + var constructorElement = name == 'new' + ? classElement.unnamedConstructor + : classElement.getNamedConstructor(name); + if (constructorElement == null && typeArguments == null) { + // If there is no constructor by this name, and no type arguments, + // do not rewrite the node. If there _are_ type arguments (like + // `prefix.C.name`, then it looks more like a constructor tearoff + // than anything else, so continue with the rewrite. + return node; + } + var operator = node.operator; var typeName = astFactory.typeName(receiver, typeArguments); 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 3262f0bbaed..21f09c6a8db 100644 --- a/pkg/analyzer/test/src/dart/resolution/constructor_reference_test.dart +++ b/pkg/analyzer/test/src/dart/resolution/constructor_reference_test.dart @@ -696,6 +696,108 @@ bar() { ); } + test_prefixedAlias_nonGeneric_named() async { + newFile('$testPackageLibPath/a.dart', content: ''' +class A { + A.foo(); +} +typedef TA = A; +'''); + await assertNoErrorsInCode(''' +import 'a.dart' as a; +bar() { + a.TA.foo; +} +'''); + + var classElement = + findElement.importFind('package:test/a.dart').class_('A'); + assertConstructorReference( + findNode.constructorReference('a.TA.foo;'), + classElement.getNamedConstructor('foo'), + classElement, + 'A Function()', + expectedPrefix: findElement.import('package:test/a.dart').prefix, + expectedTypeNameElement: + findElement.importFind('package:test/a.dart').typeAlias('TA'), + ); + } + + test_prefixedAlias_nonGeneric_unnamed() async { + newFile('$testPackageLibPath/a.dart', content: ''' +class A { + A(); +} +typedef TA = A; +'''); + await assertNoErrorsInCode(''' +import 'a.dart' as a; +bar() { + a.TA.new; +} +'''); + + var classElement = + findElement.importFind('package:test/a.dart').class_('A'); + assertConstructorReference( + findNode.constructorReference('a.TA.new;'), + classElement.unnamedConstructor, + classElement, + 'A Function()', + expectedPrefix: findElement.import('package:test/a.dart').prefix, + expectedTypeNameElement: + findElement.importFind('package:test/a.dart').typeAlias('TA'), + ); + } + + test_prefixedClass_nonGeneric_named() async { + newFile('$testPackageLibPath/a.dart', content: ''' +class A { + A.foo(); +} +'''); + await assertNoErrorsInCode(''' +import 'a.dart' as a; +bar() { + a.A.foo; +} +'''); + + var classElement = + findElement.importFind('package:test/a.dart').class_('A'); + assertConstructorReference( + findNode.constructorReference('a.A.foo;'), + classElement.getNamedConstructor('foo'), + classElement, + 'A Function()', + expectedPrefix: findElement.import('package:test/a.dart').prefix, + ); + } + + test_prefixedClass_nonGeneric_unnamed() async { + newFile('$testPackageLibPath/a.dart', content: ''' +class A { + A(); +} +'''); + await assertNoErrorsInCode(''' +import 'a.dart' as a; +bar() { + a.A.new; +} +'''); + + var classElement = + findElement.importFind('package:test/a.dart').class_('A'); + assertConstructorReference( + findNode.constructorReference('a.A.new;'), + classElement.unnamedConstructor, + classElement, + 'A Function()', + expectedPrefix: findElement.import('package:test/a.dart').prefix, + ); + } + test_typeAlias_generic_const() async { await assertNoErrorsInCode(''' class A {