Skip to content

Commit ebcfec9

Browse files
committed
mbe: Refactor concat diagnostics
1 parent fe64282 commit ebcfec9

File tree

11 files changed

+362
-442
lines changed

11 files changed

+362
-442
lines changed

compiler/rustc_expand/messages.ftl

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -133,20 +133,23 @@ expand_module_multiple_candidates =
133133
expand_must_repeat_once =
134134
this must repeat at least once
135135

136-
expand_mve_concat_invalid =
137-
invalid item within a `{"${concat(...)}"}` expression
138-
.expr_ident = expanding this `concat(...)` expression
139-
.invalid_ident = this literal produced an invalid identifier
136+
expand_mve_concat_invalid_in =
137+
invalid item within a {"`${concat(..)}`"} expression
138+
.metavar_label = expanding this metavariable
140139
.float_lit = float literals cannot be concatenated
141140
.c_str_lit = C string literals cannot be concatenated
142141
.b_str_lit = byte literals cannot be concatenated
143142
.raw_ident = raw identifiers cannot be concatenated
144-
.unsupported = unsupported input for `concat(...)`
143+
.unsupported = unsupported input for `concat(..)`
145144
.valid_types = `concat` can join {$valid}
146-
.expected_metavar = expected an identifier; got `{$found}`
147-
.expected_metavar_dollar = todo
148-
.unexpected_group = todo
149-
.hi_label = todo
145+
.expected_metavar = expected an identifier
146+
.expected_metavar_dollar = `$` indicates the start of a metavariable
147+
.invalid_metavar = expanding something
148+
.valid_metavars = {"`${concat(..)}`"} can join metavariables of type {$valid}
149+
150+
expand_mve_concat_invalid_out =
151+
invalid item within a {"`${concat(..)}`"} would produce an invalid identifier
152+
.label = todo
150153

151154
expand_mve_expected_ident =
152155
expected an identifier

compiler/rustc_expand/src/errors.rs

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -541,48 +541,58 @@ mod metavar_exprs {
541541
}
542542

