Skip to content

Commit 402a05d

Browse files
stereotype441Commit Queue
authored and
Commit Queue
committed
Add record type parsing to mini_types.dart.
We now no longer need the `expr2` function to test expressions whose type is RecordType. Also added unit tests of the type parser in mini_types.dart (which was previously tested only by virtue of its use in other tests). Change-Id: I5d351f3ff924676b4d84e65c8949f42feca0dab9 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/267522 Reviewed-by: Konstantin Shcheglov <[email protected]> Commit-Queue: Paul Berry <[email protected]>
1 parent 024a460 commit 402a05d

File tree

4 files changed

+294
-67
lines changed

4 files changed

+294
-67
lines changed

pkg/_fe_analyzer_shared/test/mini_ast.dart

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -122,11 +122,6 @@ Statement do_(List<Statement> body, Expression condition) {
122122
Expression expr(String typeStr) =>
123123
new _PlaceholderExpression(new Type(typeStr), location: computeLocation());
124124

125-
/// Creates a pseudo-expression having type [type] that otherwise has no
126-
/// effect on flow analysis.
127-
Expression expr2(Type type) =>
128-
new _PlaceholderExpression(type, location: computeLocation());
129-
130125
/// Creates a conventional `for` statement. Optional boolean [forCollection]
131126
/// indicates that this `for` statement is actually a collection element, so
132127
/// `null` should be passed to [for_bodyBegin].

pkg/_fe_analyzer_shared/test/mini_types.dart

Lines changed: 86 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,16 @@ class NonFunctionType extends Type {
8585
}
8686
}
8787

