Skip to content

Commit 80729d7

Browse files
committed
Auto merge of #155662 - mejrs:this_the_thing, r=petrochenkov
Permit `{This}` in diagnostic attribute format literals My motivation was that yesterday I wanted to write something like this and reference `$name` in the string literal. ```rust pub mod sym { // stuff here } macro_rules! my_macro { ($name:ident $(,)?) => {{ #[diagnostic::on_unknown( message = "this is not present in symbol table", note = "you must add it to rustc_span::symbol::symbol!" )] use sym::$name as name; // ... }} } ``` That is (as far as I can tell) impossible or at least very unergonomic. This adds the ability to just reference the name of the item the attribute is on. I imagine that's useful for use inside macros generally, so it's also added for some other attributes. The affected attributes are all unstable, it is not implemented for diagnostic::on_unimplemented (will do in its own PR). Note that `{This}` is already usable in `#[rustc_on_unimplemented]`, so this does not implement it but just enables some more. This PR also migrates one lint away from AttributeLintKind, and improves the messages for that lint.
2 parents 7c61a35 + c2916be commit 80729d7

23 files changed

Lines changed: 368 additions & 204 deletions

File tree

compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs

Lines changed: 87 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ use rustc_hir::attrs::diagnostic::{
66
Directive, FilterFormatString, Flag, FormatArg, FormatString, LitOrArg, Name, NameValue,
77
OnUnimplementedCondition, Piece, Predicate,
88
};
9-
use rustc_hir::lints::FormatWarning;
109
use rustc_macros::Diagnostic;
1110
use rustc_parse_format::{
1211
Argument, FormatSpec, ParseError, ParseMode, Parser, Piece as RpfPiece, Position,
@@ -19,9 +18,8 @@ use thin_vec::{ThinVec, thin_vec};
1918

2019
use crate::context::{AcceptContext, Stage};
2120
use crate::errors::{
22-
DisallowedPlaceholder, DisallowedPositionalArgument, IgnoredDiagnosticOption,
23-
InvalidFormatSpecifier, MalFormedDiagnosticAttributeLint, MissingOptionsForDiagnosticAttribute,
24-
NonMetaItemDiagnosticAttribute, WrappedParserError,
21+
FormatWarning, IgnoredDiagnosticOption, MalFormedDiagnosticAttributeLint,
22+
MissingOptionsForDiagnosticAttribute, NonMetaItemDiagnosticAttribute, WrappedParserError,
2523
};
2624
use crate::parser::{ArgParser, MetaItemListParser, MetaItemOrLitParser, MetaItemParser};
2725

@@ -88,6 +86,29 @@ impl Mode {
8886
Self::DiagnosticOnUnmatchArgs => DEFAULT,
8987
}
9088
}
89+
90+
fn allowed_format_arguments(&self) -> &'static str {
91+
match self {
92+
Self::RustcOnUnimplemented => {
93+
"see <https://rustc-dev-guide.rust-lang.org/diagnostics.html#rustc_on_unimplemented> for allowed format arguments"
94+
}
95+
Self::DiagnosticOnUnimplemented => {
96+
"only `Self` and generics of the trait are allowed as a format argument"
97+
}
98+
Self::DiagnosticOnConst => {
99+
"only `Self` and generics of the implementation are allowed as a format argument"
100+
}
101+
Self::DiagnosticOnMove => {
102+
"only `This`, `Self` and generics of the type are allowed as a format argument"
103+
}
104+
Self::DiagnosticOnUnknown => {
105+
"only `This` is allowed as a format argument, referring to the failed import"
106+
}
107+
Self::DiagnosticOnUnmatchArgs => {
108+
"only `This` is allowed as a format argument, referring to the macro's name"
109+
}
110+
}
111+
}
91112
}
92113

