Skip to content

Commit b576c49

Browse files
committed
mbe: Refactor concat diagnostics
1 parent 847b9f7 commit b576c49

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
@@ -498,48 +498,58 @@ mod metavar_exprs {
498498
use super::*;
499499

500500
#[derive(Diagnostic)]
501-
#[diag(expand_mve_concat_invalid)]
502-
pub(crate) struct MveConcatInvalid {
501+
#[diag(expand_mve_concat_invalid_in)]
502+
pub(crate) struct MveConcatInvalidTy {
503503
#[primary_span]
504+
// #[label]
504505
pub span: Span,
506+
#[label(expand_metavar_label)]
507+
pub metavar_span: Option<Span>,
505508
#[subdiagnostic]
506-
pub reason: MveConcatInvalidReason,
507-
#[help(expand_expr_ident)]
508-
pub ident_span: Span,
509+
pub reason: MveConcatInvalidTyReason,
509510
pub valid: &'static str,
510511
}
511512

512513
// TODO: can these be labels rather than notes?
513514
#[derive(Subdiagnostic)]
514-
pub(crate) enum MveConcatInvalidReason {
515-
#[note(expand_invalid_ident)]
515+
pub(crate) enum MveConcatInvalidTyReason {
516516
InvalidIdent,
517517
#[note(expand_float_lit)]
518-
#[help(expand_valid_types)]
518+
#[note(expand_valid_types)]
519519
FloatLit,
520520
#[note(expand_c_str_lit)]
521-
#[help(expand_valid_types)]
521+
#[note(expand_valid_types)]
522522
CStrLit,
523523
#[note(expand_b_str_lit)]
524-
#[help(expand_valid_types)]
524+
#[note(expand_valid_types)]
525525
ByteStrLit,
526526
#[note(expand_expected_metavar)]
527527
#[label(expand_expected_metavar_dollar)]
528528
ExpectedMetavarIdent {
529-
found: String,
530529
#[primary_span]
531530
dollar: Span,
532531
},
533532
#[note(expand_raw_ident)]
534533
RawIdentifier,
535534
#[note(expand_unsupported)]
536-
#[help(expand_valid_types)]
535+
#[note(expand_valid_types)]
537536
UnsupportedInput,
538-
#[note(expand_unexpected_group)]
539-
UnexpectedGroup,
537+
#[note(expand_invalid_metavar)]
538+
#[note(expand_valid_metavars)]
539+
InvalidMetavarTy,
540+
/// Nothing to point out because an error was already emitted.
540541
InvalidLiteral,
541542
}
542543

544+
#[derive(Diagnostic)]
545+
#[note]
546+
#[diag(expand_mve_concat_invalid_out)]
547+
pub(crate) struct MveConcatInvalidOut {
548+
#[primary_span]
549+
#[label]
550+
pub span: Span,
551+
}
552+
543553
#[derive(Diagnostic)]
544554
#[diag(expand_mve_expected_ident)]
545555
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

@@ -193,74 +192,32 @@ fn parse_concat<'psess>(
193192
};
194193

195194
let make_err = |reason| {
196-
let err = errors::MveConcatInvalid {
195+
let err = errors::MveConcatInvalidTy {
197196
span: tt.span(),
198-
ident_span: expr_ident_span,
197+
metavar_span: None,
199198
reason,
200199
valid: VALID_EXPR_CONCAT_TYPES,
201200
};
202201
Err(dcx.create_err(err))
203202
};
204203

205204
let token = match tt {
206-
TokenTree::Token(token, _) => token,
205+
TokenTree::Token(token, _) => *token,
207206
TokenTree::Delimited(..) => {
208-
return make_err(MveConcatInvalidReason::UnexpectedGroup);
207+
return make_err(MveConcatInvalidTyReason::UnsupportedInput);
209208
}
210209
};
211210

212211
let element = if let Some(dollar) = dollar {
213212
// Expecting a metavar
214213
let Some((ident, _)) = token.ident() else {
215-
return make_err(MveConcatInvalidReason::ExpectedMetavarIdent {
216-
found: pprust::token_to_string(token).into_owned(),
217-
dollar,
218-
});
214+
return make_err(MveConcatInvalidTyReason::ExpectedMetavarIdent { dollar });
219215
};
220216

221217
// Variables get passed untouched
222218
MetaVarExprConcatElem::Var(ident)
223-
} else if let TokenKind::Literal(lit) = token.kind {
224-
// Preprocess with `from_token_lit` to handle unescaping, float / int literal suffix
225-
// stripping.
226-
//
227-
// For consistent user experience, please keep this in sync with the handling of
228-
// literals in `rustc_builtin_macros::concat`!
229-
let s = match ast::LitKind::from_token_lit(lit.clone()) {
230-
Ok(ast::LitKind::Str(s, _)) => s.to_string(),
231-
Ok(ast::LitKind::Float(..)) => {
232-
return make_err(MveConcatInvalidReason::FloatLit);
233-
}
234-
Ok(ast::LitKind::Char(c)) => c.to_string(),
235-
Ok(ast::LitKind::Int(i, _)) => i.to_string(),
236-
Ok(ast::LitKind::Bool(b)) => b.to_string(),
237-
Ok(ast::LitKind::CStr(..)) => return make_err(MveConcatInvalidReason::CStrLit),
238-
Ok(ast::LitKind::Byte(..) | ast::LitKind::ByteStr(..)) => {
239-
return make_err(MveConcatInvalidReason::ByteStrLit);
240-
}
241-
Ok(ast::LitKind::Err(_guarantee)) => {
242-
// REVIEW: a diagnostic was already emitted, should we just break?
243-
return make_err(MveConcatInvalidReason::InvalidLiteral);
244-
}
245-
Err(err) => return Err(create_lit_error(psess, err, lit, token.span)),
246-
};
247-
248-
if !s.chars().all(|ch| is_id_continue(ch)) {
249-
// Check that all characters are valid in the middle of an identifier. This doesn't
250-
// guarantee that the final identifier is valid (we still need to check it later),
251-
// but it allows us to catch errors with specific arguments before expansion time;
252-
// for example, string literal "foo.bar" gets flagged before the macro is invoked.
253-
return make_err(MveConcatInvalidReason::InvalidIdent);
254-
}
255-
256-
MetaVarExprConcatElem::Ident(s)
257-
} else if let Some((elem, is_raw)) = token.ident() {
258-
if is_raw == IdentIsRaw::Yes {
259-
return make_err(MveConcatInvalidReason::RawIdentifier);
260-
}
261-
MetaVarExprConcatElem::Ident(elem.as_str().to_string())
262219
} else {
263-
return make_err(MveConcatInvalidReason::UnsupportedInput);
220+
MetaVarExprConcatElem::Ident(parse_tok_for_concat(psess, token)?)
264221
};
265222

266223
result.push(element);
@@ -327,6 +284,68 @@ fn parse_depth<'psess>(
327284
}
328285
}
329286

287+
/// Validate that a token can be concatenated as an identifier, then stringify it.
288+
pub(super) fn parse_tok_for_concat<'psess>(
289+
psess: &'psess ParseSess,
290+
token: Token,
291+
) -> PResult<'psess, String> {
292+
let dcx = psess.dcx();
293+
let make_err = |reason| {
294+
let err = errors::MveConcatInvalidTy {
295+
span: token.span,
296+
metavar_span: None,
297+
reason,
298+
valid: VALID_EXPR_CONCAT_TYPES,
299+
};
300+
Err(dcx.create_err(err))
301+
};
302+
303+
let elem = if let TokenKind::Literal(lit) = token.kind {
304+
// Preprocess with `from_token_lit` to handle unescaping, float / int literal suffix
305+
// stripping.
306+
//
307+
// For consistent user experience, please keep this in sync with the handling of
308+
// literals in `rustc_builtin_macros::concat`!
309+
let s = match ast::LitKind::from_token_lit(lit.clone()) {
310+
Ok(ast::LitKind::Str(s, _)) => s.to_string(),
311+
Ok(ast::LitKind::Float(..)) => {
312+
return make_err(MveConcatInvalidTyReason::FloatLit);
313+
}
314+
Ok(ast::LitKind::Char(c)) => c.to_string(),
315+
Ok(ast::LitKind::Int(i, _)) => i.to_string(),
316+
Ok(ast::LitKind::Bool(b)) => b.to_string(),
317+
Ok(ast::LitKind::CStr(..)) => return make_err(MveConcatInvalidTyReason::CStrLit),
318+
Ok(ast::LitKind::Byte(..) | ast::LitKind::ByteStr(..)) => {
319+
return make_err(MveConcatInvalidTyReason::ByteStrLit);
320+
}
321+
Ok(ast::LitKind::Err(_guarantee)) => {
322+
// REVIEW: a diagnostic was already emitted, should we just break?
323+
return make_err(MveConcatInvalidTyReason::InvalidLiteral);
324+
}
325+
Err(err) => return Err(create_lit_error(psess, err, lit, token.span)),
326+
};
327+
328+
if !s.chars().all(|ch| is_id_continue(ch)) {
329+
// Check that all characters are valid in the middle of an identifier. This doesn't
330+
// guarantee that the final identifier is valid (we still need to check it later),
331+
// but it allows us to catch errors with specific arguments before expansion time;
332+
// for example, string literal "foo.bar" gets flagged before the macro is invoked.
333+
return make_err(MveConcatInvalidTyReason::InvalidIdent);
334+
}
335+
336+
s
337+
} else if let Some((elem, is_raw)) = token.ident() {
338+
if is_raw == IdentIsRaw::Yes {
339+
return make_err(MveConcatInvalidTyReason::RawIdentifier);
340+
}
341+
elem.as_str().to_string()
342+
} else {
343+
return make_err(MveConcatInvalidTyReason::UnsupportedInput);
344+
};
345+
346+
Ok(elem)
347+
}
348+
330349
/// Tries to parse a generic ident. If this fails, create a missing identifier diagnostic with
331350
/// `context` explanation.
332351
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)