diff --git a/crates/hir-def/src/body/lower.rs b/crates/hir-def/src/body/lower.rs index b87e94f9c5..6117664c64 100644 --- a/crates/hir-def/src/body/lower.rs +++ b/crates/hir-def/src/body/lower.rs @@ -1598,6 +1598,10 @@ impl ExprCollector<'_> { for (id, _) in current_is_used.into_iter() { binding_list.check_is_used(self, id); } + if let &[pat] = &*pats { + // Leading pipe without real OR pattern. Leaving an one-item OR pattern may confuse later stages. + return pat; + } Pat::Or(pats.into()) } ast::Pat::ParenPat(p) => return self.collect_pat_opt(p.pat(), binding_list), diff --git a/crates/ide-assists/src/handlers/unmerge_match_arm.rs b/crates/ide-assists/src/handlers/unmerge_match_arm.rs index db789cfa33..648bf358b4 100644 --- a/crates/ide-assists/src/handlers/unmerge_match_arm.rs +++ b/crates/ide-assists/src/handlers/unmerge_match_arm.rs @@ -34,6 +34,9 @@ use crate::{AssistContext, AssistId, AssistKind, Assists}; pub(crate) fn unmerge_match_arm(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { let pipe_token = ctx.find_token_syntax_at_offset(T![|])?; let or_pat = ast::OrPat::cast(pipe_token.parent()?)?.clone_for_update(); + if or_pat.leading_pipe().is_some_and(|it| it == pipe_token) { + return None; + } let match_arm = ast::MatchArm::cast(or_pat.syntax().parent()?)?; let match_arm_body = match_arm.expr()?; diff --git a/crates/parser/src/grammar/patterns.rs b/crates/parser/src/grammar/patterns.rs index 3db1a10ca5..ed01fca2ac 100644 --- a/crates/parser/src/grammar/patterns.rs +++ b/crates/parser/src/grammar/patterns.rs @@ -21,7 +21,8 @@ const RANGE_PAT_END_FIRST: TokenSet = expressions::LITERAL_FIRST.union(paths::PATH_FIRST).union(TokenSet::new(&[T![-], T![const]])); pub(crate) fn pattern(p: &mut Parser<'_>) { - pattern_r(p, PAT_RECOVERY_SET); + let m = p.start(); + pattern_r(p, m, false, PAT_RECOVERY_SET); } /// Parses a pattern list separated by pipes `|`. @@ -36,8 +37,9 @@ pub(crate) fn pattern_single(p: &mut Parser<'_>) { /// Parses a pattern list separated by pipes `|` /// using the given `recovery_set`. pub(super) fn pattern_top_r(p: &mut Parser<'_>, recovery_set: TokenSet) { - p.eat(T![|]); - pattern_r(p, recovery_set); + let m = p.start(); + let has_leading_pipe = p.eat(T![|]); + pattern_r(p, m, has_leading_pipe, recovery_set); } // test or_pattern @@ -51,11 +53,10 @@ pub(super) fn pattern_top_r(p: &mut Parser<'_>, recovery_set: TokenSet) { // } /// Parses a pattern list separated by pipes `|`, with no leading `|`,using the /// given `recovery_set`. -fn pattern_r(p: &mut Parser<'_>, recovery_set: TokenSet) { - let m = p.start(); +fn pattern_r(p: &mut Parser<'_>, m: Marker, has_leading_pipe: bool, recovery_set: TokenSet) { pattern_single_r(p, recovery_set); - if !p.at(T![|]) { + if !p.at(T![|]) && !has_leading_pipe { m.abandon(p); return; } diff --git a/crates/parser/src/tests/top_entries.rs b/crates/parser/src/tests/top_entries.rs index c56bf0b644..7076e03ba4 100644 --- a/crates/parser/src/tests/top_entries.rs +++ b/crates/parser/src/tests/top_entries.rs @@ -195,6 +195,38 @@ fn macro_pattern() { error 0: expected pattern "#]], ); + + check( + TopEntryPoint::Pattern, + "| 42 | 43", + expect![[r#" + OR_PAT + PIPE "|" + WHITESPACE " " + LITERAL_PAT + LITERAL + INT_NUMBER "42" + WHITESPACE " " + PIPE "|" + WHITESPACE " " + LITERAL_PAT + LITERAL + INT_NUMBER "43" + "#]], + ); + + check( + TopEntryPoint::Pattern, + "| 42", + expect![[r#" + OR_PAT + PIPE "|" + WHITESPACE " " + LITERAL_PAT + LITERAL + INT_NUMBER "42" + "#]], + ); } #[test] diff --git a/crates/parser/test_data/parser/inline/ok/match_arm.rast b/crates/parser/test_data/parser/inline/ok/match_arm.rast index 8189cf0a8e..9221028162 100644 --- a/crates/parser/test_data/parser/inline/ok/match_arm.rast +++ b/crates/parser/test_data/parser/inline/ok/match_arm.rast @@ -102,9 +102,9 @@ SOURCE_FILE COMMA "," WHITESPACE "\n " MATCH_ARM - PIPE "|" - WHITESPACE " " OR_PAT + PIPE "|" + WHITESPACE " " IDENT_PAT NAME IDENT "X" @@ -132,11 +132,12 @@ SOURCE_FILE COMMA "," WHITESPACE "\n " MATCH_ARM - PIPE "|" - WHITESPACE " " - IDENT_PAT - NAME - IDENT "X" + OR_PAT + PIPE "|" + WHITESPACE " " + IDENT_PAT + NAME + IDENT "X" WHITESPACE " " FAT_ARROW "=>" WHITESPACE " " diff --git a/crates/parser/test_data/parser/inline/ok/slice_pat.rast b/crates/parser/test_data/parser/inline/ok/slice_pat.rast index dff72ba886..06c30bba59 100644 --- a/crates/parser/test_data/parser/inline/ok/slice_pat.rast +++ b/crates/parser/test_data/parser/inline/ok/slice_pat.rast @@ -43,11 +43,12 @@ SOURCE_FILE WHITESPACE " " SLICE_PAT L_BRACK "[" - PIPE "|" - WHITESPACE " " - IDENT_PAT - NAME - IDENT "a" + OR_PAT + PIPE "|" + WHITESPACE " " + IDENT_PAT + NAME + IDENT "a" COMMA "," WHITESPACE " " REST_PAT diff --git a/crates/parser/test_data/parser/inline/ok/tuple_pat.rast b/crates/parser/test_data/parser/inline/ok/tuple_pat.rast index 1a01e0f693..c7cd11f774 100644 --- a/crates/parser/test_data/parser/inline/ok/tuple_pat.rast +++ b/crates/parser/test_data/parser/inline/ok/tuple_pat.rast @@ -91,9 +91,9 @@ SOURCE_FILE WHITESPACE " " TUPLE_PAT L_PAREN "(" - PIPE "|" - WHITESPACE " " OR_PAT + PIPE "|" + WHITESPACE " " IDENT_PAT NAME IDENT "a" @@ -105,11 +105,12 @@ SOURCE_FILE IDENT "a" COMMA "," WHITESPACE " " - PIPE "|" - WHITESPACE " " - IDENT_PAT - NAME - IDENT "b" + OR_PAT + PIPE "|" + WHITESPACE " " + IDENT_PAT + NAME + IDENT "b" R_PAREN ")" WHITESPACE " " EQ "=" diff --git a/crates/parser/test_data/parser/inline/ok/tuple_pat_fields.rast b/crates/parser/test_data/parser/inline/ok/tuple_pat_fields.rast index 55baf2fdcb..96353f4697 100644 --- a/crates/parser/test_data/parser/inline/ok/tuple_pat_fields.rast +++ b/crates/parser/test_data/parser/inline/ok/tuple_pat_fields.rast @@ -110,11 +110,12 @@ SOURCE_FILE NAME_REF IDENT "S" L_PAREN "(" - PIPE "|" - WHITESPACE " " - IDENT_PAT - NAME - IDENT "a" + OR_PAT + PIPE "|" + WHITESPACE " " + IDENT_PAT + NAME + IDENT "a" R_PAREN ")" WHITESPACE " " EQ "=" diff --git a/crates/syntax/rust.ungram b/crates/syntax/rust.ungram index 3609da0bb2..02c59646a9 100644 --- a/crates/syntax/rust.ungram +++ b/crates/syntax/rust.ungram @@ -736,7 +736,7 @@ PathPat = Path OrPat = - (Pat ('|' Pat)* '|'?) + '|'? (Pat ('|' Pat)*) BoxPat = 'box' Pat diff --git a/crates/syntax/src/ast/generated/nodes.rs b/crates/syntax/src/ast/generated/nodes.rs index b9eb1abb11..23d2b355a9 100644 --- a/crates/syntax/src/ast/generated/nodes.rs +++ b/crates/syntax/src/ast/generated/nodes.rs @@ -1283,6 +1283,8 @@ pub struct OrPat { impl OrPat { #[inline] pub fn pats(&self) -> AstChildren { support::children(&self.syntax) } + #[inline] + pub fn pipe_token(&self) -> Option { support::token(&self.syntax, T![|]) } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] diff --git a/crates/syntax/src/ast/node_ext.rs b/crates/syntax/src/ast/node_ext.rs index 11f5e662e3..6ec73e76f7 100644 --- a/crates/syntax/src/ast/node_ext.rs +++ b/crates/syntax/src/ast/node_ext.rs @@ -1140,3 +1140,13 @@ impl From for ast::AnyHasAttrs { Self::new(node) } } + +impl ast::OrPat { + pub fn leading_pipe(&self) -> Option { + self.syntax + .children_with_tokens() + .find(|it| !it.kind().is_trivia()) + .and_then(NodeOrToken::into_token) + .filter(|it| it.kind() == T![|]) + } +}