Skip to content

Commit 9bc31f7

Browse files
author
John Messerly
committed
fix #25739, incorrectly expanding a generic comment multiple times
[email protected], [email protected] Review URL: https://codereview.chromium.org/1686173002 .
1 parent 5db3ae7 commit 9bc31f7

File tree

4 files changed

+81
-19
lines changed

4 files changed

+81
-19
lines changed

pkg/analyzer/lib/src/dart/ast/token.dart

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ class BeginToken extends SimpleToken {
3535
/**
3636
* A begin token that is preceded by comments.
3737
*/
38-
class BeginTokenWithComment extends BeginToken {
38+
class BeginTokenWithComment extends BeginToken implements TokenWithComment {
3939
/**
4040
* The first comment in the list of comments that precede this token.
4141
*/
@@ -79,9 +79,9 @@ class BeginTokenWithComment extends BeginToken {
7979
*/
8080
class CommentToken extends StringToken {
8181
/**
82-
* The [Token] that contains this comment.
82+
* The token that contains this comment.
8383
*/
84-
Token parent;
84+
TokenWithComment parent;
8585

8686
/**
8787
* Initialize a newly created token to represent a token of the given [type]
@@ -92,6 +92,21 @@ class CommentToken extends StringToken {
9292

9393
@override
9494
CommentToken copy() => new CommentToken(type, _value, offset);
95+
96+
/**
97+
* Remove this comment token from the list.
98+
*
99+
* This is used when we decide to interpret the comment as syntax.
100+
*/
101+
void remove() {
102+
if (previous != null) {
103+
previous.setNextWithoutSettingPrevious(next);
104+
next?.previous = previous;
105+
} else {
106+
assert(parent.precedingComments == this);
107+
parent.precedingComments = next;
108+
}
109+
}
95110
}
96111

97112
/**
@@ -149,7 +164,7 @@ class KeywordToken extends SimpleToken {
149164
/**
150165
* A keyword token that is preceded by comments.
151166
*/
152-
class KeywordTokenWithComment extends KeywordToken {
167+
class KeywordTokenWithComment extends KeywordToken implements TokenWithComment {
153168
/**
154169
* The first comment in the list of comments that precede this token.
155170
*/
@@ -340,7 +355,7 @@ class StringToken extends SimpleToken {
340355
/**
341356
* A string token that is preceded by comments.
342357
*/
343-
class StringTokenWithComment extends StringToken {
358+
class StringTokenWithComment extends StringToken implements TokenWithComment {
344359
/**
345360
* The first comment in the list of comments that precede this token.
346361
*/

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3951,6 +3951,8 @@ class Parser {
39513951
String comment = t.lexeme.substring(prefixLen, t.lexeme.length - 2);
39523952
Token list = _scanGenericMethodComment(comment, t.offset + prefixLen);
39533953
if (list != null) {
3954+
// Remove the token from the comment stream.
3955+
t.remove();
39543956
// Insert the tokens into the stream.
39553957
_injectTokenList(list);
39563958
return true;

pkg/analyzer/test/generated/parser_test.dart

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1531,6 +1531,50 @@ class Foo {
15311531
reason: 'parser recovers what it can');
15321532
}
15331533

1534+
void test_method_invalidTypeParameterExtends() {
1535+
// Regression test for https://github.com/dart-lang/sdk/issues/25739.
1536+
1537+
// TODO(jmesserly): ideally we'd be better at parser recovery here.
1538+
enableGenericMethods = true;
1539+
MethodDeclaration method = parse3(
1540+
"parseClassMember",
1541+
<Object>["C"],
1542+
"f<E>(E extends num p);",
1543+
[
1544+
ParserErrorCode.MISSING_IDENTIFIER, // `extends` is a keyword
1545+
ParserErrorCode.EXPECTED_TOKEN, // comma
1546+
ParserErrorCode.EXPECTED_TOKEN, // close paren
1547+
ParserErrorCode.MISSING_FUNCTION_BODY
1548+
]);
1549+
expect(method.parameters.toString(), '(E, extends)',
1550+
reason: 'parser recovers what it can');
1551+
}
1552+
1553+
1554+
void test_method_invalidTypeParameterExtendsComment() {
1555+
// Regression test for https://github.com/dart-lang/sdk/issues/25739.
1556+
1557+
// TODO(jmesserly): ideally we'd be better at parser recovery here.
1558+
// Also, this behavior is slightly different from how we would parse a
1559+
// normal generic method, because we "discover" the comment at a different
1560+
// point in the parser. This has a slight effect on the AST that results
1561+
// from error recovery.
1562+
enableGenericMethodComments = true;
1563+
MethodDeclaration method = parse3(
1564+
"parseClassMember",
1565+
<Object>["C"],
1566+
"f/*<E>*/(dynamic/*=E extends num*/p);",
1567+
[
1568+
ParserErrorCode.MISSING_IDENTIFIER, // `extends` is a keyword
1569+
ParserErrorCode.EXPECTED_TOKEN, // comma
1570+
ParserErrorCode.MISSING_IDENTIFIER, // `extends` is a keyword
1571+
ParserErrorCode.EXPECTED_TOKEN, // close paren
1572+
ParserErrorCode.MISSING_FUNCTION_BODY
1573+
]);
1574+
expect(method.parameters.toString(), '(E extends, extends)',
1575+
reason: 'parser recovers what it can');
1576+
}
1577+
15341578
void test_missingAssignableSelector_identifiersAssigned() {
15351579
parseExpression("x.y = y;");
15361580
}

pkg/analyzer/test/src/task/strong/strong_test_helper.dart

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ void check() {
7474

7575
// Extract expectations from the comments in the test files, and
7676
// check that all errors we emit are included in the expected map.
77-
var allLibraries = reachableLibraries(initialLibrary.element.library);
77+
var allLibraries = _reachableLibraries(initialLibrary.element.library);
7878
for (var lib in allLibraries) {
7979
for (var unit in lib.units) {
8080
var errors = <AnalysisError>[];
@@ -101,11 +101,11 @@ void checkFile(String content) {
101101
check();
102102
}
103103

104-
SourceSpanWithContext createSpanHelper(
104+
SourceSpanWithContext _createSpanHelper(
105105
LineInfo lineInfo, int start, Source source, String content,
106106
{int end}) {
107-
var startLoc = locationForOffset(lineInfo, source.uri, start);
108-
var endLoc = locationForOffset(lineInfo, source.uri, end ?? start);
107+
var startLoc = _locationForOffset(lineInfo, source.uri, start);
108+
var endLoc = _locationForOffset(lineInfo, source.uri, end ?? start);
109109

110110
var lineStart = startLoc.offset - startLoc.column;
111111
// Find the end of the line. This is not exposed directly on LineInfo, but
@@ -119,7 +119,7 @@ SourceSpanWithContext createSpanHelper(
119119

120120
if (end == null) {
121121
end = lineEnd;
122-
endLoc = locationForOffset(lineInfo, source.uri, lineEnd);
122+
endLoc = _locationForOffset(lineInfo, source.uri, lineEnd);
123123
}
124124

125125
var text = content.substring(start, end);
@@ -137,7 +137,7 @@ String _errorCodeName(ErrorCode errorCode) {
137137
}
138138
}
139139

140-
initStrongModeTests() {
140+
void initStrongModeTests() {
141141
setUp(() {
142142
AnalysisEngine.instance.processRequiredPlugins();
143143
files = new MemoryResourceProvider();
@@ -151,15 +151,14 @@ initStrongModeTests() {
151151
});
152152
}
153153

154-
// TODO(jmesserly): can we reuse the same mock SDK as Analyzer tests?
155-
SourceLocation locationForOffset(LineInfo lineInfo, Uri uri, int offset) {
154+
SourceLocation _locationForOffset(LineInfo lineInfo, Uri uri, int offset) {
156155
var loc = lineInfo.getLocation(offset);
157156
return new SourceLocation(offset,
158157
sourceUrl: uri, line: loc.lineNumber - 1, column: loc.columnNumber - 1);
159158
}
160159

161160
/// Returns all libraries transitively imported or exported from [start].
162-
List<LibraryElement> reachableLibraries(LibraryElement start) {
161+
List<LibraryElement> _reachableLibraries(LibraryElement start) {
163162
var results = <LibraryElement>[];
164163
var seen = new Set();
165164
void find(LibraryElement lib) {
@@ -237,9 +236,11 @@ List<_ErrorExpectation> _findExpectedErrors(Token beginToken) {
237236
if (c.type == TokenType.MULTI_LINE_COMMENT) {
238237
String value = c.lexeme.substring(2, c.lexeme.length - 2);
239238
if (value.contains(':')) {
240-
var offset = c.end;
241-
if (c.next?.type == TokenType.GENERIC_METHOD_TYPE_LIST) {
242-
offset += 2;
239+
int offset = t.offset;
240+
Token previous = t.previous;
241+
while (previous != null && previous.offset > c.offset) {
242+
offset = previous.offset;
243+
previous = previous.previous;
243244
}
244245
for (var expectCode in value.split(',')) {
245246
var expected = _ErrorExpectation.parse(offset, expectCode);
@@ -266,7 +267,7 @@ void _reportFailure(
266267
String formatActualError(AnalysisError error) {
267268
int offset = error.offset;
268269
int length = error.length;
269-
var span = createSpanHelper(
270+
var span = _createSpanHelper(
270271
unit.lineInfo, offset, unit.element.source, sourceCode,
271272
end: offset + length);
272273
var levelName = _actualErrorLevel(error).name.toLowerCase();
@@ -276,7 +277,7 @@ void _reportFailure(
276277

277278
String formatExpectedError(_ErrorExpectation error) {
278279
int offset = error.offset;
279-
var span = createSpanHelper(
280+
var span = _createSpanHelper(
280281
unit.lineInfo, offset, unit.element.source, sourceCode);
281282
var levelName = error.level.toString().toLowerCase();
282283
return '@$offset $levelName:${error.typeName}\n' + span.message('');

0 commit comments

Comments
 (0)