543543
#[derive(Diagnostic)]
544-
#[diag(expand_mve_concat_invalid)]
545-
pub(crate) struct MveConcatInvalid {
544+
#[diag(expand_mve_concat_invalid_in)]
545+
pub(crate) struct MveConcatInvalidTy {
546546
#[primary_span]
547+
// #[label]
547548
pub span: Span,
549+
#[label(expand_metavar_label)]
550+
pub metavar_span: Option<Span>,
548551
#[subdiagnostic]
549-
pub reason: MveConcatInvalidReason,
550-
#[help(expand_expr_ident)]
551-
pub ident_span: Span,
552+
pub reason: MveConcatInvalidTyReason,
552553
pub valid: &'static str,
553554
}
554555

555556
// TODO: can these be labels rather than notes?
556557
#[derive(Subdiagnostic)]
557-
pub(crate) enum MveConcatInvalidReason {
558-
#[note(expand_invalid_ident)]
558+
pub(crate) enum MveConcatInvalidTyReason {
559559
InvalidIdent,
560560
#[note(expand_float_lit)]
561-
#[help(expand_valid_types)]
561+
#[note(expand_valid_types)]
562562
FloatLit,
563563
#[note(expand_c_str_lit)]
564-
#[help(expand_valid_types)]
564+
#[note(expand_valid_types)]
565565
CStrLit,
566566
#[note(expand_b_str_lit)]
567-
#[help(expand_valid_types)]
567+
#[note(expand_valid_types)]
568568
ByteStrLit,
569569
#[note(expand_expected_metavar)]
570570
#[label(expand_expected_metavar_dollar)]
571571
ExpectedMetavarIdent {
572-
found: String,
573572
#[primary_span]
574573
dollar: Span,
575574
},
576575
#[note(expand_raw_ident)]
577576
RawIdentifier,
578577
#[note(expand_unsupported)]
579-
#[help(expand_valid_types)]
578+
#[note(expand_valid_types)]
580579
UnsupportedInput,
581-
#[note(expand_unexpected_group)]
582-
UnexpectedGroup,
580+
#[note(expand_invalid_metavar)]
581+
#[note(expand_valid_metavars)]
582+
InvalidMetavarTy,
583+
/// Nothing to point out because an error was already emitted.
583584
InvalidLiteral,
584585
}
585586

587+
#[derive(Diagnostic)]
588+
#[note]
589+
#[diag(expand_mve_concat_invalid_out)]
590+
pub(crate) struct MveConcatInvalidOut {
591+
#[primary_span]
592+
#[label]
593+
pub span: Span,
594+
}
595+
586596
#[derive(Diagnostic)]
587597
#[diag(expand_mve_expected_ident)]
588598
pub(crate) struct MveExpectedIdent {

compiler/rustc_expand/src/mbe/metavar_expr.rs

Lines changed: 70 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,15 @@ use rustc_ast::token::{self, Delimiter, IdentIsRaw, Token, TokenKind};
22
use rustc_ast::tokenstream::{TokenStream, TokenStreamIter, TokenTree};
33
use rustc_ast::{self as ast, LitIntType, LitKind};
44
use rustc_ast_pretty::pprust;
5-
use rustc_errors::PResult;
5+
use rustc_errors::{DiagCtxtHandle, PResult};
66
use rustc_lexer::is_id_continue;
77
use rustc_macros::{Decodable, Encodable};
88
use rustc_session::errors::create_lit_error;
99
use rustc_session::parse::ParseSess;
1010
use rustc_span::{Ident, Span, Symbol};
1111

12-
use crate::errors::{self, MveConcatInvalidReason, MveExpectedIdentContext};
12+
use crate::errors::{self, MveConcatInvalidTyReason, MveExpectedIdentContext};
1313

14-
pub(crate) const RAW_IDENT_ERR: &str = "`${concat(..)}` currently does not support raw identifiers";
1514
pub(crate) const VALID_EXPR_CONCAT_TYPES: &str =
1615
"metavariables, identifiers, string literals, and integer literals";
1716

@@ -218,74 +217,32 @@ fn parse_concat<'psess>(
218217
};
219218

220219
let make_err = |reason| {
221-
let err = errors::MveConcatInvalid {
220+
let err = errors::MveConcatInvalidTy {
222221
span: tt.span(),
223-
ident_span: expr_ident_span,
222+
metavar_span: None,
224223
reason,
225224
valid: VALID_EXPR_CONCAT_TYPES,
226225
};
227226
Err(dcx.create_err(err))
228227
};
229228

230229
let token = match tt {
231-
TokenTree::Token(token, _) => token,
230+
TokenTree::Token(token, _) => *token,
232231
TokenTree::Delimited(..) => {
233-
return make_err(MveConcatInvalidReason::UnexpectedGroup);
232+
return make_err(MveConcatInvalidTyReason::UnsupportedInput);
234233
}
235234
};
236235

237236
let element = if let Some(dollar) = dollar {
238237
// Expecting a metavar
239238
let Some((ident, _)) = token.ident() else {
240-
return make_err(MveConcatInvalidReason::ExpectedMetavarIdent {
241-
found: pprust::token_to_string(token).into_owned(),
242-
dollar,
243-
});
239+
return make_err(MveConcatInvalidTyReason::ExpectedMetavarIdent { dollar });
244240
};
245241

246242
// Variables get passed untouched
247243
MetaVarExprConcatElem::Var(ident)
248-
} else if let TokenKind::Literal(lit) = token.kind {
249-
// Preprocess with `from_token_lit` to handle unescaping, float / int literal suffix
250-
// stripping.
251-
//
252-
// For consistent user experience, please keep this in sync with the handling of
253-
// literals in `rustc_builtin_macros::concat`!
254-
let s = match ast::LitKind::from_token_lit(lit.clone()) {
255-
Ok(ast::LitKind::Str(s, _)) => s.to_string(),
256-
Ok(ast::LitKind::Float(..)) => {
257-
return make_err(MveConcatInvalidReason::FloatLit);
258-
}
259-
Ok(ast::LitKind::Char(c)) => c.to_string(),
260-
Ok(ast::LitKind::Int(i, _)) => i.to_string(),
261-
Ok(ast::LitKind::Bool(b)) => b.to_string(),
262-
Ok(ast::LitKind::CStr(..)) => return make_err(MveConcatInvalidReason::CStrLit),
263-
Ok(ast::LitKind::Byte(..) | ast::LitKind::ByteStr(..)) => {
264-
return make_err(MveConcatInvalidReason::ByteStrLit);
265-
}
266-
Ok(ast::LitKind::Err(_guarantee)) => {
267-
// REVIEW: a diagnostic was already emitted, should we just break?
268-
return make_err(MveConcatInvalidReason::InvalidLiteral);
269-
}
270-
Err(err) => return Err(create_lit_error(psess, err, lit, token.span)),
271-
};
272-
273-
if !s.chars().all(|ch| is_id_continue(ch)) {
274-
// Check that all characters are valid in the middle of an identifier. This doesn't
275-
// guarantee that the final identifier is valid (we still need to check it later),
276-
// but it allows us to catch errors with specific arguments before expansion time;
277-
// for example, string literal "foo.bar" gets flagged before the macro is invoked.
278-
return make_err(MveConcatInvalidReason::InvalidIdent);
279-
}
280-
281-
MetaVarExprConcatElem::Ident(s)
282-
} else if let Some((elem, is_raw)) = token.ident() {
283-
if is_raw == IdentIsRaw::Yes {
284-
return make_err(MveConcatInvalidReason::RawIdentifier);
285-
}
286-
MetaVarExprConcatElem::Ident(elem.as_str().to_string())
287244
} else {
288-
return make_err(MveConcatInvalidReason::UnsupportedInput);
245+
MetaVarExprConcatElem::Ident(parse_tok_for_concat(psess, token)?)
289246
};
290247

291248
result.push(element);
@@ -352,6 +309,68 @@ fn parse_depth<'psess>(
352309
}
353310
}
354311

312+
/// Validate that a token can be concatenated as an identifier, then stringify it.
313+
pub(super) fn parse_tok_for_concat<'psess>(
314+
psess: &'psess ParseSess,
315+
token: Token,
316+
) -> PResult<'psess, String> {
317+
let dcx = psess.dcx();
318+
let make_err = |reason| {
319+
let err = errors::MveConcatInvalidTy {
320+
span: token.span,
321+
metavar_span: None,
322+
reason,
323+
valid: VALID_EXPR_CONCAT_TYPES,
324+
};
325+
Err(dcx.create_err(err))
326+
};
327+
328+
let elem = if let TokenKind::Literal(lit) = token.kind {
329+
// Preprocess with `from_token_lit` to handle unescaping, float / int literal suffix
330+
// stripping.
331+
//
332+
// For consistent user experience, please keep this in sync with the handling of
333+
// literals in `rustc_builtin_macros::concat`!
334+
let s = match ast::LitKind::from_token_lit(lit.clone()) {
335+
Ok(ast::LitKind::Str(s, _)) => s.to_string(),
336+
Ok(ast::LitKind::Float(..)) => {
337+
return make_err(MveConcatInvalidTyReason::FloatLit);
338+
}
339+
Ok(ast::LitKind::Char(c)) => c.to_string(),
340+
Ok(ast::LitKind::Int(i, _)) => i.to_string(),
341+
Ok(ast::LitKind::Bool(b)) => b.to_string(),
342+
Ok(ast::LitKind::CStr(..)) => return make_err(MveConcatInvalidTyReason::CStrLit),
343+
Ok(ast::LitKind::Byte(..) | ast::LitKind::ByteStr(..)) => {
344+
return make_err(MveConcatInvalidTyReason::ByteStrLit);
345+
}
346+
Ok(ast::LitKind::Err(_guarantee)) => {
347+
// REVIEW: a diagnostic was already emitted, should we just break?
348+
return make_err(MveConcatInvalidTyReason::InvalidLiteral);
349+
}
350+
Err(err) => return Err(create_lit_error(psess, err, lit, token.span)),
351+
};
352+
353+
if !s.chars().all(|ch| is_id_continue(ch)) {
354+
// Check that all characters are valid in the middle of an identifier. This doesn't
355+
// guarantee that the final identifier is valid (we still need to check it later),
356+
// but it allows us to catch errors with specific arguments before expansion time;
357+
// for example, string literal "foo.bar" gets flagged before the macro is invoked.
358+
return make_err(MveConcatInvalidTyReason::InvalidIdent);
359+
}
360+
361+
s
362+
} else if let Some((elem, is_raw)) = token.ident() {
363+
if is_raw == IdentIsRaw::Yes {
364+
return make_err(MveConcatInvalidTyReason::RawIdentifier);
365+
}
366+
elem.as_str().to_string()
367+
} else {
368+
return make_err(MveConcatInvalidTyReason::UnsupportedInput);
369+
};
370+
371+
Ok(elem)
372+
}
373+
355374
/// Tries to parse a generic ident. If this fails, create a missing identifier diagnostic with
356375
/// `context` explanation.
357376
fn parse_ident<'psess>(

