Merge pull request #18608 from Veykril/push-rrozmpnyknkx

Better parser recovery for paths
This commit is contained in:
Lukas Wirth 2024-12-04 06:18:39 +00:00 committed by GitHub
commit ab652f7795
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 154 additions and 40 deletions

View file

@ -1759,8 +1759,9 @@ fn f() {
// NAME_REF@6..7 // NAME_REF@6..7
// IDENT@6..7 "K" // IDENT@6..7 "K"
// COLON2@7..9 "::" // COLON2@7..9 "::"
// ERROR@9..10 // PATH_SEGMENT@9..10
// L_PAREN@9..10 "(" // ERROR@9..10
// L_PAREN@9..10 "("
// EXPR_STMT@10..16 // EXPR_STMT@10..16
// CALL_EXPR@10..16 // CALL_EXPR@10..16
// PATH_EXPR@10..11 // PATH_EXPR@10..11

View file

@ -242,7 +242,7 @@ fn opt_visibility(p: &mut Parser<'_>, in_tuple_field: bool) -> bool {
// struct MyStruct(pub ()); // struct MyStruct(pub ());
if !(in_tuple_field && matches!(p.nth(1), T![ident] | T![')'])) { if !(in_tuple_field && matches!(p.nth(1), T![ident] | T![')'])) {
p.bump(T!['(']); p.bump(T!['(']);
paths::use_path(p); paths::vis_path(p);
p.expect(T![')']); p.expect(T![')']);
} }
} }
@ -252,7 +252,7 @@ fn opt_visibility(p: &mut Parser<'_>, in_tuple_field: bool) -> bool {
T![in] => { T![in] => {
p.bump(T!['(']); p.bump(T!['(']);
p.bump(T![in]); p.bump(T![in]);
paths::use_path(p); paths::vis_path(p);
p.expect(T![')']); p.expect(T![')']);
} }
_ => {} _ => {}

View file

@ -36,6 +36,14 @@ fn attr(p: &mut Parser<'_>, inner: bool) {
attr.complete(p, ATTR); attr.complete(p, ATTR);
} }
// test_err meta_recovery
// #![]
// #![p = ]
// #![p::]
// #![p:: =]
// #![unsafe]
// #![unsafe =]
// test metas // test metas
// #![simple_ident] // #![simple_ident]
// #![simple::path] // #![simple::path]
@ -63,7 +71,7 @@ pub(super) fn meta(p: &mut Parser<'_>) {
if is_unsafe { if is_unsafe {
p.expect(T!['(']); p.expect(T!['(']);
} }
paths::use_path(p); paths::attr_path(p);
match p.current() { match p.current() {
T![=] => { T![=] => {

View file

@ -168,10 +168,10 @@ pub(super) fn const_arg_expr(p: &mut Parser<'_>) {
expressions::literal(p); expressions::literal(p);
lm.complete(p, PREFIX_EXPR); lm.complete(p, PREFIX_EXPR);
} }
_ if paths::is_use_path_start(p) => { _ if paths::is_path_start(p) => {
// This shouldn't be hit by `const_arg` // This shouldn't be hit by `const_arg`
let lm = p.start(); let lm = p.start();
paths::use_path(p); paths::expr_path(p);
lm.complete(p, PATH_EXPR); lm.complete(p, PATH_EXPR);
} }
_ => { _ => {

View file

@ -19,6 +19,14 @@ pub(super) fn use_path(p: &mut Parser<'_>) {
path(p, Mode::Use); path(p, Mode::Use);
} }
pub(super) fn vis_path(p: &mut Parser<'_>) {
path(p, Mode::Vis);
}
pub(super) fn attr_path(p: &mut Parser<'_>) {
path(p, Mode::Attr);
}
pub(crate) fn type_path(p: &mut Parser<'_>) { pub(crate) fn type_path(p: &mut Parser<'_>) {
path(p, Mode::Type); path(p, Mode::Type);
} }
@ -37,15 +45,20 @@ pub(crate) fn type_path_for_qualifier(
#[derive(Clone, Copy, Eq, PartialEq)] #[derive(Clone, Copy, Eq, PartialEq)]
enum Mode { enum Mode {
Use, Use,
Attr,
Type, Type,
Expr, Expr,
Vis,
} }
fn path(p: &mut Parser<'_>, mode: Mode) { fn path(p: &mut Parser<'_>, mode: Mode) -> Option<CompletedMarker> {
let path = p.start(); let path = p.start();
path_segment(p, mode, true); if path_segment(p, mode, true).is_none() {
path.abandon(p);
return None;
}
let qual = path.complete(p, PATH); let qual = path.complete(p, PATH);
path_for_qualifier(p, mode, qual); Some(path_for_qualifier(p, mode, qual))
} }
fn path_for_qualifier( fn path_for_qualifier(
@ -71,7 +84,7 @@ const EXPR_PATH_SEGMENT_RECOVERY_SET: TokenSet =
items::ITEM_RECOVERY_SET.union(TokenSet::new(&[T![')'], T![,], T![let]])); items::ITEM_RECOVERY_SET.union(TokenSet::new(&[T![')'], T![,], T![let]]));
const TYPE_PATH_SEGMENT_RECOVERY_SET: TokenSet = types::TYPE_RECOVERY_SET; const TYPE_PATH_SEGMENT_RECOVERY_SET: TokenSet = types::TYPE_RECOVERY_SET;
fn path_segment(p: &mut Parser<'_>, mode: Mode, first: bool) { fn path_segment(p: &mut Parser<'_>, mode: Mode, first: bool) -> Option<CompletedMarker> {
let m = p.start(); let m = p.start();
// test qual_paths // test qual_paths
// type X = <A as B>::Output; // type X = <A as B>::Output;
@ -93,12 +106,7 @@ fn path_segment(p: &mut Parser<'_>, mode: Mode, first: bool) {
p.error("expected `::`"); p.error("expected `::`");
} }
} else { } else {
let empty = if first { let mut empty = if first { !p.eat(T![::]) } else { true };
p.eat(T![::]);
false
} else {
true
};
match p.current() { match p.current() {
IDENT => { IDENT => {
name_ref(p); name_ref(p);
@ -114,25 +122,29 @@ fn path_segment(p: &mut Parser<'_>, mode: Mode, first: bool) {
_ => { _ => {
let recover_set = match mode { let recover_set = match mode {
Mode::Use => items::ITEM_RECOVERY_SET, Mode::Use => items::ITEM_RECOVERY_SET,
Mode::Attr => {
items::ITEM_RECOVERY_SET.union(TokenSet::new(&[T![']'], T![=], T![#]]))
}
Mode::Vis => items::ITEM_RECOVERY_SET.union(TokenSet::new(&[T![')']])),
Mode::Type => TYPE_PATH_SEGMENT_RECOVERY_SET, Mode::Type => TYPE_PATH_SEGMENT_RECOVERY_SET,
Mode::Expr => EXPR_PATH_SEGMENT_RECOVERY_SET, Mode::Expr => EXPR_PATH_SEGMENT_RECOVERY_SET,
}; };
p.err_recover("expected identifier", recover_set); empty &= p.err_recover("expected identifier", recover_set);
if empty { if empty {
// test_err empty_segment // test_err empty_segment
// use crate::; // use crate::;
m.abandon(p); m.abandon(p);
return; return None;
} }
} }
}; };
} }
m.complete(p, PATH_SEGMENT); Some(m.complete(p, PATH_SEGMENT))
} }
fn opt_path_type_args(p: &mut Parser<'_>, mode: Mode) { fn opt_path_type_args(p: &mut Parser<'_>, mode: Mode) {
match mode { match mode {
Mode::Use => {} Mode::Use | Mode::Attr | Mode::Vis => {}
Mode::Type => { Mode::Type => {
// test typepathfn_with_coloncolon // test typepathfn_with_coloncolon
// type F = Start::(Middle) -> (Middle)::End; // type F = Start::(Middle) -> (Middle)::End;

View file

@ -258,22 +258,25 @@ impl<'t> Parser<'t> {
self.err_recover(message, TokenSet::EMPTY); self.err_recover(message, TokenSet::EMPTY);
} }
/// Create an error node and consume the next token. /// Create an error node and consume the next token unless it is in the recovery set.
pub(crate) fn err_recover(&mut self, message: &str, recovery: TokenSet) { ///
/// Returns true if recovery kicked in.
pub(crate) fn err_recover(&mut self, message: &str, recovery: TokenSet) -> bool {
if matches!(self.current(), T!['{'] | T!['}']) { if matches!(self.current(), T!['{'] | T!['}']) {
self.error(message); self.error(message);
return; return true;
} }
if self.at_ts(recovery) { if self.at_ts(recovery) {
self.error(message); self.error(message);
return; return true;
} }
let m = self.start(); let m = self.start();
self.error(message); self.error(message);
self.bump_any(); self.bump_any();
m.complete(self, ERROR); m.complete(self, ERROR);
false
} }
fn do_bump(&mut self, kind: SyntaxKind, n_raw_tokens: u8) { fn do_bump(&mut self, kind: SyntaxKind, n_raw_tokens: u8) {
@ -324,10 +327,10 @@ impl Marker {
self.bomb.defuse(); self.bomb.defuse();
let idx = self.pos as usize; let idx = self.pos as usize;
if idx == p.events.len() - 1 { if idx == p.events.len() - 1 {
match p.events.pop() { assert!(matches!(
Some(Event::Start { kind: TOMBSTONE, forward_parent: None }) => (), p.events.pop(),
_ => unreachable!(), Some(Event::Start { kind: TOMBSTONE, forward_parent: None })
} ));
} }
} }
} }

View file

@ -772,6 +772,8 @@ mod err {
run_and_expect_errors("test_data/parser/inline/err/match_arms_recovery.rs"); run_and_expect_errors("test_data/parser/inline/err/match_arms_recovery.rs");
} }
#[test] #[test]
fn meta_recovery() { run_and_expect_errors("test_data/parser/inline/err/meta_recovery.rs"); }
#[test]
fn method_call_missing_argument_list() { fn method_call_missing_argument_list() {
run_and_expect_errors("test_data/parser/inline/err/method_call_missing_argument_list.rs"); run_and_expect_errors("test_data/parser/inline/err/method_call_missing_argument_list.rs");
} }

View file

@ -9,7 +9,8 @@ SOURCE_FILE
NAME_REF NAME_REF
IDENT "foo" IDENT "foo"
COLON2 "::" COLON2 "::"
ERROR PATH_SEGMENT
INT_NUMBER "92" ERROR
INT_NUMBER "92"
SEMICOLON ";" SEMICOLON ";"
error 9: expected identifier error 9: expected identifier

View file

@ -39,8 +39,9 @@ SOURCE_FILE
IDENT "lol" IDENT "lol"
R_ANGLE ">" R_ANGLE ">"
COLON2 "::" COLON2 "::"
ERROR PATH_SEGMENT
L_ANGLE "<" ERROR
L_ANGLE "<"
TYPE_ARG TYPE_ARG
PATH_TYPE PATH_TYPE
PATH PATH
@ -91,8 +92,9 @@ SOURCE_FILE
IDENT "lol" IDENT "lol"
R_ANGLE ">" R_ANGLE ">"
COLON2 "::" COLON2 "::"
ERROR PATH_SEGMENT
L_ANGLE "<" ERROR
L_ANGLE "<"
EXPR_STMT EXPR_STMT
BIN_EXPR BIN_EXPR
PATH_EXPR PATH_EXPR

View file

@ -3,10 +3,7 @@ SOURCE_FILE
VISIBILITY VISIBILITY
PUB_KW "pub" PUB_KW "pub"
L_PAREN "(" L_PAREN "("
PATH R_PAREN ")"
PATH_SEGMENT
ERROR
R_PAREN ")"
WHITESPACE " " WHITESPACE " "
STRUCT_KW "struct" STRUCT_KW "struct"
WHITESPACE " " WHITESPACE " "
@ -15,4 +12,3 @@ SOURCE_FILE
SEMICOLON ";" SEMICOLON ";"
WHITESPACE "\n" WHITESPACE "\n"
error 4: expected identifier error 4: expected identifier
error 5: expected R_PAREN

View file

@ -0,0 +1,83 @@
SOURCE_FILE
ATTR
POUND "#"
BANG "!"
L_BRACK "["
META
R_BRACK "]"
WHITESPACE "\n"
ATTR
POUND "#"
BANG "!"
L_BRACK "["
META
PATH
PATH_SEGMENT
NAME_REF
IDENT "p"
WHITESPACE " "
EQ "="
WHITESPACE " "
R_BRACK "]"
WHITESPACE "\n"
ATTR
POUND "#"
BANG "!"
L_BRACK "["
META
PATH
PATH
PATH_SEGMENT
NAME_REF
IDENT "p"
COLON2 "::"
R_BRACK "]"
WHITESPACE "\n"
ATTR
POUND "#"
BANG "!"
L_BRACK "["
META
PATH
PATH
PATH_SEGMENT
NAME_REF
IDENT "p"
COLON2 "::"
WHITESPACE " "
EQ "="
R_BRACK "]"
WHITESPACE "\n"
ATTR
POUND "#"
BANG "!"
L_BRACK "["
META
UNSAFE_KW "unsafe"
R_BRACK "]"
WHITESPACE "\n"
ATTR
POUND "#"
BANG "!"
L_BRACK "["
META
UNSAFE_KW "unsafe"
WHITESPACE " "
EQ "="
R_BRACK "]"
WHITESPACE "\n"
error 3: expected identifier
error 11: expected expression
error 11: expected expression
error 20: expected identifier
error 28: expected identifier
error 30: expected expression
error 30: expected expression
error 41: expected L_PAREN
error 41: expected identifier
error 41: expected R_PAREN
error 52: expected L_PAREN
error 52: expected identifier
error 54: expected expression
error 54: expected expression
error 54: expected R_PAREN

View file

@ -0,0 +1,6 @@
#![]
#![p = ]
#![p::]
#![p:: =]
#![unsafe]
#![unsafe =]