From 5474347d7dfa29ef56c095c4dd8d430175091131 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96zg=C3=BCr=20T=2E=20=C3=96nsoy?= Date: Thu, 3 Sep 2026 19:06:20 +0000 Subject: [PATCH] Support lexing and parsing positional params (#7651) This implements lexing and parsing positional parameters such as `$0`. --- .../check/handle_positional_param_expr.cpp | 14 +++ toolchain/diagnostics/kind.def | 5 + toolchain/lex/lex.cpp | 109 ++++++++++++++++++ toolchain/lex/numeric_literal.cpp | 2 +- .../lex/testdata/dollar_int_literals.carbon | 101 ++++++++++++++++ toolchain/lex/token_info.h | 3 +- toolchain/lex/token_kind.def | 1 + toolchain/lex/token_kind.h | 5 + toolchain/lex/tokenized_buffer.cpp | 3 +- toolchain/lex/tokenized_buffer_test.cpp | 10 +- toolchain/parse/handle_expr.cpp | 5 + toolchain/parse/node_kind.def | 2 + .../parse/testdata/function/definition.carbon | 25 ++++ toolchain/parse/typed_nodes.h | 5 + 14 files changed, 282 insertions(+), 8 deletions(-) create mode 100644 toolchain/check/handle_positional_param_expr.cpp create mode 100644 toolchain/lex/testdata/dollar_int_literals.carbon diff --git a/toolchain/check/handle_positional_param_expr.cpp b/toolchain/check/handle_positional_param_expr.cpp new file mode 100644 index 000000000000..3bb5ac3dd7f8 --- /dev/null +++ b/toolchain/check/handle_positional_param_expr.cpp @@ -0,0 +1,14 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include "toolchain/check/context.h" +#include "toolchain/check/handle.h" +#include "toolchain/parse/node_ids.h" + +namespace Carbon::Check { +auto HandleParseNode(Context& context, Parse::PositionalParamExprId node_id) + -> bool { + return context.TODO(node_id, "HandlePositionalParamExprId"); +} +} // namespace Carbon::Check diff --git a/toolchain/diagnostics/kind.def b/toolchain/diagnostics/kind.def index 7eb31dfd3ced..8f9ebe4b6fcb 100644 --- a/toolchain/diagnostics/kind.def +++ b/toolchain/diagnostics/kind.def @@ -59,12 +59,16 @@ CARBON_DIAGNOSTIC_KIND(ErrorReadingFile) // ============================================================================ CARBON_DIAGNOSTIC_KIND(InvalidRealLiteralRadix) +CARBON_DIAGNOSTIC_KIND(CharacterOnlyAllowedAtStart) CARBON_DIAGNOSTIC_KIND(ContentBeforeStringTerminator) CARBON_DIAGNOSTIC_KIND(DecimalEscapeSequence) +CARBON_DIAGNOSTIC_KIND(DollarIntLiteralLeadingZero) +CARBON_DIAGNOSTIC_KIND(DollarIntLiteralMissingNumber) CARBON_DIAGNOSTIC_KIND(DumpSemIRRangeMissingEnd) CARBON_DIAGNOSTIC_KIND(DumpSemIRRangeMissingBegin) CARBON_DIAGNOSTIC_KIND(EmptyDigitSequence) CARBON_DIAGNOSTIC_KIND(HexadecimalEscapeMissingDigits) +CARBON_DIAGNOSTIC_KIND(InvalidCharacterInDollarIntLiteral) CARBON_DIAGNOSTIC_KIND(InvalidDigit) CARBON_DIAGNOSTIC_KIND(InvalidDigitSeparator) CARBON_DIAGNOSTIC_KIND(InvalidHorizontalWhitespaceInString) @@ -75,6 +79,7 @@ CARBON_DIAGNOSTIC_KIND(NoWhitespaceAfterCommentIntroducer) CARBON_DIAGNOSTIC_KIND(TooManyDigits) CARBON_DIAGNOSTIC_KIND(TooManyTokens) CARBON_DIAGNOSTIC_KIND(TooManyTypeBitWidthDigits) +CARBON_DIAGNOSTIC_KIND(TooManyDollarIntDigits) CARBON_DIAGNOSTIC_KIND(UnicodeEscapeMissingBracedDigits) CARBON_DIAGNOSTIC_KIND(UnicodeEscapeSurrogate) CARBON_DIAGNOSTIC_KIND(UnicodeEscapeTooLarge) diff --git a/toolchain/lex/lex.cpp b/toolchain/lex/lex.cpp index 72188d552ddd..c0a96cebb954 100644 --- a/toolchain/lex/lex.cpp +++ b/toolchain/lex/lex.cpp @@ -191,6 +191,11 @@ class [[clang::internal_linkage]] Lexer { auto LexWordAsTypeLiteralToken(llvm::StringRef word, int32_t byte_offset) -> LexResult; + // Given a lexed word, determine whether it is a dollar int literal and if so + // form the corresponding token, + auto LexWordAsDollarIntLiteralToken(llvm::StringRef word, int32_t byte_offset) + -> LexResult; + auto LexKeywordOrIdentifier(llvm::StringRef source_text, ssize_t& position) -> LexResult; @@ -286,6 +291,7 @@ static constexpr std::array IsIdStartByteTable = [] { table[c] = true; } table['_'] = true; + table['$'] = true; return table; }(); @@ -297,6 +303,8 @@ static constexpr std::array IsIdByteTable = [] { for (char c = '0'; c <= '9'; ++c) { table[c] = true; } + // Identifiers can only have `$` in start. + table['$'] = false; return table; }(); @@ -307,6 +315,12 @@ static constexpr std::array IsIdByteTable = [] { static auto ScanForIdentifierPrefixScalar(llvm::StringRef text, ssize_t i) -> llvm::StringRef { const ssize_t size = text.size(); + if (i == 0 && !text.empty()) { + if (!IsIdStartByteTable[static_cast(text[i])]) { + return {}; + } + ++i; + } while (i < size && IsIdByteTable[static_cast(text[i])]) { ++i; } @@ -410,6 +424,13 @@ static auto ScanForIdentifierPrefixX86(llvm::StringRef text) // Use `ssize_t` for performance here as we index memory in a tight loop. ssize_t i = 0; + if (!text.empty()) { + if (!IsIdStartByteTable[static_cast(text[i])]) { + return {}; + } + ++i; + } + const ssize_t size = text.size(); while ((i + 16) <= size) { __m128i input = @@ -644,6 +665,7 @@ static constexpr auto MakeDispatchTable() -> DispatchTableT { table['/'] = &DispatchLexCommentOrSlash; table['_'] = &DispatchLexKeywordOrIdentifier; + table['$'] = &DispatchLexKeywordOrIdentifier; // Note that we don't use `llvm::seq` because this needs to be `constexpr` // evaluated. for (unsigned char c = 'a'; c <= 'z'; ++c) { @@ -1453,6 +1475,89 @@ auto Lexer::LexWordAsTypeLiteralToken(llvm::StringRef word, int32_t byte_offset) return LexTokenWithPayload(kind, bit_width_payload, byte_offset); } +auto Lexer::LexWordAsDollarIntLiteralToken(llvm::StringRef word, + int32_t byte_offset) -> LexResult { + if (!word.starts_with('$')) { + return LexResult::NoMatch(); + } + + if (!has_leading_space_) { + auto prev_token = buffer_.tokens().end()[-1]; + auto kind = buffer_.GetKind(prev_token); + if (kind.is_word()) { + CARBON_DIAGNOSTIC( + CharacterOnlyAllowedAtStart, Error, + "`$` is only allowed at the start of a positional parameter"); + emitter_.Emit(word.begin(), CharacterOnlyAllowedAtStart); + + auto& prev_token_info = buffer_.token_infos_.Get(prev_token); + auto prev_token_text_size = buffer_.GetTokenText(prev_token).size(); + prev_token_info = TokenInfo(TokenKind::Error, has_leading_space_, + prev_token_text_size + word.size(), + prev_token_info.byte_offset()); + return LexResult(TokenIndex(buffer_.token_infos_.size() - 1)); + } + } + + auto diagnose_invalid_char = [&]() { + CARBON_DIAGNOSTIC( + InvalidCharacterInDollarIntLiteral, Error, + "Positional parameters can only contain digits after `$`"); + emitter_.Emit(word.begin() + 1, InvalidCharacterInDollarIntLiteral); + return LexTokenWithPayload(TokenKind::Error, word.size(), byte_offset); + }; + + if (word.size() < 2) { + CARBON_DIAGNOSTIC(DollarIntLiteralMissingNumber, Error, + "Expected digits after `$`"); + emitter_.Emit(word.begin() + 1, DollarIntLiteralMissingNumber); + return LexTokenWithPayload(TokenKind::Error, word.size(), byte_offset); + } + if (word[1] == '0' && word.size() > 2) { + CARBON_DIAGNOSTIC( + DollarIntLiteralLeadingZero, Error, + "Leading zeroes are not allowed in positional parameters"); + emitter_.Emit(word.begin() + 1, DollarIntLiteralLeadingZero); + return LexTokenWithPayload(TokenKind::Error, word.size(), byte_offset); + } + if ((word[1] < '0' || word[1] > '9')) { + return diagnose_invalid_char(); + } + + auto suffix = word.substr(1); + int64_t suffix_value; + constexpr ssize_t DigitLimit = + std::numeric_limits::digits10; + if (suffix.size() > DigitLimit) { + // See if this is not actually a dollar int literal. + if (!llvm::all_of(suffix, IsDecimalDigit)) { + return diagnose_invalid_char(); + } + + // Otherwise, diagnose and produce an error token. + CARBON_DIAGNOSTIC(TooManyDollarIntDigits, Error, + "found a positional parameter using {0} digits, " + "which is greater than the limit of {1}", + size_t, size_t); + emitter_.Emit(word.begin() + 1, TooManyDollarIntDigits, suffix.size(), + DigitLimit); + return LexTokenWithPayload(TokenKind::Error, word.size(), byte_offset); + } + + suffix_value = suffix[0] - '0'; + for (char c : suffix.drop_front()) { + if (!IsDecimalDigit(c)) { + return diagnose_invalid_char(); + } + suffix_value = suffix_value * 10 + (c - '0'); + } + CARBON_CHECK(suffix_value >= 0); + return LexTokenWithPayload( + TokenKind::DollarIntLiteral, + buffer_.value_stores_->ints().Add(suffix_value).AsTokenPayload(), + byte_offset); +} + auto Lexer::LexKeywordOrIdentifier(llvm::StringRef source_text, ssize_t& position) -> LexResult { if (static_cast(source_text[position]) > 0x7F) { @@ -1476,6 +1581,10 @@ auto Lexer::LexKeywordOrIdentifier(llvm::StringRef source_text, LexWordAsTypeLiteralToken(identifier_text, byte_offset)) { return result; } + if (LexResult result = + LexWordAsDollarIntLiteralToken(identifier_text, byte_offset)) { + return result; + } // Check if the text matches a keyword token, and if so use that. TokenKind kind = llvm::StringSwitch(identifier_text) diff --git a/toolchain/lex/numeric_literal.cpp b/toolchain/lex/numeric_literal.cpp index 089c7ac390b8..0a72ff28a375 100644 --- a/toolchain/lex/numeric_literal.cpp +++ b/toolchain/lex/numeric_literal.cpp @@ -42,7 +42,7 @@ auto NumericLiteral::Lex(llvm::StringRef source_text, int n = source_text.size(); for (; i != n; ++i) { char c = source_text[i]; - if (IsAlnum(c) || c == '_') { + if (IsAlnum(c) || c == '_' || c == '$') { if (IsLower(c) && seen_radix_point && !seen_plus_minus) { result.exponent_ = i; seen_potential_exponent = true; diff --git a/toolchain/lex/testdata/dollar_int_literals.carbon b/toolchain/lex/testdata/dollar_int_literals.carbon new file mode 100644 index 000000000000..91a7faf1abb3 --- /dev/null +++ b/toolchain/lex/testdata/dollar_int_literals.carbon @@ -0,0 +1,101 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// AUTOUPDATE +// TIP: To test this file alone, run: +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/lex/testdata/dollar_int_literals.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/lex/testdata/dollar_int_literals.carbon + +// --- valid.carbon +// CHECK:STDOUT: - filename: valid.carbon +// CHECK:STDOUT: tokens: +$0 +// CHECK:STDOUT: - { index: 1, kind: "DollarIntLiteral", line: {{ *}}[[@LINE-1]], column: 1, indent: 1, spelling: "$0", has_leading_space: true } +$1 +// CHECK:STDOUT: - { index: 2, kind: "DollarIntLiteral", line: {{ *}}[[@LINE-1]], column: 1, indent: 1, spelling: "$1", has_leading_space: true } +$42 +// CHECK:STDOUT: - { index: 3, kind: "DollarIntLiteral", line: {{ *}}[[@LINE-1]], column: 1, indent: 1, spelling: "$42", has_leading_space: true } +$100 +// CHECK:STDOUT: - { index: 4, kind: "DollarIntLiteral", line: {{ *}}[[@LINE-1]], column: 1, indent: 1, spelling: "$100", has_leading_space: true } +$999999999999 +// CHECK:STDOUT: - { index: 5, kind: "DollarIntLiteral", line: {{ *}}[[@LINE-1]], column: 1, indent: 1, spelling: "$999999999999", has_leading_space: true } + +// --- fail_invalid.carbon +// CHECK:STDOUT: - filename: fail_invalid.carbon +// CHECK:STDOUT: tokens: + +// CHECK:STDERR: fail_invalid.carbon:[[@LINE+4]]:2: error: Expected digits after `$` [DollarIntLiteralMissingNumber] +// CHECK:STDERR: $ +// CHECK:STDERR: ^ +// CHECK:STDERR: +$ +// CHECK:STDOUT: - { index: 1, kind: "Error", line: {{ *}}[[@LINE-1]], column: 1, indent: 1, spelling: "$", has_leading_space: true } +// CHECK:STDERR: fail_invalid.carbon:[[@LINE+4]]:2: error: Expected digits after `$` [DollarIntLiteralMissingNumber] +// CHECK:STDERR: $ 1 +// CHECK:STDERR: ^ +// CHECK:STDERR: +$ 1 +// CHECK:STDOUT: - { index: 2, kind: "Error", line: {{ *}}[[@LINE-1]], column: 1, indent: 1, spelling: "$", has_leading_space: true } +// CHECK:STDOUT: - { index: 3, kind: "IntLiteral", line: {{ *}}[[@LINE-2]], column: 3, indent: 1, spelling: "1", value: "1", has_leading_space: true } +// CHECK:STDERR: fail_invalid.carbon:[[@LINE+4]]:2: error: Positional parameters can only contain digits after `$` [InvalidCharacterInDollarIntLiteral] +// CHECK:STDERR: $foo +// CHECK:STDERR: ^ +// CHECK:STDERR: +$foo +// CHECK:STDOUT: - { index: 4, kind: "Error", line: {{ *}}[[@LINE-1]], column: 1, indent: 1, spelling: "$foo", has_leading_space: true } +// CHECK:STDERR: fail_invalid.carbon:[[@LINE+4]]:2: error: Positional parameters can only contain digits after `$` [InvalidCharacterInDollarIntLiteral] +// CHECK:STDERR: $1a32 +// CHECK:STDERR: ^ +// CHECK:STDERR: +$1a32 +// CHECK:STDOUT: - { index: 5, kind: "Error", line: {{ *}}[[@LINE-1]], column: 1, indent: 1, spelling: "$1a32", has_leading_space: true } +// CHECK:STDERR: fail_invalid.carbon:[[@LINE+4]]:2: error: Leading zeroes are not allowed in positional parameters [DollarIntLiteralLeadingZero] +// CHECK:STDERR: $00 +// CHECK:STDERR: ^ +// CHECK:STDERR: +$00 +// CHECK:STDOUT: - { index: 6, kind: "Error", line: {{ *}}[[@LINE-1]], column: 1, indent: 1, spelling: "$00", has_leading_space: true } +// CHECK:STDERR: fail_invalid.carbon:[[@LINE+4]]:2: error: Leading zeroes are not allowed in positional parameters [DollarIntLiteralLeadingZero] +// CHECK:STDERR: $025321 +// CHECK:STDERR: ^ +// CHECK:STDERR: +$025321 +// CHECK:STDOUT: - { index: 7, kind: "Error", line: {{ *}}[[@LINE-1]], column: 1, indent: 1, spelling: "$025321", has_leading_space: true } +// CHECK:STDERR: fail_invalid.carbon:[[@LINE+4]]:2: error: Leading zeroes are not allowed in positional parameters [DollarIntLiteralLeadingZero] +// CHECK:STDERR: $0x12 +// CHECK:STDERR: ^ +// CHECK:STDERR: +$0x12 +// CHECK:STDOUT: - { index: 8, kind: "Error", line: {{ *}}[[@LINE-1]], column: 1, indent: 1, spelling: "$0x12", has_leading_space: true } +// CHECK:STDERR: fail_invalid.carbon:[[@LINE+4]]:2: error: Leading zeroes are not allowed in positional parameters [DollarIntLiteralLeadingZero] +// CHECK:STDERR: $0b1100 +// CHECK:STDERR: ^ +// CHECK:STDERR: +$0b1100 +// CHECK:STDOUT: - { index: 9, kind: "Error", line: {{ *}}[[@LINE-1]], column: 1, indent: 1, spelling: "$0b1100", has_leading_space: true } +// CHECK:STDERR: fail_invalid.carbon:[[@LINE+4]]:2: error: invalid digit '$' in decimal numeric literal [InvalidDigit] +// CHECK:STDERR: 2$63 +// CHECK:STDERR: ^ +// CHECK:STDERR: +2$63 +// CHECK:STDOUT: - { index: 10, kind: "Error", line: {{ *}}[[@LINE-1]], column: 1, indent: 1, spelling: "2$63", has_leading_space: true } +// CHECK:STDERR: fail_invalid.carbon:[[@LINE+4]]:4: error: `$` is only allowed at the start of a positional parameter [CharacterOnlyAllowedAtStart] +// CHECK:STDERR: abc$37 +// CHECK:STDERR: ^ +// CHECK:STDERR: +abc$37 +// CHECK:STDOUT: - { index: 11, kind: "Error", line: {{ *}}[[@LINE-1]], column: 1, indent: 1, spelling: "abc$37" } +// CHECK:STDERR: fail_invalid.carbon:[[@LINE+4]]:6: error: `$` is only allowed at the start of a positional parameter [CharacterOnlyAllowedAtStart] +// CHECK:STDERR: r#foo$0 +// CHECK:STDERR: ^ +// CHECK:STDERR: +r#foo$0 +// CHECK:STDOUT: - { index: 12, kind: "Error", line: {{ *}}[[@LINE-1]], column: 1, indent: 1, spelling: "r#foo$0" } +// CHECK:STDERR: fail_invalid.carbon:[[@LINE+4]]:2: error: found a positional parameter using 19 digits, which is greater than the limit of 18 [TooManyDollarIntDigits] +// CHECK:STDERR: $9999999999999999999 +// CHECK:STDERR: ^ +// CHECK:STDERR: +$9999999999999999999 +// CHECK:STDOUT: - { index: 13, kind: "Error", line: {{ *}}[[@LINE-1]], column: 1, indent: 1, spelling: "$9999999999999999999", has_leading_space: true } diff --git a/toolchain/lex/token_info.h b/toolchain/lex/token_info.h index d53ab15fb477..b38c04c7f940 100644 --- a/toolchain/lex/token_info.h +++ b/toolchain/lex/token_info.h @@ -94,7 +94,8 @@ class TokenInfo { CARBON_DCHECK(kind() == TokenKind::IntLiteral || kind() == TokenKind::IntTypeLiteral || kind() == TokenKind::UnsignedIntTypeLiteral || - kind() == TokenKind::FloatTypeLiteral); + kind() == TokenKind::FloatTypeLiteral || + kind() == TokenKind::DollarIntLiteral); return IntId::MakeFromTokenPayload(token_payload_); } diff --git a/toolchain/lex/token_kind.def b/toolchain/lex/token_kind.def index 3e4e9b248205..bd5b90b2f4e8 100644 --- a/toolchain/lex/token_kind.def +++ b/toolchain/lex/token_kind.def @@ -248,6 +248,7 @@ CARBON_TOKEN(CharLiteral) CARBON_TOKEN(IntTypeLiteral) CARBON_TOKEN(UnsignedIntTypeLiteral) CARBON_TOKEN(FloatTypeLiteral) +CARBON_TOKEN(DollarIntLiteral) CARBON_TOKEN(FileStart) CARBON_TOKEN(FileEnd) diff --git a/toolchain/lex/token_kind.h b/toolchain/lex/token_kind.h index bb7e84e7d5c2..19a804fa724d 100644 --- a/toolchain/lex/token_kind.h +++ b/toolchain/lex/token_kind.h @@ -86,6 +86,11 @@ class TokenKind : public CARBON_ENUM_BASE(TokenKind) { *this == TokenKind::FloatTypeLiteral; } + // Test whether this kind of token is a dollar int literal. + auto is_dollar_int_literal() const -> bool { + return *this == TokenKind::DollarIntLiteral; + } + // Test whether this kind of token is a word. auto is_word() const -> bool { return *this == TokenKind::Identifier || *this == TokenKind::Underscore || diff --git a/toolchain/lex/tokenized_buffer.cpp b/toolchain/lex/tokenized_buffer.cpp index faa20ed5a8ea..d3b283f9ecb6 100644 --- a/toolchain/lex/tokenized_buffer.cpp +++ b/toolchain/lex/tokenized_buffer.cpp @@ -93,7 +93,8 @@ auto TokenizedBuffer::GetTokenText(TokenIndex token) const -> llvm::StringRef { // Refer back to the source text to avoid needing to reconstruct the // spelling from the size. - if (token_info.kind().is_sized_type_literal()) { + if (token_info.kind().is_sized_type_literal() || + token_info.kind().is_dollar_int_literal()) { llvm::StringRef suffix = source_->text() .substr(token_info.byte_offset() + 1) .take_while(IsDecimalDigit); diff --git a/toolchain/lex/tokenized_buffer_test.cpp b/toolchain/lex/tokenized_buffer_test.cpp index 22a8ee7365c1..4808d3545ea6 100644 --- a/toolchain/lex/tokenized_buffer_test.cpp +++ b/toolchain/lex/tokenized_buffer_test.cpp @@ -369,7 +369,7 @@ TEST_F(LexerTest, SplitsNumericLiteralsProperly) { } TEST_F(LexerTest, HandlesGarbageCharacters) { - constexpr char GarbageText[] = "$$💩-$\n$\0$12$\n\\\"\\\n\"x"; + constexpr char GarbageText[] = "##💩-#\n#\0#12#\n\\\"\\\n\"x"; auto& buffer = compile_helper_.GetTokenizedBuffer( llvm::StringRef(GarbageText, sizeof(GarbageText) - 1)); EXPECT_TRUE(buffer.has_errors()); @@ -381,16 +381,16 @@ TEST_F(LexerTest, HandlesGarbageCharacters) { .line = 1, .column = 1, // 💩 takes 4 bytes, and we count column as bytes offset. - .text = llvm::StringRef("$$💩", 6)}, + .text = llvm::StringRef("##💩", 6)}, {.kind = TokenKind::Minus, .line = 1, .column = 7}, - {.kind = TokenKind::Error, .line = 1, .column = 8, .text = "$"}, + {.kind = TokenKind::Error, .line = 1, .column = 8, .text = "#"}, // newline {.kind = TokenKind::Error, .line = 2, .column = 1, - .text = llvm::StringRef("$\0$", 3)}, + .text = llvm::StringRef("#\0#", 3)}, {.kind = TokenKind::IntLiteral, .line = 2, .column = 4, .text = "12"}, - {.kind = TokenKind::Error, .line = 2, .column = 6, .text = "$"}, + {.kind = TokenKind::Error, .line = 2, .column = 6, .text = "#"}, // newline {.kind = TokenKind::Backslash, .line = 3, .column = 1, .text = "\\"}, {.kind = TokenKind::Error, .line = 3, .column = 2, .text = "\"\\"}, diff --git a/toolchain/parse/handle_expr.cpp b/toolchain/parse/handle_expr.cpp index 0039c552e285..2f539069c258 100644 --- a/toolchain/parse/handle_expr.cpp +++ b/toolchain/parse/handle_expr.cpp @@ -139,6 +139,11 @@ auto HandleExprInPostfix(Context& context) -> void { context.PushState(state); break; } + case Lex::TokenKind::DollarIntLiteral: { + context.AddLeafNode(NodeKind::PositionalParamExpr, context.Consume()); + context.PushState(state); + break; + } case Lex::TokenKind::Str: { context.AddLeafNode(NodeKind::StringTypeLiteral, context.Consume()); context.PushState(state); diff --git a/toolchain/parse/node_kind.def b/toolchain/parse/node_kind.def index a94b93bd368a..e03c1eb070a7 100644 --- a/toolchain/parse/node_kind.def +++ b/toolchain/parse/node_kind.def @@ -311,6 +311,8 @@ CARBON_PARSE_NODE_KIND_EXPRESSION(PointerMemberAccessExpr) CARBON_PARSE_NODE_KIND_EXPRESSION(IntLiteral) +CARBON_PARSE_NODE_KIND_EXPRESSION(PositionalParamExpr) + CARBON_PARSE_NODE_KIND_TOKEN_LITERAL(BoolLiteralFalse, False) CARBON_PARSE_NODE_KIND_TOKEN_LITERAL(BoolLiteralTrue, True) CARBON_PARSE_NODE_KIND_TOKEN_LITERAL(CharLiteral, CharLiteral) diff --git a/toolchain/parse/testdata/function/definition.carbon b/toolchain/parse/testdata/function/definition.carbon index 8acc51f81d76..6d59e3420bc5 100644 --- a/toolchain/parse/testdata/function/definition.carbon +++ b/toolchain/parse/testdata/function/definition.carbon @@ -18,6 +18,12 @@ fn foo(bar: i64, baz: i64) { foo(baz, bar + baz); } +// --- with_positional_params.carbon + +fn foo { + foo($0, $42.0 + $86); +} + // --- with_return_type.carbon fn foo() -> f64 { @@ -116,6 +122,25 @@ fn TestRecoveryFromSpuriousEquals(); // CHECK:STDOUT: ├─FunctionDefinition '}' // CHECK:STDOUT: ├─FileEnd '' // CHECK:STDOUT: (root) +// CHECK:STDOUT: - filename: with_positional_params.carbon +// CHECK:STDOUT: ╭─FileStart '' +// CHECK:STDOUT: │ ╭─FunctionIntroducer 'fn' +// CHECK:STDOUT: │ ├─IdentifierNameNotBeforeSignature 'foo' +// CHECK:STDOUT: │ ╭─FunctionDefinitionStart '{' +// CHECK:STDOUT: │ │ ╭─IdentifierNameExpr 'foo' +// CHECK:STDOUT: │ │ ╭─CallExprStart '(' +// CHECK:STDOUT: │ │ ├─PositionalParamExpr '$0' +// CHECK:STDOUT: │ │ ├─TupleLiteralComma ',' +// CHECK:STDOUT: │ │ │ ╭─PositionalParamExpr '$42' +// CHECK:STDOUT: │ │ │ ├─IntLiteral '0' +// CHECK:STDOUT: │ │ │ ╭─MemberAccessExpr '.' +// CHECK:STDOUT: │ │ │ ├─PositionalParamExpr '$86' +// CHECK:STDOUT: │ │ ├─InfixOperatorPlus '+' +// CHECK:STDOUT: │ │ ╭─CallExpr ')' +// CHECK:STDOUT: │ ├─ExprStatement ';' +// CHECK:STDOUT: ├─FunctionDefinition '}' +// CHECK:STDOUT: ├─FileEnd '' +// CHECK:STDOUT: (root) // CHECK:STDOUT: - filename: with_return_type.carbon // CHECK:STDOUT: ╭─FileStart '' // CHECK:STDOUT: │ ╭─FunctionIntroducer 'fn' diff --git a/toolchain/parse/typed_nodes.h b/toolchain/parse/typed_nodes.h index a4dd10877707..09762792a911 100644 --- a/toolchain/parse/typed_nodes.h +++ b/toolchain/parse/typed_nodes.h @@ -1837,6 +1837,11 @@ struct NamedConstraintDefinition { Lex::CloseCurlyBraceTokenIndex token; }; +// `$0` +using PositionalParamExpr = + LeafNode; + // --------------------------------------------------------------------------- // A complete source file. Note that there is no corresponding parse node for