compiler/rustc_expand/src/mbe/transcribe.rs

Lines changed: 34 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,18 @@ use rustc_span::{
1616
};
1717
use smallvec::{SmallVec, smallvec};
1818

19+
use super::metavar_expr::parse_tok_for_concat;
1920
use crate::errors::{
20-
CountRepetitionMisplaced, MetaVarsDifSeqMatchers, MustRepeatOnce, MveUnrecognizedVar,
21+
self, CountRepetitionMisplaced, MetaVarsDifSeqMatchers, MustRepeatOnce, MveUnrecognizedVar,
2122
NoSyntaxVarsExprRepeat, VarStillRepeating,
2223
};
2324
use crate::mbe::macro_parser::NamedMatch;
2425
use crate::mbe::macro_parser::NamedMatch::*;
25-
use crate::mbe::metavar_expr::{MetaVarExprConcatElem, RAW_IDENT_ERR};
26+
use crate::mbe::metavar_expr::{MetaVarExprConcatElem, VALID_EXPR_CONCAT_TYPES};
2627
use crate::mbe::{self, KleeneOp, MetaVarExpr};
2728

29+
const VALID_METAVAR_CONCAT_TYPES: &str = "`ident`, `literal`, and `tt`";
30+
2831
/// Context needed to perform transcription of metavariable expressions.
2932
struct TranscrCtx<'psess, 'itp> {
3033
psess: &'psess ParseSess,
@@ -905,48 +908,37 @@ fn out_of_bounds_err<'a>(dcx: DiagCtxtHandle<'a>, max: usize, span: Span, ty: &s
905908
}
906909

