analyzer: add constructor references from PrefixedIdentifier

Bug: https://github.com/dart-lang/sdk/issues/46020
Change-Id: Ifafea6edb6c9ce7fdf1c1f092ff278738c273bb0
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/209300
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Samuel Rawlins <srawlins@google.com>
This commit is contained in:
Sam Rawlins 2021-08-09 15:03:48 +00:00 committed by commit-bot@chromium.org
parent 7c772a653b
commit 9b586a3da6
14 changed files with 470 additions and 24 deletions

View file

@ -141,6 +141,7 @@ const List<ErrorCode> errorCodeValues = [
CompileTimeErrorCode.CONST_WITH_TYPE_PARAMETERS,
CompileTimeErrorCode.CONST_WITH_UNDEFINED_CONSTRUCTOR,
CompileTimeErrorCode.CONST_WITH_UNDEFINED_CONSTRUCTOR_DEFAULT,
CompileTimeErrorCode.CONSTRUCTOR_TEAROFFS_NOT_ENABLED,
CompileTimeErrorCode.CONTINUE_LABEL_ON_SWITCH,
CompileTimeErrorCode.COULD_NOT_INFER,
CompileTimeErrorCode.DEFAULT_LIST_CONSTRUCTOR,

View file

@ -19,6 +19,8 @@ class AstRewriter {
AstRewriter(this._errorReporter);
/// Possibly rewrites [node] as an [ExtensionOverride] or as an
/// [InstanceCreationExpression].
AstNode methodInvocation(Scope nameScope, MethodInvocation node) {
SimpleIdentifier methodName = node.methodName;
if (methodName.isSynthetic) {
@ -140,6 +142,44 @@ class AstRewriter {
return node;
}
/// Possibly rewrites [node] 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
// represented with a [PrefixedIdentifier]. Do not rewrite such nodes.
return node;
}
if (node.parent is CommentReference) {
// TODO(srawlins): This probably should be rewritten to a
// [ConstructorReference] at some point.
return node;
}
var identifier = node.identifier;
if (identifier.isSynthetic) {
// This isn't a constructor reference.
return node;
}
var prefix = node.prefix;
var element = nameScope.lookup(prefix.name).getter;
if (element is ClassElement) {
// Example:
// class C { C.named(); }
// C.named
return _toConstructorReference(node: node, classElement: element);
} else if (element is TypeAliasElement) {
var aliasedType = element.aliasedType;
if (aliasedType is InterfaceType) {
// Example:
// class C { C.named(); }
// typedef X = C;
// X.named
return _toConstructorReference(
node: node, classElement: aliasedType.element);
}
}
return node;
}
AstNode _instanceCreation_prefix_type_name({
required MethodInvocation node,
required PrefixedIdentifier typeNameIdentifier,
@ -170,6 +210,25 @@ class AstRewriter {
return instanceCreationExpression;
}
AstNode _toConstructorReference(
{required PrefixedIdentifier node, required ClassElement classElement}) {
var name = node.identifier.name;
var constructorElement = name == 'new'
? classElement.unnamedConstructor
: classElement.getNamedConstructor(name);
if (constructorElement == null) {
return node;
}
var typeName = astFactory.typeName(node.prefix, null);
var constructorName =
astFactory.constructorName(typeName, node.period, node.identifier);
var constructorReference =
astFactory.constructorReference(constructorName: constructorName);
NodeReplacer.replace(node, constructorReference);
return constructorReference;
}
InstanceCreationExpression _toInstanceCreation_prefix_type({
required MethodInvocation node,
required SimpleIdentifier prefixIdentifier,

View file

@ -0,0 +1,76 @@
// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/src/dart/ast/ast.dart';
import 'package:analyzer/src/dart/element/member.dart';
import 'package:analyzer/src/error/codes.dart';
import 'package:analyzer/src/generated/resolver.dart';
/// A resolver for [ConstructorReference] nodes.
class ConstructorReferenceResolver {
/// The resolver driving this participant.
final ResolverVisitor _resolver;
ConstructorReferenceResolver(this._resolver);
void resolve(ConstructorReferenceImpl node) {
if (!_resolver.isConstructorTearoffsEnabled) {
_resolver.errorReporter.reportErrorForNode(
CompileTimeErrorCode.CONSTRUCTOR_TEAROFFS_NOT_ENABLED, node, []);
}
node.constructorName.accept(_resolver);
_inferArgumentTypes(node);
}
void _inferArgumentTypes(ConstructorReferenceImpl node) {
var constructorName = node.constructorName;
var typeName = constructorName.type;
var elementToInfer = _resolver.inferenceHelper.constructorElementToInfer(
constructorName: constructorName,
definingLibrary: _resolver.definingLibrary,
);
// If the constructor is generic, we'll have a ConstructorMember that
// substitutes in type arguments (possibly `dynamic`) from earlier in
// resolution.
//
// Otherwise we'll have a ConstructorElement, and we can skip inference
// because there's nothing to infer in a non-generic type.
if (elementToInfer != null) {
// TODO(leafp): Currently, we may re-infer types here, since we
// sometimes resolve multiple times. We should really check that we
// have not already inferred something. However, the obvious ways to
// check this don't work, since we may have been instantiated
// to bounds in an earlier phase, and we *do* want to do inference
// in that case.
// Get back to the uninstantiated generic constructor.
// TODO(jmesserly): should we store this earlier in resolution?
// Or look it up, instead of jumping backwards through the Member?
var rawElement = elementToInfer.element;
var constructorType = elementToInfer.asType;
var inferred = _resolver.inferenceHelper.inferTearOff(
node, constructorName.name!, constructorType) as FunctionType?;
if (inferred != null) {
typeName.type = inferred.returnType;
// Update the static element as well. This is used in some cases, such
// as computing constant values. It is stored in two places.
var constructorElement = ConstructorMember.from(
rawElement,
inferred.returnType as InterfaceType,
);
constructorName.staticElement = constructorElement;
constructorName.name?.staticElement = constructorElement;
node.staticType = inferred;
}
} else {
node.staticType = node.constructorName.staticElement!.type;
}
}
}

View file

@ -128,6 +128,9 @@ class ExitDetector extends GeneralizingAstVisitor<bool> {
return thenExpression.accept(this)! && elseExpression.accept(this)!;
}
@override
bool visitConstructorReference(ConstructorReference node) => false;
@override
bool visitContinueStatement(ContinueStatement node) {
_enclosingBlockContainsContinue = true;

View file

@ -50,7 +50,7 @@ class ConstructorElementToInfer {
typeFormals: typeParameters,
parameters: element.parameters,
returnType: element.returnType,
nullabilitySuffix: NullabilitySuffix.star,
nullabilitySuffix: NullabilitySuffix.none,
);
}
}

View file

@ -859,6 +859,16 @@ class ResolutionVisitor extends RecursiveAstVisitor<void> {
super.visitPartOfDirective(node);
}
@override
void visitPrefixedIdentifier(PrefixedIdentifier node) {
var newNode = _astRewriter.prefixedIdentifier(_nameScope, node);
if (newNode != node) {
return newNode.accept(this);
}
super.visitPrefixedIdentifier(node);
}
@override
void visitSimpleFormalParameter(covariant SimpleFormalParameterImpl node) {
ParameterElementImpl element;

View file

@ -2262,6 +2262,23 @@ class CompileTimeErrorCode extends AnalyzerErrorCode {
"Evaluation of this constant expression throws an "
"IntegerDivisionByZeroException.");
/**
* A constructor cannot be torn off without the 'constructor-tearoffs'
* language feature.
*
* There is also a [ParserError.EXPERIMENT_NOT_ENABLED] code which catches
* some cases of constructor tearoff features (like `List<int>.filled;`).
* Other constructor tearoff cases are not realized until resolution
* (like `List.filled;`).
*/
static const CompileTimeErrorCode CONSTRUCTOR_TEAROFFS_NOT_ENABLED =
CompileTimeErrorCode(
'CONSTRUCTOR_TEAROFFS_NOT_ENABLED',
"Tearing off a constructor requires the 'constructor-tearoffs' "
"language feature.",
correction: "Try updating your pubspec.yaml to set the minimum SDK "
"constraint to 2.14 or higher, and running 'pub get'.");
/**
* 16.12.2 Const: An expression of one of the forms !e, e1 && e2 or e1 || e2,
* where e, e1 and e2 are constant expressions that evaluate to a boolean

View file

@ -31,6 +31,7 @@ import 'package:analyzer/src/dart/resolver/annotation_resolver.dart';
import 'package:analyzer/src/dart/resolver/assignment_expression_resolver.dart';
import 'package:analyzer/src/dart/resolver/binary_expression_resolver.dart';
import 'package:analyzer/src/dart/resolver/body_inference_context.dart';
import 'package:analyzer/src/dart/resolver/constructor_reference_resolver.dart';
import 'package:analyzer/src/dart/resolver/extension_member_resolver.dart';
import 'package:analyzer/src/dart/resolver/flow_analysis_visitor.dart';
import 'package:analyzer/src/dart/resolver/for_resolver.dart';
@ -186,6 +187,8 @@ class ResolverVisitor extends ScopedVisitor with ErrorDetectionHelpers {
late final AssignmentExpressionResolver _assignmentExpressionResolver;
late final BinaryExpressionResolver _binaryExpressionResolver;
late final ConstructorReferenceResolver _constructorReferenceResolver =
ConstructorReferenceResolver(this);
late final FunctionExpressionInvocationResolver
_functionExpressionInvocationResolver;
late final FunctionExpressionResolver _functionExpressionResolver;
@ -250,7 +253,8 @@ class ResolverVisitor extends ScopedVisitor with ErrorDetectionHelpers {
late final FunctionReferenceResolver _functionReferenceResolver;
late final InstanceCreationExpressionResolver
_instanceCreationExpressionResolver;
_instanceCreationExpressionResolver =
InstanceCreationExpressionResolver(this);
/// Initialize a newly created visitor to resolve the nodes in an AST node.
///
@ -372,8 +376,6 @@ class ResolverVisitor extends ScopedVisitor with ErrorDetectionHelpers {
typeAnalyzer = StaticTypeAnalyzer(this, migrationResolutionHooks);
_functionReferenceResolver =
FunctionReferenceResolver(this, _isNonNullableByDefault);
_instanceCreationExpressionResolver =
InstanceCreationExpressionResolver(this);
}
bool get isConstructorTearoffsEnabled =>
@ -1226,6 +1228,11 @@ class ResolverVisitor extends ScopedVisitor with ErrorDetectionHelpers {
node.accept(typeAnalyzer);
}
@override
void visitConstructorReference(covariant ConstructorReferenceImpl node) {
_constructorReferenceResolver.resolve(node);
}
@override
void visitContinueStatement(ContinueStatement node) {
//

View file

@ -92,6 +92,10 @@ class FindNode {
return _node(search, (n) => n is ConstructorName);
}
ConstructorReference constructorReference(String search) {
return _node(search, (n) => n is ConstructorReference);
}
ContinueStatement continueStatement(String search) {
return _node(search, (n) => n is ContinueStatement);
}

View file

@ -0,0 +1,227 @@
// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:analyzer/src/error/codes.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import 'context_collection_resolution.dart';
main() {
defineReflectiveSuite(() {
defineReflectiveTests(ConstructorReferenceResolutionTest);
defineReflectiveTests(
ConstructorReferenceResolutionWithoutConstructorTearoffsTest);
});
}
@reflectiveTest
class ConstructorReferenceResolutionTest extends PubPackageResolutionTest {
test_class_generic_inferFromContext_badTypeArgument() async {
await assertErrorsInCode('''
class A<T extends num> {
A.foo();
}
A<String> Function() bar() {
return A.foo;
}
''', [
error(CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, 41, 6),
]);
var classElement = findElement.class_('A');
var constructorElement = classElement.getNamedConstructor('foo')!;
assertConstructorReference(
findNode.constructorReference('A.foo;'),
elementMatcher(constructorElement, substitution: {'T': 'Never'}),
classElement,
'A<Never> Function()',
expectedTypeNameType: 'A<Never>',
);
}
test_class_generic_named_uninstantiated() async {
await assertNoErrorsInCode('''
class A<T> {
A.foo();
}
void bar() {
A.foo;
}
''');
var classElement = findElement.class_('A');
var constructorElement = classElement.getNamedConstructor('foo')!;
assertConstructorReference(
findNode.constructorReference('A.foo;'),
elementMatcher(constructorElement, substitution: {'T': 'T'}),
classElement,
'A<T> Function<T>()',
expectedTypeNameType: 'A<T>',
);
}
test_class_generic_named_uninstantiated_bound() async {
await assertNoErrorsInCode('''
class A<T extends num> {
A.foo();
}
void bar() {
A.foo;
}
''');
var classElement = findElement.class_('A');
var constructorElement = classElement.getNamedConstructor('foo')!;
assertConstructorReference(
findNode.constructorReference('A.foo;'),
elementMatcher(constructorElement, substitution: {'T': 'T'}),
classElement,
'A<T> Function<T extends num>()',
expectedTypeNameType: 'A<T>',
);
}
test_class_nonGeneric_named() async {
await assertNoErrorsInCode('''
class A {
A.foo();
}
void bar() {
A.foo;
}
''');
var classElement = findElement.class_('A');
assertConstructorReference(
findNode.constructorReference('A.foo;'),
classElement.getNamedConstructor('foo')!,
classElement,
'A Function()',
expectedTypeNameType: 'A',
);
}
test_class_nonGeneric_unnamed() async {
await assertNoErrorsInCode('''
class A {
A();
}
bar() {
A.new;
}
''');
var classElement = findElement.class_('A');
assertConstructorReference(
findNode.constructorReference('A.new;'),
classElement.unnamedConstructor,
classElement,
'A Function()',
expectedTypeNameType: 'A',
);
}
test_classs_generic_named_inferTypeFromContext() async {
await assertNoErrorsInCode('''
class A<T> {
A.foo();
}
A<int> Function() bar() {
return A.foo;
}
''');
var classElement = findElement.class_('A');
var constructorElement = classElement.getNamedConstructor('foo')!;
assertConstructorReference(
findNode.constructorReference('A.foo;'),
elementMatcher(constructorElement, substitution: {'T': 'int'}),
classElement,
'A<int> Function()',
expectedTypeNameType: 'A<int>',
);
}
test_typeAlias_generic_named_uninstantiated() async {
await assertNoErrorsInCode('''
class A<T, U> {
A.foo();
}
typedef TA<U> = A<String, U>;
bar() {
TA.foo;
}
''');
var classElement = findElement.class_('A');
var constructorElement = classElement.getNamedConstructor('foo')!;
assertConstructorReference(
findNode.constructorReference('TA.foo;'),
elementMatcher(constructorElement,
substitution: {'T': 'String', 'U': 'U'}),
findElement.class_('A'),
'A<String, U> Function<U>()',
expectedTypeNameType: 'A<String, U>',
expectedTypeNameElement: findElement.typeAlias('TA'),
);
}
test_typeAlias_instantiated_named() async {
await assertNoErrorsInCode('''
class A<T> {
A.foo();
}
typedef TA = A<int>;
bar() {
TA.foo;
}
''');
var classElement = findElement.class_('A');
var constructorElement = classElement.getNamedConstructor('foo')!;
assertConstructorReference(
findNode.constructorReference('TA.foo;'),
elementMatcher(constructorElement, substitution: {'T': 'int'}),
classElement,
'A<int> Function()',
expectedTypeNameType: 'A<int>',
expectedTypeNameElement: findElement.typeAlias('TA'),
);
}
}
@reflectiveTest
class ConstructorReferenceResolutionWithoutConstructorTearoffsTest
extends PubPackageResolutionTest with WithoutConstructorTearoffsMixin {
test_constructorTearoff() async {
await assertErrorsInCode('''
class A {
A.foo();
}
void bar() {
A.foo;
}
''', [
error(CompileTimeErrorCode.CONSTRUCTOR_TEAROFFS_NOT_ENABLED, 39, 5),
]);
var classElement = findElement.class_('A');
assertConstructorReference(
findNode.constructorReference('A.foo;'),
classElement.getNamedConstructor('foo')!,
classElement,
'A Function()',
expectedTypeNameType: 'A',
);
}
}

View file

@ -161,6 +161,33 @@ mixin ResolutionTest implements ResourceProviderMixin {
}
}
void assertConstructorReference(
ConstructorReference node,
Object? expectedConstructorElement,
ClassElement expectedClassElement,
String expectedType, {
required String expectedTypeNameType,
PrefixElement? expectedPrefix,
Element? expectedTypeNameElement,
}) {
var actualConstructorElement = getNodeElement(node) as ConstructorElement?;
var actualConstructorName = node.constructorName.name;
if (actualConstructorName != null) {
assertConstructorElement(
actualConstructorName.staticElement as ConstructorElement?,
actualConstructorElement,
);
}
assertElement(node, expectedConstructorElement);
assertType(node, expectedType);
var typeName = node.constructorName.type;
expectedTypeNameElement ??= expectedClassElement;
assertTypeName(typeName, expectedTypeNameElement, expectedTypeNameType,
expectedPrefix: expectedPrefix);
}
void assertConstructors(ClassElement class_, List<String> expected) {
expect(
class_.constructors.map((c) {
@ -396,6 +423,9 @@ mixin ResolutionTest implements ResourceProviderMixin {
}
}
/// TODO(srawlins): Refactor to accept an `Object? expectedConstructor` which
/// can accept `elementMatcher` for generics, and simplify, similar to
/// [assertConstructorReference].
void assertInstanceCreation(
InstanceCreationExpression creation,
ClassElement expectedClassElement,
@ -406,22 +436,8 @@ mixin ResolutionTest implements ResourceProviderMixin {
PrefixElement? expectedPrefix,
Element? expectedTypeNameElement,
}) {
String expectedClassName = expectedClassElement.name;
ConstructorElement? expectedConstructorElement;
if (constructorName != null) {
expectedConstructorElement =
expectedClassElement.getNamedConstructor(constructorName);
if (expectedConstructorElement == null) {
fail("No constructor '$constructorName' in class"
" '$expectedClassName'.");
}
} else {
expectedConstructorElement = expectedClassElement.unnamedConstructor;
if (expectedConstructorElement == null) {
fail("No unnamed constructor in class '$expectedClassName'.");
}
}
var expectedConstructorElement =
_getConstructorElement(expectedClassElement, constructorName);
var actualConstructorElement =
getNodeElement(creation) as ConstructorElement?;
@ -866,6 +882,8 @@ mixin ResolutionTest implements ResourceProviderMixin {
return node.staticElement;
} else if (node is BinaryExpression) {
return node.staticElement;
} else if (node is ConstructorReference) {
return node.constructorName.staticElement;
} else if (node is Declaration) {
return node.declaredElement;
} else if (node is ExtensionOverride) {
@ -978,6 +996,16 @@ mixin ResolutionTest implements ResourceProviderMixin {
fail('Expected SimpleIdentifier: (${node.runtimeType}) $node');
}
ConstructorElement _getConstructorElement(
ClassElement classElement, String? constructorName) {
var constructorElement = constructorName == null
? classElement.unnamedConstructor
: classElement.getNamedConstructor(constructorName);
return constructorElement ??
fail("No constructor '${constructorName ?? '<unnamed>'}' in class "
"'${classElement.name}'.");
}
static String _extractReturnType(String invokeType) {
int functionIndex = invokeType.indexOf(' Function');
expect(functionIndex, isNonNegative);

View file

@ -12,6 +12,7 @@ import 'class_alias_test.dart' as class_alias;
import 'class_test.dart' as class_resolution;
import 'comment_test.dart' as comment;
import 'constant_test.dart' as constant;
import 'constructor_reference_test.dart' as constructor_reference;
import 'constructor_test.dart' as constructor;
import 'enum_test.dart' as enum_resolution;
import 'export_test.dart' as export_;
@ -76,6 +77,7 @@ main() {
comment.main();
constant.main();
constructor.main();
constructor_reference.main();
enum_resolution.main();
export_.main();
extension_method.main();

View file

@ -87,8 +87,11 @@ main() {
Foo.bar();
Foo.bar.baz();
// ^^^
// [analyzer] COMPILE_TIME_ERROR.UNDEFINED_GETTER
// [cfe] Getter not found: 'bar'.
//^^^^^^^
// [analyzer] COMPILE_TIME_ERROR.CONSTRUCTOR_TEAROFFS_NOT_ENABLED
// ^^^
// [analyzer] COMPILE_TIME_ERROR.UNDEFINED_METHOD
Foo<int>();
Foo<int>.bar();
Foo<int>.bar.baz();
@ -111,6 +114,9 @@ main() {
// [cfe] Method not found: 'Foo.bar.baz'.
Foo.bar.baz<int>();
// ^^^
// [analyzer] COMPILE_TIME_ERROR.UNDEFINED_GETTER
// [cfe] Getter not found: 'bar'.
//^^^^^^^
// [analyzer] COMPILE_TIME_ERROR.CONSTRUCTOR_TEAROFFS_NOT_ENABLED
// ^^^
// [analyzer] COMPILE_TIME_ERROR.UNDEFINED_METHOD
}

View file

@ -89,8 +89,11 @@ main() {
Foo.bar();
Foo.bar.baz();
// ^^^
// [analyzer] COMPILE_TIME_ERROR.UNDEFINED_GETTER
// [cfe] Getter not found: 'bar'.
//^^^^^^^
// [analyzer] COMPILE_TIME_ERROR.CONSTRUCTOR_TEAROFFS_NOT_ENABLED
// ^^^
// [analyzer] COMPILE_TIME_ERROR.UNDEFINED_METHOD
Foo<int>();
Foo<int>.bar();
Foo<int>.bar.baz();
@ -113,6 +116,9 @@ main() {
// [cfe] Method not found: 'Foo.bar.baz'.
Foo.bar.baz<int>();
// ^^^
// [analyzer] COMPILE_TIME_ERROR.UNDEFINED_GETTER
// [cfe] Getter not found: 'bar'.
//^^^^^^^
// [analyzer] COMPILE_TIME_ERROR.CONSTRUCTOR_TEAROFFS_NOT_ENABLED
// ^^^
// [analyzer] COMPILE_TIME_ERROR.UNDEFINED_METHOD
}