88+
/// Exception thrown if a type fails to parse properly.
89+
class ParseError extends Error {
90+
final String message;
91+
92+
ParseError(this.message);
93+
94+
@override
95+
String toString() => message;
96+
}
97+
8898
/// Representation of a promoted type parameter type suitable for unit testing
8999
/// of code in the `_fe_analyzer_shared` package. A promoted type parameter is
90100
/// often written using the syntax `a&b`, where `a` is the type parameter and
@@ -299,7 +309,7 @@ class UnknownType extends Type {
299309

300310
class _TypeParser {
301311
static final _typeTokenizationRegexp =
302-
RegExp(_identifierPattern + r'|\(|\)|<|>|,|\?|\*|&');
312+
RegExp(_identifierPattern + r'|\(|\)|<|>|,|\?|\*|&|{|}');
303313

304314
static const _identifierPattern = '[_a-zA-Z][_a-zA-Z0-9]*';
305315

@@ -320,7 +330,61 @@ class _TypeParser {
320330
}
321331

322332
Never _parseFailure(String message) {
323-
fail('Error parsing type `$_typeStr` at token $_currentToken: $message');
333+
throw ParseError(
334+
'Error parsing type `$_typeStr` at token $_currentToken: $message');
335+
}
336+
337+
List<NamedType> _parseRecordTypeNamedFields() {
338+
assert(_currentToken == '{');
339+
_next();
340+
var namedTypes = <NamedType>[];
341+
while (_currentToken != '}') {
342+
var type = _parseType();
343+
var name = _currentToken;
344+
if (_identifierRegexp.matchAsPrefix(name) == null) {
345+
_parseFailure('Expected an identifier');
346+
}
347+
namedTypes.add(NamedType(name, type));
348+
_next();
349+
if (_currentToken == ',') {
350+
_next();
351+
continue;
352+
}
353+
if (_currentToken == '}') {
354+
break;
355+
}
356+
_parseFailure('Expected `}` or `,`');
357+
}
358+
if (namedTypes.isEmpty) {
359+
_parseFailure('Must have at least one named type between {}');
360+
}
361+
_next();
362+
return namedTypes;
363+
}
364+
365+
Type _parseRecordTypeRest(List<Type> positionalTypes) {
366+
List<NamedType>? namedTypes;
367+
while (_currentToken != ')') {
368+
if (_currentToken == '{') {
369+
namedTypes = _parseRecordTypeNamedFields();
370+
if (_currentToken != ')') {
371+
_parseFailure('Expected `)`');
372+
}
373+
break;
374+
}
375+
positionalTypes.add(_parseType());
376+
if (_currentToken == ',') {
377+
_next();
378+
continue;
379+
}
380+
if (_currentToken == ')') {
381+
break;
382+
}
383+
_parseFailure('Expected `)` or `,`');
384+
}
385+
_next();
386+
return RecordType(
387+
positional: positionalTypes, named: namedTypes ?? const []);
324388
}
325389

326390
Type? _parseSuffix(Type type) {
@@ -364,6 +428,13 @@ class _TypeParser {
364428
// unsuffixedType := identifier typeArgs?
365429
// | `?`
366430
// | `(` type `)`
431+
// | `(` recordTypeFields `,` recordTypeNamedFields `)`
432+
// | `(` recordTypeFields `,`? `)`
433+
// | `(` recordTypeNamedFields? `)`
434+
// recordTypeFields := type (`,` type)*
435+
// recordTypeNamedFields := `{` recordTypeNamedField
436+
// (`,` recordTypeNamedField)* `,`? `}`
437+
// recordTypeNamedField := type identifier
367438
// typeArgs := `<` type (`,` type)* `>`
368439
// nullability := (`?` | `*`)?
369440
// suffix := `Function` `(` type (`,` type)* `)`
@@ -387,9 +458,16 @@ class _TypeParser {
387458
}
388459
if (_currentToken == '(') {
389460
_next();
461+
if (_currentToken == ')' || _currentToken == '{') {
462+
return _parseRecordTypeRest([]);
463+
}
390464
var type = _parseType();
465+
if (_currentToken == ',') {
466+
_next();
467+
return _parseRecordTypeRest([type]);
468+
}
391469
if (_currentToken != ')') {
392-
_parseFailure('Expected `)`');
470+
_parseFailure('Expected `)` or `,`');
393471
}
394472
_next();
395473
return type;
@@ -422,7 +500,7 @@ class _TypeParser {
422500
var parser = _TypeParser._(typeStr, _tokenizeTypeStr(typeStr));
423501
var result = parser._parseType();
424502
if (parser._currentToken != '<END>') {
425-
fail('Extra tokens after parsing type `$typeStr`: '
503+
throw ParseError('Extra tokens after parsing type `$typeStr`: '
426504
'${parser._tokens.sublist(parser._i, parser._tokens.length - 1)}');
427505
}
428506
return result;
@@ -434,14 +512,16 @@ class _TypeParser {
434512
for (var match in _typeTokenizationRegexp.allMatches(typeStr)) {
435513
var extraChars = typeStr.substring(lastMatchEnd, match.start).trim();
436514
if (extraChars.isNotEmpty) {
437-
fail('Unrecognized character(s) in type `$typeStr`: $extraChars');
515+
throw ParseError(
516+
'Unrecognized character(s) in type `$typeStr`: $extraChars');
438517
}
439518
result.add(typeStr.substring(match.start, match.end));
440519
lastMatchEnd = match.end;
441520
}
442521
var extraChars = typeStr.substring(lastMatchEnd).trim();
443522
if (extraChars.isNotEmpty) {
444-
fail('Unrecognized character(s) in type `$typeStr`: $extraChars');
523+
throw ParseError(
524+
'Unrecognized character(s) in type `$typeStr`: $extraChars');
445525
}
446526
result.add('<END>');
447527
return result;

pkg/_fe_analyzer_shared/test/mini_types_test.dart

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,206 @@ import 'package:test/test.dart';
77
import 'mini_types.dart';
88

99
main() {
10+
group('parse', () {
11+
var throwsParseError = throwsA(TypeMatcher<ParseError>());
12+
13+
group('non-function type:', () {
14+
test('no type args', () {
15+
var t = Type('int') as NonFunctionType;
16+
expect(t.name, 'int');
17+
expect(t.args, isEmpty);
18+
});
19+
20+
test('type arg', () {
21+
var t = Type('List<int>') as NonFunctionType;
22+
expect(t.name, 'List');
23+
expect(t.args, hasLength(1));
24+
expect(t.args[0].type, 'int');
25+
});
26+
27+
test('type args', () {
28+
var t = Type('Map<int, String>') as NonFunctionType;
29+
expect(t.name, 'Map');
30+
expect(t.args, hasLength(2));
31+
expect(t.args[0].type, 'int');
32+
expect(t.args[1].type, 'String');
33+
});
34+
35+
test('invalid type arg separator', () {
36+
expect(() => Type('Map<int) String>'), throwsParseError);
37+
});
38+
});
39+
40+
test('invalid initial token', () {
41+
expect(() => Type('<'), throwsParseError);
42+
});
43+
44+
test('unknown type', () {
45+
var t = Type('?');
46+
expect(t, TypeMatcher<UnknownType>());
47+
});
48+
49+
test('question type', () {
50+
var t = Type('int?') as QuestionType;
51+
expect(t.innerType.type, 'int');
52+
});
53+
54+
test('star type', () {
55+
var t = Type('int*') as StarType;
56+
expect(t.innerType.type, 'int');
57+
});
58+
59+
test('promoted type variable', () {
60+
var t = Type('T&int') as PromotedTypeVariableType;
61+
expect(t.innerType.type, 'T');
62+
expect(t.promotion.type, 'int');
63+
});
64+
65+
test('parenthesized type', () {
66+
var t = Type('(int)');
67+
expect(t.type, 'int');
68+
});
69+
70+
test('invalid token terminating parenthesized type', () {
71+
expect(() => Type('(?<'), throwsParseError);
72+
});
73+
74+
group('function type:', () {
75+
test('no parameters', () {
76+
var t = Type('int Function()') as FunctionType;
77+
expect(t.returnType.type, 'int');
78+
expect(t.positionalParameters, isEmpty);
79+
});
80+
81+
test('positional parameter', () {
82+
var t = Type('int Function(String)') as FunctionType;
83+
expect(t.returnType.type, 'int');
84+
expect(t.positionalParameters, hasLength(1));
85+
expect(t.positionalParameters[0].type, 'String');
86+
});
87+
88+
test('positional parameters', () {
89+
var t = Type('int Function(String, double)') as FunctionType;
90+
expect(t.returnType.type, 'int');
91+
expect(t.positionalParameters, hasLength(2));
92+
expect(t.positionalParameters[0].type, 'String');
93+
expect(t.positionalParameters[1].type, 'double');
94+
});
95+
96+
test('invalid parameter separator', () {
97+
expect(() => Type('int Function(String Function()< double)'),
98+
throwsParseError);
99+
});
100+
101+
test('invalid token after Function', () {
102+
expect(() => Type('int Function&)'), throwsParseError);
103+
});
104+
});
105+
106+
group('record type:', () {
107+
test('no fields', () {
108+
var t = Type('()') as RecordType;
109+
expect(t.positional, isEmpty);
110+
expect(t.named, isEmpty);
111+
});
112+
113+
test('named field', () {
114+
var t = Type('({int x})') as RecordType;
115+
expect(t.positional, isEmpty);
116+
expect(t.named, hasLength(1));
117+
expect(t.named[0].name, 'x');
118+
expect(t.named[0].type.type, 'int');
119+
});
120+
121+
test('named field followed by comma', () {
122+
var t = Type('({int x,})') as RecordType;
123+
expect(t.positional, isEmpty);
124+
expect(t.named, hasLength(1));
125+
expect(t.named[0].name, 'x');
126+
expect(t.named[0].type.type, 'int');
127+
});
128+
129+
test('named field followed by invalid token', () {
130+
expect(() => Type('({int x))'), throwsParseError);
131+
});
132+
133+
test('named field name is not an identifier', () {
134+
expect(() => Type('({int )})'), throwsParseError);
135+
});
136+
137+
test('named fields', () {
138+
var t = Type('({int x, String y})') as RecordType;
139+
expect(t.positional, isEmpty);
140+
expect(t.named, hasLength(2));
141+
expect(t.named[0].name, 'x');
142+
expect(t.named[0].type.type, 'int');
143+
expect(t.named[1].name, 'y');
144+
expect(t.named[1].type.type, 'String');
145+
});
146+
147+
test('curly braces followed by invalid token', () {
148+
expect(() => Type('({int x}&'), throwsParseError);
149+
});
150+
151+
test('curly braces but no named fields', () {
152+
expect(() => Type('({})'), throwsParseError);
153+
});
154+
155+
test('positional field', () {
156+
var t = Type('(int,)') as RecordType;
157+
expect(t.named, isEmpty);
158+
expect(t.positional, hasLength(1));
159+
expect(t.positional[0].type, 'int');
160+
});
161+
162+
group('positional fields:', () {
163+
test('two', () {
164+
var t = Type('(int, String)') as RecordType;
165+
expect(t.named, isEmpty);
166+
expect(t.positional, hasLength(2));
167+
expect(t.positional[0].type, 'int');
168+
expect(t.positional[1].type, 'String');
169+
});
170+
171+
test('three', () {
172+
var t = Type('(int, String, double)') as RecordType;
173+
expect(t.named, isEmpty);
174+
expect(t.positional, hasLength(3));
175+
expect(t.positional[0].type, 'int');
176+
expect(t.positional[1].type, 'String');
177+
expect(t.positional[2].type, 'double');
178+
});
179+
});
180+
181+
test('named and positional fields', () {
182+
var t = Type('(int, {String x})') as RecordType;
183+
expect(t.positional, hasLength(1));
184+
expect(t.positional[0].type, 'int');
185+
expect(t.named, hasLength(1));
186+
expect(t.named[0].name, 'x');
187+
expect(t.named[0].type.type, 'String');
188+
});
189+
190+
test('terminated by invalid token', () {
191+
expect(() => Type('(int, String('), throwsParseError);
192+
});
193+
});
194+
195+
group('invalid token:', () {
196+
test('before other tokens', () {
197+
expect(() => Type('#int'), throwsParseError);
198+
});
199+
200+
test('at end', () {
201+
expect(() => Type('int#'), throwsParseError);
202+
});
203+
});
204+
205+
test('extra token after type', () {
206+
expect(() => Type('int)'), throwsParseError);
207+
});
208+
});
209+
10210
group('recursivelyDemote:', () {
11211
group('FunctionType:', () {
12212
group('return type:', () {

0 commit comments

Comments
 (0)