Update parser to allow unnamed constructor reference in comment reference
Code like `/// See also [List.new].` will be accepted as a valid comment
reference.
Test in front_end, analyzer, analysis_server.
Remove TODOs in analyzer for reporting a reference to an element whose
library is not the library in which the comment is found.
Bug: https://github.com/dart-lang/sdk/issues/47446
Change-Id: Ifa19278f92ac003082b62b121c66f0559c0152e8
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/216583
Commit-Queue: Samuel Rawlins <srawlins@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Jens Johansen <jensj@google.com>
diff --git a/pkg/_fe_analyzer_shared/lib/src/parser/parser_impl.dart b/pkg/_fe_analyzer_shared/lib/src/parser/parser_impl.dart
index 2d4f96e..896bede 100644
--- a/pkg/_fe_analyzer_shared/lib/src/parser/parser_impl.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/parser/parser_impl.dart
@@ -8257,6 +8257,16 @@
if (token.isIdentifier && optional('.', token.next!)) {
prefix = token;
period = token.next!;
+ Token identifier = period.next!;
+ if (identifier.kind == KEYWORD_TOKEN && optional('new', identifier)) {
+ // Treat `new` after `.` is as an identifier so that it can represent an
+ // unnamed constructor. This support is separate from the
+ // constructor-tearoffs feature.
+ rewriter.replaceTokenFollowing(
+ period,
+ new StringToken(TokenType.IDENTIFIER, identifier.lexeme,
+ identifier.charOffset));
+ }
token = period.next!;
}
if (token.isEof) {
diff --git a/pkg/analysis_server/test/search/element_references_test.dart b/pkg/analysis_server/test/search/element_references_test.dart
index 55ab879..326a8eb 100644
--- a/pkg/analysis_server/test/search/element_references_test.dart
+++ b/pkg/analysis_server/test/search/element_references_test.dart
@@ -95,30 +95,32 @@
Future<void> test_constructor_unnamed() async {
addTestFile('''
/// [new A] 1
+/// [A.new] 2
class A {
A() {}
- A.other() : this(); // 2
+ A.other() : this(); // 3
}
class B extends A {
- B() : super(); // 3
- factory B.other() = A; // 4
+ B() : super(); // 4
+ factory B.other() = A; // 5
}
void f() {
- A(); // 5
- A.new; // 6
+ A(); // 6
+ A.new; // 7
}
''');
await findElementReferences('A() {}', false);
expect(searchElement!.kind, ElementKind.CONSTRUCTOR);
- expect(results, hasLength(6));
+ expect(results, hasLength(7));
assertHasResult(SearchResultKind.REFERENCE, '] 1', 0);
- assertHasResult(SearchResultKind.INVOCATION, '(); // 2', 0);
+ assertHasResult(SearchResultKind.REFERENCE, '.new] 2', 4);
assertHasResult(SearchResultKind.INVOCATION, '(); // 3', 0);
- assertHasResult(SearchResultKind.REFERENCE, '; // 4', 0);
- assertHasResult(SearchResultKind.INVOCATION, '(); // 5', 0);
- assertHasResult(SearchResultKind.REFERENCE, '.new; // 6', 4);
+ assertHasResult(SearchResultKind.INVOCATION, '(); // 4', 0);
+ assertHasResult(SearchResultKind.REFERENCE, '; // 5', 0);
+ assertHasResult(SearchResultKind.INVOCATION, '(); // 6', 0);
+ assertHasResult(SearchResultKind.REFERENCE, '.new; // 7', 4);
}
Future<void> test_constructor_unnamed_potential() async {
diff --git a/pkg/analyzer/lib/src/generated/element_resolver.dart b/pkg/analyzer/lib/src/generated/element_resolver.dart
index 7f2a709..daa682e 100644
--- a/pkg/analyzer/lib/src/generated/element_resolver.dart
+++ b/pkg/analyzer/lib/src/generated/element_resolver.dart
@@ -129,27 +129,19 @@
if (identifier is SimpleIdentifierImpl) {
var element = _resolveSimpleIdentifier(identifier);
if (element == null) {
- // TODO(brianwilkerson) Report this error?
- // resolver.reportError(
- // CompileTimeErrorCode.UNDEFINED_IDENTIFIER,
- // simpleIdentifier,
- // simpleIdentifier.getName());
- } else {
- if (element.library == null || element.library != _definingLibrary) {
- // TODO(brianwilkerson) Report this error?
- }
- identifier.staticElement = element;
- if (node.newKeyword != null) {
- if (element is ClassElement) {
- var constructor = element.unnamedConstructor;
- if (constructor == null) {
- // TODO(brianwilkerson) Report this error.
- } else {
- identifier.staticElement = constructor;
- }
- } else {
+ return;
+ }
+ identifier.staticElement = element;
+ if (node.newKeyword != null) {
+ if (element is ClassElement) {
+ var constructor = element.unnamedConstructor;
+ if (constructor == null) {
// TODO(brianwilkerson) Report this error.
+ } else {
+ identifier.staticElement = constructor;
}
+ } else {
+ // TODO(brianwilkerson) Report this error.
}
}
} else if (identifier is PrefixedIdentifierImpl) {
@@ -160,7 +152,6 @@
var name = identifier.identifier;
if (prefixElement == null) {
-// resolver.reportError(CompileTimeErrorCode.UNDEFINED_IDENTIFIER, prefix, prefix.getName());
return;
}
@@ -173,11 +164,6 @@
return;
}
- var library = prefixElement.library;
- if (library != _definingLibrary) {
- // TODO(brianwilkerson) Report this error.
- }
-
if (node.newKeyword == null) {
if (prefixElement is ClassElement) {
name.staticElement = prefixElement.getMethod(name.name) ??
diff --git a/pkg/analyzer/test/generated/new_as_identifier_parser_test.dart b/pkg/analyzer/test/generated/new_as_identifier_parser_test.dart
index 3f6571e..7fbe061 100644
--- a/pkg/analyzer/test/generated/new_as_identifier_parser_test.dart
+++ b/pkg/analyzer/test/generated/new_as_identifier_parser_test.dart
@@ -256,6 +256,14 @@
expect(methodInvocation.argumentList, isNotNull);
}
+ void test_constructor_tearoff_in_comment_reference() {
+ createParser('');
+ var commentReference = parseCommentReference('C.new', 5)!;
+ var identifier = commentReference.identifier as PrefixedIdentifier;
+ expect(identifier.prefix.name, 'C');
+ expect(identifier.identifier.name, 'new');
+ }
+
void test_constructor_tearoff_method_invocation() {
var methodInvocation =
parseExpression('C.new.toString()', featureSet: constructorTearoffs)
diff --git a/pkg/front_end/parser_testcases/general/new_as_identifier.dart b/pkg/front_end/parser_testcases/general/new_as_identifier.dart
index 32a868f..30b926d 100644
--- a/pkg/front_end/parser_testcases/general/new_as_identifier.dart
+++ b/pkg/front_end/parser_testcases/general/new_as_identifier.dart
@@ -7,6 +7,7 @@
//
// Unless otherwise noted, these tests cases should not result in a parse error.
+/// See [C.new].
class C {
C.new();
diff --git a/pkg/front_end/parser_testcases/general/new_as_identifier.dart.expect b/pkg/front_end/parser_testcases/general/new_as_identifier.dart.expect
index e3d208d..89b9611 100644
--- a/pkg/front_end/parser_testcases/general/new_as_identifier.dart.expect
+++ b/pkg/front_end/parser_testcases/general/new_as_identifier.dart.expect
@@ -1,42 +1,42 @@
Problems reported:
-parser/general/new_as_identifier:15:44: Expected an identifier, but got 'new'.
+parser/general/new_as_identifier:16:44: Expected an identifier, but got 'new'.
C.constructor_field_initializer() : this.new = null;
^^^
-parser/general/new_as_identifier:15:39: Expected an assignment after the field name.
+parser/general/new_as_identifier:16:39: Expected an assignment after the field name.
C.constructor_field_initializer() : this.new = null;
^^^^
-parser/general/new_as_identifier:15:44: Expected a function body, but got 'new'.
+parser/general/new_as_identifier:16:44: Expected a function body, but got 'new'.
C.constructor_field_initializer() : this.new = null;
^^^
-parser/general/new_as_identifier:15:44: Expected a class member, but got 'new'.
+parser/general/new_as_identifier:16:44: Expected a class member, but got 'new'.
C.constructor_field_initializer() : this.new = null;
^^^
-parser/general/new_as_identifier:15:48: Operator declarations must be preceded by the keyword 'operator'.
+parser/general/new_as_identifier:16:48: Operator declarations must be preceded by the keyword 'operator'.
C.constructor_field_initializer() : this.new = null;
^
-parser/general/new_as_identifier:15:48: The string '=' isn't a user-definable operator.
+parser/general/new_as_identifier:16:48: The string '=' isn't a user-definable operator.
C.constructor_field_initializer() : this.new = null;
^
-parser/general/new_as_identifier:15:48: A method declaration needs an explicit list of parameters.
+parser/general/new_as_identifier:16:48: A method declaration needs an explicit list of parameters.
C.constructor_field_initializer() : this.new = null;
^
-parser/general/new_as_identifier:15:50: Expected a function body, but got 'null'.
+parser/general/new_as_identifier:16:50: Expected a function body, but got 'null'.
C.constructor_field_initializer() : this.new = null;
^^^^
-parser/general/new_as_identifier:15:50: Expected a class member, but got 'null'.
+parser/general/new_as_identifier:16:50: Expected a class member, but got 'null'.
C.constructor_field_initializer() : this.new = null;
^^^^
-parser/general/new_as_identifier:15:54: Expected a class member, but got ';'.
+parser/general/new_as_identifier:16:54: Expected a class member, but got ';'.
C.constructor_field_initializer() : this.new = null;
^