Skip to content

Commit

Permalink
[analyzer][cfe] Introduce parsing for null-aware elements
Browse files Browse the repository at this point in the history
Part of #55949
Closes #55954

Change-Id: I885772f292f6d70425d6eba15bad8b0c6dc86a1f
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/370240
Reviewed-by: Keerti Parthasarathy <[email protected]>
Commit-Queue: Chloe Stefantsova <[email protected]>
Reviewed-by: Johnni Winther <[email protected]>
  • Loading branch information
chloestefantsova authored and Commit Queue committed Jun 28, 2024
1 parent 4a8b3c7 commit a4c9471
Show file tree
Hide file tree
Showing 61 changed files with 2,561 additions and 86 deletions.
12 changes: 10 additions & 2 deletions pkg/_fe_analyzer_shared/lib/src/parser/forwarding_listener.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1649,8 +1649,11 @@ class ForwardingListener implements Listener {
}

@override
void handleLiteralMapEntry(Token colon, Token endToken) {
listener?.handleLiteralMapEntry(colon, endToken);
void handleLiteralMapEntry(Token colon, Token endToken,
{Token? nullAwareKeyToken, Token? nullAwareValueToken}) {
listener?.handleLiteralMapEntry(colon, endToken,
nullAwareKeyToken: nullAwareKeyToken,
nullAwareValueToken: nullAwareValueToken);
}

@override
Expand Down Expand Up @@ -1909,6 +1912,11 @@ class ForwardingListener implements Listener {
listener?.endConstantPattern(constKeyword);
}

@override
void handleNullAwareElement(Token nullAwareToken) {
listener?.handleNullAwareElement(nullAwareToken);
}

@override
void handleObjectPattern(
Token firstIdentifier, Token? dot, Token? secondIdentifier) {
Expand Down
15 changes: 14 additions & 1 deletion pkg/_fe_analyzer_shared/lib/src/parser/listener.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1113,8 +1113,14 @@ class Listener implements UnescapeErrorListener {
logEvent("LibraryName");
}

/// Called after parsing a map entry. Either the key or the value or both can
/// start with the null-aware token `?`. In that case, [nullAwareKeyToken] and
/// [nullAwareValueToken] are set appropriately. Substructures:
/// - expression
/// - expression
// TODO(jensj): Should this have a `beginToken`?
void handleLiteralMapEntry(Token colon, Token endToken) {
void handleLiteralMapEntry(Token colon, Token endToken,
{Token? nullAwareKeyToken, Token? nullAwareValueToken}) {
logEvent("LiteralMapEntry");
}

Expand Down Expand Up @@ -1882,6 +1888,13 @@ class Listener implements UnescapeErrorListener {
logEvent("SpreadExpression");
}

/// Called after parsing a list or set element that starts with the null-aware
/// token `?`. Substructures:
/// - expression
void handleNullAwareElement(Token nullAwareToken) {
logEvent("NullAwareElement");
}

/// Called after parsing an element of a list or map pattern that starts with
/// `...`. Substructures:
/// - pattern (if hasSubPattern is `true`)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ LiteralEntryInfo computeLiteralEntry(Token token) {
return new ForCondition();
} else if (optional('...', next) || optional('...?', next)) {
return spreadOperator;
} else if (optional('?', next)) {
return nullAwareEntry;
}
return simpleEntry;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ const LiteralEntryInfo ifCondition = const IfCondition();
/// preceded by a '...' spread operator.
const LiteralEntryInfo spreadOperator = const SpreadOperator();

/// [nullAwareEntry] is for parsing a null-aware element in a literal list or
/// set, preceded by the `?` null-aware marker, or a null-aware map entry in a
/// map literal, where either the key or the value is preceded by the `?`
/// null-aware marker.
const NullAwareEntry nullAwareEntry = const NullAwareEntry();

/// The first step when processing a `for` control flow collection entry.
class ForCondition extends LiteralEntryInfo {
bool _inStyle = false;
Expand Down Expand Up @@ -86,6 +92,9 @@ class ForCondition extends LiteralEntryInfo {
);
} else if (optional('...', next) || optional('...?', next)) {
return _inStyle ? const ForInSpread() : const ForSpread();
} else if (optional('?', next)) {
return new Nested(nullAwareEntry,
_inStyle ? const ForInComplete() : const ForComplete());
}
return _inStyle ? const ForInEntry() : const ForEntry();
}
Expand Down Expand Up @@ -180,6 +189,8 @@ class IfCondition extends LiteralEntryInfo {
return new Nested(ifCondition, const IfComplete());
} else if (optional('...', next) || optional('...?', next)) {
return const IfSpread();
} else if (optional('?', next)) {
return new Nested(nullAwareEntry, const IfComplete());
}
return const IfEntry();
}
Expand Down Expand Up @@ -308,3 +319,30 @@ class Nested extends LiteralEntryInfo {
return nestedStep != null ? this : lastStep;
}
}