93114
fn merge_directives<S: Stage>(
@@ -257,22 +278,13 @@ fn parse_directive_items<'p, S: Stage>(
257278
match parse_format_string(input.name, snippet, input.span, mode) {
258279
Ok((f, warnings)) => {
259280
for warning in warnings {
260-
let (FormatWarning::InvalidSpecifier { span, .. }
261-
| FormatWarning::PositionalArgument { span, .. }
262-
| FormatWarning::DisallowedPlaceholder { span }) = warning;
281+
let (FormatWarning::InvalidSpecifier { span }
282+
| FormatWarning::PositionalArgument { span }
283+
| FormatWarning::IndexedArgument { span }
284+
| FormatWarning::DisallowedPlaceholder { span, .. }) = warning;
263285
cx.emit_dyn_lint(
264286
MALFORMED_DIAGNOSTIC_FORMAT_LITERALS,
265-
move |dcx, level| match warning {
266-
FormatWarning::PositionalArgument { .. } => {
267-
DisallowedPositionalArgument.into_diag(dcx, level)
268-
}
269-
FormatWarning::InvalidSpecifier { .. } => {
270-
InvalidFormatSpecifier.into_diag(dcx, level)
271-
}
272-
FormatWarning::DisallowedPlaceholder { .. } => {
273-
DisallowedPlaceholder.into_diag(dcx, level)
274-
}
275-
},
287+
move |dcx, level| warning.into_diag(dcx, level),
276288
span,
277289
);
278290
}
@@ -422,38 +434,74 @@ fn parse_arg(
422434
is_source_literal: bool,
423435
) -> FormatArg {
424436
let span = slice_span(input_span, arg.position_span.clone(), is_source_literal);
425-
if matches!(mode, Mode::DiagnosticOnUnknown) {
426-
warnings.push(FormatWarning::DisallowedPlaceholder { span });
427-
return FormatArg::AsIs(sym::empty_braces);
428-
}
429437

430438
match arg.position {
431439
// Something like "hello {name}"
432440
Position::ArgumentNamed(name) => match (mode, Symbol::intern(name)) {
433-
// Only `#[rustc_on_unimplemented]` can use these
434-
(Mode::RustcOnUnimplemented { .. }, sym::ItemContext) => FormatArg::ItemContext,
435-
(Mode::RustcOnUnimplemented { .. } | Mode::DiagnosticOnUnmatchArgs, sym::This) => {
436-
FormatArg::This
441+
(Mode::RustcOnUnimplemented, sym::ItemContext) => FormatArg::ItemContext,
442+
443+
// Like `{This}`, but sugared.
444+
// FIXME(mejrs) maybe rename/rework this or something
445+
// if we want to apply this to other attrs?
446+
(Mode::RustcOnUnimplemented, sym::Trait) => FormatArg::Trait,
447+
448+
// Some diagnostic attributes can use `{This}` to refer to the annotated item.
449+
// For those that don't, we continue and maybe use it as a generic parameter.
450+
//
451+
// FIXME(mejrs) `DiagnosticOnUnimplemented` is intentionally not here;
452+
// that requires lang approval which is best kept for a standalone PR.
453+
(
454+
Mode::RustcOnUnimplemented
455+
| Mode::DiagnosticOnUnknown
456+
| Mode::DiagnosticOnMove
457+
| Mode::DiagnosticOnUnmatchArgs,
458+
sym::This,
459+
) => FormatArg::This,
460+
461+
// `{Self}`; the self type.
462+
// - For trait declaration attributes that's the type that does not implement it.
463+
// - for trait impl attributes, the implemented for type.
464+
// - For ADT attributes, that's the type (which will be identical to `{This}`)
465+
// - For everything else it doesn't make sense.
466+
(
467+
Mode::RustcOnUnimplemented
468+
| Mode::DiagnosticOnUnimplemented
469+
| Mode::DiagnosticOnMove
470+
| Mode::DiagnosticOnConst,
471+
kw::SelfUpper,
472+
) => FormatArg::SelfUpper,
473+
474+
// Generic parameters.
475+
// FIXME(mejrs) unfortunately, all the "special" symbols above might fall through,
476+
// but at this time we are not aware of what generic parameters the trait actually has.
477+
// If we find `ItemContext` or something we have to assume that's a generic parameter.
478+
// We lint against that in `check_attr.rs` though.
479+
(
480+
Mode::RustcOnUnimplemented
481+
| Mode::DiagnosticOnUnimplemented
482+
| Mode::DiagnosticOnMove
483+
| Mode::DiagnosticOnConst,
484+
generic_param,
485+
) => FormatArg::GenericParam { generic_param, span },
486+
487+
// Generics are explicitly not allowed, we print those back as is.
488+
(Mode::DiagnosticOnUnknown | Mode::DiagnosticOnUnmatchArgs, as_is) => {
489+
warnings.push(FormatWarning::DisallowedPlaceholder {
490+
span,
491+
attr: mode.as_str(),
492+
allowed: mode.allowed_format_arguments(),
493+
});
494+
return FormatArg::AsIs(Symbol::intern(&format!("{{{as_is}}}")));
437495
}
438-
(Mode::RustcOnUnimplemented { .. }, sym::Trait) => FormatArg::Trait,
439-
// Any attribute can use these
440-
(_, kw::SelfUpper) => FormatArg::SelfUpper,
441-
(_, generic_param) => FormatArg::GenericParam { generic_param, span },
442496
},
443497

444498
// `{:1}` and `{}` are ignored
445499
Position::ArgumentIs(idx) => {
446-
warnings.push(FormatWarning::PositionalArgument {
447-
span,
448-
help: format!("use `{{{idx}}}` to print a number in braces"),
449-
});
500+
warnings.push(FormatWarning::IndexedArgument { span });
450501
FormatArg::AsIs(Symbol::intern(&format!("{{{idx}}}")))
451502
}
452503
Position::ArgumentImplicitlyIs(_) => {
453-
warnings.push(FormatWarning::PositionalArgument {
454-
span,
455-
help: String::from("use `{{}}` to print empty braces"),
456-
});
504+
warnings.push(FormatWarning::PositionalArgument { span });
457505
FormatArg::AsIs(sym::empty_braces)
458506
}
459507
}
@@ -473,7 +521,7 @@ fn warn_on_format_spec(
473521
.as_ref()
474522
.map(|inner| slice_span(input_span, inner.clone(), is_source_literal))
475523
.unwrap_or(input_span);
476-
warnings.push(FormatWarning::InvalidSpecifier { span, name: spec.ty.into() })
524+
warnings.push(FormatWarning::InvalidSpecifier { span })
477525
}
478526
}
479527

compiler/rustc_attr_parsing/src/errors.rs

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -357,23 +357,6 @@ pub(crate) struct MalFormedDiagnosticAttributeLint {
357357
pub span: Span,
358358
}
359359

360-
#[derive(Diagnostic)]
361-
#[diag("positional format arguments are not allowed here")]
362-
#[help(
363-
"only named format arguments with the name of one of the generic types are allowed in this context"
364-
)]
365-
pub(crate) struct DisallowedPositionalArgument;
366-
367-
#[derive(Diagnostic)]
368-
#[diag("format arguments are not allowed here")]
369-
#[help("consider removing this format argument")]
370-
pub(crate) struct DisallowedPlaceholder;
371-
372-
#[derive(Diagnostic)]
373-
#[diag("invalid format specifier")]
374-
#[help("no format specifier are supported in this position")]
375-
pub(crate) struct InvalidFormatSpecifier;
376-
377360
#[derive(Diagnostic)]
378361
#[diag("{$description}")]
379362
pub(crate) struct WrappedParserError<'a> {
@@ -407,3 +390,34 @@ pub(crate) struct MissingOptionsForDiagnosticAttribute {
407390
"only literals are allowed as values for the `message`, `note` and `label` options. These options must be separated by a comma"
408391
)]
409392
pub(crate) struct NonMetaItemDiagnosticAttribute;
393+
394+
#[derive(Diagnostic, Clone, Copy)]
395+
pub(crate) enum FormatWarning {
396+
#[diag("positional arguments are not permitted in diagnostic attributes")]
397+
#[help("you can print empty braces by escaping them")]
398+
PositionalArgument {
399+
#[label("remove this format argument")]
400+
span: Span,
401+
},
402+
403+
#[diag("indexed format arguments are not permitted in diagnostic attributes")]
404+
IndexedArgument {
405+
#[label("remove this format argument")]
406+
span: Span,
407+
},
408+
409+
#[diag("format specifiers are not permitted in diagnostic attributes")]
410+
InvalidSpecifier {
411+
#[label("remove this format specifier")]
412+
span: Span,
413+
},
414+
415+
#[diag("this format argument is not allowed in `#[{$attr}]`")]
416+
#[note("{$allowed}")]
417+
DisallowedPlaceholder {
418+
#[label("remove this format argument")]
419+
span: Span,
420+
attr: &'static str,
421+
allowed: &'static str,
422+
},
423+
}

compiler/rustc_hir/src/lints.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
use rustc_data_structures::sync::{DynSend, DynSync};
22
use rustc_error_messages::MultiSpan;
33
use rustc_errors::{Diag, DiagCtxtHandle, Level};
4+
pub use rustc_lint_defs::AttributeLintKind;
45
use rustc_lint_defs::LintId;
5-
pub use rustc_lint_defs::{AttributeLintKind, FormatWarning};
66

77
use crate::HirId;
88

compiler/rustc_lint_defs/src/lib.rs

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -658,13 +658,6 @@ pub enum AttributeLintKind {
658658
UnexpectedCfgValue((Symbol, Span), Option<(Symbol, Span)>),
659659
}
660660

661-
#[derive(Debug, Clone, HashStable_Generic)]
662-
pub enum FormatWarning {
663-
PositionalArgument { span: Span, help: String },
664-
InvalidSpecifier { name: String, span: Span },
665-
DisallowedPlaceholder { span: Span },
666-
}
667-
668661
pub type RegisteredTools = FxIndexSet<Ident>;
669662

670663
/// Declares a static item of type `&'static Lint`.

compiler/rustc_passes/src/check_attr.rs

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -201,9 +201,6 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
201201
},
202202
Attribute::Parsed(AttributeKind::OnUnimplemented{directive,..}) => {self.check_diagnostic_on_unimplemented(hir_id, directive.as_deref())},
203203
Attribute::Parsed(AttributeKind::OnConst{span, ..}) => {self.check_diagnostic_on_const(*span, hir_id, target, item)},
204-
Attribute::Parsed(AttributeKind::OnUnmatchArgs { directive, .. }) => {
205-
self.check_diagnostic_on_unmatch_args(hir_id, directive.as_deref())
206-
},
207204
Attribute::Parsed(AttributeKind::OnMove { directive , .. }) => {
208205
self.check_diagnostic_on_move(hir_id, directive.as_deref())
209206
},
@@ -256,6 +253,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
256253
| AttributeKind::NoMangle(..)
257254
| AttributeKind::NoStd { .. }
258255
| AttributeKind::OnUnknown { .. }
256+
| AttributeKind::OnUnmatchArgs { .. }
259257
| AttributeKind::Optimize(..)
260258
| AttributeKind::PanicRuntime
261259
| AttributeKind::PatchableFunctionEntry { .. }
@@ -562,20 +560,6 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
562560
// ...whose generics would that be, anyway? The traits' or the impls'?
563561
}
564562

