Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit cecbed9

Browse files
committed
Auto merge of rust-lang#15360 - lowr:fix/mbe-transcribe-path-of-different-types, r=HKalbasi
Fixup path fragments upon MBE transcription Fixes rust-lang#14367 There are roughly two types of paths: paths in expression context, where a separator `::` between an identifier and its following generic argument list is mandatory, and paths in type context, where `::` can be omitted. Unlike rustc, we need to transform the parsed fragments back into tokens during transcription. When the matched path fragment is a type-context path and is transcribed as an expression-context path, verbatim transcription would cause a syntax error. This PR fixes up path fragments by inserting `::` to make sure they are syntactically correct in all contexts. Note that this works because expression-context paths are a strict superset of type-context paths.
2 parents 712b538 + fd7435d commit cecbed9

File tree

5 files changed

+89
-5
lines changed

5 files changed

+89
-5
lines changed

crates/hir-def/src/macro_expansion_tests/mbe.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -848,6 +848,37 @@ fn foo() {
848848
);
849849
}
850850

851+
#[test]
852+
fn test_type_path_is_transcribed_as_expr_path() {
853+
check(
854+
r#"
855+
macro_rules! m {
856+
($p:path) => { let $p; }
857+
}
858+
fn test() {
859+
m!(S)
860+
m!(S<i32>)
861+
m!(S<S<i32>>)
862+
m!(S<{ module::CONST < 42 }>)
863+
}
864+
"#,
865+
expect![[r#"
866+
macro_rules! m {
867+
($p:path) => { let $p; }
868+
}
869+
fn test() {
870+
let S;
871+
let S:: <i32> ;
872+
let S:: <S:: <i32>> ;
873+
let S:: < {
874+
module::CONST<42
875+
}
876+
> ;
877+
}
878+
"#]],
879+
);
880+
}
881+
851882
#[test]
852883
fn test_expr() {
853884
check(

crates/mbe/src/expander.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,4 +123,14 @@ enum Fragment {
123123
/// proc-macro delimiter=none. As we later discovered, "none" delimiters are
124124
/// tricky to handle in the parser, and rustc doesn't handle those either.
125125
Expr(tt::TokenTree),
126+
/// There are roughly two types of paths: paths in expression context, where a
127+
/// separator `::` between an identifier and its following generic argument list
128+
/// is mandatory, and paths in type context, where `::` can be omitted.
129+
///
130+
/// Unlike rustc, we need to transform the parsed fragments back into tokens
131+
/// during transcription. When the matched path fragment is a type-context path
132+
/// and is trasncribed as an expression-context path, verbatim transcription
133+
/// would cause a syntax error. We need to fix it up just before transcribing;
134+
/// see `transcriber::fix_up_and_push_path_tt()`.
135+
Path(tt::TokenTree),
126136
}

crates/mbe/src/expander/matcher.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -742,7 +742,11 @@ fn match_meta_var(
742742
is_2021: bool,
743743
) -> ExpandResult<Option<Fragment>> {
744744
let fragment = match kind {
745-
MetaVarKind::Path => parser::PrefixEntryPoint::Path,
745+
MetaVarKind::Path => {
746+
return input
747+
.expect_fragment(parser::PrefixEntryPoint::Path)
748+
.map(|it| it.map(Fragment::Path));
749+
}
746750
MetaVarKind::Ty => parser::PrefixEntryPoint::Ty,
747751
MetaVarKind::Pat if is_2021 => parser::PrefixEntryPoint::PatTop,
748752
MetaVarKind::Pat => parser::PrefixEntryPoint::Pat,
@@ -771,7 +775,7 @@ fn match_meta_var(
771775
.expect_fragment(parser::PrefixEntryPoint::Expr)
772776
.map(|tt| tt.map(Fragment::Expr));
773777
}
774-
_ => {
778+
MetaVarKind::Ident | MetaVarKind::Tt | MetaVarKind::Lifetime | MetaVarKind::Literal => {
775779
let tt_result = match kind {
776780
MetaVarKind::Ident => input
777781
.expect_ident()
@@ -799,7 +803,7 @@ fn match_meta_var(
799803
})
800804
.map_err(|()| ExpandError::binding_error("expected literal"))
801805
}
802-
_ => Err(ExpandError::UnexpectedToken),
806+
_ => unreachable!(),
803807
};
804808
return tt_result.map(|it| Some(Fragment::Tokens(it))).into();
805809
}

crates/mbe/src/expander/transcriber.rs

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -400,7 +400,8 @@ fn push_fragment(buf: &mut Vec<tt::TokenTree>, fragment: Fragment) {
400400
}
401401
buf.push(tt.into())
402402
}
403-
Fragment::Tokens(tt) | Fragment::Expr(tt) => buf.push(tt),
403+
Fragment::Path(tt::TokenTree::Subtree(tt)) => fix_up_and_push_path_tt(buf, tt),
404+
Fragment::Tokens(tt) | Fragment::Expr(tt) | Fragment::Path(tt) => buf.push(tt),
404405
}
405406
}
406407

@@ -411,6 +412,45 @@ fn push_subtree(buf: &mut Vec<tt::TokenTree>, tt: tt::Subtree) {
411412
}
412413
}
413414

415+
/// Inserts the path separator `::` between an identifier and its following generic
416+
/// argument list, and then pushes into the buffer. See [`Fragment::Path`] for why
417+
/// we need this fixup.
418+
fn fix_up_and_push_path_tt(buf: &mut Vec<tt::TokenTree>, subtree: tt::Subtree) {
419+
stdx::always!(matches!(subtree.delimiter.kind, tt::DelimiterKind::Invisible));
420+
let mut prev_was_ident = false;
421+
// Note that we only need to fix up the top-level `TokenTree`s because the
422+
// context of the paths in the descendant `Subtree`s won't be changed by the
423+
// mbe transcription.
424+
for tt in subtree.token_trees {
425+
if prev_was_ident {
426+
// Pedantically, `(T) -> U` in `FnOnce(T) -> U` is treated as a generic
427+
// argument list and thus needs `::` between it and `FnOnce`. However in
428+
// today's Rust this type of path *semantically* cannot appear as a
429+
// top-level expression-context path, so we can safely ignore it.
430+
if let tt::TokenTree::Leaf(tt::Leaf::Punct(tt::Punct { char: '<', .. })) = tt {
431+
buf.push(
432+
tt::Leaf::Punct(tt::Punct {
433+
char: ':',
434+
spacing: tt::Spacing::Joint,
435+
span: tt::Span::unspecified(),
436+
})
437+
.into(),
438+
);
439+
buf.push(
440+
tt::Leaf::Punct(tt::Punct {
441+
char: ':',
442+
spacing: tt::Spacing::Alone,
443+
span: tt::Span::unspecified(),
444+
})
445+
.into(),
446+
);
447+
}
448+
}
449+
prev_was_ident = matches!(tt, tt::TokenTree::Leaf(tt::Leaf::Ident(_)));
450+
buf.push(tt);
451+
}
452+
}
453+
414454
/// Handles `${count(t, depth)}`. `our_depth` is the recursion depth and `count_depth` is the depth
415455
/// defined by the metavar expression.
416456
fn count(

crates/tt/src/lib.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,6 @@ impl_from!(Literal<Span>, Punct<Span>, Ident<Span> for Leaf);
122122

123123
#[derive(Clone, PartialEq, Eq, Hash)]
124124
pub struct Subtree<Span> {
125-
// FIXME, this should not be Option
126125
pub delimiter: Delimiter<Span>,
127126
pub token_trees: Vec<TokenTree<Span>>,
128127
}

0 commit comments

Comments
 (0)