907910
/// Extracts an metavariable symbol that can be an identifier, a token tree or a literal.
908-
// TODO: use the same logic as for metavar_expr
909911
fn extract_symbol_from_pnr<'tx>(
910912
tscx: &mut TranscrCtx<'tx, '_>,
911913
pnr: &ParseNtResult,
912-
span_err: Span,
913-
) -> PResult<'tx, Symbol> {
914-
let dcx = tscx.psess.dcx();
915-
match pnr {
916-
ParseNtResult::Ident(nt_ident, is_raw) => {
917-
if let IdentIsRaw::Yes = is_raw {
918-
Err(dcx.struct_span_err(span_err, RAW_IDENT_ERR))
919-
} else {
920-
Ok(nt_ident.name)
921-
}
922-
}
923-
ParseNtResult::Tt(TokenTree::Token(
924-
Token { kind: TokenKind::Ident(symbol, is_raw), .. },
925-
_,
926-
)) => {
927-
if let IdentIsRaw::Yes = is_raw {
928-
Err(dcx.struct_span_err(span_err, RAW_IDENT_ERR))
929-
} else {
930-
Ok(*symbol)
931-
}
914+
metavar_span: Span,
915+
) -> PResult<'tx, String> {
916+
// Reconstruct a `Token` so we can share logic with expression parsing.
917+
let token = match pnr {
918+
ParseNtResult::Tt(TokenTree::Token(tok, _)) => *tok,
919+
ParseNtResult::Ident(Ident { name, span }, ident_is_raw) => {
920+
Token { kind: TokenKind::Ident(*name, *ident_is_raw), span: *span }
921+
}
922+
923+
ParseNtResult::Literal(l) if let ExprKind::Lit(lit) = l.kind => {
924+
Token { kind: TokenKind::Literal(lit), span: l.span }
925+
}
926+
_ => {
927+
let err = errors::MveConcatInvalidTy {
928+
span: pnr.span(),
929+
metavar_span: Some(metavar_span),
930+
reason: errors::MveConcatInvalidTyReason::InvalidMetavarTy,
931+
valid: VALID_METAVAR_CONCAT_TYPES,
932+
};
933+
return Err(tscx.psess.dcx().create_err(err));
932934
}
933-
ParseNtResult::Tt(TokenTree::Token(
934-
Token {
935-
kind: TokenKind::Literal(Lit { kind: LitKind::Str, symbol, suffix: None }),
936-
..
937-
},
938-
_,
939-
)) => Ok(*symbol),
940-
ParseNtResult::Literal(expr)
941-
if let ExprKind::Lit(Lit { kind: LitKind::Str, symbol, suffix: None }) = &expr.kind =>
942-
{
943-
Ok(*symbol)
944-
}
945-
_ => Err(dcx
946-
.struct_err(
947-
"metavariables of `${concat(..)}` must be of type `ident`, `literal` or `tt`",
948-
)
949-
.with_note("currently only string literals are supported")
950-
.with_span(span_err)),
951-
}
935+
};
936+
937+
parse_tok_for_concat(tscx.psess, token)
938+
// _ => Err(dcx
939+
// .struct_err(
940+
// "metavariables of `${concat(..)}` must be of type `ident`, `literal` or `tt`",
941+
// )
942+
// .with_note("currently only string literals are supported")
943+
// .with_span(span_err)),
952944
}

