Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3546,6 +3546,7 @@ dependencies = [
"rustc_lexer",
"rustc_macros",
"rustc_parse",
"rustc_parse_format",
"rustc_session",
"rustc_span",
"rustc_target",
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_attr_parsing/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ rustc_hir = { path = "../rustc_hir" }
rustc_lexer = { path = "../rustc_lexer" }
rustc_macros = { path = "../rustc_macros" }
rustc_parse = { path = "../rustc_parse" }
rustc_parse_format = { path = "../rustc_parse_format" }
rustc_session = { path = "../rustc_session" }
rustc_span = { path = "../rustc_span" }
rustc_target = { path = "../rustc_target" }
Expand Down
117 changes: 117 additions & 0 deletions compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
#![allow(warnings)]

use std::ops::Range;

use rustc_hir::attrs::diagnostic::{FormatArg, FormatString, Piece};
use rustc_hir::lints::FormatWarning;
use rustc_parse_format::{
Argument, FormatSpec, ParseError, ParseMode, Parser, Piece as RpfPiece, Position,
};
use rustc_span::{InnerSpan, Span, Symbol, kw, sym};

pub mod on_unimplemented;

#[derive(Copy, Clone)]
pub(crate) enum Ctx {
// `#[rustc_on_unimplemented]`
RustcOnUnimplemented,
// `#[diagnostic::...]`
DiagnosticOnUnimplemented,
}

pub(crate) fn parse_format_string(
input: Symbol,
snippet: Option<String>,
span: Span,
ctx: Ctx,
) -> Result<(FormatString, Vec<FormatWarning>), ParseError> {
let s = input.as_str();
let mut parser = Parser::new(s, None, snippet, false, ParseMode::Diagnostic);
let pieces: Vec<_> = parser.by_ref().collect();

if let Some(err) = parser.errors.into_iter().next() {
return Err(err);
}
let mut warnings = Vec::new();

let pieces = pieces
.into_iter()
.map(|piece| match piece {
RpfPiece::Lit(lit) => Piece::Lit(Symbol::intern(lit)),
RpfPiece::NextArgument(arg) => {
warn_on_format_spec(&arg.format, &mut warnings, span, parser.is_source_literal);
let arg = parse_arg(&arg, ctx, &mut warnings, span, parser.is_source_literal);
Piece::Arg(arg)
}
})
.collect();

Ok((FormatString { input, pieces, span }, warnings))
}

fn parse_arg(
arg: &Argument<'_>,
ctx: Ctx,
warnings: &mut Vec<FormatWarning>,
input_span: Span,
is_source_literal: bool,
) -> FormatArg {
let span = slice_span(input_span, arg.position_span.clone(), is_source_literal);

match arg.position {
// Something like "hello {name}"
Position::ArgumentNamed(name) => match (ctx, Symbol::intern(name)) {
// Only `#[rustc_on_unimplemented]` can use these
(Ctx::RustcOnUnimplemented { .. }, sym::ItemContext) => FormatArg::ItemContext,
(Ctx::RustcOnUnimplemented { .. }, sym::This) => FormatArg::This,
(Ctx::RustcOnUnimplemented { .. }, sym::Trait) => FormatArg::Trait,
// Any attribute can use these
(
Ctx::RustcOnUnimplemented { .. } | Ctx::DiagnosticOnUnimplemented { .. },
kw::SelfUpper,
) => FormatArg::SelfUpper,
(
Ctx::RustcOnUnimplemented { .. } | Ctx::DiagnosticOnUnimplemented { .. },
generic_param,
) => FormatArg::GenericParam { generic_param, span },
},

// `{:1}` and `{}` are ignored
Position::ArgumentIs(idx) => {
warnings.push(FormatWarning::PositionalArgument {
span,
help: format!("use `{{{idx}}}` to print a number in braces"),
});
FormatArg::AsIs(Symbol::intern(&format!("{{{idx}}}")))
}
Position::ArgumentImplicitlyIs(_) => {
warnings.push(FormatWarning::PositionalArgument {
span,
help: String::from("use `{{}}` to print empty braces"),
});
FormatArg::AsIs(sym::empty_braces)
}
}
}

/// `#[rustc_on_unimplemented]` and `#[diagnostic::...]` don't actually do anything
/// with specifiers, so emit a warning if they are used.
fn warn_on_format_spec(
spec: &FormatSpec<'_>,
warnings: &mut Vec<FormatWarning>,
input_span: Span,
is_source_literal: bool,
) {
if spec.ty != "" {
let span = spec
.ty_span
.as_ref()
.map(|inner| slice_span(input_span, inner.clone(), is_source_literal))
.unwrap_or(input_span);
warnings.push(FormatWarning::InvalidSpecifier { span, name: spec.ty.into() })
}
}

fn slice_span(input: Span, Range { start, end }: Range<usize>, is_source_literal: bool) -> Span {
if is_source_literal { input.from_inner(InnerSpan { start, end }) } else { input }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
#![allow(warnings)]
use rustc_hir::attrs::diagnostic::OnUnimplementedDirective;
use rustc_hir::lints::AttributeLintKind;
use rustc_session::lint::builtin::{
MALFORMED_DIAGNOSTIC_ATTRIBUTES, MALFORMED_DIAGNOSTIC_FORMAT_LITERALS,
};
use thin_vec::thin_vec;

use crate::attributes::diagnostic::*;
use crate::attributes::prelude::*;
use crate::attributes::template;
/// Folds all uses of `#[rustc_on_unimplemented]` and `#[diagnostic::on_unimplemented]`.
/// FIXME(mejrs): add an example
#[derive(Default)]
pub struct OnUnimplementedParser {
directive: Option<(Span, OnUnimplementedDirective)>,
}

impl<S: Stage> AttributeParser<S> for OnUnimplementedParser {
const ATTRIBUTES: AcceptMapping<Self, S> = &[
(&[sym::diagnostic, sym::on_unimplemented], template!(Word), |this, cx, args| {
let span = cx.attr_span;

let items = match args {
ArgParser::List(items) => items,
ArgParser::NoArgs => {
cx.emit_lint(
MALFORMED_DIAGNOSTIC_ATTRIBUTES,
AttributeLintKind::MissingOptionsForOnUnimplemented,
span,
);
return;
}
ArgParser::NameValue(_) => {
cx.emit_lint(
MALFORMED_DIAGNOSTIC_ATTRIBUTES,
AttributeLintKind::MalformedOnUnimplementedAttr { span },
span,
);
return;
}
};

let Some(directive) = parse_directive_items(cx, Ctx::DiagnosticOnUnimplemented, items)
else {
return;
};
merge_directives(cx, &mut this.directive, (span, directive));
}),
// todo (&[sym::rustc_on_unimplemented], template!(Word), |this, cx, args| {}),
];
const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS);

fn finalize(mut self, cx: &FinalizeContext<'_, '_, S>) -> Option<AttributeKind> {
self.directive.map(|(span, directive)| AttributeKind::OnUnimplemented {
span,
directive: Some(Box::new(directive)),
})
}
}

fn merge_directives<S: Stage>(
cx: &mut AcceptContext<'_, '_, S>,
first: &mut Option<(Span, OnUnimplementedDirective)>,
later: (Span, OnUnimplementedDirective),
) {
if let Some((first_span, first)) = first {
merge(cx, &mut first.message, later.1.message, "message");
merge(cx, &mut first.label, later.1.label, "label");
first.notes.extend(later.1.notes);
} else {
*first = Some(later);
}
}

fn merge<T, S: Stage>(
cx: &mut AcceptContext<'_, '_, S>,
first: &mut Option<(Span, T)>,
later: Option<(Span, T)>,
option_name: &'static str,
) {
match (first, later) {
(Some(_) | None, None) => {}
(Some((first_span, _)), Some((later_span, _))) => {
cx.emit_lint(
MALFORMED_DIAGNOSTIC_ATTRIBUTES,
AttributeLintKind::IgnoredDiagnosticOption {
first_span: *first_span,
later_span,
option_name,
},
later_span,
);
}
(first @ None, Some(later)) => {
first.get_or_insert(later);
}
}
}

fn parse_directive_items<S: Stage>(
cx: &mut AcceptContext<S>,
ctx: Ctx,
items: &MetaItemListParser,
) -> Option<OnUnimplementedDirective> {
let condition = None;
let mut message = None;
let mut label = None;
let mut notes = ThinVec::new();
let mut parent_label = None;
let mut subcommands = ThinVec::new();
let mut append_const_msg = None;

for item in items.mixed() {
// At this point, we are expecting any of:
// message = "..", label = "..", note = ".."
let Some((name, value, value_span)) = (try {
let item = item.meta_item()?;
let name = item.ident()?.name;
let nv = item.args().name_value()?;
let value = nv.value_as_str()?;
(name, value, nv.value_span)
}) else {
let span = item.span();
cx.emit_lint(
MALFORMED_DIAGNOSTIC_ATTRIBUTES,
AttributeLintKind::MalformedOnUnimplementedAttr { span },
span,
);
continue;
};

let mut parse = |input| {
let snippet = cx.sess.source_map().span_to_snippet(value_span).ok();
let is_snippet = snippet.is_some();
match parse_format_string(input, snippet, value_span, ctx) {
Ok((f, warnings)) => {
for warning in warnings {
let (FormatWarning::InvalidSpecifier { span, .. }
| FormatWarning::PositionalArgument { span, .. }) = warning;
cx.emit_lint(
MALFORMED_DIAGNOSTIC_FORMAT_LITERALS,
AttributeLintKind::MalformedDiagnosticFormat { warning },
span,
);
}

f
}
Err(e) => {
cx.emit_lint(
MALFORMED_DIAGNOSTIC_FORMAT_LITERALS,
AttributeLintKind::DiagnosticWrappedParserError {
description: e.description,
label: e.label,
span: slice_span(value_span, e.span, is_snippet),
},
value_span,
);
// We could not parse the input, just use it as-is.
FormatString { input, span: value_span, pieces: thin_vec![Piece::Lit(input)] }
}
}
};
match name {
sym::message => {
if message.is_none() {
message.insert((item.span(), parse(value)));
} else {
// warn
}
}
sym::label => {
if label.is_none() {
label.insert((item.span(), parse(value)));
} else {
// warn
}
}
sym::note => notes.push(parse(value)),
_other => {
let span = item.span();
cx.emit_lint(
MALFORMED_DIAGNOSTIC_ATTRIBUTES,
AttributeLintKind::MalformedOnUnimplementedAttr { span },
span,
);
continue;
}
}
}

Some(OnUnimplementedDirective {
condition,
subcommands,
message,
label,
notes,
parent_label,
append_const_msg,
})
}
1 change: 1 addition & 0 deletions compiler/rustc_attr_parsing/src/attributes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ pub(crate) mod confusables;
pub(crate) mod crate_level;
pub(crate) mod debugger;
pub(crate) mod deprecation;
pub(crate) mod diagnostic;
pub(crate) mod do_not_recommend;
pub(crate) mod doc;
pub(crate) mod dummy;
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_attr_parsing/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ use crate::attributes::confusables::*;
use crate::attributes::crate_level::*;
use crate::attributes::debugger::*;
use crate::attributes::deprecation::*;
use crate::attributes::diagnostic::on_unimplemented::*;
use crate::attributes::do_not_recommend::*;
use crate::attributes::doc::*;
use crate::attributes::dummy::*;
Expand Down Expand Up @@ -147,6 +148,7 @@ attribute_parsers!(
DocParser,
MacroUseParser,
NakedParser,
OnUnimplementedParser,
StabilityParser,
UsedParser,
// tidy-alphabetical-end
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_attr_parsing/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
#![feature(decl_macro)]
#![feature(if_let_guard)]
#![feature(iter_intersperse)]
#![feature(try_blocks)]
#![recursion_limit = "256"]
// tidy-alphabetical-end

Expand Down
28 changes: 3 additions & 25 deletions compiler/rustc_error_codes/src/error_codes/E0230.md
Original file line number Diff line number Diff line change
@@ -1,26 +1,4 @@
The `#[rustc_on_unimplemented]` attribute lets you specify a custom error
message for when a particular trait isn't implemented on a type placed in a
position that needs that trait. For example, when the following code is
compiled:
#### Note: this error code is no longer emitted by the compiler.

```compile_fail,E0230
#![feature(rustc_attrs)]
#![allow(internal_features)]

#[rustc_on_unimplemented = "error on `{Self}` with params `<{A},{B}>`"] // error
trait BadAnnotation<A> {}
```

There will be an error about `bool` not implementing `Index<u8>`, followed by a
note saying "the type `bool` cannot be indexed by `u8`".

As you can see, you can specify type parameters in curly braces for
instantiation with the actual types (using the regular format string syntax) in
a given situation. Furthermore, `{Self}` will be instantiated to the type (in
this case, `bool`) that we tried to use.

This error appears when the curly braces contain an identifier which doesn't
match with any of the type parameters or the string `Self`. This might happen
if you misspelled a type parameter, or if you intended to use literal curly
braces. If it is the latter, escape the curly braces with a second curly brace
of the same type; e.g., a literal `{` is `{{`.
The `#[rustc_on_unimplemented]` attribute used to raise this error for various
misuses of the attribute; these are now warnings.
Loading
Loading