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),
+    ]);
+  }
+}