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 @@ -3548,6 +3548,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,200 @@
#![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]`.
/// TODO: 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();
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,
},
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 @@ -35,6 +35,7 @@ use crate::attributes::crate_level::{
};
use crate::attributes::debugger::DebuggerViualizerParser;
use crate::attributes::deprecation::DeprecationParser;
use crate::attributes::diagnostic::on_unimplemented::OnUnimplementedParser;
use crate::attributes::do_not_recommend::DoNotRecommendParser;
use crate::attributes::doc::DocParser;
use crate::attributes::dummy::DummyParser;
Expand Down Expand Up @@ -186,6 +187,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
8 changes: 8 additions & 0 deletions compiler/rustc_hir/src/attrs/data_structures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use rustc_span::{ErrorGuaranteed, Ident, Span, Symbol};
pub use rustc_target::spec::SanitizerSet;
use thin_vec::ThinVec;

use crate::attrs::diagnostic::*;
use crate::attrs::pretty_printing::PrintAttribute;
use crate::limit::Limit;
use crate::{DefaultBodyStability, PartialConstStability, RustcVersion, Stability};
Expand Down Expand Up @@ -984,6 +985,13 @@ pub enum AttributeKind {
/// Represents `#[rustc_objc_selector]`
ObjcSelector { methname: Symbol, span: Span },

/// Represents `#[rustc_on_unimplemented]` and `#[diagnostic::on_unimplemented]`.
OnUnimplemented {
span: Span,
/// None if the directive was malformed in some way.
directive: Option<Box<OnUnimplementedDirective>>,
},

/// Represents `#[optimize(size|speed)]`
Optimize(OptimizeAttr, Span),

Expand Down
Loading
Loading