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>
This commit is contained in:
Sam Rawlins 2021-08-03 03:06:41 +00:00 committed by commit-bot@chromium.org
parent cf3387efd3
commit 9ec112a509
8 changed files with 186 additions and 21 deletions

View file

@ -440,7 +440,7 @@ C c() => C.new(C.new());
Future<void> test_rename_removed() async {
setPackageContent('''
class C {
C.new([C c]);
C.updated([C c]);
}
''');
addPackageDataFile('''
@ -454,7 +454,7 @@ transforms:
inClass: 'C'
changes:
- kind: 'rename'
newName: 'new'
newName: 'updated'
''');
await resolveTestCode('''
import '$importUri';
@ -462,7 +462,7 @@ C c() => C(C());
''');
await assertHasFix('''
import '$importUri';
C c() => C.new(C.new());
C c() => C.updated(C.updated());
''');
}
}

View file

@ -948,6 +948,10 @@ class ClassElementImpl extends AbstractClassElementImpl
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;

View file

@ -282,19 +282,6 @@ class MethodInvocationResolver {
);
}
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 @@ class MethodInvocationResolver {
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.

View file

@ -376,6 +376,9 @@ class ResolverVisitor extends ScopedVisitor with ErrorDetectionHelpers {
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) {

View file

@ -106,9 +106,11 @@ class ElementFactory {
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;

View file

@ -172,6 +172,10 @@ class ElementBuilder extends ThrowingAstVisitor<void> {
) {
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);

View file

@ -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 @@ import 'context_collection_resolution.dart';
main() {
defineReflectiveSuite(() {
defineReflectiveTests(InstanceCreationTest);
defineReflectiveTests(InstanceCreationWithoutConstructorTearoffsTest);
});
}
@ -426,4 +428,75 @@ void f() {
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');
}
}

View file

@ -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 @@ import '../dart/resolution/context_collection_resolution.dart';
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 @@ f() {
]);
}
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 @@ void f() {
]);
}
}
@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),
]);
}
}