From 8586cad77c175a0c2ce11c0579c537aa195e6da2 Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Sat, 29 Mar 2025 02:04:10 +0100 Subject: [PATCH] Documentation and finishing touches --- compiler/rustc_span/src/hygiene.rs | 19 +++ .../traits/on_unimplemented.rs | 76 ++++++++-- .../traits/on_unimplemented_condition.rs | 69 +++++++--- .../traits/on_unimplemented_format.rs | 130 +++++++++++------- 4 files changed, 215 insertions(+), 79 deletions(-) diff --git a/compiler/rustc_span/src/hygiene.rs b/compiler/rustc_span/src/hygiene.rs index 9be16f8ce0c..f6d69da9951 100644 --- a/compiler/rustc_span/src/hygiene.rs +++ b/compiler/rustc_span/src/hygiene.rs @@ -1270,6 +1270,25 @@ impl DesugaringKind { DesugaringKind::PatTyRange => "pattern type", } } + + /// For use with `rustc_unimplemented` to support conditions + /// like `from_desugaring = "QuestionMark"` + pub fn matches(&self, value: &str) -> bool { + match self { + DesugaringKind::CondTemporary => value == "CondTemporary", + DesugaringKind::Async => value == "Async", + DesugaringKind::Await => value == "Await", + DesugaringKind::QuestionMark => value == "QuestionMark", + DesugaringKind::TryBlock => value == "TryBlock", + DesugaringKind::YeetExpr => value == "YeetExpr", + DesugaringKind::OpaqueTy => value == "OpaqueTy", + DesugaringKind::ForLoop => value == "ForLoop", + DesugaringKind::WhileLoop => value == "WhileLoop", + DesugaringKind::BoundModifier => value == "BoundModifier", + DesugaringKind::Contract => value == "Contract", + DesugaringKind::PatTyRange => value == "PatTyRange", + } + } } #[derive(Default)] diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented.rs index 7d8ecba3b2e..0478f3a7f11 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented.rs @@ -6,6 +6,7 @@ use rustc_errors::codes::*; use rustc_errors::{ErrorGuaranteed, struct_span_code_err}; use rustc_hir::def_id::{DefId, LocalDefId}; use rustc_hir::{AttrArgs, Attribute}; +use rustc_macros::LintDiagnostic; use rustc_middle::bug; use rustc_middle::ty::print::PrintTraitRefExt; use rustc_middle::ty::{self, GenericArgsRef, GenericParamDef, GenericParamDefKind, TyCtxt}; @@ -17,7 +18,6 @@ use {rustc_attr_parsing as attr, rustc_hir as hir}; use super::{ObligationCauseCode, PredicateObligation}; use crate::error_reporting::TypeErrCtxt; use crate::error_reporting::traits::on_unimplemented_condition::{Condition, ConditionOptions}; -use crate::error_reporting::traits::on_unimplemented_format::errors::*; use crate::error_reporting::traits::on_unimplemented_format::{Ctx, FormatArgs, FormatString}; use crate::errors::{ EmptyOnClauseInOnUnimplemented, InvalidOnClauseInOnUnimplemented, NoValueInOnUnimplemented, @@ -112,10 +112,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { // FIXME(-Zlower-impl-trait-in-trait-to-assoc-ty): HIR is not present for RPITITs, // but I guess we could synthesize one here. We don't see any errors that rely on // that yet, though. - let item_context = self - .describe_enclosure(obligation.cause.body_id) - .map(|t| t.to_owned()) - .unwrap_or(String::new()); + let item_context = self.describe_enclosure(obligation.cause.body_id).unwrap_or(""); let direct = match obligation.cause.code() { ObligationCauseCode::BuiltinDerived(..) @@ -128,7 +125,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { } }; - let from_desugaring = obligation.cause.span.desugaring_kind().map(|k| format!("{k:?}")); + let from_desugaring = obligation.cause.span.desugaring_kind(); let cause = if let ObligationCauseCode::MainFunctionType = obligation.cause.code() { Some("MainFunctionType".to_string()) @@ -253,8 +250,8 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { } })); - let this = self.tcx.def_path_str(trait_pred.trait_ref.def_id).to_string(); - let trait_sugared = trait_pred.trait_ref.print_trait_sugared().to_string(); + let this = self.tcx.def_path_str(trait_pred.trait_ref.def_id); + let trait_sugared = trait_pred.trait_ref.print_trait_sugared(); let condition_options = ConditionOptions { self_types, @@ -268,6 +265,8 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { // Unlike the generic_args earlier, // this one is *not* collected under `with_no_trimmed_paths!` // for printing the type to the user + // + // This includes `Self`, as it is the first parameter in `own_params`. let generic_args = self .tcx .generics_of(trait_pred.trait_ref.def_id) @@ -341,6 +340,63 @@ pub enum AppendConstMessage { Custom(Symbol, Span), } +#[derive(LintDiagnostic)] +#[diag(trait_selection_malformed_on_unimplemented_attr)] +#[help] +pub struct MalformedOnUnimplementedAttrLint { + #[label] + pub span: Span, +} + +impl MalformedOnUnimplementedAttrLint { + pub fn new(span: Span) -> Self { + Self { span } + } +} + +#[derive(LintDiagnostic)] +#[diag(trait_selection_missing_options_for_on_unimplemented_attr)] +#[help] +pub struct MissingOptionsForOnUnimplementedAttr; + +#[derive(LintDiagnostic)] +#[diag(trait_selection_ignored_diagnostic_option)] +pub struct IgnoredDiagnosticOption { + pub option_name: &'static str, + #[label] + pub span: Span, + #[label(trait_selection_other_label)] + pub prev_span: Span, +} + +impl IgnoredDiagnosticOption { + pub fn maybe_emit_warning<'tcx>( + tcx: TyCtxt<'tcx>, + item_def_id: DefId, + new: Option, + old: Option, + option_name: &'static str, + ) { + if let (Some(new_item), Some(old_item)) = (new, old) { + if let Some(item_def_id) = item_def_id.as_local() { + tcx.emit_node_span_lint( + UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES, + tcx.local_def_id_to_hir_id(item_def_id), + new_item, + IgnoredDiagnosticOption { span: new_item, prev_span: old_item, option_name }, + ); + } + } + } +} + +#[derive(LintDiagnostic)] +#[diag(trait_selection_wrapped_parser_error)] +pub struct WrappedParserError { + pub description: String, + pub label: String, +} + impl<'tcx> OnUnimplementedDirective { fn parse( tcx: TyCtxt<'tcx>, @@ -664,7 +720,7 @@ impl<'tcx> OnUnimplementedDirective { tcx: TyCtxt<'tcx>, trait_ref: ty::TraitRef<'tcx>, condition_options: &ConditionOptions, - args: &FormatArgs, + args: &FormatArgs<'tcx>, ) -> OnUnimplementedNote { let mut message = None; let mut label = None; @@ -784,7 +840,7 @@ impl<'tcx> OnUnimplementedFormatString { &self, tcx: TyCtxt<'tcx>, trait_ref: ty::TraitRef<'tcx>, - args: &FormatArgs, + args: &FormatArgs<'tcx>, ) -> String { let trait_def_id = trait_ref.def_id; let ctx = if self.is_diagnostic_namespace_variant { diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_condition.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_condition.rs index 491ba6d7ffc..116cfb01cb6 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_condition.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_condition.rs @@ -2,19 +2,10 @@ use rustc_ast::MetaItemInner; use rustc_attr_parsing as attr; use rustc_middle::ty::{self, TyCtxt}; use rustc_parse_format::{ParseMode, Parser, Piece, Position}; -use rustc_span::{Span, Symbol, kw, sym}; - -pub static ALLOWED_CONDITION_SYMBOLS: &[Symbol] = &[ - sym::from_desugaring, - sym::direct, - sym::cause, - sym::integral, - sym::integer_, - sym::float, - sym::_Self, - sym::crate_local, -]; +use rustc_span::{DesugaringKind, Span, Symbol, kw, sym}; +/// A predicate in an attribute using on, all, any, +/// similar to a cfg predicate. #[derive(Debug)] pub struct Condition { pub inner: MetaItemInner, @@ -31,8 +22,7 @@ impl Condition { // `with_no_visible_paths` is also used when generating the options, // so we need to match it here. ty::print::with_no_visible_paths!({ - let mut parser = Parser::new(v.as_str(), None, None, false, ParseMode::Format); - let constructed_message = (&mut parser) + Parser::new(v.as_str(), None, None, false, ParseMode::Format) .map(|p| match p { Piece::Lit(s) => s.to_owned(), Piece::NextArgument(a) => match a.position { @@ -47,8 +37,7 @@ impl Condition { Position::ArgumentIs(idx) => format!("{{{idx}}}"), }, }) - .collect(); - constructed_message + .collect() }) }); @@ -57,13 +46,57 @@ impl Condition { } } +/// Used with `Condition::matches_predicate` to test whether the condition applies +/// +/// For example, given a +/// ```rust,ignore (just an example) +/// #[rustc_on_unimplemented( +/// on(all(from_desugaring = "QuestionMark"), +/// message = "the `?` operator can only be used in {ItemContext} \ +/// that returns `Result` or `Option` \ +/// (or another type that implements `{FromResidual}`)", +/// label = "cannot use the `?` operator in {ItemContext} that returns `{Self}`", +/// parent_label = "this function should return `Result` or `Option` to accept `?`" +/// ), +/// )] +/// pub trait FromResidual::Residual> { +/// ... +/// } +/// +/// async fn an_async_function() -> u32 { +/// let x: Option = None; +/// x?; //~ ERROR the `?` operator +/// 22 +/// } +/// ``` +/// it will look like this: +/// +/// ```rust,ignore (just an example) +/// ConditionOptions { +/// self_types: ["u32", "{integral}"], +/// from_desugaring: Some("QuestionMark"), +/// cause: None, +/// crate_local: false, +/// direct: true, +/// generic_args: [("Self","u32"), +/// ("R", "core::option::Option"), +/// ("R", "core::option::Option" ), +/// ], +/// } +/// ``` #[derive(Debug)] pub struct ConditionOptions { + /// All the self types that may apply. + /// for example pub self_types: Vec, - pub from_desugaring: Option, + // The kind of compiler desugaring. + pub from_desugaring: Option, + /// Match on a variant of [rustc_infer::traits::ObligationCauseCode] pub cause: Option, pub crate_local: bool, + /// Is the obligation "directly" user-specified, rather than derived? pub direct: bool, + // A list of the generic arguments and their reified types pub generic_args: Vec<(Symbol, String)>, } @@ -74,7 +107,7 @@ impl ConditionOptions { // from_desugaring as a flag (sym::from_desugaring, None) => self.from_desugaring.is_some(), // from_desugaring as key == value - (sym::from_desugaring, v) => *v == self.from_desugaring, + (sym::from_desugaring, Some(v)) if let Some(ds) = self.from_desugaring => ds.matches(v), (sym::cause, Some(value)) => self.cause.as_deref() == Some(value), (sym::crate_local, None) => self.crate_local, (sym::direct, None) => self.direct, diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_format.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_format.rs index 1bf15a34509..f835406122b 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_format.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_format.rs @@ -1,5 +1,8 @@ +use std::fmt; + use errors::*; use rustc_middle::ty::TyCtxt; +use rustc_middle::ty::print::TraitRefPrintSugared; use rustc_parse_format::{ Alignment, Argument, Count, FormatSpec, InnerSpan, ParseError, ParseMode, Parser, Piece as RpfPiece, Position, @@ -8,28 +11,39 @@ use rustc_session::lint::builtin::UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES; use rustc_span::def_id::DefId; use rustc_span::{BytePos, Pos, Span, Symbol, kw, sym}; -#[allow(dead_code)] +/// Like [std::fmt::Arguments] this is a string that has been parsed into "pieces", +/// either as string pieces or dynamic arguments. +#[derive(Debug)] pub struct FormatString { + #[allow(dead_code, reason = "Debug impl")] input: Symbol, - input_span: Span, + span: Span, pieces: Vec, - // the formatting string was parsed succesfully but with warnings + /// The formatting string was parsed succesfully but with warnings pub warnings: Vec, } +#[derive(Debug)] enum Piece { Lit(String), Arg(FormatArg), } -pub enum FormatArg { +#[derive(Debug)] +enum FormatArg { // A generic parameter, like `{T}` if we're on the `From` trait. - GenericParam { generic_param: Symbol, span: Span }, + GenericParam { + generic_param: Symbol, + }, // `{Self}` SelfUpper, + /// `{This}` or `{TraitName}` This, + /// The sugared form of the trait Trait, + /// what we're in, like a function, method, closure etc. ItemContext, + /// What the user typed, if it doesn't match anything we can use. AsIs(String), } @@ -40,6 +54,7 @@ pub enum Ctx<'tcx> { DiagnosticOnUnimplemented { tcx: TyCtxt<'tcx>, trait_def_id: DefId }, } +#[derive(Debug)] pub enum FormatWarning { UnknownParam { argument_name: Symbol, span: Span }, PositionalArgument { span: Span, help: String }, @@ -95,26 +110,58 @@ impl FormatWarning { } } +/// Arguments to fill a [FormatString] with. +/// +/// For example, given a +/// ```rust,ignore (just an example) +/// +/// #[rustc_on_unimplemented( +/// on(all(from_desugaring = "QuestionMark"), +/// message = "the `?` operator can only be used in {ItemContext} \ +/// that returns `Result` or `Option` \ +/// (or another type that implements `{FromResidual}`)", +/// label = "cannot use the `?` operator in {ItemContext} that returns `{Self}`", +/// parent_label = "this function should return `Result` or `Option` to accept `?`" +/// ), +/// )] +/// pub trait FromResidual::Residual> { +/// ... +/// } +/// +/// async fn an_async_function() -> u32 { +/// let x: Option = None; +/// x?; //~ ERROR the `?` operator +/// 22 +/// } +/// ``` +/// it will look like this: +/// +/// ```rust,ignore (just an example) +/// FormatArgs { +/// this: "FromResidual", +/// trait_sugared: "FromResidual>", +/// item_context: "an async function", +/// generic_args: [("Self", "u32"), ("R", "Option")], +/// } +/// ``` #[derive(Debug)] -pub struct ConditionOptions { - pub self_types: Vec, - pub from_desugaring: Option, - pub cause: Option, - pub crate_local: bool, - pub direct: bool, - pub generic_args: Vec<(Symbol, String)>, -} - -#[derive(Debug)] -pub struct FormatArgs { +pub struct FormatArgs<'tcx> { pub this: String, - pub trait_sugared: String, - pub item_context: String, + pub trait_sugared: TraitRefPrintSugared<'tcx>, + pub item_context: &'static str, pub generic_args: Vec<(Symbol, String)>, } impl FormatString { - pub fn parse(input: Symbol, input_span: Span, ctx: &Ctx<'_>) -> Result> { + pub fn span(&self) -> Span { + self.span + } + + pub fn parse<'tcx>( + input: Symbol, + span: Span, + ctx: &Ctx<'tcx>, + ) -> Result> { let s = input.as_str(); let mut parser = Parser::new(s, None, None, false, ParseMode::Format); let mut pieces = Vec::new(); @@ -126,28 +173,28 @@ impl FormatString { pieces.push(Piece::Lit(lit.into())); } RpfPiece::NextArgument(arg) => { - warn_on_format_spec(arg.format, &mut warnings, input_span); - let arg = parse_arg(&arg, ctx, &mut warnings, input_span); + warn_on_format_spec(arg.format, &mut warnings, span); + let arg = parse_arg(&arg, ctx, &mut warnings, span); pieces.push(Piece::Arg(arg)); } } } if parser.errors.is_empty() { - Ok(FormatString { input, input_span, pieces, warnings }) + Ok(FormatString { input, pieces, span, warnings }) } else { Err(parser.errors) } } - pub fn format(&self, args: &FormatArgs) -> String { + pub fn format(&self, args: &FormatArgs<'_>) -> String { let mut ret = String::new(); for piece in &self.pieces { match piece { Piece::Lit(s) | Piece::Arg(FormatArg::AsIs(s)) => ret.push_str(&s), // `A` if we have `trait Trait {}` and `note = "i'm the actual type of {A}"` - Piece::Arg(FormatArg::GenericParam { generic_param, .. }) => { + Piece::Arg(FormatArg::GenericParam { generic_param }) => { // Should always be some but we can't raise errors here let value = match args.generic_args.iter().find(|(p, _)| p == generic_param) { Some((_, val)) => val.to_string(), @@ -166,17 +213,19 @@ impl FormatString { // It's only `rustc_onunimplemented` from here Piece::Arg(FormatArg::This) => ret.push_str(&args.this), - Piece::Arg(FormatArg::Trait) => ret.push_str(&args.trait_sugared), - Piece::Arg(FormatArg::ItemContext) => ret.push_str(&args.item_context), + Piece::Arg(FormatArg::Trait) => { + let _ = fmt::write(&mut ret, format_args!("{}", &args.trait_sugared)); + } + Piece::Arg(FormatArg::ItemContext) => ret.push_str(args.item_context), } } ret } } -fn parse_arg( +fn parse_arg<'tcx>( arg: &Argument<'_>, - ctx: &Ctx<'_>, + ctx: &Ctx<'tcx>, warnings: &mut Vec, input_span: Span, ) -> FormatArg { @@ -233,7 +282,7 @@ fn parse_arg( Ctx::RustcOnUnimplemented { .. } | Ctx::DiagnosticOnUnimplemented { .. }, generic_param, ) if generics.own_params.iter().any(|param| param.name == generic_param) => { - FormatArg::GenericParam { generic_param, span } + FormatArg::GenericParam { generic_param } } (_, argument_name) => { @@ -286,6 +335,7 @@ fn warn_on_format_spec(spec: FormatSpec<'_>, warnings: &mut Vec, } } +/// Helper function because `Span` and `rustc_parse_format::InnerSpan` don't know about each other fn slice_span(input: Span, inner: InnerSpan) -> Span { let InnerSpan { start, end } = inner; let span = input.data(); @@ -300,8 +350,6 @@ fn slice_span(input: Span, inner: InnerSpan) -> Span { pub mod errors { use rustc_macros::LintDiagnostic; - use rustc_middle::ty::TyCtxt; - use rustc_session::lint::builtin::UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES; use rustc_span::Ident; use super::*; @@ -324,26 +372,6 @@ pub mod errors { #[help] pub struct InvalidFormatSpecifier; - #[derive(LintDiagnostic)] - #[diag(trait_selection_wrapped_parser_error)] - pub struct WrappedParserError { - pub description: String, - pub label: String, - } - #[derive(LintDiagnostic)] - #[diag(trait_selection_malformed_on_unimplemented_attr)] - #[help] - pub struct MalformedOnUnimplementedAttrLint { - #[label] - pub span: Span, - } - - impl MalformedOnUnimplementedAttrLint { - pub fn new(span: Span) -> Self { - Self { span } - } - } - #[derive(LintDiagnostic)] #[diag(trait_selection_missing_options_for_on_unimplemented_attr)] #[help]