Skip to content

Commit 126c5fa

Browse files
stereotype441commit-bot@chromium.org
authored andcommitted
Initial parser support for constructor tear-offs.
This CL adds parser support for use of `<typeArguments>` as a selector. This allows expressions like `List<int>` (type literal with type arguments), `f<int>` (function tear-off with type arguments), `C.m<int>` (static method tear-off with type arguments), `EXPR.m<int>` (instance method tear-off with type arguments), and `EXPR<int>` (tear-off of `.call` method with type arguments). I will add parser support for `.new` as a constructor name in a follow-up CL. Change-Id: I157e732276421e8c3fd20c38c67ae9643993bd85 Bug: #46020, #46044. Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/197102 Commit-Queue: Paul Berry <[email protected]> Reviewed-by: Konstantin Shcheglov <[email protected]> Reviewed-by: Johnni Winther <[email protected]> Reviewed-by: Jens Johansen <[email protected]>
1 parent 4a0d89b commit 126c5fa

File tree

48 files changed

+4492
-191
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+4492
-191
lines changed

pkg/_fe_analyzer_shared/lib/src/parser/forwarding_listener.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -780,6 +780,11 @@ class ForwardingListener implements Listener {
780780
listener?.endFunctionTypedFormalParameter(nameToken, question);
781781
}
782782

783+
@override
784+
void handleTypeArgumentApplication(Token openAngleBracket) {
785+
listener?.handleTypeArgumentApplication(openAngleBracket);
786+
}
787+
783788
@override
784789
void endHide(Token hideKeyword) {
785790
listener?.endHide(hideKeyword);

pkg/_fe_analyzer_shared/lib/src/parser/listener.dart

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1727,4 +1727,19 @@ class Listener implements UnescapeErrorListener {
17271727
/// This event is generated by the parser when the parser's
17281728
/// `parseOneCommentReference` method is called.
17291729
void handleNoCommentReference() {}
1730+
1731+
/// An expression was encountered consisting of type arguments applied to a
1732+
/// subexpression. This could validly represent any of the following:
1733+
/// - A type literal (`var x = List<int>;`)
1734+
/// - A function tear-off with type arguments (`var x = f<int>;` or
1735+
/// `var x = importPrefix.f<int>;`)
1736+
/// - A static method tear-off with type arguments (`var x = ClassName.m<int>`
1737+
/// or `var x = importPrefix.ClassName.m<int>;`)
1738+
/// - An instance method tear-off with type arguments (`var x = EXPR.m<int>;`)
1739+
///
1740+
/// Or, in the event of invalid code, it could represent type arguments
1741+
/// erroneously applied to some other expression type (e.g.
1742+
/// `var x = (f)<int>;`). The client is responsible for reporting an error if
1743+
/// this occurs.
1744+
void handleTypeArgumentApplication(Token openAngleBracket) {}
17301745
}

pkg/_fe_analyzer_shared/lib/src/parser/parser_impl.dart

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4793,7 +4793,10 @@ class Parser {
47934793
listener.handleNonNullAssertExpression(bangToken);
47944794
}
47954795
token = typeArg.parseArguments(bangToken, this);
4796-
assert(optional('(', token.next!));
4796+
if (!optional('(', token.next!)) {
4797+
listener.handleTypeArgumentApplication(bangToken.next!);
4798+
typeArg = noTypeParamOrArg;
4799+
}
47974800
}
47984801