class NullAwareEntry extends LiteralEntryInfo {
const NullAwareEntry() : super(false, 0);

@override
Token parse(Token token, Parser parser) {
final Token entryNullAwareToken = token.next!;
assert(optional('?', entryNullAwareToken));
token = parser.parseExpression(entryNullAwareToken);
if (optional(':', token.next!)) {
Token colon = token.next!;
Token next = colon.next!;
if (optional('?', next)) {
token = parser.parseExpression(next);
parser.listener.handleLiteralMapEntry(colon, token,
nullAwareKeyToken: entryNullAwareToken, nullAwareValueToken: next);
} else {
token = parser.parseExpression(colon);
parser.listener.handleLiteralMapEntry(colon, token,
nullAwareKeyToken: entryNullAwareToken, nullAwareValueToken: null);
}
} else {
parser.listener.handleNullAwareElement(entryNullAwareToken);
}
return token;
}
}
30 changes: 26 additions & 4 deletions pkg/_fe_analyzer_shared/lib/src/parser/parser_impl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6991,17 +6991,39 @@ class Parser {
hasSetEntry ??= !isMapEntry;
if (isMapEntry) {
Token colon = token.next!;
token = parseExpression(colon);
listener.handleLiteralMapEntry(colon, token.next!);
Token next = colon.next!;
if (optional('?', next)) {
// Null-aware value. For example:
// <int, String>{ x: ?y }
token = parseExpression(next);
listener.handleLiteralMapEntry(colon, token,
nullAwareKeyToken: null, nullAwareValueToken: next);
} else {
// Non null-aware entry. For example:
// <bool, num>{ x: y }
token = parseExpression(colon);
listener.handleLiteralMapEntry(colon, token.next!);
}
}
} else {
while (info != null) {
if (info.hasEntry) {
token = parseExpression(token);
if (optional(':', token.next!)) {
Token colon = token.next!;
token = parseExpression(colon);
listener.handleLiteralMapEntry(colon, token.next!);
Token next = colon.next!;
if (optional('?', next)) {
token = parseExpression(next);
// Null-aware value. For example:
// <double, Symbol>{ if (b) x: ?y }
listener.handleLiteralMapEntry(colon, token,
nullAwareKeyToken: null, nullAwareValueToken: next);
} else {
// Non null-aware entry. For example:
// <String, int>{ if (b) x : y }
token = parseExpression(colon);
listener.handleLiteralMapEntry(colon, token.next!);
}
}
} else {
token = info.parse(token, this);
Expand Down
29 changes: 28 additions & 1 deletion pkg/analyzer/lib/src/fasta/ast_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,9 @@ class AstBuilder extends StackListener {
/// `true` if class-modifiers is enabled
final bool enableClassModifiers;

/// `true` if null-aware elements is enabled
final bool enableNullAwareElements;

final FeatureSet _featureSet;

final LineInfo _lineInfo;
Expand Down Expand Up @@ -174,6 +177,8 @@ class AstBuilder extends StackListener {
enableInlineClass = _featureSet.isEnabled(Feature.inline_class),
enableSealedClass = _featureSet.isEnabled(Feature.sealed_class),
enableClassModifiers = _featureSet.isEnabled(Feature.class_modifiers),
enableNullAwareElements =
_featureSet.isEnabled(Feature.null_aware_elements),
uri = uri ?? fileUri;

@override
Expand Down Expand Up @@ -4746,10 +4751,20 @@ class AstBuilder extends StackListener {
}

@override
void handleLiteralMapEntry(Token colon, Token endToken) {
void handleLiteralMapEntry(Token colon, Token endToken,
{Token? nullAwareKeyToken, Token? nullAwareValueToken}) {
assert(optional(':', colon));
debugEvent("LiteralMapEntry");

// TODO(cstefantsova): Handle null-aware map entries.
if (!enableNullAwareElements &&
(nullAwareKeyToken != null || nullAwareValueToken != null)) {
_reportFeatureNotEnabled(
feature: ExperimentalFeatures.null_aware_elements,
startToken: nullAwareKeyToken ?? nullAwareValueToken!,
);
}

var value = pop() as ExpressionImpl;
var key = pop() as ExpressionImpl;
push(
Expand Down Expand Up @@ -5036,6 +5051,18 @@ class AstBuilder extends StackListener {
);
}

@override
void handleNullAwareElement(Token nullAwareElement) {
debugEvent('NullAwareElement');
// TODO(cstefantsova): Handle null-aware elements.
if (!enableNullAwareElements) {
_reportFeatureNotEnabled(
feature: ExperimentalFeatures.null_aware_elements,
startToken: nullAwareElement,
);
}
}

@override
void handleNullCheckPattern(Token question) {
debugEvent('NullCheckPattern');
Expand Down
40 changes: 38 additions & 2 deletions pkg/front_end/lib/src/kernel/body_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4995,11 +4995,29 @@ class BodyBuilder extends StackListenerImpl
}

@override
void handleLiteralMapEntry(Token colon, Token endToken) {
void handleLiteralMapEntry(Token colon, Token endToken,
{Token? nullAwareKeyToken, Token? nullAwareValueToken}) {
debugEvent("LiteralMapEntry");
Expression value = popForValue();
Expression key = popForValue();
push(forest.createMapEntry(offsetForToken(colon), key, value));
if (nullAwareKeyToken == null && nullAwareValueToken == null) {
push(forest.createMapEntry(offsetForToken(colon), key, value));
} else {
if (!libraryFeatures.nullAwareElements.isEnabled) {
addProblem(
templateExperimentNotEnabledOffByDefault
.withArguments(ExperimentalFlag.nullAwareElements.name),
(nullAwareKeyToken ?? nullAwareValueToken!).offset,
noLength);
}
// TODO(cstefantsova): Replace the following no-op with the node for
// handling null-aware elements.
push(forest.createSpreadElement(
offsetForToken(nullAwareKeyToken ?? nullAwareValueToken!),
forest.createNullLiteral(
offsetForToken(nullAwareKeyToken ?? nullAwareValueToken!)),
isNullAware: true));
}
}

String symbolPartToString(name) {
Expand Down Expand Up @@ -7129,6 +7147,24 @@ class BodyBuilder extends StackListenerImpl
typeInferrer.assignedVariables.storeInfo(node, assignedVariablesInfo);
}

@override
void handleNullAwareElement(Token nullAwareElement) {
debugEvent("NullAwareElement");
// TODO(cstefantsova): Replace the following no-op with the node for
// handling null-aware elements.
if (!libraryFeatures.nullAwareElements.isEnabled) {
addProblem(
templateExperimentNotEnabledOffByDefault
.withArguments(ExperimentalFlag.nullAwareElements.name),
nullAwareElement.offset,
noLength);
}
pop(); // Expression.
push(forest.createSpreadElement(offsetForToken(nullAwareElement),
forest.createNullLiteral(offsetForToken(nullAwareElement)),
isNullAware: true));
}

@override
void handleSpreadExpression(Token spreadToken) {
debugEvent("SpreadExpression");
Expand Down
8 changes: 7 additions & 1 deletion pkg/front_end/lib/src/kernel/macro/annotation_parser.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1967,7 +1967,8 @@ class _MacroListener implements Listener {
}

@override
void handleLiteralMapEntry(Token colon, Token endToken) {
void handleLiteralMapEntry(Token colon, Token endToken,
{Token? nullAwareKeyToken, Token? nullAwareValueToken}) {
_unhandled('map entry');
}

Expand Down Expand Up @@ -2188,6 +2189,11 @@ class _MacroListener implements Listener {
_unsupported();
}

@override
void handleNullAwareElement(Token spreadToken) {
_unsupported();
}

@override
void handleObjectPattern(
Token firstIdentifier, Token? dot, Token? secondIdentifier) {
Expand Down
Loading

0 comments on commit a4c9471

Please sign in to comment.