From db6db2aacc083381c6b268c12c42bde92816ab4c Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Mon, 21 Apr 2025 03:43:15 +0300 Subject: [PATCH] Allow wrapping `builtin#offset_of` fields argument in parentheses This is necessary to correctly handle nested fields (`foo.bar`), see the comments in the code for explanation. --- crates/parser/src/grammar/expressions/atom.rs | 12 ++++++ crates/parser/test_data/generated/runner.rs | 4 ++ .../parser/inline/ok/offset_of_parens.rast | 42 +++++++++++++++++++ .../parser/inline/ok/offset_of_parens.rs | 3 ++ 4 files changed, 61 insertions(+) create mode 100644 crates/parser/test_data/parser/inline/ok/offset_of_parens.rast create mode 100644 crates/parser/test_data/parser/inline/ok/offset_of_parens.rs diff --git a/crates/parser/src/grammar/expressions/atom.rs b/crates/parser/src/grammar/expressions/atom.rs index 407320e1d0..c66afed91c 100644 --- a/crates/parser/src/grammar/expressions/atom.rs +++ b/crates/parser/src/grammar/expressions/atom.rs @@ -258,6 +258,15 @@ fn builtin_expr(p: &mut Parser<'_>) -> Option { p.expect(T!['(']); type_(p); p.expect(T![,]); + // Due to our incomplete handling of macro groups, especially + // those with empty delimiters, we wrap `expr` fragments in + // parentheses sometimes. Since `offset_of` is a macro, and takes + // `expr`, the field names could be wrapped in parentheses. + let wrapped_in_parens = p.eat(T!['(']); + // test offset_of_parens + // fn foo() { + // builtin#offset_of(Foo, (bar.baz.0)); + // } while !p.at(EOF) && !p.at(T![')']) { name_ref_mod_path_or_index(p); if !p.at(T![')']) { @@ -265,6 +274,9 @@ fn builtin_expr(p: &mut Parser<'_>) -> Option { } } p.expect(T![')']); + if wrapped_in_parens { + p.expect(T![')']); + } Some(m.complete(p, OFFSET_OF_EXPR)) } else if p.at_contextual_kw(T![format_args]) { p.bump_remap(T![format_args]); diff --git a/crates/parser/test_data/generated/runner.rs b/crates/parser/test_data/generated/runner.rs index 2ea29345ed..a8fe61e7af 100644 --- a/crates/parser/test_data/generated/runner.rs +++ b/crates/parser/test_data/generated/runner.rs @@ -416,6 +416,10 @@ mod ok { run_and_expect_no_errors("test_data/parser/inline/ok/nocontentexpr_after_item.rs"); } #[test] + fn offset_of_parens() { + run_and_expect_no_errors("test_data/parser/inline/ok/offset_of_parens.rs"); + } + #[test] fn or_pattern() { run_and_expect_no_errors("test_data/parser/inline/ok/or_pattern.rs"); } #[test] fn param_list() { run_and_expect_no_errors("test_data/parser/inline/ok/param_list.rs"); } diff --git a/crates/parser/test_data/parser/inline/ok/offset_of_parens.rast b/crates/parser/test_data/parser/inline/ok/offset_of_parens.rast new file mode 100644 index 0000000000..4e23455cfc --- /dev/null +++ b/crates/parser/test_data/parser/inline/ok/offset_of_parens.rast @@ -0,0 +1,42 @@ +SOURCE_FILE + FN + FN_KW "fn" + WHITESPACE " " + NAME + IDENT "foo" + PARAM_LIST + L_PAREN "(" + R_PAREN ")" + WHITESPACE " " + BLOCK_EXPR + STMT_LIST + L_CURLY "{" + WHITESPACE "\n " + EXPR_STMT + OFFSET_OF_EXPR + BUILTIN_KW "builtin" + POUND "#" + OFFSET_OF_KW "offset_of" + L_PAREN "(" + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Foo" + COMMA "," + WHITESPACE " " + L_PAREN "(" + NAME_REF + IDENT "bar" + DOT "." + NAME_REF + IDENT "baz" + DOT "." + NAME_REF + INT_NUMBER "0" + R_PAREN ")" + R_PAREN ")" + SEMICOLON ";" + WHITESPACE "\n" + R_CURLY "}" + WHITESPACE "\n" diff --git a/crates/parser/test_data/parser/inline/ok/offset_of_parens.rs b/crates/parser/test_data/parser/inline/ok/offset_of_parens.rs new file mode 100644 index 0000000000..a797d5c820 --- /dev/null +++ b/crates/parser/test_data/parser/inline/ok/offset_of_parens.rs @@ -0,0 +1,3 @@ +fn foo() { + builtin#offset_of(Foo, (bar.baz.0)); +}