analyzer: Support declaring and invoking unnamed constructor via 'new'
This adds support for both declaring an unnamed constructor with the
explicit name, "new", and support for invoking an unnamed constructor
as a named constructor named "new".
The parser will report EXPERIMENT_NOT_ENABLED if the experiment is not
enabled, as the parser takes care around the keyword, "new".
Tearoff support will be separate.
Bug: https://github.com/dart-lang/sdk/issues/46020
Change-Id: Iaf3af333dd22337b560aa7f4e5811a4cb38b2a7f
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/208760
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Samuel Rawlins <srawlins@google.com>
diff --git a/pkg/analysis_server/test/src/services/correction/fix/data_driven/data_driven_test.dart b/pkg/analysis_server/test/src/services/correction/fix/data_driven/data_driven_test.dart
index e983738..4022696 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/data_driven/data_driven_test.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/data_driven/data_driven_test.dart
@@ -440,7 +440,7 @@
Future<void> test_rename_removed() async {
setPackageContent('''
class C {
- C.new([C c]);
+ C.updated([C c]);
}
''');
addPackageDataFile('''
@@ -454,7 +454,7 @@
inClass: 'C'
changes:
- kind: 'rename'
- newName: 'new'
+ newName: 'updated'
''');
await resolveTestCode('''
import '$importUri';
@@ -462,7 +462,7 @@
''');
await assertHasFix('''
import '$importUri';
-C c() => C.new(C.new());
+C c() => C.updated(C.updated());
''');
}
}
diff --git a/pkg/analyzer/lib/src/dart/element/element.dart b/pkg/analyzer/lib/src/dart/element/element.dart
index ae8a8fc..549e1bc 100644
--- a/pkg/analyzer/lib/src/dart/element/element.dart
+++ b/pkg/analyzer/lib/src/dart/element/element.dart
@@ -948,6 +948,10 @@
static ConstructorElement? getNamedConstructorFromList(
String name, List<ConstructorElement> constructors) {
+ if (name == 'new') {
+ // An unnamed constructor declared with `C.new(` is modeled as unnamed.
+ name = '';
+ }
for (ConstructorElement element in constructors) {
if (element.name == name) {
return element;
diff --git a/pkg/analyzer/lib/src/dart/resolver/method_invocation_resolver.dart b/pkg/analyzer/lib/src/dart/resolver/method_invocation_resolver.dart
index 9de1a34..5c7a513 100644
--- a/pkg/analyzer/lib/src/dart/resolver/method_invocation_resolver.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/method_invocation_resolver.dart
@@ -282,19 +282,6 @@
);
}
- void _reportUndefinedMethod(
- MethodInvocationImpl node,
- String name,
- ClassElement typeReference,
- List<WhyNotPromotedGetter> whyNotPromotedList) {
- _setDynamicResolution(node, whyNotPromotedList: whyNotPromotedList);
- _resolver.errorReporter.reportErrorForNode(
- CompileTimeErrorCode.UNDEFINED_METHOD,
- node.methodName,
- [name, typeReference.displayName],
- );
- }
-
void _reportUseOfVoidType(MethodInvocationImpl node, AstNode errorNode,
List<WhyNotPromotedGetter> whyNotPromotedList) {
_setDynamicResolution(node, whyNotPromotedList: whyNotPromotedList);
@@ -779,7 +766,26 @@
return;
}
- _reportUndefinedMethod(node, name, receiver, whyNotPromotedList);
+ _setDynamicResolution(node, whyNotPromotedList: whyNotPromotedList);
+ if (nameNode.name == 'new') {
+ // Attempting to invoke the unnamed constructor via `C.new(`.
+ if (_resolver.isConstructorTearoffsEnabled) {
+ _resolver.errorReporter.reportErrorForNode(
+ CompileTimeErrorCode.NEW_WITH_UNDEFINED_CONSTRUCTOR_DEFAULT,
+ nameNode,
+ [receiver.displayName],
+ );
+ } else {
+ // [ParserErrorCode.EXPERIMENT_NOT_ENABLED] is reported by the parser.
+ // Do not report extra errors.
+ }
+ } else {
+ _resolver.errorReporter.reportErrorForNode(
+ CompileTimeErrorCode.UNDEFINED_METHOD,
+ node.methodName,
+ [name, receiver.displayName],
+ );
+ }
}
/// If the given [type] is a type parameter, replace with its bound.
diff --git a/pkg/analyzer/lib/src/generated/resolver.dart b/pkg/analyzer/lib/src/generated/resolver.dart
index 813a2f9..6a09580 100644
--- a/pkg/analyzer/lib/src/generated/resolver.dart
+++ b/pkg/analyzer/lib/src/generated/resolver.dart
@@ -376,6 +376,9 @@
InstanceCreationExpressionResolver(this);
}
+ bool get isConstructorTearoffsEnabled =>
+ _featureSet.isEnabled(Feature.constructor_tearoffs);
+
/// Return the object providing promoted or declared types of variables.
LocalVariableTypeProvider get localVariableTypeProvider {
if (flowAnalysis != null) {
diff --git a/pkg/analyzer/lib/src/generated/testing/element_factory.dart b/pkg/analyzer/lib/src/generated/testing/element_factory.dart
index 6045950..ed9b4f8 100644
--- a/pkg/analyzer/lib/src/generated/testing/element_factory.dart
+++ b/pkg/analyzer/lib/src/generated/testing/element_factory.dart
@@ -106,9 +106,11 @@
static ConstructorElementImpl constructorElement(
ClassElement definingClass, String? name, bool isConst,
[List<DartType> argumentTypes = const []]) {
- ConstructorElementImpl constructor = name == null
- ? ConstructorElementImpl("", -1)
- : ConstructorElementImpl(name, 0);
+ var offset = name == null ? -1 : 0;
+ // An unnamed constructor declared with `C.new(` is modeled as unnamed.
+ var constructor = name == null || name == 'new'
+ ? ConstructorElementImpl('', offset)
+ : ConstructorElementImpl(name, offset);
if (name != null) {
if (name.isEmpty) {
constructor.nameEnd = definingClass.name.length;
diff --git a/pkg/analyzer/lib/src/summary2/element_builder.dart b/pkg/analyzer/lib/src/summary2/element_builder.dart
index 7bb241f..b5b366b 100644
--- a/pkg/analyzer/lib/src/summary2/element_builder.dart
+++ b/pkg/analyzer/lib/src/summary2/element_builder.dart
@@ -172,6 +172,10 @@
) {
var nameNode = node.name ?? node.returnType;
var name = node.name?.name ?? '';
+ if (name == 'new') {
+ // An unnamed constructor declared with `C.new(` is modeled as unnamed.
+ name = '';
+ }
var nameOffset = nameNode.offset;
var element = ConstructorElementImpl(name, nameOffset);
diff --git a/pkg/analyzer/test/src/dart/resolution/instance_creation_test.dart b/pkg/analyzer/test/src/dart/resolution/instance_creation_test.dart
index 206f003..4d27277 100644
--- a/pkg/analyzer/test/src/dart/resolution/instance_creation_test.dart
+++ b/pkg/analyzer/test/src/dart/resolution/instance_creation_test.dart
@@ -2,6 +2,7 @@
// 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/dart/error/syntactic_errors.dart';
import 'package:analyzer/src/error/codes.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
@@ -10,6 +11,7 @@
main() {
defineReflectiveSuite(() {
defineReflectiveTests(InstanceCreationTest);
+ defineReflectiveTests(InstanceCreationWithoutConstructorTearoffsTest);
});
}
@@ -426,4 +428,75 @@
expectedSubstitution: {'T': 'String'},
);
}
+
+ test_unnamed_declaredNew() async {
+ await assertNoErrorsInCode('''
+class A {
+ A.new(int a);
+}
+
+void f() {
+ A(0);
+}
+
+''');
+
+ var creation = findNode.instanceCreation('A(0)');
+ assertInstanceCreation(creation, findElement.class_('A'), 'A');
+ }
+
+ test_unnamedViaNew_declaredNew() async {
+ await assertNoErrorsInCode('''
+class A {
+ A.new(int a);
+}
+
+void f() {
+ A.new(0);
+}
+
+''');
+
+ var creation = findNode.instanceCreation('A.new(0)');
+ assertInstanceCreation(creation, findElement.class_('A'), 'A');
+ }
+
+ test_unnamedViaNew_declaredUnnamed() async {
+ await assertNoErrorsInCode('''
+class A {
+ A(int a);
+}
+
+void f() {
+ A.new(0);
+}
+
+''');
+
+ var creation = findNode.instanceCreation('A.new(0)');
+ assertInstanceCreation(creation, findElement.class_('A'), 'A');
+ }
+}
+
+@reflectiveTest
+class InstanceCreationWithoutConstructorTearoffsTest
+ extends PubPackageResolutionTest with WithoutConstructorTearoffsMixin {
+ test_unnamedViaNew() async {
+ await assertErrorsInCode('''
+class A {
+ A(int a);
+}
+
+void f() {
+ A.new(0);
+}
+
+''', [
+ error(ParserErrorCode.EXPERIMENT_NOT_ENABLED, 40, 3),
+ ]);
+
+ // Resolution should continue even though the experiment is not enabled.
+ var creation = findNode.instanceCreation('A.new(0)');
+ assertInstanceCreation(creation, findElement.class_('A'), 'A');
+ }
}
diff --git a/pkg/analyzer/test/src/diagnostics/new_with_undefined_constructor_test.dart b/pkg/analyzer/test/src/diagnostics/new_with_undefined_constructor_test.dart
index 602e5ff..4ebf06c 100644
--- a/pkg/analyzer/test/src/diagnostics/new_with_undefined_constructor_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/new_with_undefined_constructor_test.dart
@@ -2,6 +2,7 @@
// 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/dart/error/syntactic_errors.dart';
import 'package:analyzer/src/error/codes.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
@@ -10,11 +11,16 @@
main() {
defineReflectiveSuite(() {
defineReflectiveTests(NewWithUndefinedConstructorTest);
+ defineReflectiveTests(
+ NewWithUndefinedConstructorWithoutConstructorTearoffsTest);
});
}
@reflectiveTest
-class NewWithUndefinedConstructorTest extends PubPackageResolutionTest {
+class NewWithUndefinedConstructorTest extends PubPackageResolutionTest
+ with NewWithUndefinedConstructorTestCases {}
+
+mixin NewWithUndefinedConstructorTestCases on PubPackageResolutionTest {
test_default() async {
await assertErrorsInCode('''
class A {
@@ -28,6 +34,43 @@
]);
}
+ test_default_noKeyword() async {
+ await assertErrorsInCode('''
+class A {
+ A.name() {}
+}
+f() {
+ A();
+}
+''', [
+ error(CompileTimeErrorCode.NEW_WITH_UNDEFINED_CONSTRUCTOR_DEFAULT, 34, 1),
+ ]);
+ }
+
+ test_default_unnamedViaNew() async {
+ await assertErrorsInCode('''
+class A {
+ A.name() {}
+}
+f() {
+ A.new();
+}
+''', [
+ error(CompileTimeErrorCode.NEW_WITH_UNDEFINED_CONSTRUCTOR_DEFAULT, 36, 3),
+ ]);
+ }
+
+ test_defaultViaNew() async {
+ await assertNoErrorsInCode('''
+class A {
+ A.new() {}
+}
+f() {
+ A();
+}
+''');
+ }
+
test_defined_named() async {
await assertNoErrorsInCode(r'''
class A {
@@ -111,3 +154,33 @@
]);
}
}
+
+@reflectiveTest
+class NewWithUndefinedConstructorWithoutConstructorTearoffsTest
+ extends PubPackageResolutionTest with WithoutConstructorTearoffsMixin {
+ test_defaultViaNew() async {
+ await assertErrorsInCode('''
+class A {
+ A.new() {}
+}
+f() {
+ A();
+}
+''', [
+ error(ParserErrorCode.EXPERIMENT_NOT_ENABLED, 14, 3),
+ ]);
+ }
+
+ test_unnamedViaNew() async {
+ await assertErrorsInCode('''
+class A {
+ A.named() {}
+}
+f() {
+ A.new();
+}
+''', [
+ error(ParserErrorCode.EXPERIMENT_NOT_ENABLED, 37, 3),
+ ]);
+ }
+}