565-
/// Checks use of generic formatting parameters in `#[diagnostic::on_unmatch_args]`.
566-
fn check_diagnostic_on_unmatch_args(&self, hir_id: HirId, directive: Option<&Directive>) {
567-
if let Some(directive) = directive {
568-
directive.visit_params(&mut |argument_name, span| {
569-
self.tcx.emit_node_span_lint(
570-
MALFORMED_DIAGNOSTIC_FORMAT_LITERALS,
571-
hir_id,
572-
span,
573-
errors::OnUnmatchArgsMalformedFormatLiterals { name: argument_name },
574-
)
575-
});
576-
}
577-
}
578-
579563
/// Checks use of generic formatting parameters in `#[diagnostic::on_move]`
580564
fn check_diagnostic_on_move(&self, hir_id: HirId, directive: Option<&Directive>) {
581565
if let Some(directive) = directive {

compiler/rustc_passes/src/errors.rs

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1303,10 +1303,3 @@ pub(crate) struct UnknownFormatParameterForOnUnimplementedAttr {
13031303
pub(crate) struct OnMoveMalformedFormatLiterals {
13041304
pub name: Symbol,
13051305
}
1306-
1307-
#[derive(Diagnostic)]
1308-
#[diag("unknown parameter `{$name}`")]
1309-
#[help(r#"use {"`{This}`"} to refer to the macro name"#)]
1310-
pub(crate) struct OnUnmatchArgsMalformedFormatLiterals {
1311-
pub name: Symbol,
1312-
}

compiler/rustc_resolve/src/imports.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
33
use std::mem;
44

5+
use itertools::Itertools;
56
use rustc_ast::{Item, NodeId};
67
use rustc_attr_parsing::AttributeParser;
78
use rustc_data_structures::fx::{FxHashSet, FxIndexSet};
@@ -879,8 +880,14 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
879880
let (message, label, notes) =
880881
// Feature gating for `on_unknown_attr` happens initialization of the field
881882
if let Some(directive) = errors[0].1.on_unknown_attr.as_ref().map(|a| &a.directive) {
883+
let this = errors.iter().map(|(_import, err)| {
884+
885+
// Is this unwrap_or reachable?
886+
err.segment.unwrap_or(kw::Underscore)
887+
}).join(", ");
888+
882889
let args = FormatArgs {
883-
this: paths.join(", "),
890+
this,
884891
// Unused
885892
this_sugared: String::new(),
886893
// Unused

compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ use rustc_hir as hir;
44
use rustc_hir::attrs::diagnostic::{ConditionOptions, CustomDiagnostic, FormatArgs};
55
use rustc_hir::def_id::LocalDefId;
66
use rustc_hir::find_attr;
7-
pub use rustc_hir::lints::FormatWarning;
87
use rustc_middle::ty::print::PrintTraitRefExt;
98
use rustc_middle::ty::{self, GenericParamDef, GenericParamDefKind};
109
use rustc_span::Symbol;

tests/ui/diagnostic_namespace/multiline_spans.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,17 +31,17 @@ pub trait MultiLine4 {}
3131
#[diagnostic::on_unimplemented(message = "here is a big \
3232
multiline string \
3333
{Self:+}")]
34-
//~^ ERROR invalid format specifier [malformed_diagnostic_format_literals]
34+
//~^ ERROR format specifiers are not permitted in diagnostic attributes [malformed_diagnostic_format_literals]
3535
pub trait MultiLineFmt {}
3636

3737
#[diagnostic::on_unimplemented(message = "here is a big \
3838
multiline string {Self:X}")]
39-
//~^ ERROR invalid format specifier [malformed_diagnostic_format_literals]
39+
//~^ ERROR format specifiers are not permitted in diagnostic attributes [malformed_diagnostic_format_literals]
4040
pub trait MultiLineFmt2 {}
4141

4242
#[diagnostic::on_unimplemented(message = "here is a big \
4343
multiline string {Self:#}")]
44-
//~^ ERROR invalid format specifier [malformed_diagnostic_format_literals]
44+
//~^ ERROR format specifiers are not permitted in diagnostic attributes [malformed_diagnostic_format_literals]
4545
pub trait MultiLineFmt3 {}
4646

4747

@@ -51,5 +51,5 @@ pub trait MultiLineFmt3 {}
5151
\
5252
\
5353
multiline string {Self:?}")]
54-
//~^ ERROR invalid format specifier [malformed_diagnostic_format_literals]
54+
//~^ ERROR format specifiers are not permitted in diagnostic attributes [malformed_diagnostic_format_literals]
5555
pub trait MultiLineFmt4 {}

0 commit comments

Comments
 (0)