compiler/rustc_parse/src/parser/mod.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1657,3 +1657,24 @@ pub enum ParseNtResult {
16571657
Path(P<ast::Path>),
16581658
Vis(P<ast::Visibility>),
16591659
}
1660+
1661+
impl ParseNtResult {
1662+
#[inline]
1663+
pub fn span(&self) -> Span {
1664+
match self {
1665+
ParseNtResult::Tt(token_tree) => token_tree.span(),
1666+
ParseNtResult::Ident(ident, ..) => ident.span,
1667+
ParseNtResult::Lifetime(ident, ..) => ident.span,
1668+
ParseNtResult::Item(item) => item.span,
1669+
ParseNtResult::Block(block) => block.span,
1670+
ParseNtResult::Stmt(stmt) => stmt.span,
1671+
ParseNtResult::Pat(pat, ..) => pat.span,
1672+
ParseNtResult::Expr(expr, ..) => expr.span,
1673+
ParseNtResult::Literal(expr) => expr.span,
1674+
ParseNtResult::Ty(ty) => ty.span,
1675+
ParseNtResult::Meta(attr_item) => attr_item.span(),
1676+
ParseNtResult::Path(path) => path.span,
1677+
ParseNtResult::Vis(visibility) => visibility.span,
1678+
}
1679+
}
1680+
}

0 commit comments

Comments
 (0)