Skip to content

Commit 9b586a3

Browse files
srawlinscommit-bot@chromium.org
authored and
commit-bot@chromium.org
committed
analyzer: add constructor references from PrefixedIdentifier
Bug: #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>
1 parent 7c772a6 commit 9b586a3

File tree

14 files changed

+470
-24
lines changed

14 files changed

+470
-24
lines changed

pkg/analyzer/lib/error/error.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ const List<ErrorCode> errorCodeValues = [
141141
CompileTimeErrorCode.CONST_WITH_TYPE_PARAMETERS,
142142
CompileTimeErrorCode.CONST_WITH_UNDEFINED_CONSTRUCTOR,
143143
CompileTimeErrorCode.CONST_WITH_UNDEFINED_CONSTRUCTOR_DEFAULT,
144+
CompileTimeErrorCode.CONSTRUCTOR_TEAROFFS_NOT_ENABLED,
144145
CompileTimeErrorCode.CONTINUE_LABEL_ON_SWITCH,
145146
CompileTimeErrorCode.COULD_NOT_INFER,
146147
CompileTimeErrorCode.DEFAULT_LIST_CONSTRUCTOR,

pkg/analyzer/lib/src/dart/resolver/ast_rewrite.dart

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ class AstRewriter {
1919

2020
AstRewriter(this._errorReporter);
2121

22+
/// Possibly rewrites [node] as an [ExtensionOverride] or as an
23+
/// [InstanceCreationExpression].
2224
AstNode methodInvocation(Scope nameScope, MethodInvocation node) {
2325
SimpleIdentifier methodName = node.methodName;
2426
if (methodName.isSynthetic) {
@@ -140,6 +142,44 @@ class AstRewriter {
140142
return node;
141143
}
142144

145+
/// Possibly rewrites [node] as a [ConstructorReference].
146+
AstNode prefixedIdentifier(Scope nameScope, PrefixedIdentifier node) {
147+
if (node.parent is Annotation) {
148+
// An annotations which is a const constructor invocation can initially be
149+
// represented with a [PrefixedIdentifier]. Do not rewrite such nodes.
150+
return node;
151+
}
152+
if (node.parent is CommentReference) {
153+
// TODO(srawlins): This probably should be rewritten to a
154+
// [ConstructorReference] at some point.
155+
return node;
156+
}
157+
var identifier = node.identifier;
158+
if (identifier.isSynthetic) {
159+
// This isn't a constructor reference.
160+
return node;
161+
}
162+
var prefix = node.prefix;
163+
var element = nameScope.lookup(prefix.name).getter;
164+
if (element is ClassElement) {
165+
// Example:
166+
// class C { C.named(); }
167+
// C.named
168+
return _toConstructorReference(node: node, classElement: element);
169+
} else if (element is TypeAliasElement) {
170+
var aliasedType = element.aliasedType;
171+
if (aliasedType is InterfaceType) {
172+
// Example:
173+
// class C { C.named(); }
174+
// typedef X = C;
175+
// X.named
176+
return _toConstructorReference(
177+
node: node, classElement: aliasedType.element);
178+
}
179+
}
180+
return node;
181+
}
182+
143183
AstNode _instanceCreation_prefix_type_name({
144184
required MethodInvocation node,
145185
required PrefixedIdentifier typeNameIdentifier,
@@ -170,6 +210,25 @@ class AstRewriter {
170210
return instanceCreationExpression;
171211
}
172212

213+
AstNode _toConstructorReference(
214+
{required PrefixedIdentifier node, required ClassElement classElement}) {
215+
var name = node.identifier.name;
216+
var constructorElement = name == 'new'
217+
? classElement.unnamedConstructor
218+
: classElement.getNamedConstructor(name);
219+
if (constructorElement == null) {
220+
return node;
221+
}
222+
223+
var typeName = astFactory.typeName(node.prefix, null);
224+
var constructorName =
225+
astFactory.constructorName(typeName, node.period, node.identifier);
226+
var constructorReference =
227+
astFactory.constructorReference(constructorName: constructorName);
228+
NodeReplacer.replace(node, constructorReference);
229+
return constructorReference;
230+
}
231+
173232
InstanceCreationExpression _toInstanceCreation_prefix_type({
174233
required MethodInvocation node,
175234
required SimpleIdentifier prefixIdentifier,
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'package:analyzer/dart/ast/ast.dart';
6+
import 'package:analyzer/dart/element/type.dart';
7+
import 'package:analyzer/src/dart/ast/ast.dart';
8+
import 'package:analyzer/src/dart/element/member.dart';
9+
import 'package:analyzer/src/error/codes.dart';
10+
import 'package:analyzer/src/generated/resolver.dart';
11+
12+
/// A resolver for [ConstructorReference] nodes.
13+
class ConstructorReferenceResolver {
14+
/// The resolver driving this participant.
15+
final ResolverVisitor _resolver;
16+
17+
ConstructorReferenceResolver(this._resolver);
18+
19+
void resolve(ConstructorReferenceImpl node) {
20+
if (!_resolver.isConstructorTearoffsEnabled) {
21+
_resolver.errorReporter.reportErrorForNode(
22+
CompileTimeErrorCode.CONSTRUCTOR_TEAROFFS_NOT_ENABLED, node, []);
23+
}
24+
node.constructorName.accept(_resolver);
25+
_inferArgumentTypes(node);
26+
}
27+
28+
void _inferArgumentTypes(ConstructorReferenceImpl node) {
29+
var constructorName = node.constructorName;
30+
var typeName = constructorName.type;
31+
var elementToInfer = _resolver.inferenceHelper.constructorElementToInfer(
32+
constructorName: constructorName,
33+
definingLibrary: _resolver.definingLibrary,
34+
);
35+
36+
// If the constructor is generic, we'll have a ConstructorMember that
37+
// substitutes in type arguments (possibly `dynamic`) from earlier in
38+
// resolution.
39+
//
40+
// Otherwise we'll have a ConstructorElement, and we can skip inference
41+
// because there's nothing to infer in a non-generic type.
42+
if (elementToInfer != null) {
43+
// TODO(leafp): Currently, we may re-infer types here, since we
44+
// sometimes resolve multiple times. We should really check that we
45+
// have not already inferred something. However, the obvious ways to
46+
// check this don't work, since we may have been instantiated
47+
// to bounds in an earlier phase, and we *do* want to do inference
48+
// in that case.
49+
50+
// Get back to the uninstantiated generic constructor.
51+
// TODO(jmesserly): should we store this earlier in resolution?
52+
// Or look it up, instead of jumping backwards through the Member?
53+
var rawElement = elementToInfer.element;
54+
var constructorType = elementToInfer.asType;
55+
56+
var inferred = _resolver.inferenceHelper.inferTearOff(
57+
node, constructorName.name!, constructorType) as FunctionType?;
58+
59+
if (inferred != null) {
60+
typeName.type = inferred.returnType;
61+
62+
// Update the static element as well. This is used in some cases, such
63+
// as computing constant values. It is stored in two places.
64+
var constructorElement = ConstructorMember.from(
65+
rawElement,
66+
inferred.returnType as InterfaceType,
67+
);
68+
constructorName.staticElement = constructorElement;
69+
constructorName.name?.staticElement = constructorElement;
70+
node.staticType = inferred;
71+
}
72+
} else {
73+
node.staticType = node.constructorName.staticElement!.type;
74+
}
75+
}
76+
}

pkg/analyzer/lib/src/dart/resolver/exit_detector.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,9 @@ class ExitDetector extends GeneralizingAstVisitor<bool> {
128128
return thenExpression.accept(this)! && elseExpression.accept(this)!;
129129
}
130130

131+
@override
132+
bool visitConstructorReference(ConstructorReference node) => false;
133+
131134
@override
132135
bool visitContinueStatement(ContinueStatement node) {
133136
_enclosingBlockContainsContinue = true;

pkg/analyzer/lib/src/dart/resolver/invocation_inference_helper.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ class ConstructorElementToInfer {
5050
typeFormals: typeParameters,
5151
parameters: element.parameters,
5252
returnType: element.returnType,
53-
nullabilitySuffix: NullabilitySuffix.star,
53+
nullabilitySuffix: NullabilitySuffix.none,
5454
);
5555
}
5656
}

pkg/analyzer/lib/src/dart/resolver/resolution_visitor.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -859,6 +859,16 @@ class ResolutionVisitor extends RecursiveAstVisitor<void> {
859859
super.visitPartOfDirective(node);
860860
}
861861

862+
@override
863+
void visitPrefixedIdentifier(PrefixedIdentifier node) {
864+
var newNode = _astRewriter.prefixedIdentifier(_nameScope, node);
865+
if (newNode != node) {
866+
return newNode.accept(this);
867+
}
868+
869+
super.visitPrefixedIdentifier(node);
870+
}
871+
862872
@override
863873
void visitSimpleFormalParameter(covariant SimpleFormalParameterImpl node) {
864874
ParameterElementImpl element;

pkg/analyzer/lib/src/error/codes.dart

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2262,6 +2262,23 @@ class CompileTimeErrorCode extends AnalyzerErrorCode {
22622262
"Evaluation of this constant expression throws an "
22632263
"IntegerDivisionByZeroException.");
22642264

2265+
/**
2266+
* A constructor cannot be torn off without the 'constructor-tearoffs'
2267+
* language feature.
2268+
*
2269+
* There is also a [ParserError.EXPERIMENT_NOT_ENABLED] code which catches
2270+
* some cases of constructor tearoff features (like `List<int>.filled;`).
2271+
* Other constructor tearoff cases are not realized until resolution
2272+
* (like `List.filled;`).
2273+
*/
2274+
static const CompileTimeErrorCode CONSTRUCTOR_TEAROFFS_NOT_ENABLED =
2275+
CompileTimeErrorCode(
2276+
'CONSTRUCTOR_TEAROFFS_NOT_ENABLED',
2277+
"Tearing off a constructor requires the 'constructor-tearoffs' "
2278+
"language feature.",
2279+
correction: "Try updating your pubspec.yaml to set the minimum SDK "
2280+
"constraint to 2.14 or higher, and running 'pub get'.");
2281+
22652282
/**
22662283
* 16.12.2 Const: An expression of one of the forms !e, e1 && e2 or e1 || e2,
22672284
* where e, e1 and e2 are constant expressions that evaluate to a boolean

pkg/analyzer/lib/src/generated/resolver.dart

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import 'package:analyzer/src/dart/resolver/annotation_resolver.dart';
3131
import 'package:analyzer/src/dart/resolver/assignment_expression_resolver.dart';
3232
import 'package:analyzer/src/dart/resolver/binary_expression_resolver.dart';
3333
import 'package:analyzer/src/dart/resolver/body_inference_context.dart';
34+
import 'package:analyzer/src/dart/resolver/constructor_reference_resolver.dart';
3435
import 'package:analyzer/src/dart/resolver/extension_member_resolver.dart';
3536
import 'package:analyzer/src/dart/resolver/flow_analysis_visitor.dart';
3637
import 'package:analyzer/src/dart/resolver/for_resolver.dart';
@@ -186,6 +187,8 @@ class ResolverVisitor extends ScopedVisitor with ErrorDetectionHelpers {
186187

187188
late final AssignmentExpressionResolver _assignmentExpressionResolver;
188189
late final BinaryExpressionResolver _binaryExpressionResolver;
190+
late final ConstructorReferenceResolver _constructorReferenceResolver =
191+
ConstructorReferenceResolver(this);
189192
late final FunctionExpressionInvocationResolver
190193
_functionExpressionInvocationResolver;
191194
late final FunctionExpressionResolver _functionExpressionResolver;
@@ -250,7 +253,8 @@ class ResolverVisitor extends ScopedVisitor with ErrorDetectionHelpers {
250253
late final FunctionReferenceResolver _functionReferenceResolver;
251254

252255
late final InstanceCreationExpressionResolver
253-
_instanceCreationExpressionResolver;
256+
_instanceCreationExpressionResolver =
257+
InstanceCreationExpressionResolver(this);
254258

255259
/// Initialize a newly created visitor to resolve the nodes in an AST node.
256260
///
@@ -372,8 +376,6 @@ class ResolverVisitor extends ScopedVisitor with ErrorDetectionHelpers {
372376
typeAnalyzer = StaticTypeAnalyzer(this, migrationResolutionHooks);
373377
_functionReferenceResolver =
374378
FunctionReferenceResolver(this, _isNonNullableByDefault);
375-
_instanceCreationExpressionResolver =
376-
InstanceCreationExpressionResolver(this);
377379
}
378380

379381
bool get isConstructorTearoffsEnabled =>
@@ -1226,6 +1228,11 @@ class ResolverVisitor extends ScopedVisitor with ErrorDetectionHelpers {
12261228
node.accept(typeAnalyzer);
12271229
}
12281230

1231+
@override
1232+
void visitConstructorReference(covariant ConstructorReferenceImpl node) {
1233+
_constructorReferenceResolver.resolve(node);
1234+
}
1235+
12291236
@override
12301237
void visitContinueStatement(ContinueStatement node) {
12311238
//

pkg/analyzer/lib/src/test_utilities/find_node.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,10 @@ class FindNode {
9292
return _node(search, (n) => n is ConstructorName);
9393
}
9494

95+
ConstructorReference constructorReference(String search) {
96+
return _node(search, (n) => n is ConstructorReference);
97+
}
98+
9599
ContinueStatement continueStatement(String search) {
96100
return _node(search, (n) => n is ContinueStatement);
97101
}

0 commit comments

Comments
 (0)