mirror of
https://github.com/dart-lang/sdk
synced 2024-11-02 12:24:24 +00:00
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:
parent
7399e4e631
commit
f274c79333
7 changed files with 205 additions and 5 deletions
|
@ -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(
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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'.
|
||||
|
|
|
@ -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'.
|
||||
|
|
Loading…
Reference in a new issue