47994802
return _parsePrecedenceExpressionLoop(
@@ -4861,7 +4864,10 @@ class Parser {
48614864
listener.handleNonNullAssertExpression(bangToken);
48624865
}
48634866
token = typeArg.parseArguments(bangToken, this);
4864-
assert(optional('(', token.next!));
4867+
if (!optional('(', token.next!)) {
4868+
listener.handleTypeArgumentApplication(bangToken.next!);
4869+
typeArg = noTypeParamOrArg;
4870+
}
48654871
}
48664872
} else if (identical(type, TokenType.OPEN_PAREN) ||
48674873
identical(type, TokenType.OPEN_SQUARE_BRACKET)) {
@@ -5174,8 +5180,13 @@ class Parser {
51745180
TypeParamOrArgInfo typeArg = computeTypeParamOrArg(identifier);
51755181
if (typeArg != noTypeParamOrArg) {
51765182
Token endTypeArguments = typeArg.skip(identifier);
5177-
if (optional(".", endTypeArguments.next!)) {
5178-
return parseImplicitCreationExpression(token, typeArg);
5183+
Token afterTypeArguments = endTypeArguments.next!;
5184+
if (optional(".", afterTypeArguments)) {
5185+
Token afterPeriod = afterTypeArguments.next!;
5186+
if (afterPeriod.isIdentifier &&
5187+
optional('(', afterPeriod.next!)) {
5188+
return parseImplicitCreationExpression(token, typeArg);
5189+
}
51795190
}
51805191
}
51815192
}

pkg/_fe_analyzer_shared/lib/src/parser/type_info.dart

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,53 @@ TypeParamOrArgInfo computeTypeParamOrArg(Token token,
348348
/// possible other constructs will pass (e.g., 'a < C, D > 3').
349349
TypeParamOrArgInfo computeMethodTypeArguments(Token token) {
350350
TypeParamOrArgInfo typeArg = computeTypeParamOrArg(token);
351-
return optional('(', typeArg.skip(token).next!) && !typeArg.recovered
351+
return mayFollowTypeArgs(typeArg.skip(token).next!) && !typeArg.recovered
352352
? typeArg
353353
: noTypeParamOrArg;
354354
}
355+
356+
/// Indicates whether the given [token] is allowed to follow a list of type
357+
/// arguments used as a selector after an expression.
358+
///
359+
/// This is used for disambiguating constructs like `f(a<b,c>(d))` and
360+
/// `f(a<b,c>-d)`. In the case of `f(a<b,c>(d))`, `true` will be returned,
361+
/// indicating that the `<` and `>` should be interpreted as delimiting type
362+
/// arguments (so one argument is being passed to `f` -- a call to the generic
363+
/// function `a`). In the case of `f(a<b,c>-d)`, `false` will be returned,
364+
/// indicating that the `<` and `>` should be interpreted as operators (so two
365+
/// arguments are being passed to `f`: `a < b` and `c > -d`).
366+
bool mayFollowTypeArgs(Token token) {
367+
const Set<String> tokensThatMayFollowTypeArg = {
368+
'(',
369+
')',
370+
']',
371+
'}',
372+
':',
373+
';',
374+
',',
375+
'.',
376+
'?',
377+
'==',
378+
'!=',
379+
'..',
380+
'?.',
381+
'??',
382+
'?..',
383+
'&',
384+
'|',
385+
'^',
386+
'+',
387+
'*',
388+
'%',
389+
'/',
390+
'~/'
391+
};
392+
if (token.type == TokenType.EOF) {
393+
// The spec doesn't have anything to say about this case, since an
394+
// expression can't occur at the end of a file, but for testing it's to our
395+
// advantage to allow EOF after type arguments, so that an isolated `f<x>`
396+
// can be parsed as an expression.
397+
return true;
398+
}
399+
return tokensThatMayFollowTypeArg.contains(token.lexeme);
400+
}

pkg/analyzer/lib/dart/analysis/features.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ abstract class Feature {
1616
/// Feature information for non-nullability by default.
1717
static final non_nullable = ExperimentalFeatures.non_nullable;
1818

19+
/// Feature information for constructor tear-offs.
20+
static final constructor_tearoffs = ExperimentalFeatures.constructor_tearoffs;
21+
1922
/// Feature information for control flow collections.
2023
static final control_flow_collections =
2124
ExperimentalFeatures.control_flow_collections;

pkg/analyzer/lib/src/fasta/ast_builder.dart

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,9 @@ class AstBuilder extends StackListener {
140140
/// `true` if variance behavior is enabled
141141
final bool enableVariance;
142142

143+
/// `true` if constructor tearoffs are enabled
144+
final bool enableConstructorTearoffs;
145+
143146
final FeatureSet _featureSet;
144147

145148
AstBuilder(ErrorReporter errorReporter, this.fileUri, this.isFullAst,
@@ -155,6 +158,8 @@ class AstBuilder extends StackListener {
155158
enableNonFunctionTypeAliases =
156159
_featureSet.isEnabled(Feature.nonfunction_type_aliases),
157160
enableVariance = _featureSet.isEnabled(Feature.variance),
161+
enableConstructorTearoffs =
162+
_featureSet.isEnabled(Feature.constructor_tearoffs),
158163
uri = uri ?? fileUri;
159164

160165
NodeList<ClassMember> get currentDeclarationMembers {
@@ -3513,6 +3518,39 @@ class AstBuilder extends StackListener {
35133518
push(ast.typeName(name, arguments, question: questionMark));
35143519
}
35153520

3521+
@override
3522+
void handleTypeArgumentApplication(Token openAngleBracket) {
3523+
var typeArguments = pop() as TypeArgumentList;
3524+
var receiver = pop() as Expression;
3525+
if (!enableConstructorTearoffs) {
3526+
var feature = ExperimentalFeatures.constructor_tearoffs;
3527+
handleRecoverableError(
3528+
templateExperimentNotEnabled.withArguments(
3529+
feature.enableString,
3530+
_versionAsString(ExperimentStatus.currentVersion),
3531+
),
3532+
typeArguments.leftBracket,
3533+
typeArguments.rightBracket,
3534+
);
3535+
// Since analyzer visitors don't yet support constructor tear-offs, create
3536+
// a FunctionExpressionInvocation with a synthetic argument list instead.
3537+
// TODO(paulberry): once we have visitor support for constructor
3538+
// tear-offs, fall through and return a FunctionReference instead since
3539+
// that should lead to better quality error recovery.
3540+
var syntheticOffset = typeArguments.rightBracket.end;
3541+
push(ast.functionExpressionInvocation(
3542+
receiver,
3543+
typeArguments,
3544+
ast.argumentList(
3545+
SyntheticToken(TokenType.OPEN_PAREN, syntheticOffset),
3546+
[],
3547+
SyntheticToken(TokenType.CLOSE_PAREN, syntheticOffset))));
3548+
return;
3549+
}
3550+
push(ast.functionReference(
3551+
function: receiver, typeArguments: typeArguments));
3552+
}
3553+
35163554
@override
35173555
void handleTypeVariablesDefined(Token token, int count) {
35183556
debugEvent("handleTypeVariablesDefined");

0 commit comments

Comments
 (0)