From 199ee4084349361ae44c6e4b0cbc9a5e72913b20 Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Tue, 25 Mar 2025 14:48:12 +0100 Subject: [PATCH 01/39] Move errors --- .../src/error_reporting/traits/mod.rs | 1 + .../traits/on_unimplemented.rs | 79 +---------------- .../traits/on_unimplemented_format.rs | 86 +++++++++++++++++++ 3 files changed, 89 insertions(+), 77 deletions(-) create mode 100644 compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_format.rs diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/mod.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/mod.rs index 8ff7030717a..7fd1c0d2743 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/mod.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/mod.rs @@ -2,6 +2,7 @@ pub mod ambiguity; pub mod call_kind; mod fulfillment_errors; pub mod on_unimplemented; +pub mod on_unimplemented_format; mod overflow; pub mod suggestions; 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 f0c6e51f2a4..9914e828a93 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 @@ -7,18 +7,18 @@ 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 as _; use rustc_middle::ty::{self, GenericArgsRef, GenericParamDefKind, TyCtxt}; use rustc_parse_format::{ParseMode, Parser, Piece, Position}; use rustc_session::lint::builtin::UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES; -use rustc_span::{Ident, Span, Symbol, kw, sym}; +use rustc_span::{Span, Symbol, kw, sym}; use tracing::{debug, info}; 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_format::errors::*; use crate::errors::{ EmptyOnClauseInOnUnimplemented, InvalidOnClauseInOnUnimplemented, NoValueInOnUnimplemented, }; @@ -320,81 +320,6 @@ 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 { - 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 { - 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_unknown_format_parameter_for_on_unimplemented_attr)] -#[help] -pub struct UnknownFormatParameterForOnUnimplementedAttr { - argument_name: Symbol, - trait_name: Ident, -} - -#[derive(LintDiagnostic)] -#[diag(trait_selection_disallowed_positional_argument)] -#[help] -pub struct DisallowedPositionalArgument; - -#[derive(LintDiagnostic)] -#[diag(trait_selection_invalid_format_specifier)] -#[help] -pub struct InvalidFormatSpecifier; - -#[derive(LintDiagnostic)] -#[diag(trait_selection_wrapped_parser_error)] -pub struct WrappedParserError { - description: String, - label: String, -} - impl<'tcx> OnUnimplementedDirective { fn parse( tcx: TyCtxt<'tcx>, 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 new file mode 100644 index 00000000000..40a47921fc1 --- /dev/null +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_format.rs @@ -0,0 +1,86 @@ +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::*; + + #[derive(LintDiagnostic)] + #[diag(trait_selection_unknown_format_parameter_for_on_unimplemented_attr)] + #[help] + pub struct UnknownFormatParameterForOnUnimplementedAttr { + pub argument_name: Symbol, + pub trait_name: Ident, + } + + #[derive(LintDiagnostic)] + #[diag(trait_selection_disallowed_positional_argument)] + #[help] + pub struct DisallowedPositionalArgument; + + #[derive(LintDiagnostic)] + #[diag(trait_selection_invalid_format_specifier)] + #[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] + 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, + }, + ); + } + } + } + } +} From 2007c8994db6d46533f84f4697fea5734c6df53d Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Thu, 27 Mar 2025 15:39:23 +0100 Subject: [PATCH 02/39] Write the format string parserand split it from conditions parser --- compiler/rustc_span/src/symbol.rs | 1 + .../src/error_reporting/traits/mod.rs | 1 + .../traits/on_unimplemented.rs | 399 ++++++------------ .../traits/on_unimplemented_condition.rs | 64 +++ .../traits/on_unimplemented_format.rs | 344 +++++++++++++++ .../on_unimplemented/broken_format.stderr | 48 +-- ...ons_of_the_internal_rustc_attribute.stderr | 80 ++-- ...t_fail_parsing_on_invalid_options_1.stderr | 8 +- tests/ui/on-unimplemented/bad-annotation.rs | 4 +- .../ui/on-unimplemented/bad-annotation.stderr | 22 +- tests/ui/on-unimplemented/impl-substs.stderr | 2 +- 11 files changed, 613 insertions(+), 360 deletions(-) create mode 100644 compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_condition.rs diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 31847ae3b46..0c458db0b23 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -372,6 +372,7 @@ symbols! { SyncUnsafeCell, T, Target, + This, ToOwned, ToString, TokenStream, diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/mod.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/mod.rs index 7fd1c0d2743..78f9287b407 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/mod.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/mod.rs @@ -2,6 +2,7 @@ pub mod ambiguity; pub mod call_kind; mod fulfillment_errors; pub mod on_unimplemented; +pub mod on_unimplemented_condition; pub mod on_unimplemented_format; mod overflow; pub mod suggestions; 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 9914e828a93..6a7f3223421 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 @@ -1,7 +1,7 @@ use std::iter; use std::path::PathBuf; -use rustc_ast::MetaItemInner; +use rustc_ast::{LitKind, MetaItem, MetaItemInner, MetaItemKind, MetaItemLit}; use rustc_data_structures::fx::FxHashMap; use rustc_errors::codes::*; use rustc_errors::{ErrorGuaranteed, struct_span_code_err}; @@ -10,35 +10,21 @@ use rustc_hir::{AttrArgs, Attribute}; use rustc_middle::bug; use rustc_middle::ty::print::PrintTraitRefExt as _; use rustc_middle::ty::{self, GenericArgsRef, GenericParamDefKind, TyCtxt}; -use rustc_parse_format::{ParseMode, Parser, Piece, Position}; use rustc_session::lint::builtin::UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES; -use rustc_span::{Span, Symbol, kw, sym}; +use rustc_span::{Span, Symbol, sym}; use tracing::{debug, info}; 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; use crate::error_reporting::traits::on_unimplemented_format::errors::*; +use crate::error_reporting::traits::on_unimplemented_format::{Ctx, FormatString}; use crate::errors::{ EmptyOnClauseInOnUnimplemented, InvalidOnClauseInOnUnimplemented, NoValueInOnUnimplemented, }; use crate::infer::InferCtxtExt; -/// The symbols which are always allowed in a format string -static ALLOWED_FORMAT_SYMBOLS: &[Symbol] = &[ - kw::SelfUpper, - sym::ItemContext, - sym::from_desugaring, - sym::direct, - sym::cause, - sym::integral, - sym::integer_, - sym::float, - sym::_Self, - sym::crate_local, - sym::Trait, -]; - impl<'tcx> TypeErrCtxt<'_, 'tcx> { fn impl_similar_to( &self, @@ -275,6 +261,8 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { } })); + flags.push((sym::This, Some(self.tcx.def_path_str(trait_pred.trait_ref.def_id)))); + if let Ok(Some(command)) = OnUnimplementedDirective::of_item(self.tcx, def_id) { command.evaluate(self.tcx, trait_pred.trait_ref, &flags, long_ty_file) } else { @@ -283,19 +271,23 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { } } +/// Represents a format string in a on_unimplemented attribute, +/// like the "content" in `#[diagnostic::on_unimplemented(message = "content")]` #[derive(Clone, Debug)] pub struct OnUnimplementedFormatString { - symbol: Symbol, - span: Span, - is_diagnostic_namespace_variant: bool, + /// Symbol of the format string, i.e. `"content"` + pub symbol: Symbol, + ///The span of the format string, i.e. `"content"` + pub span: Span, + pub is_diagnostic_namespace_variant: bool, } #[derive(Debug)] pub struct OnUnimplementedDirective { - pub condition: Option, + pub condition: Option, pub subcommands: Vec, - pub message: Option, - pub label: Option, + pub message: Option<(Span, OnUnimplementedFormatString)>, + pub label: Option<(Span, OnUnimplementedFormatString)>, pub notes: Vec, pub parent_label: Option, pub append_const_msg: Option, @@ -332,12 +324,12 @@ impl<'tcx> OnUnimplementedDirective { let mut errored = None; let mut item_iter = items.iter(); - let parse_value = |value_str, value_span| { + let parse_value = |value_str, span| { OnUnimplementedFormatString::try_parse( tcx, item_def_id, value_str, - value_span, + span, is_diagnostic_namespace_variant, ) .map(Some) @@ -359,7 +351,7 @@ impl<'tcx> OnUnimplementedDirective { } true }); - Some(cond.clone()) + Some(Condition { inner: cond.clone() }) }; let mut message = None; @@ -369,24 +361,36 @@ impl<'tcx> OnUnimplementedDirective { let mut subcommands = vec![]; let mut append_const_msg = None; + let get_value_and_span = |item: &_, key| { + if let MetaItemInner::MetaItem(MetaItem { + path, + kind: MetaItemKind::NameValue(MetaItemLit { span, kind: LitKind::Str(s, _), .. }), + .. + }) = item + && *path == key + { + Some((*s, *span)) + } else { + None + } + }; + for item in item_iter { - if item.has_name(sym::message) && message.is_none() { - if let Some(message_) = item.value_str() { - message = parse_value(message_, item.span())?; + if let Some((message_, span)) = get_value_and_span(item, sym::message) + && message.is_none() + { + message = parse_value(message_, span)?.map(|l| (item.span(), l)); + continue; + } else if let Some((label_, span)) = get_value_and_span(item, sym::label) + && label.is_none() + { + label = parse_value(label_, span)?.map(|l| (item.span(), l)); + continue; + } else if let Some((note_, span)) = get_value_and_span(item, sym::note) { + if let Some(note) = parse_value(note_, span)? { + notes.push(note); continue; } - } else if item.has_name(sym::label) && label.is_none() { - if let Some(label_) = item.value_str() { - label = parse_value(label_, item.span())?; - continue; - } - } else if item.has_name(sym::note) { - if let Some(note_) = item.value_str() { - if let Some(note) = parse_value(note_, item.span())? { - notes.push(note); - continue; - } - } } else if item.has_name(sym::parent_label) && parent_label.is_none() && !is_diagnostic_namespace_variant @@ -479,15 +483,15 @@ impl<'tcx> OnUnimplementedDirective { IgnoredDiagnosticOption::maybe_emit_warning( tcx, item_def_id, - directive.message.as_ref().map(|f| f.span), - aggr.message.as_ref().map(|f| f.span), + directive.message.as_ref().map(|f| f.0), + aggr.message.as_ref().map(|f| f.0), "message", ); IgnoredDiagnosticOption::maybe_emit_warning( tcx, item_def_id, - directive.label.as_ref().map(|f| f.span), - aggr.label.as_ref().map(|f| f.span), + directive.label.as_ref().map(|f| f.0), + aggr.label.as_ref().map(|f| f.0), "label", ); IgnoredDiagnosticOption::maybe_emit_warning( @@ -561,13 +565,16 @@ impl<'tcx> OnUnimplementedDirective { condition: None, message: None, subcommands: vec![], - label: Some(OnUnimplementedFormatString::try_parse( - tcx, - item_def_id, - value, + label: Some(( attr.span(), - is_diagnostic_namespace_variant, - )?), + OnUnimplementedFormatString::try_parse( + tcx, + item_def_id, + value, + attr.value_span().unwrap_or(attr.span()), + is_diagnostic_namespace_variant, + )?, + )), notes: Vec::new(), parent_label: None, append_const_msg: None, @@ -643,27 +650,7 @@ impl<'tcx> OnUnimplementedDirective { for command in self.subcommands.iter().chain(Some(self)).rev() { debug!(?command); if let Some(ref condition) = command.condition - && !attr::eval_condition(condition, &tcx.sess, Some(tcx.features()), &mut |cfg| { - let value = cfg.value.map(|v| { - // `with_no_visible_paths` is also used when generating the options, - // so we need to match it here. - ty::print::with_no_visible_paths!( - OnUnimplementedFormatString { - symbol: v, - span: cfg.span, - is_diagnostic_namespace_variant: false - } - .format( - tcx, - trait_ref, - &options_map, - long_ty_file - ) - ) - }); - - options.contains(&(cfg.name, value)) - }) + && !condition.matches_predicate(tcx, options, &options_map) { debug!("evaluate: skipping {:?} due to condition", command); continue; @@ -687,8 +674,8 @@ impl<'tcx> OnUnimplementedDirective { } OnUnimplementedNote { - label: label.map(|l| l.format(tcx, trait_ref, &options_map, long_ty_file)), - message: message.map(|m| m.format(tcx, trait_ref, &options_map, long_ty_file)), + label: label.map(|l| l.1.format(tcx, trait_ref, &options_map, long_ty_file)), + message: message.map(|m| m.1.format(tcx, trait_ref, &options_map, long_ty_file)), notes: notes .into_iter() .map(|n| n.format(tcx, trait_ref, &options_map, long_ty_file)) @@ -705,144 +692,65 @@ impl<'tcx> OnUnimplementedFormatString { tcx: TyCtxt<'tcx>, item_def_id: DefId, from: Symbol, - value_span: Span, + span: Span, is_diagnostic_namespace_variant: bool, ) -> Result { - let result = OnUnimplementedFormatString { - symbol: from, - span: value_span, - is_diagnostic_namespace_variant, - }; + let result = + OnUnimplementedFormatString { symbol: from, span, is_diagnostic_namespace_variant }; result.verify(tcx, item_def_id)?; Ok(result) } fn verify(&self, tcx: TyCtxt<'tcx>, item_def_id: DefId) -> Result<(), ErrorGuaranteed> { - let trait_def_id = if tcx.is_trait(item_def_id) { - item_def_id + let trait_def_id = if tcx.is_trait(item_def_id) { item_def_id } else { return Ok(()) }; + + let ctx = if self.is_diagnostic_namespace_variant { + Ctx::DiagnosticOnUnimplemented { tcx, trait_def_id } } else { - tcx.trait_id_of_impl(item_def_id) - .expect("expected `on_unimplemented` to correspond to a trait") + Ctx::RustcOnUnimplemented { tcx, trait_def_id } }; - let trait_name = tcx.item_ident(trait_def_id); - let generics = tcx.generics_of(item_def_id); - let s = self.symbol.as_str(); - let mut parser = Parser::new(s, None, None, false, ParseMode::Format); + let mut result = Ok(()); - for token in &mut parser { - match token { - Piece::Lit(_) => (), // Normal string, no need to check it - Piece::NextArgument(a) => { - let format_spec = a.format; - if self.is_diagnostic_namespace_variant - && (format_spec.ty_span.is_some() - || format_spec.width_span.is_some() - || format_spec.precision_span.is_some() - || format_spec.fill_span.is_some()) - { + + match FormatString::parse(self.symbol, self.span, &ctx) { + // Warnings about format specifiers, deprecated parameters, wrong parameters etc. + // In other words we'd like to let the author know, but we can still try to format the string later + Ok(FormatString { warnings, .. }) => { + for w in warnings { + w.emit_warning(tcx, trait_def_id) + } + } + // Errors from the underlying `rustc_parse_format::Parser` + Err(errors) => { + // we cannot return errors from processing the format string as hard error here + // as the diagnostic namespace guarantees that malformed input cannot cause an error + // + // if we encounter any error while processing we nevertheless want to show it as warning + // so that users are aware that something is not correct + for e in errors { + if self.is_diagnostic_namespace_variant { 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), self.span, - InvalidFormatSpecifier, + WrappedParserError { description: e.description, label: e.label }, ); } - } - match a.position { - Position::ArgumentNamed(s) => { - match Symbol::intern(s) { - // `{ThisTraitsName}` is allowed - s if s == trait_name.name - && !self.is_diagnostic_namespace_variant => - { - () - } - s if ALLOWED_FORMAT_SYMBOLS.contains(&s) - && !self.is_diagnostic_namespace_variant => - { - () - } - // So is `{A}` if A is a type parameter - s if generics.own_params.iter().any(|param| param.name == s) => (), - s => { - if self.is_diagnostic_namespace_variant { - 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), - self.span, - UnknownFormatParameterForOnUnimplementedAttr { - argument_name: s, - trait_name, - }, - ); - } - } else { - result = Err(struct_span_code_err!( - tcx.dcx(), - self.span, - E0230, - "there is no parameter `{}` on {}", - s, - if trait_def_id == item_def_id { - format!("trait `{trait_name}`") - } else { - "impl".to_string() - } - ) - .emit()); - } - } - } - } - // `{:1}` and `{}` are not to be used - Position::ArgumentIs(..) | Position::ArgumentImplicitlyIs(_) => { - if self.is_diagnostic_namespace_variant { - 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), - self.span, - DisallowedPositionalArgument, - ); - } - } else { - let reported = struct_span_code_err!( - tcx.dcx(), - self.span, - E0231, - "only named generic parameters are allowed" - ) - .emit(); - result = Err(reported); - } - } + } else { + let reported = struct_span_code_err!( + tcx.dcx(), + self.span, + E0231, + "{}", + e.description, + ) + .emit(); + result = Err(reported); } } } } - // we cannot return errors from processing the format string as hard error here - // as the diagnostic namespace guarantees that malformed input cannot cause an error - // - // if we encounter any error while processing we nevertheless want to show it as warning - // so that users are aware that something is not correct - for e in parser.errors { - if self.is_diagnostic_namespace_variant { - 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), - self.span, - WrappedParserError { description: e.description, label: e.label }, - ); - } - } else { - let reported = - struct_span_code_err!(tcx.dcx(), self.span, E0231, "{}", e.description,).emit(); - result = Err(reported); - } - } result } @@ -854,95 +762,26 @@ impl<'tcx> OnUnimplementedFormatString { options: &FxHashMap, long_ty_file: &mut Option, ) -> String { - let name = tcx.item_name(trait_ref.def_id); - let trait_str = tcx.def_path_str(trait_ref.def_id); - let generics = tcx.generics_of(trait_ref.def_id); - let generic_map = generics - .own_params - .iter() - .filter_map(|param| { - let value = match param.kind { - GenericParamDefKind::Type { .. } | GenericParamDefKind::Const { .. } => { - if let Some(ty) = trait_ref.args[param.index as usize].as_type() { - tcx.short_string(ty, long_ty_file) - } else { - trait_ref.args[param.index as usize].to_string() - } - } - GenericParamDefKind::Lifetime => return None, - }; - let name = param.name; - Some((name, value)) - }) - .collect::>(); - let empty_string = String::new(); - - let s = self.symbol.as_str(); - let mut parser = Parser::new(s, None, None, false, ParseMode::Format); - let item_context = (options.get(&sym::ItemContext)).unwrap_or(&empty_string); - let constructed_message = (&mut parser) - .map(|p| match p { - Piece::Lit(s) => s.to_owned(), - Piece::NextArgument(a) => match a.position { - Position::ArgumentNamed(arg) => { - let s = Symbol::intern(arg); - match generic_map.get(&s) { - Some(val) => val.to_string(), - None if self.is_diagnostic_namespace_variant => { - format!("{{{arg}}}") - } - None if s == name => trait_str.clone(), - None => { - if let Some(val) = options.get(&s) { - val.clone() - } else if s == sym::from_desugaring { - // don't break messages using these two arguments incorrectly - String::new() - } else if s == sym::ItemContext - && !self.is_diagnostic_namespace_variant - { - item_context.clone() - } else if s == sym::integral { - String::from("{integral}") - } else if s == sym::integer_ { - String::from("{integer}") - } else if s == sym::float { - String::from("{float}") - } else { - bug!( - "broken on_unimplemented {:?} for {:?}: \ - no argument matching {:?}", - self.symbol, - trait_ref, - s - ) - } - } - } - } - Position::ArgumentImplicitlyIs(_) if self.is_diagnostic_namespace_variant => { - String::from("{}") - } - Position::ArgumentIs(idx) if self.is_diagnostic_namespace_variant => { - format!("{{{idx}}}") - } - _ => bug!("broken on_unimplemented {:?} - bad format arg", self.symbol), - }, - }) - .collect(); - // we cannot return errors from processing the format string as hard error here - // as the diagnostic namespace guarantees that malformed input cannot cause an error - // - // if we encounter any error while processing the format string - // we don't want to show the potentially half assembled formatted string, - // therefore we fall back to just showing the input string in this case - // - // The actual parser errors are emitted earlier - // as lint warnings in OnUnimplementedFormatString::verify - if self.is_diagnostic_namespace_variant && !parser.errors.is_empty() { - String::from(s) + let trait_def_id = trait_ref.def_id; + let ctx = if self.is_diagnostic_namespace_variant { + Ctx::DiagnosticOnUnimplemented { tcx, trait_def_id } } else { - constructed_message + Ctx::RustcOnUnimplemented { tcx, trait_def_id } + }; + + if let Ok(s) = FormatString::parse(self.symbol, self.span, &ctx) { + s.format(tcx, trait_ref, options, long_ty_file) + } else { + // we cannot return errors from processing the format string as hard error here + // as the diagnostic namespace guarantees that malformed input cannot cause an error + // + // if we encounter any error while processing the format string + // we don't want to show the potentially half assembled formatted string, + // therefore we fall back to just showing the input string in this case + // + // The actual parser errors are emitted earlier + // as lint warnings in OnUnimplementedFormatString::verify + self.symbol.as_str().into() } } } 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 new file mode 100644 index 00000000000..4c852c1023a --- /dev/null +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_condition.rs @@ -0,0 +1,64 @@ +use rustc_ast::MetaItemInner; +use rustc_attr_parsing as attr; +use rustc_data_structures::fx::FxHashMap; +use rustc_middle::ty::{self, TyCtxt}; +use rustc_parse_format::{ParseMode, Parser, Piece, Position}; +use rustc_span::{Span, Symbol, 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, +]; + +#[derive(Debug)] +pub struct Condition { + pub inner: MetaItemInner, +} + +impl Condition { + pub fn span(&self) -> Span { + self.inner.span() + } + + pub fn matches_predicate<'tcx>( + &self, + tcx: TyCtxt<'tcx>, + options: &[(Symbol, Option)], + options_map: &FxHashMap, + ) -> bool { + attr::eval_condition(&self.inner, tcx.sess, Some(tcx.features()), &mut |cfg| { + let value = cfg.value.map(|v| { + // `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) + .map(|p| match p { + Piece::Lit(s) => s.to_owned(), + Piece::NextArgument(a) => match a.position { + Position::ArgumentNamed(arg) => { + let s = Symbol::intern(arg); + match options_map.get(&s) { + Some(val) => val.to_string(), + None => format!("{{{arg}}}"), + } + } + Position::ArgumentImplicitlyIs(_) => String::from("{}"), + Position::ArgumentIs(idx) => format!("{{{idx}}}"), + }, + }) + .collect(); + constructed_message + }) + }); + + options.contains(&(cfg.name, value)) + }) + } +} 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 40a47921fc1..21db207496c 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,3 +1,347 @@ +use std::fmt::Write; +use std::path::PathBuf; + +use errors::*; +use rustc_data_structures::fx::FxHashMap; +use rustc_middle::span_bug; +use rustc_middle::ty::{self, GenericParamDefKind, TyCtxt}; +use rustc_parse_format::{ + Alignment, Argument, Count, FormatSpec, InnerSpan, ParseError, ParseMode, Parser, + Piece as RpfPiece, Position, +}; +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}; + +pub struct FormatString { + input: Symbol, + input_span: Span, + pieces: Vec, + // the formatting string was parsed succesfully but with warnings + pub warnings: Vec, +} + +enum Piece { + Lit(String), + Arg(FormatArg), +} + +pub enum FormatArg { + // A generic parameter, like `{T}` if we're on the `From` trait. + GenericParam { generic_param: Symbol, span: Span }, + // `{Self}` + SelfUpper, + This, + Trait, + ItemContext, + AsIs(String), +} + +pub enum Ctx<'tcx> { + // `#[rustc_on_unimplemented]` + RustcOnUnimplemented { tcx: TyCtxt<'tcx>, trait_def_id: DefId }, + // `#[diagnostic::...]` + DiagnosticOnUnimplemented { tcx: TyCtxt<'tcx>, trait_def_id: DefId }, +} + +pub enum FormatWarning { + UnknownParam { argument_name: Symbol, span: Span }, + PositionalArgument { span: Span, help: String }, + InvalidSpecifier { name: String, span: Span }, + FutureIncompat { span: Span, help: String }, +} + +impl FormatWarning { + pub fn emit_warning<'tcx>(&self, tcx: TyCtxt<'tcx>, item_def_id: DefId) { + match *self { + FormatWarning::UnknownParam { argument_name, span } => { + let this = tcx.item_ident(item_def_id); + 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), + span, + UnknownFormatParameterForOnUnimplementedAttr { + argument_name, + trait_name: this, + }, + ); + } + } + FormatWarning::PositionalArgument { span, .. } => { + 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), + span, + DisallowedPositionalArgument, + ); + } + } + FormatWarning::InvalidSpecifier { span, .. } => { + 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), + span, + InvalidFormatSpecifier, + ); + } + } + FormatWarning::FutureIncompat { .. } => { + // We've never deprecated anything in diagnostic namespace format strings + // but if we do we will emit a warning here + + // FIXME(mejrs) in a couple releases, start emitting warnings for + // #[rustc_on_unimplemented] deprecated args + } + } + } +} + +impl FormatString { + pub fn parse(input: Symbol, input_span: Span, ctx: &Ctx<'_>) -> Result> { + let s = input.as_str(); + let mut parser = Parser::new(s, None, None, false, ParseMode::Format); + let mut pieces = Vec::new(); + let mut warnings = Vec::new(); + + for piece in &mut parser { + match piece { + RpfPiece::Lit(lit) => { + 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); + pieces.push(Piece::Arg(arg)); + } + } + } + + if parser.errors.is_empty() { + Ok(FormatString { input, input_span, pieces, warnings }) + } else { + Err(parser.errors) + } + } + + pub fn format<'tcx>( + &self, + tcx: TyCtxt<'tcx>, + trait_ref: ty::TraitRef<'tcx>, + options: &FxHashMap, + long_ty_file: &mut Option, + ) -> String { + let generics = tcx.generics_of(trait_ref.def_id); + let generic_map = generics + .own_params + .iter() + .filter_map(|param| { + let value = match param.kind { + GenericParamDefKind::Type { .. } | GenericParamDefKind::Const { .. } => { + if let Some(ty) = trait_ref.args[param.index as usize].as_type() { + tcx.short_string(ty, long_ty_file) + } else { + trait_ref.args[param.index as usize].to_string() + } + } + GenericParamDefKind::Lifetime => return None, + }; + let name = param.name; + Some((name, value)) + }) + .collect::>(); + + 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, span }) => { + // Should always be some but we can't raise errors here + if let Some(value) = generic_map.get(&generic_param) { + ret.push_str(value); + } else if cfg!(debug_assertions) { + span_bug!(*span, "invalid generic parameter"); + } else { + let _ = ret.write_fmt(format_args!("{{{}}}", generic_param.as_str())); + } + } + // `{Self}` + Piece::Arg(FormatArg::SelfUpper) => { + let Some(slf) = generic_map.get(&kw::SelfUpper) else { + span_bug!( + self.input_span, + "broken format string {:?} for {:?}: \ + no argument matching `Self`", + self.input, + trait_ref, + ) + }; + ret.push_str(&slf); + } + + // It's only `rustc_onunimplemented` from here + Piece::Arg(FormatArg::This) => { + let Some(this) = options.get(&sym::This) else { + span_bug!( + self.input_span, + "broken format string {:?} for {:?}: \ + no argument matching This", + self.input, + trait_ref, + ) + }; + ret.push_str(this); + } + Piece::Arg(FormatArg::Trait) => { + let Some(this) = options.get(&sym::Trait) else { + span_bug!( + self.input_span, + "broken format string {:?} for {:?}: \ + no argument matching Trait", + self.input, + trait_ref, + ) + }; + ret.push_str(this); + } + Piece::Arg(FormatArg::ItemContext) => { + let itemcontext = options.get(&sym::ItemContext); + ret.push_str(itemcontext.unwrap_or(&String::new())); + } + } + } + ret + } +} + +fn parse_arg( + arg: &Argument<'_>, + ctx: &Ctx<'_>, + warnings: &mut Vec, + input_span: Span, +) -> FormatArg { + let (Ctx::RustcOnUnimplemented { tcx, trait_def_id } + | Ctx::DiagnosticOnUnimplemented { tcx, trait_def_id }) = ctx; + let trait_name = tcx.item_ident(*trait_def_id); + let generics = tcx.generics_of(trait_def_id); + let span = slice_span(input_span, arg.position_span); + + match arg.position { + // Something like "hello {name}" + Position::ArgumentNamed(name) => match (ctx, Symbol::intern(name)) { + // accepted, but deprecated + (Ctx::RustcOnUnimplemented { .. }, sym::_Self) => { + warnings + .push(FormatWarning::FutureIncompat { span, help: String::from("use {Self}") }); + FormatArg::SelfUpper + } + ( + Ctx::RustcOnUnimplemented { .. }, + sym::from_desugaring + | sym::crate_local + | sym::direct + | sym::cause + | sym::float + | sym::integer_ + | sym::integral, + ) => { + warnings.push(FormatWarning::FutureIncompat { + span, + help: String::from("don't use this in a format string"), + }); + FormatArg::AsIs(String::new()) + } + + // 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, + // `{ThisTraitsName}`. Some attrs in std use this, but I'd like to change it to the more general `{This}` + // because that'll be simpler to parse and extend in the future + (Ctx::RustcOnUnimplemented { .. }, name) if name == trait_name.name => { + warnings + .push(FormatWarning::FutureIncompat { span, help: String::from("use {This}") }); + FormatArg::This + } + + // Any attribute can use these + ( + Ctx::RustcOnUnimplemented { .. } | Ctx::DiagnosticOnUnimplemented { .. }, + kw::SelfUpper, + ) => FormatArg::SelfUpper, + ( + Ctx::RustcOnUnimplemented { .. } | Ctx::DiagnosticOnUnimplemented { .. }, + generic_param, + ) if generics.own_params.iter().any(|param| param.name == generic_param) => { + FormatArg::GenericParam { generic_param, span } + } + + (_, argument_name) => { + warnings.push(FormatWarning::UnknownParam { argument_name, span }); + FormatArg::AsIs(format!("{{{}}}", argument_name.as_str())) + } + }, + + // `{:1}` and `{}` are ignored + Position::ArgumentIs(idx) => { + warnings.push(FormatWarning::PositionalArgument { + span, + help: format!("use `{{{idx}}}` to print a number in braces"), + }); + FormatArg::AsIs(format!("{{{idx}}}")) + } + Position::ArgumentImplicitlyIs(_) => { + warnings.push(FormatWarning::PositionalArgument { + span, + help: String::from("use `{{}}` to print empty braces"), + }); + FormatArg::AsIs(String::from("{}")) + } + } +} + +/// `#[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, input_span: Span) { + if !matches!( + spec, + FormatSpec { + fill: None, + fill_span: None, + align: Alignment::AlignUnknown, + sign: None, + alternate: false, + zero_pad: false, + debug_hex: None, + precision: Count::CountImplied, + precision_span: None, + width: Count::CountImplied, + width_span: None, + ty: _, + ty_span: _, + }, + ) { + let span = spec.ty_span.map(|inner| slice_span(input_span, inner)).unwrap_or(input_span); + warnings.push(FormatWarning::InvalidSpecifier { span, name: spec.ty.into() }) + } +} + +fn slice_span(input: Span, inner: InnerSpan) -> Span { + let InnerSpan { start, end } = inner; + let span = input.data(); + + Span::new( + span.lo + BytePos::from_usize(start), + span.lo + BytePos::from_usize(end), + span.ctxt, + span.parent, + ) +} + pub mod errors { use rustc_macros::LintDiagnostic; use rustc_middle::ty::TyCtxt; diff --git a/tests/ui/diagnostic_namespace/on_unimplemented/broken_format.stderr b/tests/ui/diagnostic_namespace/on_unimplemented/broken_format.stderr index 7fd51c7527f..a82a1e78da0 100644 --- a/tests/ui/diagnostic_namespace/on_unimplemented/broken_format.stderr +++ b/tests/ui/diagnostic_namespace/on_unimplemented/broken_format.stderr @@ -1,52 +1,52 @@ warning: unmatched `}` found - --> $DIR/broken_format.rs:2:32 + --> $DIR/broken_format.rs:2:42 | LL | #[diagnostic::on_unimplemented(message = "{{Test } thing")] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^ | = note: `#[warn(unknown_or_malformed_diagnostic_attributes)]` on by default warning: positional format arguments are not allowed here - --> $DIR/broken_format.rs:7:32 + --> $DIR/broken_format.rs:7:49 | LL | #[diagnostic::on_unimplemented(message = "Test {}")] - | ^^^^^^^^^^^^^^^^^^^ + | ^ | = help: only named format arguments with the name of one of the generic types are allowed in this context warning: positional format arguments are not allowed here - --> $DIR/broken_format.rs:12:32 + --> $DIR/broken_format.rs:12:49 | LL | #[diagnostic::on_unimplemented(message = "Test {1:}")] - | ^^^^^^^^^^^^^^^^^^^^^ + | ^ | = help: only named format arguments with the name of one of the generic types are allowed in this context warning: invalid format specifier - --> $DIR/broken_format.rs:17:32 + --> $DIR/broken_format.rs:17:42 | LL | #[diagnostic::on_unimplemented(message = "Test {Self:123}")] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^ | = help: no format specifier are supported in this position warning: expected `}`, found `!` - --> $DIR/broken_format.rs:22:32 + --> $DIR/broken_format.rs:22:42 | LL | #[diagnostic::on_unimplemented(message = "Test {Self:!}")] - | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^ warning: unmatched `}` found - --> $DIR/broken_format.rs:22:32 + --> $DIR/broken_format.rs:22:42 | LL | #[diagnostic::on_unimplemented(message = "Test {Self:!}")] - | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^ warning: unmatched `}` found - --> $DIR/broken_format.rs:2:32 + --> $DIR/broken_format.rs:2:42 | LL | #[diagnostic::on_unimplemented(message = "{{Test } thing")] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^ | = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` @@ -70,10 +70,10 @@ LL | fn check_1(_: impl ImportantTrait1) {} | ^^^^^^^^^^^^^^^ required by this bound in `check_1` warning: positional format arguments are not allowed here - --> $DIR/broken_format.rs:7:32 + --> $DIR/broken_format.rs:7:49 | LL | #[diagnostic::on_unimplemented(message = "Test {}")] - | ^^^^^^^^^^^^^^^^^^^ + | ^ | = help: only named format arguments with the name of one of the generic types are allowed in this context = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` @@ -98,10 +98,10 @@ LL | fn check_2(_: impl ImportantTrait2) {} | ^^^^^^^^^^^^^^^ required by this bound in `check_2` warning: positional format arguments are not allowed here - --> $DIR/broken_format.rs:12:32 + --> $DIR/broken_format.rs:12:49 | LL | #[diagnostic::on_unimplemented(message = "Test {1:}")] - | ^^^^^^^^^^^^^^^^^^^^^ + | ^ | = help: only named format arguments with the name of one of the generic types are allowed in this context = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` @@ -126,10 +126,10 @@ LL | fn check_3(_: impl ImportantTrait3) {} | ^^^^^^^^^^^^^^^ required by this bound in `check_3` warning: invalid format specifier - --> $DIR/broken_format.rs:17:32 + --> $DIR/broken_format.rs:17:42 | LL | #[diagnostic::on_unimplemented(message = "Test {Self:123}")] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^ | = help: no format specifier are supported in this position = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` @@ -154,18 +154,18 @@ LL | fn check_4(_: impl ImportantTrait4) {} | ^^^^^^^^^^^^^^^ required by this bound in `check_4` warning: expected `}`, found `!` - --> $DIR/broken_format.rs:22:32 + --> $DIR/broken_format.rs:22:42 | LL | #[diagnostic::on_unimplemented(message = "Test {Self:!}")] - | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^ | = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` warning: unmatched `}` found - --> $DIR/broken_format.rs:22:32 + --> $DIR/broken_format.rs:22:42 | LL | #[diagnostic::on_unimplemented(message = "Test {Self:!}")] - | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^ | = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` diff --git a/tests/ui/diagnostic_namespace/on_unimplemented/do_not_accept_options_of_the_internal_rustc_attribute.stderr b/tests/ui/diagnostic_namespace/on_unimplemented/do_not_accept_options_of_the_internal_rustc_attribute.stderr index bb455d92940..88816a98dcf 100644 --- a/tests/ui/diagnostic_namespace/on_unimplemented/do_not_accept_options_of_the_internal_rustc_attribute.stderr +++ b/tests/ui/diagnostic_namespace/on_unimplemented/do_not_accept_options_of_the_internal_rustc_attribute.stderr @@ -39,82 +39,82 @@ LL | #[diagnostic::on_unimplemented = "Message"] = help: only `message`, `note` and `label` are allowed as options warning: there is no parameter `from_desugaring` on trait `Baz` - --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:33:5 + --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:33:17 | LL | message = "{from_desugaring}{direct}{cause}{integral}{integer}", - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^ | = help: expect either a generic argument name or `{Self}` as format argument warning: there is no parameter `direct` on trait `Baz` - --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:33:5 + --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:33:34 | LL | message = "{from_desugaring}{direct}{cause}{integral}{integer}", - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^ | = help: expect either a generic argument name or `{Self}` as format argument warning: there is no parameter `cause` on trait `Baz` - --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:33:5 + --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:33:42 | LL | message = "{from_desugaring}{direct}{cause}{integral}{integer}", - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^ | = help: expect either a generic argument name or `{Self}` as format argument warning: there is no parameter `integral` on trait `Baz` - --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:33:5 + --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:33:49 | LL | message = "{from_desugaring}{direct}{cause}{integral}{integer}", - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^ | = help: expect either a generic argument name or `{Self}` as format argument warning: there is no parameter `integer` on trait `Baz` - --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:33:5 + --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:33:59 | LL | message = "{from_desugaring}{direct}{cause}{integral}{integer}", - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^ | = help: expect either a generic argument name or `{Self}` as format argument warning: there is no parameter `float` on trait `Baz` - --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:44:5 + --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:44:15 | LL | label = "{float}{_Self}{crate_local}{Trait}{ItemContext}" - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^ | = help: expect either a generic argument name or `{Self}` as format argument warning: there is no parameter `_Self` on trait `Baz` - --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:44:5 + --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:44:22 | LL | label = "{float}{_Self}{crate_local}{Trait}{ItemContext}" - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^ | = help: expect either a generic argument name or `{Self}` as format argument warning: there is no parameter `crate_local` on trait `Baz` - --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:44:5 + --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:44:29 | LL | label = "{float}{_Self}{crate_local}{Trait}{ItemContext}" - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^ | = help: expect either a generic argument name or `{Self}` as format argument warning: there is no parameter `Trait` on trait `Baz` - --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:44:5 + --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:44:42 | LL | label = "{float}{_Self}{crate_local}{Trait}{ItemContext}" - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^ | = help: expect either a generic argument name or `{Self}` as format argument warning: there is no parameter `ItemContext` on trait `Baz` - --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:44:5 + --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:44:49 | LL | label = "{float}{_Self}{crate_local}{Trait}{ItemContext}" - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^ | = help: expect either a generic argument name or `{Self}` as format argument @@ -191,91 +191,91 @@ LL | fn takes_bar(_: impl Bar) {} | ^^^ required by this bound in `takes_bar` warning: there is no parameter `from_desugaring` on trait `Baz` - --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:33:5 + --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:33:17 | LL | message = "{from_desugaring}{direct}{cause}{integral}{integer}", - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^ | = help: expect either a generic argument name or `{Self}` as format argument = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` warning: there is no parameter `direct` on trait `Baz` - --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:33:5 + --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:33:34 | LL | message = "{from_desugaring}{direct}{cause}{integral}{integer}", - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^ | = help: expect either a generic argument name or `{Self}` as format argument = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` warning: there is no parameter `cause` on trait `Baz` - --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:33:5 + --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:33:42 | LL | message = "{from_desugaring}{direct}{cause}{integral}{integer}", - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^ | = help: expect either a generic argument name or `{Self}` as format argument = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` warning: there is no parameter `integral` on trait `Baz` - --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:33:5 + --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:33:49 | LL | message = "{from_desugaring}{direct}{cause}{integral}{integer}", - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^ | = help: expect either a generic argument name or `{Self}` as format argument = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` warning: there is no parameter `integer` on trait `Baz` - --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:33:5 + --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:33:59 | LL | message = "{from_desugaring}{direct}{cause}{integral}{integer}", - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^ | = help: expect either a generic argument name or `{Self}` as format argument = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` warning: there is no parameter `float` on trait `Baz` - --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:44:5 + --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:44:15 | LL | label = "{float}{_Self}{crate_local}{Trait}{ItemContext}" - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^ | = help: expect either a generic argument name or `{Self}` as format argument = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` warning: there is no parameter `_Self` on trait `Baz` - --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:44:5 + --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:44:22 | LL | label = "{float}{_Self}{crate_local}{Trait}{ItemContext}" - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^ | = help: expect either a generic argument name or `{Self}` as format argument = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` warning: there is no parameter `crate_local` on trait `Baz` - --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:44:5 + --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:44:29 | LL | label = "{float}{_Self}{crate_local}{Trait}{ItemContext}" - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^ | = help: expect either a generic argument name or `{Self}` as format argument = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` warning: there is no parameter `Trait` on trait `Baz` - --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:44:5 + --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:44:42 | LL | label = "{float}{_Self}{crate_local}{Trait}{ItemContext}" - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^ | = help: expect either a generic argument name or `{Self}` as format argument = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` warning: there is no parameter `ItemContext` on trait `Baz` - --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:44:5 + --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:44:49 | LL | label = "{float}{_Self}{crate_local}{Trait}{ItemContext}" - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^ | = help: expect either a generic argument name or `{Self}` as format argument = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` diff --git a/tests/ui/diagnostic_namespace/on_unimplemented/do_not_fail_parsing_on_invalid_options_1.stderr b/tests/ui/diagnostic_namespace/on_unimplemented/do_not_fail_parsing_on_invalid_options_1.stderr index 11263580b15..4dd8c1afca0 100644 --- a/tests/ui/diagnostic_namespace/on_unimplemented/do_not_fail_parsing_on_invalid_options_1.stderr +++ b/tests/ui/diagnostic_namespace/on_unimplemented/do_not_fail_parsing_on_invalid_options_1.stderr @@ -47,10 +47,10 @@ LL | #[diagnostic::on_unimplemented] = help: at least one of the `message`, `note` and `label` options are expected warning: there is no parameter `DoesNotExist` on trait `Test` - --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:31:32 + --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:31:44 | LL | #[diagnostic::on_unimplemented(message = "{DoesNotExist}")] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^ | = help: expect either a generic argument name or `{Self}` as format argument @@ -167,10 +167,10 @@ LL | fn take_whatever(_: impl Whatever) {} | ^^^^^^^^ required by this bound in `take_whatever` warning: there is no parameter `DoesNotExist` on trait `Test` - --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:31:32 + --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:31:44 | LL | #[diagnostic::on_unimplemented(message = "{DoesNotExist}")] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^ | = help: expect either a generic argument name or `{Self}` as format argument = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` diff --git a/tests/ui/on-unimplemented/bad-annotation.rs b/tests/ui/on-unimplemented/bad-annotation.rs index 3f0f69749bf..0936b25847c 100644 --- a/tests/ui/on-unimplemented/bad-annotation.rs +++ b/tests/ui/on-unimplemented/bad-annotation.rs @@ -20,12 +20,12 @@ trait BadAnnotation1 {} #[rustc_on_unimplemented = "Unimplemented trait error on `{Self}` with params `<{A},{B},{C}>`"] -//~^ ERROR there is no parameter `C` on trait `BadAnnotation2` +//~^ WARNING there is no parameter `C` on trait `BadAnnotation2` trait BadAnnotation2 {} #[rustc_on_unimplemented = "Unimplemented trait error on `{Self}` with params `<{A},{B},{}>`"] -//~^ ERROR only named generic parameters are allowed +//~^ ERROR positional format arguments are not allowed here trait BadAnnotation3 {} diff --git a/tests/ui/on-unimplemented/bad-annotation.stderr b/tests/ui/on-unimplemented/bad-annotation.stderr index 4ceea779b29..1fefd93aa7e 100644 --- a/tests/ui/on-unimplemented/bad-annotation.stderr +++ b/tests/ui/on-unimplemented/bad-annotation.stderr @@ -11,17 +11,22 @@ LL | #[rustc_on_unimplemented = "message"] LL | #[rustc_on_unimplemented(/*opt*/ message = "...", /*opt*/ label = "...", /*opt*/ note = "...")] | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -error[E0230]: there is no parameter `C` on trait `BadAnnotation2` - --> $DIR/bad-annotation.rs:22:1 +warning: there is no parameter `C` on trait `BadAnnotation2` + --> $DIR/bad-annotation.rs:22:90 | LL | #[rustc_on_unimplemented = "Unimplemented trait error on `{Self}` with params `<{A},{B},{C}>`"] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^ + | + = help: expect either a generic argument name or `{Self}` as format argument + = note: `#[warn(unknown_or_malformed_diagnostic_attributes)]` on by default -error[E0231]: only named generic parameters are allowed - --> $DIR/bad-annotation.rs:27:1 +warning: positional format arguments are not allowed here + --> $DIR/bad-annotation.rs:27:90 | LL | #[rustc_on_unimplemented = "Unimplemented trait error on `{Self}` with params `<{A},{B},{}>`"] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^ + | + = help: only named format arguments with the name of one of the generic types are allowed in this context error[E0232]: this attribute must have a valid value --> $DIR/bad-annotation.rs:32:26 @@ -77,7 +82,6 @@ LL | #[rustc_on_unimplemented(on(desugared, on(desugared, message="x")), message | = note: eg `#[rustc_on_unimplemented(message="foo")]` -error: aborting due to 10 previous errors +error: aborting due to 8 previous errors; 2 warnings emitted -Some errors have detailed explanations: E0230, E0231, E0232. -For more information about an error, try `rustc --explain E0230`. +For more information about this error, try `rustc --explain E0232`. diff --git a/tests/ui/on-unimplemented/impl-substs.stderr b/tests/ui/on-unimplemented/impl-substs.stderr index b85d45eba5b..0eabe971492 100644 --- a/tests/ui/on-unimplemented/impl-substs.stderr +++ b/tests/ui/on-unimplemented/impl-substs.stderr @@ -2,7 +2,7 @@ error[E0277]: the trait bound `(i32, i32, i32): Foo` is not satisfied --> $DIR/impl-substs.rs:13:23 | LL | Foo::::foo((1i32, 1i32, 1i32)); - | ----------------- ^^^^^^^^^^^^^^^^^^ an impl did not match: usize _ _ + | ----------------- ^^^^^^^^^^^^^^^^^^ an impl did not match: usize {B} {C} | | | required by a bound introduced by this call | From ba9f51b05503a567cb637db03eaa943c59e36922 Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Thu, 27 Mar 2025 23:09:44 +0100 Subject: [PATCH 03/39] Parse condition options into a struct --- .../traits/on_unimplemented.rs | 178 ++++++++++-------- .../traits/on_unimplemented_condition.rs | 45 +++-- .../traits/on_unimplemented_format.rs | 110 ++++------- 3 files changed, 168 insertions(+), 165 deletions(-) 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 6a7f3223421..7d8ecba3b2e 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 @@ -2,14 +2,13 @@ use std::iter; use std::path::PathBuf; use rustc_ast::{LitKind, MetaItem, MetaItemInner, MetaItemKind, MetaItemLit}; -use rustc_data_structures::fx::FxHashMap; 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_middle::bug; -use rustc_middle::ty::print::PrintTraitRefExt as _; -use rustc_middle::ty::{self, GenericArgsRef, GenericParamDefKind, TyCtxt}; +use rustc_middle::ty::print::PrintTraitRefExt; +use rustc_middle::ty::{self, GenericArgsRef, GenericParamDef, GenericParamDefKind, TyCtxt}; use rustc_session::lint::builtin::UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES; use rustc_span::{Span, Symbol, sym}; use tracing::{debug, info}; @@ -17,9 +16,9 @@ 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; +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, FormatString}; +use crate::error_reporting::traits::on_unimplemented_format::{Ctx, FormatArgs, FormatString}; use crate::errors::{ EmptyOnClauseInOnUnimplemented, InvalidOnClauseInOnUnimplemented, NoValueInOnUnimplemented, }; @@ -107,86 +106,81 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { .unwrap_or_else(|| (trait_pred.def_id(), trait_pred.skip_binder().trait_ref.args)); let trait_pred = trait_pred.skip_binder(); - let mut flags = vec![]; + let mut self_types = vec![]; + let mut generic_args: Vec<(Symbol, String)> = vec![]; + let mut crate_local = false; // 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 enclosure = self.describe_enclosure(obligation.cause.body_id).map(|t| t.to_owned()); - flags.push((sym::ItemContext, enclosure)); + let item_context = self + .describe_enclosure(obligation.cause.body_id) + .map(|t| t.to_owned()) + .unwrap_or(String::new()); - match obligation.cause.code() { + let direct = match obligation.cause.code() { ObligationCauseCode::BuiltinDerived(..) | ObligationCauseCode::ImplDerived(..) - | ObligationCauseCode::WellFormedDerived(..) => {} + | ObligationCauseCode::WellFormedDerived(..) => false, _ => { // this is a "direct", user-specified, rather than derived, // obligation. - flags.push((sym::direct, None)); + true } - } + }; - if let Some(k) = obligation.cause.span.desugaring_kind() { - flags.push((sym::from_desugaring, None)); - flags.push((sym::from_desugaring, Some(format!("{k:?}")))); - } + let from_desugaring = obligation.cause.span.desugaring_kind().map(|k| format!("{k:?}")); - if let ObligationCauseCode::MainFunctionType = obligation.cause.code() { - flags.push((sym::cause, Some("MainFunctionType".to_string()))); - } - - flags.push((sym::Trait, Some(trait_pred.trait_ref.print_trait_sugared().to_string()))); + let cause = if let ObligationCauseCode::MainFunctionType = obligation.cause.code() { + Some("MainFunctionType".to_string()) + } else { + None + }; // Add all types without trimmed paths or visible paths, ensuring they end up with // their "canonical" def path. ty::print::with_no_trimmed_paths!(ty::print::with_no_visible_paths!({ let generics = self.tcx.generics_of(def_id); let self_ty = trait_pred.self_ty(); - // This is also included through the generics list as `Self`, - // but the parser won't allow you to use it - flags.push((sym::_Self, Some(self_ty.to_string()))); + self_types.push(self_ty.to_string()); if let Some(def) = self_ty.ty_adt_def() { // We also want to be able to select self's original // signature with no type arguments resolved - flags.push(( - sym::_Self, - Some(self.tcx.type_of(def.did()).instantiate_identity().to_string()), - )); + self_types.push(self.tcx.type_of(def.did()).instantiate_identity().to_string()); } - for param in generics.own_params.iter() { - let value = match param.kind { + for GenericParamDef { name, kind, index, .. } in generics.own_params.iter() { + let value = match kind { GenericParamDefKind::Type { .. } | GenericParamDefKind::Const { .. } => { - args[param.index as usize].to_string() + args[*index as usize].to_string() } GenericParamDefKind::Lifetime => continue, }; - let name = param.name; - flags.push((name, Some(value))); + generic_args.push((*name, value)); - if let GenericParamDefKind::Type { .. } = param.kind { - let param_ty = args[param.index as usize].expect_ty(); + if let GenericParamDefKind::Type { .. } = kind { + let param_ty = args[*index as usize].expect_ty(); if let Some(def) = param_ty.ty_adt_def() { // We also want to be able to select the parameter's // original signature with no type arguments resolved - flags.push(( - name, - Some(self.tcx.type_of(def.did()).instantiate_identity().to_string()), + generic_args.push(( + *name, + self.tcx.type_of(def.did()).instantiate_identity().to_string(), )); } } } if let Some(true) = self_ty.ty_adt_def().map(|def| def.did().is_local()) { - flags.push((sym::crate_local, None)); + crate_local = true; } // Allow targeting all integers using `{integral}`, even if the exact type was resolved if self_ty.is_integral() { - flags.push((sym::_Self, Some("{integral}".to_owned()))); + self_types.push("{integral}".to_owned()); } if self_ty.is_array_slice() { - flags.push((sym::_Self, Some("&[]".to_owned()))); + self_types.push("&[]".to_owned()); } if self_ty.is_fn() { @@ -201,53 +195,51 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { hir::Safety::Unsafe => "unsafe fn", } }; - flags.push((sym::_Self, Some(shortname.to_owned()))); + self_types.push(shortname.to_owned()); } // Slices give us `[]`, `[{ty}]` if let ty::Slice(aty) = self_ty.kind() { - flags.push((sym::_Self, Some("[]".to_string()))); + self_types.push("[]".to_owned()); if let Some(def) = aty.ty_adt_def() { // We also want to be able to select the slice's type's original // signature with no type arguments resolved - flags.push(( - sym::_Self, - Some(format!("[{}]", self.tcx.type_of(def.did()).instantiate_identity())), - )); + self_types + .push(format!("[{}]", self.tcx.type_of(def.did()).instantiate_identity())); } if aty.is_integral() { - flags.push((sym::_Self, Some("[{integral}]".to_string()))); + self_types.push("[{integral}]".to_string()); } } // Arrays give us `[]`, `[{ty}; _]` and `[{ty}; N]` if let ty::Array(aty, len) = self_ty.kind() { - flags.push((sym::_Self, Some("[]".to_string()))); + self_types.push("[]".to_string()); let len = len.try_to_target_usize(self.tcx); - flags.push((sym::_Self, Some(format!("[{aty}; _]")))); + self_types.push(format!("[{aty}; _]")); if let Some(n) = len { - flags.push((sym::_Self, Some(format!("[{aty}; {n}]")))); + self_types.push(format!("[{aty}; {n}]")); } if let Some(def) = aty.ty_adt_def() { // We also want to be able to select the array's type's original // signature with no type arguments resolved let def_ty = self.tcx.type_of(def.did()).instantiate_identity(); - flags.push((sym::_Self, Some(format!("[{def_ty}; _]")))); + self_types.push(format!("[{def_ty}; _]")); if let Some(n) = len { - flags.push((sym::_Self, Some(format!("[{def_ty}; {n}]")))); + self_types.push(format!("[{def_ty}; {n}]")); } } if aty.is_integral() { - flags.push((sym::_Self, Some("[{integral}; _]".to_string()))); + self_types.push("[{integral}; _]".to_string()); if let Some(n) = len { - flags.push((sym::_Self, Some(format!("[{{integral}}; {n}]")))); + self_types.push(format!("[{{integral}}; {n}]")); } } } if let ty::Dynamic(traits, _, _) = self_ty.kind() { for t in traits.iter() { if let ty::ExistentialPredicate::Trait(trait_ref) = t.skip_binder() { - flags.push((sym::_Self, Some(self.tcx.def_path_str(trait_ref.def_id)))) + self_types.push(self.tcx.def_path_str(trait_ref.def_id)); } } } @@ -257,14 +249,51 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { && let ty::Slice(sty) = ref_ty.kind() && sty.is_integral() { - flags.push((sym::_Self, Some("&[{integral}]".to_owned()))); + self_types.push("&[{integral}]".to_owned()); } })); - flags.push((sym::This, Some(self.tcx.def_path_str(trait_pred.trait_ref.def_id)))); + 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 condition_options = ConditionOptions { + self_types, + from_desugaring, + cause, + crate_local, + direct, + generic_args, + }; + + // Unlike the generic_args earlier, + // this one is *not* collected under `with_no_trimmed_paths!` + // for printing the type to the user + let generic_args = self + .tcx + .generics_of(trait_pred.trait_ref.def_id) + .own_params + .iter() + .filter_map(|param| { + let value = match param.kind { + GenericParamDefKind::Type { .. } | GenericParamDefKind::Const { .. } => { + if let Some(ty) = trait_pred.trait_ref.args[param.index as usize].as_type() + { + self.tcx.short_string(ty, long_ty_file) + } else { + trait_pred.trait_ref.args[param.index as usize].to_string() + } + } + GenericParamDefKind::Lifetime => return None, + }; + let name = param.name; + Some((name, value)) + }) + .collect(); + + let format_args = FormatArgs { this, trait_sugared, generic_args, item_context }; if let Ok(Some(command)) = OnUnimplementedDirective::of_item(self.tcx, def_id) { - command.evaluate(self.tcx, trait_pred.trait_ref, &flags, long_ty_file) + command.evaluate(self.tcx, trait_pred.trait_ref, &condition_options, &format_args) } else { OnUnimplementedNote::default() } @@ -634,23 +663,23 @@ impl<'tcx> OnUnimplementedDirective { &self, tcx: TyCtxt<'tcx>, trait_ref: ty::TraitRef<'tcx>, - options: &[(Symbol, Option)], - long_ty_file: &mut Option, + condition_options: &ConditionOptions, + args: &FormatArgs, ) -> OnUnimplementedNote { let mut message = None; let mut label = None; let mut notes = Vec::new(); let mut parent_label = None; let mut append_const_msg = None; - info!("evaluate({:?}, trait_ref={:?}, options={:?})", self, trait_ref, options); - - let options_map: FxHashMap = - options.iter().filter_map(|(k, v)| v.clone().map(|v| (*k, v))).collect(); + info!( + "evaluate({:?}, trait_ref={:?}, options={:?}, args ={:?})", + self, trait_ref, condition_options, args + ); for command in self.subcommands.iter().chain(Some(self)).rev() { debug!(?command); if let Some(ref condition) = command.condition - && !condition.matches_predicate(tcx, options, &options_map) + && !condition.matches_predicate(tcx, condition_options) { debug!("evaluate: skipping {:?} due to condition", command); continue; @@ -674,14 +703,10 @@ impl<'tcx> OnUnimplementedDirective { } OnUnimplementedNote { - label: label.map(|l| l.1.format(tcx, trait_ref, &options_map, long_ty_file)), - message: message.map(|m| m.1.format(tcx, trait_ref, &options_map, long_ty_file)), - notes: notes - .into_iter() - .map(|n| n.format(tcx, trait_ref, &options_map, long_ty_file)) - .collect(), - parent_label: parent_label - .map(|e_s| e_s.format(tcx, trait_ref, &options_map, long_ty_file)), + label: label.map(|l| l.1.format(tcx, trait_ref, args)), + message: message.map(|m| m.1.format(tcx, trait_ref, args)), + notes: notes.into_iter().map(|n| n.format(tcx, trait_ref, args)).collect(), + parent_label: parent_label.map(|e_s| e_s.format(tcx, trait_ref, args)), append_const_msg, } } @@ -759,8 +784,7 @@ impl<'tcx> OnUnimplementedFormatString { &self, tcx: TyCtxt<'tcx>, trait_ref: ty::TraitRef<'tcx>, - options: &FxHashMap, - long_ty_file: &mut Option, + args: &FormatArgs, ) -> String { let trait_def_id = trait_ref.def_id; let ctx = if self.is_diagnostic_namespace_variant { @@ -770,7 +794,7 @@ impl<'tcx> OnUnimplementedFormatString { }; if let Ok(s) = FormatString::parse(self.symbol, self.span, &ctx) { - s.format(tcx, trait_ref, options, long_ty_file) + s.format(args) } else { // we cannot return errors from processing the format string as hard error here // as the diagnostic namespace guarantees that malformed input cannot cause an error 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 4c852c1023a..491ba6d7ffc 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 @@ -1,9 +1,8 @@ use rustc_ast::MetaItemInner; use rustc_attr_parsing as attr; -use rustc_data_structures::fx::FxHashMap; use rustc_middle::ty::{self, TyCtxt}; use rustc_parse_format::{ParseMode, Parser, Piece, Position}; -use rustc_span::{Span, Symbol, sym}; +use rustc_span::{Span, Symbol, kw, sym}; pub static ALLOWED_CONDITION_SYMBOLS: &[Symbol] = &[ sym::from_desugaring, @@ -26,12 +25,7 @@ impl Condition { self.inner.span() } - pub fn matches_predicate<'tcx>( - &self, - tcx: TyCtxt<'tcx>, - options: &[(Symbol, Option)], - options_map: &FxHashMap, - ) -> bool { + pub fn matches_predicate<'tcx>(&self, tcx: TyCtxt<'tcx>, options: &ConditionOptions) -> bool { attr::eval_condition(&self.inner, tcx.sess, Some(tcx.features()), &mut |cfg| { let value = cfg.value.map(|v| { // `with_no_visible_paths` is also used when generating the options, @@ -44,8 +38,8 @@ impl Condition { Piece::NextArgument(a) => match a.position { Position::ArgumentNamed(arg) => { let s = Symbol::intern(arg); - match options_map.get(&s) { - Some(val) => val.to_string(), + match options.generic_args.iter().find(|(k, _)| *k == s) { + Some((_, val)) => val.to_string(), None => format!("{{{arg}}}"), } } @@ -58,7 +52,36 @@ impl Condition { }) }); - options.contains(&(cfg.name, value)) + options.contains(cfg.name, &value) }) } } + +#[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)>, +} + +impl ConditionOptions { + pub fn contains(&self, key: Symbol, value: &Option) -> bool { + match (key, value) { + (sym::_Self | kw::SelfUpper, Some(value)) => self.self_types.contains(&value), + // 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::cause, Some(value)) => self.cause.as_deref() == Some(value), + (sym::crate_local, None) => self.crate_local, + (sym::direct, None) => self.direct, + (other, Some(value)) => { + self.generic_args.iter().any(|(k, v)| *k == other && v == value) + } + _ => false, + } + } +} 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 21db207496c..1bf15a34509 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,10 +1,5 @@ -use std::fmt::Write; -use std::path::PathBuf; - use errors::*; -use rustc_data_structures::fx::FxHashMap; -use rustc_middle::span_bug; -use rustc_middle::ty::{self, GenericParamDefKind, TyCtxt}; +use rustc_middle::ty::TyCtxt; use rustc_parse_format::{ Alignment, Argument, Count, FormatSpec, InnerSpan, ParseError, ParseMode, Parser, Piece as RpfPiece, Position, @@ -13,6 +8,7 @@ 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)] pub struct FormatString { input: Symbol, input_span: Span, @@ -99,6 +95,24 @@ impl FormatWarning { } } +#[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 this: String, + pub trait_sugared: String, + pub item_context: String, + pub generic_args: Vec<(Symbol, String)>, +} + impl FormatString { pub fn parse(input: Symbol, input_span: Span, ctx: &Ctx<'_>) -> Result> { let s = input.as_str(); @@ -126,92 +140,34 @@ impl FormatString { } } - pub fn format<'tcx>( - &self, - tcx: TyCtxt<'tcx>, - trait_ref: ty::TraitRef<'tcx>, - options: &FxHashMap, - long_ty_file: &mut Option, - ) -> String { - let generics = tcx.generics_of(trait_ref.def_id); - let generic_map = generics - .own_params - .iter() - .filter_map(|param| { - let value = match param.kind { - GenericParamDefKind::Type { .. } | GenericParamDefKind::Const { .. } => { - if let Some(ty) = trait_ref.args[param.index as usize].as_type() { - tcx.short_string(ty, long_ty_file) - } else { - trait_ref.args[param.index as usize].to_string() - } - } - GenericParamDefKind::Lifetime => return None, - }; - let name = param.name; - Some((name, value)) - }) - .collect::>(); - + 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, span }) => { + Piece::Arg(FormatArg::GenericParam { generic_param, .. }) => { // Should always be some but we can't raise errors here - if let Some(value) = generic_map.get(&generic_param) { - ret.push_str(value); - } else if cfg!(debug_assertions) { - span_bug!(*span, "invalid generic parameter"); - } else { - let _ = ret.write_fmt(format_args!("{{{}}}", generic_param.as_str())); - } + let value = match args.generic_args.iter().find(|(p, _)| p == generic_param) { + Some((_, val)) => val.to_string(), + None => generic_param.to_string(), + }; + ret.push_str(&value); } // `{Self}` Piece::Arg(FormatArg::SelfUpper) => { - let Some(slf) = generic_map.get(&kw::SelfUpper) else { - span_bug!( - self.input_span, - "broken format string {:?} for {:?}: \ - no argument matching `Self`", - self.input, - trait_ref, - ) + let slf = match args.generic_args.iter().find(|(p, _)| *p == kw::SelfUpper) { + Some((_, val)) => val.to_string(), + None => "Self".to_string(), }; ret.push_str(&slf); } // It's only `rustc_onunimplemented` from here - Piece::Arg(FormatArg::This) => { - let Some(this) = options.get(&sym::This) else { - span_bug!( - self.input_span, - "broken format string {:?} for {:?}: \ - no argument matching This", - self.input, - trait_ref, - ) - }; - ret.push_str(this); - } - Piece::Arg(FormatArg::Trait) => { - let Some(this) = options.get(&sym::Trait) else { - span_bug!( - self.input_span, - "broken format string {:?} for {:?}: \ - no argument matching Trait", - self.input, - trait_ref, - ) - }; - ret.push_str(this); - } - Piece::Arg(FormatArg::ItemContext) => { - let itemcontext = options.get(&sym::ItemContext); - ret.push_str(itemcontext.unwrap_or(&String::new())); - } + 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), } } ret 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 04/39] 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] From 9abaa9d4dffeb897a1dbff97d32d3b6ac190be21 Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Sat, 29 Mar 2025 11:46:03 +0100 Subject: [PATCH 05/39] Disable usage on trait impls and aliases --- .../traits/on_unimplemented.rs | 17 +++++++--- tests/crashes/130627.rs | 20 ------------ .../ui/diagnostic_namespace/on_impl_trait.rs | 17 ++++++++++ .../diagnostic_namespace/on_impl_trait.stderr | 31 +++++++++++++++++++ tests/ui/on-unimplemented/impl-substs.stderr | 2 +- .../ui/on-unimplemented/multiple-impls.stderr | 12 +++---- tests/ui/on-unimplemented/on-impl.stderr | 4 +-- 7 files changed, 68 insertions(+), 35 deletions(-) delete mode 100644 tests/crashes/130627.rs create mode 100644 tests/ui/diagnostic_namespace/on_impl_trait.rs create mode 100644 tests/ui/diagnostic_namespace/on_impl_trait.stderr 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 0478f3a7f11..ca51f177f96 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 @@ -553,6 +553,13 @@ impl<'tcx> OnUnimplementedDirective { } pub fn of_item(tcx: TyCtxt<'tcx>, item_def_id: DefId) -> Result, ErrorGuaranteed> { + if !tcx.is_trait(item_def_id) { + // It could be a trait_alias (`trait MyTrait = SomeOtherTrait`) + // or an implementation (`impl MyTrait for Foo {}`) + // + // We don't support those. + return Ok(None); + } if let Some(attr) = tcx.get_attr(item_def_id, sym::rustc_on_unimplemented) { return Self::parse_attribute(attr, false, tcx, item_def_id); } else { @@ -782,8 +789,10 @@ impl<'tcx> OnUnimplementedFormatString { Ok(result) } - fn verify(&self, tcx: TyCtxt<'tcx>, item_def_id: DefId) -> Result<(), ErrorGuaranteed> { - let trait_def_id = if tcx.is_trait(item_def_id) { item_def_id } else { return Ok(()) }; + fn verify(&self, tcx: TyCtxt<'tcx>, trait_def_id: DefId) -> Result<(), ErrorGuaranteed> { + if !tcx.is_trait(trait_def_id) { + return Ok(()); + }; let ctx = if self.is_diagnostic_namespace_variant { Ctx::DiagnosticOnUnimplemented { tcx, trait_def_id } @@ -810,10 +819,10 @@ impl<'tcx> OnUnimplementedFormatString { // so that users are aware that something is not correct for e in errors { if self.is_diagnostic_namespace_variant { - if let Some(item_def_id) = item_def_id.as_local() { + if let Some(trait_def_id) = trait_def_id.as_local() { tcx.emit_node_span_lint( UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES, - tcx.local_def_id_to_hir_id(item_def_id), + tcx.local_def_id_to_hir_id(trait_def_id), self.span, WrappedParserError { description: e.description, label: e.label }, ); diff --git a/tests/crashes/130627.rs b/tests/crashes/130627.rs deleted file mode 100644 index 59d3606592b..00000000000 --- a/tests/crashes/130627.rs +++ /dev/null @@ -1,20 +0,0 @@ -//@ known-bug: #130627 - -#![feature(trait_alias)] - -trait Test {} - -#[diagnostic::on_unimplemented( - message="message", - label="label", - note="note" -)] -trait Alias = Test; - -// Use trait alias as bound on type parameter. -fn foo(v: &T) { -} - -pub fn main() { - foo(&1); -} diff --git a/tests/ui/diagnostic_namespace/on_impl_trait.rs b/tests/ui/diagnostic_namespace/on_impl_trait.rs new file mode 100644 index 00000000000..32a492c53a9 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_impl_trait.rs @@ -0,0 +1,17 @@ +// used to ICE, see +// Instead it should just ignore the diagnostic attribute +#![feature(trait_alias)] + +trait Test {} + +#[diagnostic::on_unimplemented(message = "blah", label = "blah", note = "blah")] +//~^ WARN `#[diagnostic::on_unimplemented]` can only be applied to trait definitions +trait Alias = Test; + +// Use trait alias as bound on type parameter. +fn foo(v: &T) {} + +pub fn main() { + foo(&1); + //~^ ERROR the trait bound `{integer}: Alias` is not satisfied +} diff --git a/tests/ui/diagnostic_namespace/on_impl_trait.stderr b/tests/ui/diagnostic_namespace/on_impl_trait.stderr new file mode 100644 index 00000000000..59b9c31bc53 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_impl_trait.stderr @@ -0,0 +1,31 @@ +warning: `#[diagnostic::on_unimplemented]` can only be applied to trait definitions + --> $DIR/on_impl_trait.rs:7:1 + | +LL | #[diagnostic::on_unimplemented(message = "blah", label = "blah", note = "blah")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `#[warn(unknown_or_malformed_diagnostic_attributes)]` on by default + +error[E0277]: the trait bound `{integer}: Alias` is not satisfied + --> $DIR/on_impl_trait.rs:15:9 + | +LL | foo(&1); + | --- ^^ the trait `Test` is not implemented for `{integer}` + | | + | required by a bound introduced by this call + | +help: this trait has no implementations, consider adding one + --> $DIR/on_impl_trait.rs:5:1 + | +LL | trait Test {} + | ^^^^^^^^^^ + = note: required for `{integer}` to implement `Alias` +note: required by a bound in `foo` + --> $DIR/on_impl_trait.rs:12:11 + | +LL | fn foo(v: &T) {} + | ^^^^^ required by this bound in `foo` + +error: aborting due to 1 previous error; 1 warning emitted + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/on-unimplemented/impl-substs.stderr b/tests/ui/on-unimplemented/impl-substs.stderr index 0eabe971492..2d83845ecb8 100644 --- a/tests/ui/on-unimplemented/impl-substs.stderr +++ b/tests/ui/on-unimplemented/impl-substs.stderr @@ -2,7 +2,7 @@ error[E0277]: the trait bound `(i32, i32, i32): Foo` is not satisfied --> $DIR/impl-substs.rs:13:23 | LL | Foo::::foo((1i32, 1i32, 1i32)); - | ----------------- ^^^^^^^^^^^^^^^^^^ an impl did not match: usize {B} {C} + | ----------------- ^^^^^^^^^^^^^^^^^^ the trait `Foo` is not implemented for `(i32, i32, i32)` | | | required by a bound introduced by this call | diff --git a/tests/ui/on-unimplemented/multiple-impls.stderr b/tests/ui/on-unimplemented/multiple-impls.stderr index ba4e43ff359..2afc9b1bf3b 100644 --- a/tests/ui/on-unimplemented/multiple-impls.stderr +++ b/tests/ui/on-unimplemented/multiple-impls.stderr @@ -15,11 +15,10 @@ error[E0277]: the trait bound `[i32]: Index>` is not satisfied --> $DIR/multiple-impls.rs:36:33 | LL | Index::index(&[] as &[i32], Foo(2u32)); - | ------------ ^^^^^^^^^ on impl for Foo + | ------------ ^^^^^^^^^ the trait `Index>` is not implemented for `[i32]` | | | required by a bound introduced by this call | - = help: the trait `Index>` is not implemented for `[i32]` = help: the following other types implement trait `Index`: `[i32]` implements `Index>` `[i32]` implements `Index>` @@ -28,11 +27,10 @@ error[E0277]: the trait bound `[i32]: Index>` is not satisfied --> $DIR/multiple-impls.rs:39:33 | LL | Index::index(&[] as &[i32], Bar(2u32)); - | ------------ ^^^^^^^^^ on impl for Bar + | ------------ ^^^^^^^^^ the trait `Index>` is not implemented for `[i32]` | | | required by a bound introduced by this call | - = help: the trait `Index>` is not implemented for `[i32]` = help: the following other types implement trait `Index`: `[i32]` implements `Index>` `[i32]` implements `Index>` @@ -52,9 +50,8 @@ error[E0277]: the trait bound `[i32]: Index>` is not satisfied --> $DIR/multiple-impls.rs:36:5 | LL | Index::index(&[] as &[i32], Foo(2u32)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ on impl for Foo + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Index>` is not implemented for `[i32]` | - = help: the trait `Index>` is not implemented for `[i32]` = help: the following other types implement trait `Index`: `[i32]` implements `Index>` `[i32]` implements `Index>` @@ -63,9 +60,8 @@ error[E0277]: the trait bound `[i32]: Index>` is not satisfied --> $DIR/multiple-impls.rs:39:5 | LL | Index::index(&[] as &[i32], Bar(2u32)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ on impl for Bar + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Index>` is not implemented for `[i32]` | - = help: the trait `Index>` is not implemented for `[i32]` = help: the following other types implement trait `Index`: `[i32]` implements `Index>` `[i32]` implements `Index>` diff --git a/tests/ui/on-unimplemented/on-impl.stderr b/tests/ui/on-unimplemented/on-impl.stderr index 5e7e2c4ea77..922db9db116 100644 --- a/tests/ui/on-unimplemented/on-impl.stderr +++ b/tests/ui/on-unimplemented/on-impl.stderr @@ -2,7 +2,7 @@ error[E0277]: the trait bound `[i32]: Index` is not satisfied --> $DIR/on-impl.rs:22:47 | LL | Index::::index(&[1, 2, 3] as &[i32], 2u32); - | ------------------- ^^^^ a usize is required to index into a slice + | ------------------- ^^^^ the trait `Index` is not implemented for `[i32]` | | | required by a bound introduced by this call | @@ -14,7 +14,7 @@ error[E0277]: the trait bound `[i32]: Index` is not satisfied --> $DIR/on-impl.rs:22:5 | LL | Index::::index(&[1, 2, 3] as &[i32], 2u32); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ a usize is required to index into a slice + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Index` is not implemented for `[i32]` | = help: the trait `Index` is not implemented for `[i32]` but trait `Index` is implemented for it From 10ec5cbe96b8b1ba96fbacc207d6a2f0d19ca9e2 Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Sat, 29 Mar 2025 13:48:14 +0100 Subject: [PATCH 06/39] Raise errors on bad rustc_on_unimplemented format strings again --- .../traits/on_unimplemented.rs | 38 +++++++++++++++++-- tests/ui/on-unimplemented/bad-annotation.rs | 2 +- .../ui/on-unimplemented/bad-annotation.stderr | 14 +++---- 3 files changed, 41 insertions(+), 13 deletions(-) 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 ca51f177f96..4c4491269b7 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 @@ -18,7 +18,9 @@ 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::{Ctx, FormatArgs, FormatString}; +use crate::error_reporting::traits::on_unimplemented_format::{ + Ctx, FormatArgs, FormatString, FormatWarning, +}; use crate::errors::{ EmptyOnClauseInOnUnimplemented, InvalidOnClauseInOnUnimplemented, NoValueInOnUnimplemented, }; @@ -806,8 +808,38 @@ impl<'tcx> OnUnimplementedFormatString { // Warnings about format specifiers, deprecated parameters, wrong parameters etc. // In other words we'd like to let the author know, but we can still try to format the string later Ok(FormatString { warnings, .. }) => { - for w in warnings { - w.emit_warning(tcx, trait_def_id) + if self.is_diagnostic_namespace_variant { + for w in warnings { + w.emit_warning(tcx, trait_def_id) + } + } else { + for w in warnings { + match w { + FormatWarning::UnknownParam { argument_name, span } => { + let reported = struct_span_code_err!( + tcx.dcx(), + span, + E0230, + "cannot find parameter {} on this trait", + argument_name, + ) + .emit(); + result = Err(reported); + } + FormatWarning::PositionalArgument { span, .. } => { + let reported = struct_span_code_err!( + tcx.dcx(), + span, + E0231, + "positional format arguments are not allowed here" + ) + .emit(); + result = Err(reported); + } + FormatWarning::InvalidSpecifier { .. } + | FormatWarning::FutureIncompat { .. } => {} + } + } } } // Errors from the underlying `rustc_parse_format::Parser` diff --git a/tests/ui/on-unimplemented/bad-annotation.rs b/tests/ui/on-unimplemented/bad-annotation.rs index 0936b25847c..f2b97865759 100644 --- a/tests/ui/on-unimplemented/bad-annotation.rs +++ b/tests/ui/on-unimplemented/bad-annotation.rs @@ -20,7 +20,7 @@ trait BadAnnotation1 {} #[rustc_on_unimplemented = "Unimplemented trait error on `{Self}` with params `<{A},{B},{C}>`"] -//~^ WARNING there is no parameter `C` on trait `BadAnnotation2` +//~^ ERROR cannot find parameter C on this trait trait BadAnnotation2 {} diff --git a/tests/ui/on-unimplemented/bad-annotation.stderr b/tests/ui/on-unimplemented/bad-annotation.stderr index 1fefd93aa7e..afd737dc85e 100644 --- a/tests/ui/on-unimplemented/bad-annotation.stderr +++ b/tests/ui/on-unimplemented/bad-annotation.stderr @@ -11,22 +11,17 @@ LL | #[rustc_on_unimplemented = "message"] LL | #[rustc_on_unimplemented(/*opt*/ message = "...", /*opt*/ label = "...", /*opt*/ note = "...")] | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -warning: there is no parameter `C` on trait `BadAnnotation2` +error[E0230]: cannot find parameter C on this trait --> $DIR/bad-annotation.rs:22:90 | LL | #[rustc_on_unimplemented = "Unimplemented trait error on `{Self}` with params `<{A},{B},{C}>`"] | ^ - | - = help: expect either a generic argument name or `{Self}` as format argument - = note: `#[warn(unknown_or_malformed_diagnostic_attributes)]` on by default -warning: positional format arguments are not allowed here +error[E0231]: positional format arguments are not allowed here --> $DIR/bad-annotation.rs:27:90 | LL | #[rustc_on_unimplemented = "Unimplemented trait error on `{Self}` with params `<{A},{B},{}>`"] | ^ - | - = help: only named format arguments with the name of one of the generic types are allowed in this context error[E0232]: this attribute must have a valid value --> $DIR/bad-annotation.rs:32:26 @@ -82,6 +77,7 @@ LL | #[rustc_on_unimplemented(on(desugared, on(desugared, message="x")), message | = note: eg `#[rustc_on_unimplemented(message="foo")]` -error: aborting due to 8 previous errors; 2 warnings emitted +error: aborting due to 10 previous errors -For more information about this error, try `rustc --explain E0232`. +Some errors have detailed explanations: E0230, E0231, E0232. +For more information about an error, try `rustc --explain E0230`. From 40e76c157548442fce755ec9cc2274aef11454ba Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Tue, 1 Apr 2025 02:47:12 +0200 Subject: [PATCH 07/39] Test that `Self` properly works in filters --- .../ui/on-unimplemented/use_self_no_underscore.rs | 14 ++++++++++++++ .../use_self_no_underscore.stderr | 15 +++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 tests/ui/on-unimplemented/use_self_no_underscore.rs create mode 100644 tests/ui/on-unimplemented/use_self_no_underscore.stderr diff --git a/tests/ui/on-unimplemented/use_self_no_underscore.rs b/tests/ui/on-unimplemented/use_self_no_underscore.rs new file mode 100644 index 00000000000..045ef1a5d3f --- /dev/null +++ b/tests/ui/on-unimplemented/use_self_no_underscore.rs @@ -0,0 +1,14 @@ +#![feature(rustc_attrs)] + +#[rustc_on_unimplemented(on( + all(A = "{integer}", any(Self = "[{integral}; _]",)), + message = "an array of type `{Self}` cannot be built directly from an iterator", +))] +pub trait FromIterator: Sized { + fn from_iter>(iter: T) -> Self; +} +fn main() { + let iter = 0..42_8; + let x: [u8; 8] = FromIterator::from_iter(iter); + //~^ ERROR an array of type `[u8; 8]` cannot be built directly from an iterator +} diff --git a/tests/ui/on-unimplemented/use_self_no_underscore.stderr b/tests/ui/on-unimplemented/use_self_no_underscore.stderr new file mode 100644 index 00000000000..d01aee3485f --- /dev/null +++ b/tests/ui/on-unimplemented/use_self_no_underscore.stderr @@ -0,0 +1,15 @@ +error[E0277]: an array of type `[u8; 8]` cannot be built directly from an iterator + --> $DIR/use_self_no_underscore.rs:12:22 + | +LL | let x: [u8; 8] = FromIterator::from_iter(iter); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `FromIterator<{integer}>` is not implemented for `[u8; 8]` + | +help: this trait has no implementations, consider adding one + --> $DIR/use_self_no_underscore.rs:7:1 + | +LL | pub trait FromIterator: Sized { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0277`. From 4a5369b476fb17aa2db05b5b551eca0cfa6b2c6b Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Thu, 10 Apr 2025 16:06:59 +0200 Subject: [PATCH 08/39] Remove rustc_on_unimplemented on impl tests --- src/tools/tidy/src/issues.txt | 1 - tests/ui/on-unimplemented/impl-substs.rs | 15 ---- tests/ui/on-unimplemented/impl-substs.stderr | 15 ---- tests/ui/on-unimplemented/issue-104140.rs | 8 --- tests/ui/on-unimplemented/issue-104140.stderr | 15 ---- tests/ui/on-unimplemented/multiple-impls.rs | 42 ----------- .../ui/on-unimplemented/multiple-impls.stderr | 71 ------------------- tests/ui/on-unimplemented/on-impl.rs | 25 ------- tests/ui/on-unimplemented/on-impl.stderr | 25 ------- 9 files changed, 217 deletions(-) delete mode 100644 tests/ui/on-unimplemented/impl-substs.rs delete mode 100644 tests/ui/on-unimplemented/impl-substs.stderr delete mode 100644 tests/ui/on-unimplemented/issue-104140.rs delete mode 100644 tests/ui/on-unimplemented/issue-104140.stderr delete mode 100644 tests/ui/on-unimplemented/multiple-impls.rs delete mode 100644 tests/ui/on-unimplemented/multiple-impls.stderr delete mode 100644 tests/ui/on-unimplemented/on-impl.rs delete mode 100644 tests/ui/on-unimplemented/on-impl.stderr diff --git a/src/tools/tidy/src/issues.txt b/src/tools/tidy/src/issues.txt index d2ae9b1f6ef..5fa4a9fc2e3 100644 --- a/src/tools/tidy/src/issues.txt +++ b/src/tools/tidy/src/issues.txt @@ -3138,7 +3138,6 @@ ui/nll/user-annotations/issue-55241.rs ui/nll/user-annotations/issue-55748-pat-types-constrain-bindings.rs ui/nll/user-annotations/issue-57731-ascibed-coupled-types.rs ui/numbers-arithmetic/issue-8460.rs -ui/on-unimplemented/issue-104140.rs ui/or-patterns/issue-64879-trailing-before-guard.rs ui/or-patterns/issue-67514-irrefutable-param.rs ui/or-patterns/issue-68785-irrefutable-param-with-at.rs diff --git a/tests/ui/on-unimplemented/impl-substs.rs b/tests/ui/on-unimplemented/impl-substs.rs deleted file mode 100644 index fe9c50ec3d4..00000000000 --- a/tests/ui/on-unimplemented/impl-substs.rs +++ /dev/null @@ -1,15 +0,0 @@ -#![feature(rustc_attrs)] - -trait Foo { - fn foo(self); -} - -#[rustc_on_unimplemented = "an impl did not match: {A} {B} {C}"] -impl Foo for (A, B, C) { - fn foo(self) {} -} - -fn main() { - Foo::::foo((1i32, 1i32, 1i32)); - //~^ ERROR the trait bound `(i32, i32, i32): Foo` is not satisfied -} diff --git a/tests/ui/on-unimplemented/impl-substs.stderr b/tests/ui/on-unimplemented/impl-substs.stderr deleted file mode 100644 index 2d83845ecb8..00000000000 --- a/tests/ui/on-unimplemented/impl-substs.stderr +++ /dev/null @@ -1,15 +0,0 @@ -error[E0277]: the trait bound `(i32, i32, i32): Foo` is not satisfied - --> $DIR/impl-substs.rs:13:23 - | -LL | Foo::::foo((1i32, 1i32, 1i32)); - | ----------------- ^^^^^^^^^^^^^^^^^^ the trait `Foo` is not implemented for `(i32, i32, i32)` - | | - | required by a bound introduced by this call - | - = help: the trait `Foo` is not implemented for `(i32, i32, i32)` - but trait `Foo` is implemented for it - = help: for that trait implementation, expected `i32`, found `usize` - -error: aborting due to 1 previous error - -For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/on-unimplemented/issue-104140.rs b/tests/ui/on-unimplemented/issue-104140.rs deleted file mode 100644 index ade3f727004..00000000000 --- a/tests/ui/on-unimplemented/issue-104140.rs +++ /dev/null @@ -1,8 +0,0 @@ -#![feature(rustc_attrs)] - -trait Foo {} - -#[rustc_on_unimplemented] //~ ERROR malformed `rustc_on_unimplemented` attribute input -impl Foo for u32 {} - -fn main() {} diff --git a/tests/ui/on-unimplemented/issue-104140.stderr b/tests/ui/on-unimplemented/issue-104140.stderr deleted file mode 100644 index 3c317135dd4..00000000000 --- a/tests/ui/on-unimplemented/issue-104140.stderr +++ /dev/null @@ -1,15 +0,0 @@ -error: malformed `rustc_on_unimplemented` attribute input - --> $DIR/issue-104140.rs:5:1 - | -LL | #[rustc_on_unimplemented] - | ^^^^^^^^^^^^^^^^^^^^^^^^^ - | -help: the following are the possible correct uses - | -LL | #[rustc_on_unimplemented = "message"] - | +++++++++++ -LL | #[rustc_on_unimplemented(/*opt*/ message = "...", /*opt*/ label = "...", /*opt*/ note = "...")] - | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - -error: aborting due to 1 previous error - diff --git a/tests/ui/on-unimplemented/multiple-impls.rs b/tests/ui/on-unimplemented/multiple-impls.rs deleted file mode 100644 index b74957ebcd4..00000000000 --- a/tests/ui/on-unimplemented/multiple-impls.rs +++ /dev/null @@ -1,42 +0,0 @@ -// Test if the on_unimplemented message override works - -#![feature(rustc_attrs)] - - -struct Foo(T); -struct Bar(T); - -#[rustc_on_unimplemented = "trait message"] -trait Index { - type Output: ?Sized; - fn index(&self, index: Idx) -> &Self::Output; -} - -#[rustc_on_unimplemented = "on impl for Foo"] -impl Index> for [i32] { - type Output = i32; - fn index(&self, _index: Foo) -> &i32 { - loop {} - } -} - -#[rustc_on_unimplemented = "on impl for Bar"] -impl Index> for [i32] { - type Output = i32; - fn index(&self, _index: Bar) -> &i32 { - loop {} - } -} - - -fn main() { - Index::index(&[] as &[i32], 2u32); - //~^ ERROR E0277 - //~| ERROR E0277 - Index::index(&[] as &[i32], Foo(2u32)); - //~^ ERROR E0277 - //~| ERROR E0277 - Index::index(&[] as &[i32], Bar(2u32)); - //~^ ERROR E0277 - //~| ERROR E0277 -} diff --git a/tests/ui/on-unimplemented/multiple-impls.stderr b/tests/ui/on-unimplemented/multiple-impls.stderr deleted file mode 100644 index 2afc9b1bf3b..00000000000 --- a/tests/ui/on-unimplemented/multiple-impls.stderr +++ /dev/null @@ -1,71 +0,0 @@ -error[E0277]: the trait bound `[i32]: Index` is not satisfied - --> $DIR/multiple-impls.rs:33:33 - | -LL | Index::index(&[] as &[i32], 2u32); - | ------------ ^^^^ trait message - | | - | required by a bound introduced by this call - | - = help: the trait `Index` is not implemented for `[i32]` - = help: the following other types implement trait `Index`: - `[i32]` implements `Index>` - `[i32]` implements `Index>` - -error[E0277]: the trait bound `[i32]: Index>` is not satisfied - --> $DIR/multiple-impls.rs:36:33 - | -LL | Index::index(&[] as &[i32], Foo(2u32)); - | ------------ ^^^^^^^^^ the trait `Index>` is not implemented for `[i32]` - | | - | required by a bound introduced by this call - | - = help: the following other types implement trait `Index`: - `[i32]` implements `Index>` - `[i32]` implements `Index>` - -error[E0277]: the trait bound `[i32]: Index>` is not satisfied - --> $DIR/multiple-impls.rs:39:33 - | -LL | Index::index(&[] as &[i32], Bar(2u32)); - | ------------ ^^^^^^^^^ the trait `Index>` is not implemented for `[i32]` - | | - | required by a bound introduced by this call - | - = help: the following other types implement trait `Index`: - `[i32]` implements `Index>` - `[i32]` implements `Index>` - -error[E0277]: the trait bound `[i32]: Index` is not satisfied - --> $DIR/multiple-impls.rs:33:5 - | -LL | Index::index(&[] as &[i32], 2u32); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ trait message - | - = help: the trait `Index` is not implemented for `[i32]` - = help: the following other types implement trait `Index`: - `[i32]` implements `Index>` - `[i32]` implements `Index>` - -error[E0277]: the trait bound `[i32]: Index>` is not satisfied - --> $DIR/multiple-impls.rs:36:5 - | -LL | Index::index(&[] as &[i32], Foo(2u32)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Index>` is not implemented for `[i32]` - | - = help: the following other types implement trait `Index`: - `[i32]` implements `Index>` - `[i32]` implements `Index>` - -error[E0277]: the trait bound `[i32]: Index>` is not satisfied - --> $DIR/multiple-impls.rs:39:5 - | -LL | Index::index(&[] as &[i32], Bar(2u32)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Index>` is not implemented for `[i32]` - | - = help: the following other types implement trait `Index`: - `[i32]` implements `Index>` - `[i32]` implements `Index>` - -error: aborting due to 6 previous errors - -For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/on-unimplemented/on-impl.rs b/tests/ui/on-unimplemented/on-impl.rs deleted file mode 100644 index ab3e67d01fe..00000000000 --- a/tests/ui/on-unimplemented/on-impl.rs +++ /dev/null @@ -1,25 +0,0 @@ -// Test if the on_unimplemented message override works - -#![feature(rustc_attrs)] - - -#[rustc_on_unimplemented = "invalid"] -trait Index { - type Output: ?Sized; - fn index(&self, index: Idx) -> &Self::Output; -} - -#[rustc_on_unimplemented = "a usize is required to index into a slice"] -impl Index for [i32] { - type Output = i32; - fn index(&self, index: usize) -> &i32 { - &self[index] - } -} - - -fn main() { - Index::::index(&[1, 2, 3] as &[i32], 2u32); - //~^ ERROR E0277 - //~| ERROR E0277 -} diff --git a/tests/ui/on-unimplemented/on-impl.stderr b/tests/ui/on-unimplemented/on-impl.stderr deleted file mode 100644 index 922db9db116..00000000000 --- a/tests/ui/on-unimplemented/on-impl.stderr +++ /dev/null @@ -1,25 +0,0 @@ -error[E0277]: the trait bound `[i32]: Index` is not satisfied - --> $DIR/on-impl.rs:22:47 - | -LL | Index::::index(&[1, 2, 3] as &[i32], 2u32); - | ------------------- ^^^^ the trait `Index` is not implemented for `[i32]` - | | - | required by a bound introduced by this call - | - = help: the trait `Index` is not implemented for `[i32]` - but trait `Index` is implemented for it - = help: for that trait implementation, expected `usize`, found `u32` - -error[E0277]: the trait bound `[i32]: Index` is not satisfied - --> $DIR/on-impl.rs:22:5 - | -LL | Index::::index(&[1, 2, 3] as &[i32], 2u32); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Index` is not implemented for `[i32]` - | - = help: the trait `Index` is not implemented for `[i32]` - but trait `Index` is implemented for it - = help: for that trait implementation, expected `usize`, found `u32` - -error: aborting due to 2 previous errors - -For more information about this error, try `rustc --explain E0277`. From 1c1febc59db038876d7fe78a1f056bf324fdff6a Mon Sep 17 00:00:00 2001 From: onur-ozkan Date: Tue, 1 Apr 2025 11:19:57 +0300 Subject: [PATCH 09/39] add new config option: `include` Signed-off-by: onur-ozkan --- src/bootstrap/src/core/config/config.rs | 25 ++++++++++++++++++++++- src/bootstrap/src/utils/change_tracker.rs | 5 +++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/bootstrap/src/core/config/config.rs b/src/bootstrap/src/core/config/config.rs index 25ec64f90b5..2e881081290 100644 --- a/src/bootstrap/src/core/config/config.rs +++ b/src/bootstrap/src/core/config/config.rs @@ -701,6 +701,7 @@ pub(crate) struct TomlConfig { target: Option>, dist: Option, profile: Option, + include: Option>, } /// This enum is used for deserializing change IDs from TOML, allowing both numeric values and the string `"ignore"`. @@ -753,7 +754,7 @@ trait Merge { impl Merge for TomlConfig { fn merge( &mut self, - TomlConfig { build, install, llvm, gcc, rust, dist, target, profile, change_id }: Self, + TomlConfig { build, install, llvm, gcc, rust, dist, target, profile, change_id, include }: Self, replace: ReplaceOpt, ) { fn do_merge(x: &mut Option, y: Option, replace: ReplaceOpt) { @@ -766,6 +767,17 @@ impl Merge for TomlConfig { } } + for include_path in include.clone().unwrap_or_default() { + let included_toml = Config::get_toml(&include_path).unwrap_or_else(|e| { + eprintln!( + "ERROR: Failed to parse default config profile at '{}': {e}", + include_path.display() + ); + exit!(2); + }); + self.merge(included_toml, ReplaceOpt::Override); + } + self.change_id.inner.merge(change_id.inner, replace); self.profile.merge(profile, replace); @@ -1600,6 +1612,17 @@ impl Config { toml.merge(included_toml, ReplaceOpt::IgnoreDuplicate); } + for include_path in toml.include.clone().unwrap_or_default() { + let included_toml = get_toml(&include_path).unwrap_or_else(|e| { + eprintln!( + "ERROR: Failed to parse default config profile at '{}': {e}", + include_path.display() + ); + exit!(2); + }); + toml.merge(included_toml, ReplaceOpt::Override); + } + let mut override_toml = TomlConfig::default(); for option in flags.set.iter() { fn get_table(option: &str) -> Result { diff --git a/src/bootstrap/src/utils/change_tracker.rs b/src/bootstrap/src/utils/change_tracker.rs index 48b6f77e8a5..3f1885a425f 100644 --- a/src/bootstrap/src/utils/change_tracker.rs +++ b/src/bootstrap/src/utils/change_tracker.rs @@ -396,4 +396,9 @@ pub const CONFIG_CHANGE_HISTORY: &[ChangeInfo] = &[ severity: ChangeSeverity::Info, summary: "Added a new option `build.compiletest-use-stage0-libtest` to force `compiletest` to use the stage 0 libtest.", }, + ChangeInfo { + change_id: 138934, + severity: ChangeSeverity::Info, + summary: "Added new option `include` to create config extensions.", + }, ]; From 89e3befe63f35b7556614d85ce63214f62a9a771 Mon Sep 17 00:00:00 2001 From: onur-ozkan Date: Tue, 25 Mar 2025 20:17:32 +0300 Subject: [PATCH 10/39] document config extensions Signed-off-by: onur-ozkan --- .../rustc-dev-guide/src/building/suggested.md | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/doc/rustc-dev-guide/src/building/suggested.md b/src/doc/rustc-dev-guide/src/building/suggested.md index 43ff2ba726f..5d37a78af88 100644 --- a/src/doc/rustc-dev-guide/src/building/suggested.md +++ b/src/doc/rustc-dev-guide/src/building/suggested.md @@ -20,6 +20,42 @@ your `.git/hooks` folder as `pre-push` (without the `.sh` extension!). You can also install the hook as a step of running `./x setup`! +## Config extensions + +When working on different tasks, you might need to switch between different bootstrap configurations. +Sometimes you may want to keep an old configuration for future use. But saving raw config values in +random files and manually copying and pasting them can quickly become messy, especially if you have a +long history of different configurations. + +To simplify managing multiple configurations, you can create config extensions. + +For example, you can create a simple config file named `cross.toml`: + +```toml +[build] +build = "x86_64-unknown-linux-gnu" +host = ["i686-unknown-linux-gnu"] +target = ["i686-unknown-linux-gnu"] + + +[llvm] +download-ci-llvm = false + +[target.x86_64-unknown-linux-gnu] +llvm-config = "/path/to/llvm-19/bin/llvm-config" +``` + +Then, include this in your `bootstrap.toml`: + +```toml +include = ["cross.toml"] +``` + +You can also include extensions within extensions recursively. + +**Note:** In the `include` field, the overriding logic follows a right-to-left order. Also, the outer +extension/config always overrides the inner ones. + ## Configuring `rust-analyzer` for `rustc` ### Project-local rust-analyzer setup From 4e80659b32a794513646b93890c9a6eeb103de17 Mon Sep 17 00:00:00 2001 From: onur-ozkan Date: Tue, 1 Apr 2025 11:44:33 +0300 Subject: [PATCH 11/39] implement cyclic inclusion handling Signed-off-by: onur-ozkan --- src/bootstrap/src/core/config/config.rs | 51 ++++++++++++++++++------- 1 file changed, 38 insertions(+), 13 deletions(-) diff --git a/src/bootstrap/src/core/config/config.rs b/src/bootstrap/src/core/config/config.rs index 2e881081290..86566d0eee1 100644 --- a/src/bootstrap/src/core/config/config.rs +++ b/src/bootstrap/src/core/config/config.rs @@ -6,6 +6,7 @@ use std::cell::{Cell, RefCell}; use std::collections::{BTreeSet, HashMap, HashSet}; use std::fmt::{self, Display}; +use std::hash::Hash; use std::io::IsTerminal; use std::path::{Path, PathBuf, absolute}; use std::process::Command; @@ -748,19 +749,25 @@ enum ReplaceOpt { } trait Merge { - fn merge(&mut self, other: Self, replace: ReplaceOpt); + fn merge( + &mut self, + included_extensions: &mut HashSet, + other: Self, + replace: ReplaceOpt, + ); } impl Merge for TomlConfig { fn merge( &mut self, + included_extensions: &mut HashSet, TomlConfig { build, install, llvm, gcc, rust, dist, target, profile, change_id, include }: Self, replace: ReplaceOpt, ) { fn do_merge(x: &mut Option, y: Option, replace: ReplaceOpt) { if let Some(new) = y { if let Some(original) = x { - original.merge(new, replace); + original.merge(&mut Default::default(), new, replace); } else { *x = Some(new); } @@ -775,11 +782,20 @@ impl Merge for TomlConfig { ); exit!(2); }); - self.merge(included_toml, ReplaceOpt::Override); + + assert!( + included_extensions.insert(include_path.clone()), + "Cyclic inclusion detected: '{}' is being included again before its previous inclusion was fully processed.", + include_path.display() + ); + + self.merge(included_extensions, included_toml, ReplaceOpt::Override); + + included_extensions.remove(&include_path); } - self.change_id.inner.merge(change_id.inner, replace); - self.profile.merge(profile, replace); + self.change_id.inner.merge(&mut Default::default(), change_id.inner, replace); + self.profile.merge(&mut Default::default(), profile, replace); do_merge(&mut self.build, build, replace); do_merge(&mut self.install, install, replace); @@ -794,7 +810,7 @@ impl Merge for TomlConfig { (Some(original_target), Some(new_target)) => { for (triple, new) in new_target { if let Some(original) = original_target.get_mut(&triple) { - original.merge(new, replace); + original.merge(&mut Default::default(), new, replace); } else { original_target.insert(triple, new); } @@ -815,7 +831,7 @@ macro_rules! define_config { } impl Merge for $name { - fn merge(&mut self, other: Self, replace: ReplaceOpt) { + fn merge(&mut self, _included_extensions: &mut HashSet, other: Self, replace: ReplaceOpt) { $( match replace { ReplaceOpt::IgnoreDuplicate => { @@ -915,7 +931,12 @@ macro_rules! define_config { } impl Merge for Option { - fn merge(&mut self, other: Self, replace: ReplaceOpt) { + fn merge( + &mut self, + _included_extensions: &mut HashSet, + other: Self, + replace: ReplaceOpt, + ) { match replace { ReplaceOpt::IgnoreDuplicate => { if self.is_none() { @@ -1609,7 +1630,7 @@ impl Config { ); exit!(2); }); - toml.merge(included_toml, ReplaceOpt::IgnoreDuplicate); + toml.merge(&mut Default::default(), included_toml, ReplaceOpt::IgnoreDuplicate); } for include_path in toml.include.clone().unwrap_or_default() { @@ -1620,7 +1641,7 @@ impl Config { ); exit!(2); }); - toml.merge(included_toml, ReplaceOpt::Override); + toml.merge(&mut Default::default(), included_toml, ReplaceOpt::Override); } let mut override_toml = TomlConfig::default(); @@ -1631,7 +1652,7 @@ impl Config { let mut err = match get_table(option) { Ok(v) => { - override_toml.merge(v, ReplaceOpt::ErrorOnDuplicate); + override_toml.merge(&mut Default::default(), v, ReplaceOpt::ErrorOnDuplicate); continue; } Err(e) => e, @@ -1642,7 +1663,11 @@ impl Config { if !value.contains('"') { match get_table(&format!(r#"{key}="{value}""#)) { Ok(v) => { - override_toml.merge(v, ReplaceOpt::ErrorOnDuplicate); + override_toml.merge( + &mut Default::default(), + v, + ReplaceOpt::ErrorOnDuplicate, + ); continue; } Err(e) => err = e, @@ -1652,7 +1677,7 @@ impl Config { eprintln!("failed to parse override `{option}`: `{err}"); exit!(2) } - toml.merge(override_toml, ReplaceOpt::Override); + toml.merge(&mut Default::default(), override_toml, ReplaceOpt::Override); config.change_id = toml.change_id.inner; From 78cb4538ee203b08441382b5cd64b1989ed5b3b9 Mon Sep 17 00:00:00 2001 From: onur-ozkan Date: Tue, 1 Apr 2025 11:53:32 +0300 Subject: [PATCH 12/39] document `include` in `bootstrap.example.toml` Signed-off-by: onur-ozkan --- bootstrap.example.toml | 5 +++++ src/doc/rustc-dev-guide/src/building/suggested.md | 5 +++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/bootstrap.example.toml b/bootstrap.example.toml index 0927f648635..0a314f65ec2 100644 --- a/bootstrap.example.toml +++ b/bootstrap.example.toml @@ -19,6 +19,11 @@ # Note that this has no default value (x.py uses the defaults in `bootstrap.example.toml`). #profile = +# Inherits configuration values from different configuration files (a.k.a. config extensions). +# Supports absolute paths, and uses the current directory (where the bootstrap was invoked) +# as the base if the given path is not absolute. +#include = [] + # Keeps track of major changes made to this configuration. # # This value also represents ID of the PR that caused major changes. Meaning, diff --git a/src/doc/rustc-dev-guide/src/building/suggested.md b/src/doc/rustc-dev-guide/src/building/suggested.md index 5d37a78af88..b2e258be079 100644 --- a/src/doc/rustc-dev-guide/src/building/suggested.md +++ b/src/doc/rustc-dev-guide/src/building/suggested.md @@ -53,8 +53,9 @@ include = ["cross.toml"] You can also include extensions within extensions recursively. -**Note:** In the `include` field, the overriding logic follows a right-to-left order. Also, the outer -extension/config always overrides the inner ones. +**Note:** In the `include` field, the overriding logic follows a right-to-left order. For example, +in `include = ["a.toml", "b.toml"]`, extension `b.toml` overrides `a.toml`. Also, parent extensions +always overrides the inner ones. ## Configuring `rust-analyzer` for `rustc` From 3f70f197f2d131a1f57912f202072b2447d3e326 Mon Sep 17 00:00:00 2001 From: onur-ozkan Date: Tue, 1 Apr 2025 21:56:58 +0000 Subject: [PATCH 13/39] apply nit notes Signed-off-by: onur-ozkan --- bootstrap.example.toml | 3 + src/bootstrap/src/core/config/config.rs | 95 ++++++++++++++++++------- 2 files changed, 72 insertions(+), 26 deletions(-) diff --git a/bootstrap.example.toml b/bootstrap.example.toml index 0a314f65ec2..72c4492d465 100644 --- a/bootstrap.example.toml +++ b/bootstrap.example.toml @@ -22,6 +22,9 @@ # Inherits configuration values from different configuration files (a.k.a. config extensions). # Supports absolute paths, and uses the current directory (where the bootstrap was invoked) # as the base if the given path is not absolute. +# +# The overriding logic follows a right-to-left order. For example, in `include = ["a.toml", "b.toml"]`, +# extension `b.toml` overrides `a.toml`. Also, parent extensions always overrides the inner ones. #include = [] # Keeps track of major changes made to this configuration. diff --git a/src/bootstrap/src/core/config/config.rs b/src/bootstrap/src/core/config/config.rs index 86566d0eee1..e70ac74c7da 100644 --- a/src/bootstrap/src/core/config/config.rs +++ b/src/bootstrap/src/core/config/config.rs @@ -751,6 +751,7 @@ enum ReplaceOpt { trait Merge { fn merge( &mut self, + parent_config_path: Option, included_extensions: &mut HashSet, other: Self, replace: ReplaceOpt, @@ -760,6 +761,7 @@ trait Merge { impl Merge for TomlConfig { fn merge( &mut self, + parent_config_path: Option, included_extensions: &mut HashSet, TomlConfig { build, install, llvm, gcc, rust, dist, target, profile, change_id, include }: Self, replace: ReplaceOpt, @@ -767,19 +769,27 @@ impl Merge for TomlConfig { fn do_merge(x: &mut Option, y: Option, replace: ReplaceOpt) { if let Some(new) = y { if let Some(original) = x { - original.merge(&mut Default::default(), new, replace); + original.merge(None, &mut Default::default(), new, replace); } else { *x = Some(new); } } } - for include_path in include.clone().unwrap_or_default() { + let parent_dir = parent_config_path + .as_ref() + .and_then(|p| p.parent().map(ToOwned::to_owned)) + .unwrap_or_default(); + + for include_path in include.clone().unwrap_or_default().iter().rev() { + let include_path = parent_dir.join(include_path); + let include_path = include_path.canonicalize().unwrap_or_else(|e| { + eprintln!("ERROR: Failed to canonicalize '{}' path: {e}", include_path.display()); + exit!(2); + }); + let included_toml = Config::get_toml(&include_path).unwrap_or_else(|e| { - eprintln!( - "ERROR: Failed to parse default config profile at '{}': {e}", - include_path.display() - ); + eprintln!("ERROR: Failed to parse '{}': {e}", include_path.display()); exit!(2); }); @@ -789,13 +799,20 @@ impl Merge for TomlConfig { include_path.display() ); - self.merge(included_extensions, included_toml, ReplaceOpt::Override); + self.merge( + Some(include_path.clone()), + included_extensions, + included_toml, + // Ensures that parent configuration always takes precedence + // over child configurations. + ReplaceOpt::IgnoreDuplicate, + ); included_extensions.remove(&include_path); } - self.change_id.inner.merge(&mut Default::default(), change_id.inner, replace); - self.profile.merge(&mut Default::default(), profile, replace); + self.change_id.inner.merge(None, &mut Default::default(), change_id.inner, replace); + self.profile.merge(None, &mut Default::default(), profile, replace); do_merge(&mut self.build, build, replace); do_merge(&mut self.install, install, replace); @@ -810,7 +827,7 @@ impl Merge for TomlConfig { (Some(original_target), Some(new_target)) => { for (triple, new) in new_target { if let Some(original) = original_target.get_mut(&triple) { - original.merge(&mut Default::default(), new, replace); + original.merge(None, &mut Default::default(), new, replace); } else { original_target.insert(triple, new); } @@ -831,7 +848,13 @@ macro_rules! define_config { } impl Merge for $name { - fn merge(&mut self, _included_extensions: &mut HashSet, other: Self, replace: ReplaceOpt) { + fn merge( + &mut self, + _parent_config_path: Option, + _included_extensions: &mut HashSet, + other: Self, + replace: ReplaceOpt + ) { $( match replace { ReplaceOpt::IgnoreDuplicate => { @@ -933,6 +956,7 @@ macro_rules! define_config { impl Merge for Option { fn merge( &mut self, + _parent_config_path: Option, _included_extensions: &mut HashSet, other: Self, replace: ReplaceOpt, @@ -1581,7 +1605,8 @@ impl Config { // but not if `bootstrap.toml` hasn't been created. let mut toml = if !using_default_path || toml_path.exists() { config.config = Some(if cfg!(not(test)) { - toml_path.canonicalize().unwrap() + toml_path = toml_path.canonicalize().unwrap(); + toml_path.clone() } else { toml_path.clone() }); @@ -1609,6 +1634,24 @@ impl Config { toml.profile = Some("dist".into()); } + // Reverse the list to ensure the last added config extension remains the most dominant. + // For example, given ["a.toml", "b.toml"], "b.toml" should take precedence over "a.toml". + // + // This must be handled before applying the `profile` since `include`s should always take + // precedence over `profile`s. + for include_path in toml.include.clone().unwrap_or_default().iter().rev() { + let included_toml = get_toml(include_path).unwrap_or_else(|e| { + eprintln!("ERROR: Failed to parse '{}': {e}", include_path.display()); + exit!(2); + }); + toml.merge( + Some(toml_path.join(include_path)), + &mut Default::default(), + included_toml, + ReplaceOpt::IgnoreDuplicate, + ); + } + if let Some(include) = &toml.profile { // Allows creating alias for profile names, allowing // profiles to be renamed while maintaining back compatibility @@ -1630,18 +1673,12 @@ impl Config { ); exit!(2); }); - toml.merge(&mut Default::default(), included_toml, ReplaceOpt::IgnoreDuplicate); - } - - for include_path in toml.include.clone().unwrap_or_default() { - let included_toml = get_toml(&include_path).unwrap_or_else(|e| { - eprintln!( - "ERROR: Failed to parse default config profile at '{}': {e}", - include_path.display() - ); - exit!(2); - }); - toml.merge(&mut Default::default(), included_toml, ReplaceOpt::Override); + toml.merge( + Some(include_path), + &mut Default::default(), + included_toml, + ReplaceOpt::IgnoreDuplicate, + ); } let mut override_toml = TomlConfig::default(); @@ -1652,7 +1689,12 @@ impl Config { let mut err = match get_table(option) { Ok(v) => { - override_toml.merge(&mut Default::default(), v, ReplaceOpt::ErrorOnDuplicate); + override_toml.merge( + None, + &mut Default::default(), + v, + ReplaceOpt::ErrorOnDuplicate, + ); continue; } Err(e) => e, @@ -1664,6 +1706,7 @@ impl Config { match get_table(&format!(r#"{key}="{value}""#)) { Ok(v) => { override_toml.merge( + None, &mut Default::default(), v, ReplaceOpt::ErrorOnDuplicate, @@ -1677,7 +1720,7 @@ impl Config { eprintln!("failed to parse override `{option}`: `{err}"); exit!(2) } - toml.merge(&mut Default::default(), override_toml, ReplaceOpt::Override); + toml.merge(None, &mut Default::default(), override_toml, ReplaceOpt::Override); config.change_id = toml.change_id.inner; From 8e6f50bb4d30801d04619a4cd6406b320eb8919f Mon Sep 17 00:00:00 2001 From: onur-ozkan Date: Tue, 15 Apr 2025 10:44:19 +0300 Subject: [PATCH 14/39] fix path and the ordering logic Signed-off-by: onur-ozkan --- src/bootstrap/src/core/config/config.rs | 54 +++++++++++++------------ 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/src/bootstrap/src/core/config/config.rs b/src/bootstrap/src/core/config/config.rs index e70ac74c7da..19ed442c500 100644 --- a/src/bootstrap/src/core/config/config.rs +++ b/src/bootstrap/src/core/config/config.rs @@ -776,6 +776,30 @@ impl Merge for TomlConfig { } } + self.change_id.inner.merge(None, &mut Default::default(), change_id.inner, replace); + self.profile.merge(None, &mut Default::default(), profile, replace); + + do_merge(&mut self.build, build, replace); + do_merge(&mut self.install, install, replace); + do_merge(&mut self.llvm, llvm, replace); + do_merge(&mut self.gcc, gcc, replace); + do_merge(&mut self.rust, rust, replace); + do_merge(&mut self.dist, dist, replace); + + match (self.target.as_mut(), target) { + (_, None) => {} + (None, Some(target)) => self.target = Some(target), + (Some(original_target), Some(new_target)) => { + for (triple, new) in new_target { + if let Some(original) = original_target.get_mut(&triple) { + original.merge(None, &mut Default::default(), new, replace); + } else { + original_target.insert(triple, new); + } + } + } + } + let parent_dir = parent_config_path .as_ref() .and_then(|p| p.parent().map(ToOwned::to_owned)) @@ -810,30 +834,6 @@ impl Merge for TomlConfig { included_extensions.remove(&include_path); } - - self.change_id.inner.merge(None, &mut Default::default(), change_id.inner, replace); - self.profile.merge(None, &mut Default::default(), profile, replace); - - do_merge(&mut self.build, build, replace); - do_merge(&mut self.install, install, replace); - do_merge(&mut self.llvm, llvm, replace); - do_merge(&mut self.gcc, gcc, replace); - do_merge(&mut self.rust, rust, replace); - do_merge(&mut self.dist, dist, replace); - - match (self.target.as_mut(), target) { - (_, None) => {} - (None, Some(target)) => self.target = Some(target), - (Some(original_target), Some(new_target)) => { - for (triple, new) in new_target { - if let Some(original) = original_target.get_mut(&triple) { - original.merge(None, &mut Default::default(), new, replace); - } else { - original_target.insert(triple, new); - } - } - } - } } } @@ -1640,12 +1640,14 @@ impl Config { // This must be handled before applying the `profile` since `include`s should always take // precedence over `profile`s. for include_path in toml.include.clone().unwrap_or_default().iter().rev() { - let included_toml = get_toml(include_path).unwrap_or_else(|e| { + let include_path = toml_path.parent().unwrap().join(include_path); + + let included_toml = get_toml(&include_path).unwrap_or_else(|e| { eprintln!("ERROR: Failed to parse '{}': {e}", include_path.display()); exit!(2); }); toml.merge( - Some(toml_path.join(include_path)), + Some(include_path), &mut Default::default(), included_toml, ReplaceOpt::IgnoreDuplicate, From 7dfb457745d7f873eb5db1c3fb39d58fa768740d Mon Sep 17 00:00:00 2001 From: onur-ozkan Date: Tue, 15 Apr 2025 11:53:21 +0300 Subject: [PATCH 15/39] add FIXME note in `TomlConfig::merge` Signed-off-by: onur-ozkan --- src/bootstrap/src/core/config/config.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/bootstrap/src/core/config/config.rs b/src/bootstrap/src/core/config/config.rs index 19ed442c500..b7afd81dfdb 100644 --- a/src/bootstrap/src/core/config/config.rs +++ b/src/bootstrap/src/core/config/config.rs @@ -812,6 +812,8 @@ impl Merge for TomlConfig { exit!(2); }); + // FIXME: Similar to `Config::parse_inner`, allow passing a custom `get_toml` from the caller to + // improve testability since `Config::get_toml` does nothing when `cfg(test)` is enabled. let included_toml = Config::get_toml(&include_path).unwrap_or_else(|e| { eprintln!("ERROR: Failed to parse '{}': {e}", include_path.display()); exit!(2); From 6d52b51d3e6321b7a1d24e5f995aa709057153e3 Mon Sep 17 00:00:00 2001 From: onur-ozkan Date: Tue, 15 Apr 2025 19:10:31 +0300 Subject: [PATCH 16/39] add comment in `TomlConfig::merge` about the merge order Signed-off-by: onur-ozkan --- src/bootstrap/src/core/config/config.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/bootstrap/src/core/config/config.rs b/src/bootstrap/src/core/config/config.rs index b7afd81dfdb..e15aab4ad50 100644 --- a/src/bootstrap/src/core/config/config.rs +++ b/src/bootstrap/src/core/config/config.rs @@ -805,6 +805,8 @@ impl Merge for TomlConfig { .and_then(|p| p.parent().map(ToOwned::to_owned)) .unwrap_or_default(); + // `include` handled later since we ignore duplicates using `ReplaceOpt::IgnoreDuplicate` to + // keep the upper-level configuration to take precedence. for include_path in include.clone().unwrap_or_default().iter().rev() { let include_path = parent_dir.join(include_path); let include_path = include_path.canonicalize().unwrap_or_else(|e| { From 2024e26881ea8bdfb41f53e257464be4332abc79 Mon Sep 17 00:00:00 2001 From: Chris Denton Date: Tue, 15 Apr 2025 01:01:51 +0000 Subject: [PATCH 17/39] Don't canonicalize crate paths --- compiler/rustc_metadata/src/locator.rs | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/compiler/rustc_metadata/src/locator.rs b/compiler/rustc_metadata/src/locator.rs index 112954eca0d..f0a898d678c 100644 --- a/compiler/rustc_metadata/src/locator.rs +++ b/compiler/rustc_metadata/src/locator.rs @@ -427,12 +427,21 @@ impl<'a> CrateLocator<'a> { let (rlibs, rmetas, dylibs) = candidates.entry(hash.to_string()).or_default(); - let path = - try_canonicalize(&spf.path).unwrap_or_else(|_| spf.path.to_path_buf()); - if seen_paths.contains(&path) { - continue; - }; - seen_paths.insert(path.clone()); + { + // As a perforamnce optimisation we canonicalize the path and skip + // ones we've already seeen. This allows us to ignore crates + // we know are exactual equal to ones we've already found. + // Going to the same crate through different symlinks does not change the result. + let path = try_canonicalize(&spf.path) + .unwrap_or_else(|_| spf.path.to_path_buf()); + if seen_paths.contains(&path) { + continue; + }; + seen_paths.insert(path); + } + // Use the original path (potentially with unresolved symlinks), + // filesystem code should not care, but this is nicer for diagnostics. + let path = spf.path.to_path_buf(); match kind { CrateFlavor::Rlib => rlibs.insert(path, search_path.kind), CrateFlavor::Rmeta => rmetas.insert(path, search_path.kind), From 52f35d013116559035621eb83d0e9680ecf74a49 Mon Sep 17 00:00:00 2001 From: Chris Denton Date: Tue, 15 Apr 2025 12:55:33 +0000 Subject: [PATCH 18/39] Test for relative paths in crate path diagnostics --- .../crateresolve1-1.rs | 6 ++++ .../crateresolve1-2.rs | 6 ++++ .../multiple-candidates.rs | 3 ++ .../multiple-candidates.stderr | 12 +++++++ .../rmake.rs | 34 +++++++++++++++++++ 5 files changed, 61 insertions(+) create mode 100644 tests/run-make/crate-loading-multiple-candidates/crateresolve1-1.rs create mode 100644 tests/run-make/crate-loading-multiple-candidates/crateresolve1-2.rs create mode 100644 tests/run-make/crate-loading-multiple-candidates/multiple-candidates.rs create mode 100644 tests/run-make/crate-loading-multiple-candidates/multiple-candidates.stderr create mode 100644 tests/run-make/crate-loading-multiple-candidates/rmake.rs diff --git a/tests/run-make/crate-loading-multiple-candidates/crateresolve1-1.rs b/tests/run-make/crate-loading-multiple-candidates/crateresolve1-1.rs new file mode 100644 index 00000000000..fe00f041a86 --- /dev/null +++ b/tests/run-make/crate-loading-multiple-candidates/crateresolve1-1.rs @@ -0,0 +1,6 @@ +#![crate_name = "crateresolve1"] +#![crate_type = "lib"] + +pub fn f() -> isize { + 10 +} diff --git a/tests/run-make/crate-loading-multiple-candidates/crateresolve1-2.rs b/tests/run-make/crate-loading-multiple-candidates/crateresolve1-2.rs new file mode 100644 index 00000000000..0fb8591b3a5 --- /dev/null +++ b/tests/run-make/crate-loading-multiple-candidates/crateresolve1-2.rs @@ -0,0 +1,6 @@ +#![crate_name = "crateresolve1"] +#![crate_type = "lib"] + +pub fn f() -> isize { + 20 +} diff --git a/tests/run-make/crate-loading-multiple-candidates/multiple-candidates.rs b/tests/run-make/crate-loading-multiple-candidates/multiple-candidates.rs new file mode 100644 index 00000000000..27cd7ca5c20 --- /dev/null +++ b/tests/run-make/crate-loading-multiple-candidates/multiple-candidates.rs @@ -0,0 +1,3 @@ +extern crate crateresolve1; + +fn main() {} diff --git a/tests/run-make/crate-loading-multiple-candidates/multiple-candidates.stderr b/tests/run-make/crate-loading-multiple-candidates/multiple-candidates.stderr new file mode 100644 index 00000000000..de7fc3b0feb --- /dev/null +++ b/tests/run-make/crate-loading-multiple-candidates/multiple-candidates.stderr @@ -0,0 +1,12 @@ +error[E0464]: multiple candidates for `rlib` dependency `crateresolve1` found + --> multiple-candidates.rs:1:1 + | +LL | extern crate crateresolve1; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: candidate #1: ./mylibs/libcrateresolve1-1.rlib + = note: candidate #2: ./mylibs/libcrateresolve1-2.rlib + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0464`. diff --git a/tests/run-make/crate-loading-multiple-candidates/rmake.rs b/tests/run-make/crate-loading-multiple-candidates/rmake.rs new file mode 100644 index 00000000000..ce090850500 --- /dev/null +++ b/tests/run-make/crate-loading-multiple-candidates/rmake.rs @@ -0,0 +1,34 @@ +//@ needs-symlink +//@ ignore-cross-compile + +// Tests that the multiple candidate dependencies diagnostic prints relative +// paths if a relative library path was passed in. + +use run_make_support::{bare_rustc, diff, rfs, rustc}; + +fn main() { + // Check that relative paths are preserved in the diagnostic + rfs::create_dir("mylibs"); + rustc().input("crateresolve1-1.rs").out_dir("mylibs").extra_filename("-1").run(); + rustc().input("crateresolve1-2.rs").out_dir("mylibs").extra_filename("-2").run(); + check("./mylibs"); + + // Check that symlinks aren't followed when printing the diagnostic + rfs::rename("mylibs", "original"); + rfs::symlink_dir("original", "mylibs"); + check("./mylibs"); +} + +fn check(library_path: &str) { + let out = rustc() + .input("multiple-candidates.rs") + .library_search_path(library_path) + .ui_testing() + .run_fail() + .stderr_utf8(); + diff() + .expected_file("multiple-candidates.stderr") + .normalize(r"\\", "/") + .actual_text("(rustc)", &out) + .run(); +} From 827047895a4c036f652b7105443f6305164f0e25 Mon Sep 17 00:00:00 2001 From: onur-ozkan Date: Wed, 16 Apr 2025 20:53:51 +0300 Subject: [PATCH 19/39] resolve config include FIXME Signed-off-by: onur-ozkan --- src/bootstrap/src/core/config/config.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/bootstrap/src/core/config/config.rs b/src/bootstrap/src/core/config/config.rs index e15aab4ad50..0c221abc969 100644 --- a/src/bootstrap/src/core/config/config.rs +++ b/src/bootstrap/src/core/config/config.rs @@ -814,9 +814,7 @@ impl Merge for TomlConfig { exit!(2); }); - // FIXME: Similar to `Config::parse_inner`, allow passing a custom `get_toml` from the caller to - // improve testability since `Config::get_toml` does nothing when `cfg(test)` is enabled. - let included_toml = Config::get_toml(&include_path).unwrap_or_else(|e| { + let included_toml = Config::get_toml_inner(&include_path).unwrap_or_else(|e| { eprintln!("ERROR: Failed to parse '{}': {e}", include_path.display()); exit!(2); }); @@ -1424,13 +1422,15 @@ impl Config { Self::get_toml(&builder_config_path) } - #[cfg(test)] - pub(crate) fn get_toml(_: &Path) -> Result { - Ok(TomlConfig::default()) + pub(crate) fn get_toml(file: &Path) -> Result { + #[cfg(test)] + return Ok(TomlConfig::default()); + + #[cfg(not(test))] + Self::get_toml_inner(file) } - #[cfg(not(test))] - pub(crate) fn get_toml(file: &Path) -> Result { + fn get_toml_inner(file: &Path) -> Result { let contents = t!(fs::read_to_string(file), format!("config file {} not found", file.display())); // Deserialize to Value and then TomlConfig to prevent the Deserialize impl of From d14df2652df492fe0f19c5e0ae5041b39b5a4d20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Wed, 16 Apr 2025 13:24:01 +0200 Subject: [PATCH 20/39] Make `parent` in `download_auto_job_metrics` optional --- src/ci/citool/src/main.rs | 2 +- src/ci/citool/src/metrics.rs | 23 ++++++++++++----------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/ci/citool/src/main.rs b/src/ci/citool/src/main.rs index a1956da352f..0fee862f572 100644 --- a/src/ci/citool/src/main.rs +++ b/src/ci/citool/src/main.rs @@ -180,7 +180,7 @@ fn postprocess_metrics( } fn post_merge_report(db: JobDatabase, current: String, parent: String) -> anyhow::Result<()> { - let metrics = download_auto_job_metrics(&db, &parent, ¤t)?; + let metrics = download_auto_job_metrics(&db, Some(&parent), ¤t)?; println!("\nComparing {parent} (parent) -> {current} (this PR)\n"); diff --git a/src/ci/citool/src/metrics.rs b/src/ci/citool/src/metrics.rs index a816fb3c4f1..3d8b1ad84cf 100644 --- a/src/ci/citool/src/metrics.rs +++ b/src/ci/citool/src/metrics.rs @@ -46,24 +46,25 @@ pub struct JobMetrics { /// `parent` and `current` should be commit SHAs. pub fn download_auto_job_metrics( job_db: &JobDatabase, - parent: &str, + parent: Option<&str>, current: &str, ) -> anyhow::Result> { let mut jobs = HashMap::default(); for job in &job_db.auto_jobs { eprintln!("Downloading metrics of job {}", job.name); - let metrics_parent = match download_job_metrics(&job.name, parent) { - Ok(metrics) => Some(metrics), - Err(error) => { - eprintln!( - r#"Did not find metrics for job `{}` at `{parent}`: {error:?}. + let metrics_parent = + parent.and_then(|parent| match download_job_metrics(&job.name, parent) { + Ok(metrics) => Some(metrics), + Err(error) => { + eprintln!( + r#"Did not find metrics for job `{}` at `{parent}`: {error:?}. Maybe it was newly added?"#, - job.name - ); - None - } - }; + job.name + ); + None + } + }); let metrics_current = download_job_metrics(&job.name, current)?; jobs.insert( job.name.clone(), From 111c15c48e618b30a0cf11d91b135d87d73053a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Wed, 16 Apr 2025 17:20:53 +0200 Subject: [PATCH 21/39] Extract function for normalizing path delimiters to `utils` --- src/ci/citool/src/analysis.rs | 13 ++++++------- src/ci/citool/src/utils.rs | 6 ++++++ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/ci/citool/src/analysis.rs b/src/ci/citool/src/analysis.rs index 9fc7c309bfb..62974be2dbe 100644 --- a/src/ci/citool/src/analysis.rs +++ b/src/ci/citool/src/analysis.rs @@ -8,9 +8,9 @@ use build_helper::metrics::{ }; use crate::github::JobInfoResolver; -use crate::metrics; use crate::metrics::{JobMetrics, JobName, get_test_suites}; use crate::utils::{output_details, pluralize}; +use crate::{metrics, utils}; /// Outputs durations of individual bootstrap steps from the gathered bootstrap invocations, /// and also a table with summarized information about executed tests. @@ -394,18 +394,17 @@ fn aggregate_tests(metrics: &JsonRoot) -> TestSuiteData { // Poor man's detection of doctests based on the "(line XYZ)" suffix let is_doctest = matches!(suite.metadata, TestSuiteMetadata::CargoPackage { .. }) && test.name.contains("(line"); - let test_entry = Test { name: generate_test_name(&test.name), stage, is_doctest }; + let test_entry = Test { + name: utils::normalize_path_delimiters(&test.name).to_string(), + stage, + is_doctest, + }; tests.insert(test_entry, test.outcome.clone()); } } TestSuiteData { tests } } -/// Normalizes Windows-style path delimiters to Unix-style paths. -fn generate_test_name(name: &str) -> String { - name.replace('\\', "/") -} - /// Prints test changes in Markdown format to stdout. fn report_test_diffs( diff: AggregatedTestDiffs, diff --git a/src/ci/citool/src/utils.rs b/src/ci/citool/src/utils.rs index a4c6ff85ef7..0367d349a1e 100644 --- a/src/ci/citool/src/utils.rs +++ b/src/ci/citool/src/utils.rs @@ -1,3 +1,4 @@ +use std::borrow::Cow; use std::path::Path; use anyhow::Context; @@ -28,3 +29,8 @@ where func(); println!("\n"); } + +/// Normalizes Windows-style path delimiters to Unix-style paths. +pub fn normalize_path_delimiters(name: &str) -> Cow { + if name.contains("\\") { name.replace('\\', "/").into() } else { name.into() } +} From c8a882b7b58a7b8f6b276ab64117b55bbe2626e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Thu, 17 Apr 2025 09:41:12 +0200 Subject: [PATCH 22/39] Add command to `citool` for generating a test dashboard --- src/ci/citool/Cargo.lock | 67 ++++++ src/ci/citool/Cargo.toml | 1 + src/ci/citool/src/main.rs | 16 +- src/ci/citool/src/test_dashboard/mod.rs | 239 +++++++++++++++++++++ src/ci/citool/templates/layout.askama | 71 ++++++ src/ci/citool/templates/test_group.askama | 22 ++ src/ci/citool/templates/test_suites.askama | 18 ++ 7 files changed, 433 insertions(+), 1 deletion(-) create mode 100644 src/ci/citool/src/test_dashboard/mod.rs create mode 100644 src/ci/citool/templates/layout.askama create mode 100644 src/ci/citool/templates/test_group.askama create mode 100644 src/ci/citool/templates/test_suites.askama diff --git a/src/ci/citool/Cargo.lock b/src/ci/citool/Cargo.lock index 2fe219f368b..43321d12caf 100644 --- a/src/ci/citool/Cargo.lock +++ b/src/ci/citool/Cargo.lock @@ -64,12 +64,63 @@ version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" +[[package]] +name = "askama" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4744ed2eef2645831b441d8f5459689ade2ab27c854488fbab1fbe94fce1a7" +dependencies = [ + "askama_derive", + "itoa", + "percent-encoding", + "serde", + "serde_json", +] + +[[package]] +name = "askama_derive" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d661e0f57be36a5c14c48f78d09011e67e0cb618f269cca9f2fd8d15b68c46ac" +dependencies = [ + "askama_parser", + "basic-toml", + "memchr", + "proc-macro2", + "quote", + "rustc-hash", + "serde", + "serde_derive", + "syn", +] + +[[package]] +name = "askama_parser" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf315ce6524c857bb129ff794935cf6d42c82a6cff60526fe2a63593de4d0d4f" +dependencies = [ + "memchr", + "serde", + "serde_derive", + "winnow", +] + [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "basic-toml" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba62675e8242a4c4e806d12f11d136e626e6c8361d6b829310732241652a178a" +dependencies = [ + "serde", +] + [[package]] name = "build_helper" version = "0.1.0" @@ -104,6 +155,7 @@ name = "citool" version = "0.1.0" dependencies = [ "anyhow", + "askama", "build_helper", "clap", "csv", @@ -646,6 +698,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + [[package]] name = "rustls" version = "0.23.23" @@ -1026,6 +1084,15 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "winnow" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63d3fcd9bba44b03821e7d699eeee959f3126dcc4aa8e4ae18ec617c2a5cea10" +dependencies = [ + "memchr", +] + [[package]] name = "write16" version = "1.0.0" diff --git a/src/ci/citool/Cargo.toml b/src/ci/citool/Cargo.toml index f18436a1263..0e2aba3b9e3 100644 --- a/src/ci/citool/Cargo.toml +++ b/src/ci/citool/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" [dependencies] anyhow = "1" +askama = "0.13" clap = { version = "4.5", features = ["derive"] } csv = "1" diff = "0.1" diff --git a/src/ci/citool/src/main.rs b/src/ci/citool/src/main.rs index 0fee862f572..a7a289fc3d4 100644 --- a/src/ci/citool/src/main.rs +++ b/src/ci/citool/src/main.rs @@ -4,6 +4,7 @@ mod datadog; mod github; mod jobs; mod metrics; +mod test_dashboard; mod utils; use std::collections::{BTreeMap, HashMap}; @@ -22,6 +23,7 @@ use crate::datadog::upload_datadog_metric; use crate::github::JobInfoResolver; use crate::jobs::RunType; use crate::metrics::{JobMetrics, download_auto_job_metrics, download_job_metrics, load_metrics}; +use crate::test_dashboard::generate_test_dashboard; use crate::utils::load_env_var; const CI_DIRECTORY: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/.."); @@ -234,6 +236,14 @@ enum Args { /// Current commit that will be compared to `parent`. current: String, }, + /// Generate a directory containing a HTML dashboard of test results from a CI run. + TestDashboard { + /// Commit SHA that was tested on CI to analyze. + current: String, + /// Output path for the HTML directory. + #[clap(long)] + output_dir: PathBuf, + }, } #[derive(clap::ValueEnum, Clone)] @@ -275,7 +285,11 @@ fn main() -> anyhow::Result<()> { postprocess_metrics(metrics_path, parent, job_name)?; } Args::PostMergeReport { current, parent } => { - post_merge_report(load_db(default_jobs_file)?, current, parent)?; + post_merge_report(load_db(&default_jobs_file)?, current, parent)?; + } + Args::TestDashboard { current, output_dir } => { + let db = load_db(&default_jobs_file)?; + generate_test_dashboard(db, ¤t, &output_dir)?; } } diff --git a/src/ci/citool/src/test_dashboard/mod.rs b/src/ci/citool/src/test_dashboard/mod.rs new file mode 100644 index 00000000000..ad9fe029e15 --- /dev/null +++ b/src/ci/citool/src/test_dashboard/mod.rs @@ -0,0 +1,239 @@ +use std::collections::{BTreeMap, HashMap}; +use std::fs::File; +use std::io::BufWriter; +use std::path::{Path, PathBuf}; + +use askama::Template; +use build_helper::metrics::{TestOutcome, TestSuiteMetadata}; + +use crate::jobs::JobDatabase; +use crate::metrics::{JobMetrics, JobName, download_auto_job_metrics, get_test_suites}; +use crate::utils::normalize_path_delimiters; + +pub struct TestInfo { + name: String, + jobs: Vec, +} + +struct JobTestResult { + job_name: String, + outcome: TestOutcome, +} + +#[derive(Default)] +struct TestSuiteInfo { + name: String, + tests: BTreeMap, +} + +/// Generate a set of HTML files into a directory that contain a dashboard of test results. +pub fn generate_test_dashboard( + db: JobDatabase, + current: &str, + output_dir: &Path, +) -> anyhow::Result<()> { + let metrics = download_auto_job_metrics(&db, None, current)?; + + let suites = gather_test_suites(&metrics); + + std::fs::create_dir_all(output_dir)?; + + let test_count = suites.test_count(); + write_page(output_dir, "index.html", &TestSuitesPage { suites, test_count })?; + + Ok(()) +} + +fn write_page(dir: &Path, name: &str, template: &T) -> anyhow::Result<()> { + let mut file = BufWriter::new(File::create(dir.join(name))?); + Template::write_into(template, &mut file)?; + Ok(()) +} + +fn gather_test_suites(job_metrics: &HashMap) -> TestSuites { + struct CoarseTestSuite<'a> { + kind: TestSuiteKind, + tests: BTreeMap>, + } + + let mut suites: HashMap = HashMap::new(); + + // First, gather tests from all jobs, stages and targets, and aggregate them per suite + for (job, metrics) in job_metrics { + let test_suites = get_test_suites(&metrics.current); + for suite in test_suites { + let (suite_name, stage, target, kind) = match &suite.metadata { + TestSuiteMetadata::CargoPackage { crates, stage, target, .. } => { + (crates.join(","), *stage, target, TestSuiteKind::Cargo) + } + TestSuiteMetadata::Compiletest { suite, stage, target, .. } => { + (suite.clone(), *stage, target, TestSuiteKind::Compiletest) + } + }; + let suite_entry = suites + .entry(suite_name.clone()) + .or_insert_with(|| CoarseTestSuite { kind, tests: Default::default() }); + let test_metadata = TestMetadata { job, stage, target }; + + for test in &suite.tests { + let test_name = normalize_test_name(&test.name, &suite_name); + let test_entry = suite_entry + .tests + .entry(test_name.clone()) + .or_insert_with(|| Test { name: test_name, passed: vec![], ignored: vec![] }); + match test.outcome { + TestOutcome::Passed => { + test_entry.passed.push(test_metadata); + } + TestOutcome::Ignored { ignore_reason: _ } => { + test_entry.ignored.push(test_metadata); + } + TestOutcome::Failed => { + eprintln!("Warning: failed test"); + } + } + } + } + } + + // Then, split the suites per directory + let mut suites = suites.into_iter().collect::>(); + suites.sort_by(|a, b| a.1.kind.cmp(&b.1.kind).then_with(|| a.0.cmp(&b.0))); + + let mut target_suites = vec![]; + for (suite_name, suite) in suites { + let suite = match suite.kind { + TestSuiteKind::Compiletest => TestSuite { + name: suite_name.clone(), + kind: TestSuiteKind::Compiletest, + group: build_test_group(&suite_name, suite.tests), + }, + TestSuiteKind::Cargo => { + let mut tests: Vec<_> = suite.tests.into_iter().collect(); + tests.sort_by(|a, b| a.0.cmp(&b.0)); + TestSuite { + name: format!("[cargo] {}", suite_name.clone()), + kind: TestSuiteKind::Cargo, + group: TestGroup { + name: suite_name, + root_tests: tests.into_iter().map(|t| t.1).collect(), + groups: vec![], + }, + } + } + }; + target_suites.push(suite); + } + + TestSuites { suites: target_suites } +} + +/// Recursively expand a test group based on filesystem hierarchy. +fn build_test_group<'a>(name: &str, tests: BTreeMap>) -> TestGroup<'a> { + let mut root_tests = vec![]; + let mut subdirs: BTreeMap>> = Default::default(); + + // Split tests into root tests and tests located in subdirectories + for (name, test) in tests { + let mut components = Path::new(&name).components().peekable(); + let subdir = components.next().unwrap(); + + if components.peek().is_none() { + // This is a root test + root_tests.push(test); + } else { + // This is a test in a nested directory + let subdir_tests = + subdirs.entry(subdir.as_os_str().to_str().unwrap().to_string()).or_default(); + let test_name = + components.into_iter().collect::().to_str().unwrap().to_string(); + subdir_tests.insert(test_name, test); + } + } + let dirs = subdirs + .into_iter() + .map(|(name, tests)| { + let group = build_test_group(&name, tests); + (name, group) + }) + .collect(); + + TestGroup { name: name.to_string(), root_tests, groups: dirs } +} + +/// Compiletest tests start with `[suite] tests/[suite]/a/b/c...`. +/// Remove the `[suite] tests/[suite]/` prefix so that we can find the filesystem path. +/// Also normalizes path delimiters. +fn normalize_test_name(name: &str, suite_name: &str) -> String { + let name = normalize_path_delimiters(name); + let name = name.as_ref(); + let name = name.strip_prefix(&format!("[{suite_name}]")).unwrap_or(name).trim(); + let name = name.strip_prefix("tests/").unwrap_or(name); + let name = name.strip_prefix(suite_name).unwrap_or(name); + name.trim_start_matches("/").to_string() +} + +#[derive(serde::Serialize)] +struct TestSuites<'a> { + suites: Vec>, +} + +impl<'a> TestSuites<'a> { + fn test_count(&self) -> u64 { + self.suites.iter().map(|suite| suite.group.test_count()).sum::() + } +} + +#[derive(serde::Serialize)] +struct TestSuite<'a> { + name: String, + kind: TestSuiteKind, + group: TestGroup<'a>, +} + +#[derive(Debug, serde::Serialize)] +struct Test<'a> { + name: String, + passed: Vec>, + ignored: Vec>, +} + +#[derive(Clone, Copy, Debug, serde::Serialize)] +struct TestMetadata<'a> { + job: &'a str, + stage: u32, + target: &'a str, +} + +// We have to use a template for the TestGroup instead of a macro, because +// macros cannot be recursive in askama at the moment. +#[derive(Template, serde::Serialize)] +#[template(path = "test_group.askama")] +/// Represents a group of tests +struct TestGroup<'a> { + name: String, + /// Tests located directly in this directory + root_tests: Vec>, + /// Nested directories with additional tests + groups: Vec<(String, TestGroup<'a>)>, +} + +impl<'a> TestGroup<'a> { + fn test_count(&self) -> u64 { + let root = self.root_tests.len() as u64; + self.groups.iter().map(|(_, group)| group.test_count()).sum::() + root + } +} + +#[derive(PartialEq, Eq, PartialOrd, Ord, serde::Serialize)] +enum TestSuiteKind { + Compiletest, + Cargo, +} + +#[derive(Template)] +#[template(path = "test_suites.askama")] +struct TestSuitesPage<'a> { + suites: TestSuites<'a>, + test_count: u64, +} diff --git a/src/ci/citool/templates/layout.askama b/src/ci/citool/templates/layout.askama new file mode 100644 index 00000000000..2e830aaa9f5 --- /dev/null +++ b/src/ci/citool/templates/layout.askama @@ -0,0 +1,71 @@ + + + + Rust CI Test Dashboard + + + + +{% block content %}{% endblock %} + + diff --git a/src/ci/citool/templates/test_group.askama b/src/ci/citool/templates/test_group.askama new file mode 100644 index 00000000000..a0b7fa863e5 --- /dev/null +++ b/src/ci/citool/templates/test_group.askama @@ -0,0 +1,22 @@ +
  • +
    +{{ name }} ({{ test_count() }} test{{ test_count() | pluralize }}) + +{% if !groups.is_empty() %} +
      + {% for (dir_name, subgroup) in groups %} + {{ subgroup|safe }} + {% endfor %} +
    +{% endif %} + +{% if !root_tests.is_empty() %} +
      + {% for test in root_tests %} +
    • {{ test.name }} ({{ test.passed.len() }} passed, {{ test.ignored.len() }} ignored)
    • + {% endfor %} +
    +{% endif %} + +
    +
  • diff --git a/src/ci/citool/templates/test_suites.askama b/src/ci/citool/templates/test_suites.askama new file mode 100644 index 00000000000..a6f8d0e1abe --- /dev/null +++ b/src/ci/citool/templates/test_suites.askama @@ -0,0 +1,18 @@ +{% extends "layout.askama" %} + +{% block content %} +

    Rust CI Test Dashboard

    +
    +
    + Total tests: {{ test_count }} +
    + +
      + {% for suite in suites.suites %} + {% if suite.kind == TestSuiteKind::Compiletest %} + {{ suite.group|safe }} + {% endif %} + {% endfor %} +
    +
    +{% endblock %} From a326afd5dd810427c72ed81e705c0d903e74edcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Thu, 17 Apr 2025 16:30:23 +0200 Subject: [PATCH 23/39] Add buttons for expanding and collapsing all test suites --- src/ci/citool/templates/layout.askama | 57 ++------------- src/ci/citool/templates/test_suites.askama | 81 +++++++++++++++++++++- 2 files changed, 84 insertions(+), 54 deletions(-) diff --git a/src/ci/citool/templates/layout.askama b/src/ci/citool/templates/layout.askama index 2e830aaa9f5..3b3b6f23741 100644 --- a/src/ci/citool/templates/layout.askama +++ b/src/ci/citool/templates/layout.askama @@ -3,69 +3,20 @@ Rust CI Test Dashboard {% block content %}{% endblock %} +{% block scripts %}{% endblock %} diff --git a/src/ci/citool/templates/test_suites.askama b/src/ci/citool/templates/test_suites.askama index a6f8d0e1abe..a8cedc65f24 100644 --- a/src/ci/citool/templates/test_suites.askama +++ b/src/ci/citool/templates/test_suites.askama @@ -4,7 +4,11 @@

    Rust CI Test Dashboard

    - Total tests: {{ test_count }} + Total tests: {{ test_count }} +
    + + +
      @@ -16,3 +20,78 @@
    {% endblock %} + +{% block styles %} +h1 { + text-align: center; + color: #333333; + margin-bottom: 30px; +} + +.summary { + display: flex; + justify-content: space-between; +} + +.test-suites { + background: white; + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + padding: 20px; +} + +ul { + padding-left: 0; +} + +li { + list-style: none; + padding-left: 20px; +} +summary { + margin-bottom: 5px; + padding: 6px; + background-color: #F4F4F4; + border: 1px solid #ddd; + border-radius: 4px; + cursor: pointer; +} +summary:hover { + background-color: #CFCFCF; +} + +/* Style the disclosure triangles */ +details > summary { + list-style: none; + position: relative; +} + +details > summary::before { + content: "▶"; + position: absolute; + left: -15px; + transform: rotate(0); + transition: transform 0.2s; +} + +details[open] > summary::before { + transform: rotate(90deg); +} +{% endblock %} + +{% block scripts %} + +{% endblock %} From 4b310338f8d2a67cbc863ee799206709e95da6b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Thu, 17 Apr 2025 16:35:34 +0200 Subject: [PATCH 24/39] Add a note about how to find tests that haven't been executed anywhere. --- src/ci/citool/src/test_dashboard/mod.rs | 60 ++++------------------ src/ci/citool/templates/test_suites.askama | 17 ++++-- 2 files changed, 22 insertions(+), 55 deletions(-) diff --git a/src/ci/citool/src/test_dashboard/mod.rs b/src/ci/citool/src/test_dashboard/mod.rs index ad9fe029e15..163e9c1acea 100644 --- a/src/ci/citool/src/test_dashboard/mod.rs +++ b/src/ci/citool/src/test_dashboard/mod.rs @@ -10,22 +10,6 @@ use crate::jobs::JobDatabase; use crate::metrics::{JobMetrics, JobName, download_auto_job_metrics, get_test_suites}; use crate::utils::normalize_path_delimiters; -pub struct TestInfo { - name: String, - jobs: Vec, -} - -struct JobTestResult { - job_name: String, - outcome: TestOutcome, -} - -#[derive(Default)] -struct TestSuiteInfo { - name: String, - tests: BTreeMap, -} - /// Generate a set of HTML files into a directory that contain a dashboard of test results. pub fn generate_test_dashboard( db: JobDatabase, @@ -33,7 +17,6 @@ pub fn generate_test_dashboard( output_dir: &Path, ) -> anyhow::Result<()> { let metrics = download_auto_job_metrics(&db, None, current)?; - let suites = gather_test_suites(&metrics); std::fs::create_dir_all(output_dir)?; @@ -52,27 +35,27 @@ fn write_page(dir: &Path, name: &str, template: &T) -> anyhow::Resu fn gather_test_suites(job_metrics: &HashMap) -> TestSuites { struct CoarseTestSuite<'a> { - kind: TestSuiteKind, tests: BTreeMap>, } let mut suites: HashMap = HashMap::new(); // First, gather tests from all jobs, stages and targets, and aggregate them per suite + // Only work with compiletest suites. for (job, metrics) in job_metrics { let test_suites = get_test_suites(&metrics.current); for suite in test_suites { - let (suite_name, stage, target, kind) = match &suite.metadata { - TestSuiteMetadata::CargoPackage { crates, stage, target, .. } => { - (crates.join(","), *stage, target, TestSuiteKind::Cargo) + let (suite_name, stage, target) = match &suite.metadata { + TestSuiteMetadata::CargoPackage { .. } => { + continue; } TestSuiteMetadata::Compiletest { suite, stage, target, .. } => { - (suite.clone(), *stage, target, TestSuiteKind::Compiletest) + (suite.clone(), *stage, target) } }; let suite_entry = suites .entry(suite_name.clone()) - .or_insert_with(|| CoarseTestSuite { kind, tests: Default::default() }); + .or_insert_with(|| CoarseTestSuite { tests: Default::default() }); let test_metadata = TestMetadata { job, stage, target }; for test in &suite.tests { @@ -98,29 +81,13 @@ fn gather_test_suites(job_metrics: &HashMap) -> TestSuites // Then, split the suites per directory let mut suites = suites.into_iter().collect::>(); - suites.sort_by(|a, b| a.1.kind.cmp(&b.1.kind).then_with(|| a.0.cmp(&b.0))); + suites.sort_by(|a, b| a.0.cmp(&b.0)); let mut target_suites = vec![]; for (suite_name, suite) in suites { - let suite = match suite.kind { - TestSuiteKind::Compiletest => TestSuite { - name: suite_name.clone(), - kind: TestSuiteKind::Compiletest, - group: build_test_group(&suite_name, suite.tests), - }, - TestSuiteKind::Cargo => { - let mut tests: Vec<_> = suite.tests.into_iter().collect(); - tests.sort_by(|a, b| a.0.cmp(&b.0)); - TestSuite { - name: format!("[cargo] {}", suite_name.clone()), - kind: TestSuiteKind::Cargo, - group: TestGroup { - name: suite_name, - root_tests: tests.into_iter().map(|t| t.1).collect(), - groups: vec![], - }, - } - } + let suite = TestSuite { + name: suite_name.clone(), + group: build_test_group(&suite_name, suite.tests), }; target_suites.push(suite); } @@ -187,7 +154,6 @@ impl<'a> TestSuites<'a> { #[derive(serde::Serialize)] struct TestSuite<'a> { name: String, - kind: TestSuiteKind, group: TestGroup<'a>, } @@ -225,12 +191,6 @@ impl<'a> TestGroup<'a> { } } -#[derive(PartialEq, Eq, PartialOrd, Ord, serde::Serialize)] -enum TestSuiteKind { - Compiletest, - Cargo, -} - #[derive(Template)] #[template(path = "test_suites.askama")] struct TestSuitesPage<'a> { diff --git a/src/ci/citool/templates/test_suites.askama b/src/ci/citool/templates/test_suites.askama index a8cedc65f24..bb3d9e36391 100644 --- a/src/ci/citool/templates/test_suites.askama +++ b/src/ci/citool/templates/test_suites.askama @@ -1,10 +1,15 @@ {% extends "layout.askama" %} {% block content %} -

    Rust CI Test Dashboard

    +

    Rust CI test dashboard

    - Total tests: {{ test_count }} +
    +
    Total tests: {{ test_count }}
    +
    + To find tests that haven't been executed anywhere, click on "Open all" and search for "(0 passed". +
    +
    @@ -13,9 +18,7 @@
      {% for suite in suites.suites %} - {% if suite.kind == TestSuiteKind::Compiletest %} - {{ suite.group|safe }} - {% endif %} + {{ suite.group|safe }} {% endfor %}
    @@ -33,6 +36,10 @@ h1 { justify-content: space-between; } +.test-count { + font-size: 1.2em; +} + .test-suites { background: white; border-radius: 8px; From 1a6e0d52e5b008cfd48f78285bb3655ecfd5d73e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Thu, 17 Apr 2025 17:14:26 +0200 Subject: [PATCH 25/39] Render test revisions separately --- src/ci/citool/src/test_dashboard/mod.rs | 43 ++++++++++++++++++----- src/ci/citool/templates/test_group.askama | 13 ++++++- 2 files changed, 46 insertions(+), 10 deletions(-) diff --git a/src/ci/citool/src/test_dashboard/mod.rs b/src/ci/citool/src/test_dashboard/mod.rs index 163e9c1acea..c16385baa3b 100644 --- a/src/ci/citool/src/test_dashboard/mod.rs +++ b/src/ci/citool/src/test_dashboard/mod.rs @@ -60,19 +60,27 @@ fn gather_test_suites(job_metrics: &HashMap) -> TestSuites for test in &suite.tests { let test_name = normalize_test_name(&test.name, &suite_name); - let test_entry = suite_entry - .tests - .entry(test_name.clone()) - .or_insert_with(|| Test { name: test_name, passed: vec![], ignored: vec![] }); + let (test_name, variant_name) = match test_name.rsplit_once('#') { + Some((name, variant)) => (name.to_string(), variant.to_string()), + None => (test_name, "".to_string()), + }; + let test_entry = suite_entry.tests.entry(test_name.clone()).or_insert_with(|| { + Test { name: test_name.clone(), revisions: Default::default() } + }); + let variant_entry = test_entry + .revisions + .entry(variant_name) + .or_insert_with(|| TestResults { passed: vec![], ignored: vec![] }); + match test.outcome { TestOutcome::Passed => { - test_entry.passed.push(test_metadata); + variant_entry.passed.push(test_metadata); } TestOutcome::Ignored { ignore_reason: _ } => { - test_entry.ignored.push(test_metadata); + variant_entry.ignored.push(test_metadata); } TestOutcome::Failed => { - eprintln!("Warning: failed test"); + eprintln!("Warning: failed test {test_name}"); } } } @@ -158,12 +166,29 @@ struct TestSuite<'a> { } #[derive(Debug, serde::Serialize)] -struct Test<'a> { - name: String, +struct TestResults<'a> { passed: Vec>, ignored: Vec>, } +#[derive(Debug, serde::Serialize)] +struct Test<'a> { + name: String, + revisions: BTreeMap>, +} + +impl<'a> Test<'a> { + /// If this is a test without revisions, it will have a single entry in `revisions` with + /// an empty string as the revision name. + fn single_test(&self) -> Option<&TestResults<'a>> { + if self.revisions.len() == 1 { + self.revisions.iter().next().take_if(|e| e.0.is_empty()).map(|e| e.1) + } else { + None + } + } +} + #[derive(Clone, Copy, Debug, serde::Serialize)] struct TestMetadata<'a> { job: &'a str, diff --git a/src/ci/citool/templates/test_group.askama b/src/ci/citool/templates/test_group.askama index a0b7fa863e5..535d98e0c24 100644 --- a/src/ci/citool/templates/test_group.askama +++ b/src/ci/citool/templates/test_group.askama @@ -13,7 +13,18 @@ {% if !root_tests.is_empty() %}
      {% for test in root_tests %} -
    • {{ test.name }} ({{ test.passed.len() }} passed, {{ test.ignored.len() }} ignored)
    • +
    • + {% if let Some(result) = test.single_test() %} + {{ test.name }} ({{ result.passed.len() }} passed, {{ result.ignored.len() }} ignored) + {% else %} + {{ test.name }} ({{ test.revisions.len() }} revision{{ test.revisions.len() | pluralize }}) +
        + {% for (revision, result) in test.revisions %} +
      • #{{ revision }} ({{ result.passed.len() }} passed, {{ result.ignored.len() }} ignored)
      • + {% endfor %} +
      + {% endif %} +
    • {% endfor %}
    {% endif %} From d2c1763336080030a235dae56f6096e9deb2ec9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Thu, 17 Apr 2025 17:18:38 +0200 Subject: [PATCH 26/39] Create a macro for rendering test results --- src/ci/citool/templates/test_group.askama | 8 ++++++-- src/ci/citool/templates/test_suites.askama | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/ci/citool/templates/test_group.askama b/src/ci/citool/templates/test_group.askama index 535d98e0c24..ba19d9258d8 100644 --- a/src/ci/citool/templates/test_group.askama +++ b/src/ci/citool/templates/test_group.askama @@ -1,3 +1,7 @@ +{% macro test_result(r) -%} +passed: {{ r.passed.len() }}, ignored: {{ r.ignored.len() }} +{%- endmacro %} +
  • {{ name }} ({{ test_count() }} test{{ test_count() | pluralize }}) @@ -15,12 +19,12 @@ {% for test in root_tests %}
  • {% if let Some(result) = test.single_test() %} - {{ test.name }} ({{ result.passed.len() }} passed, {{ result.ignored.len() }} ignored) + {{ test.name }} ({% call test_result(result) %}) {% else %} {{ test.name }} ({{ test.revisions.len() }} revision{{ test.revisions.len() | pluralize }})
      {% for (revision, result) in test.revisions %} -
    • #{{ revision }} ({{ result.passed.len() }} passed, {{ result.ignored.len() }} ignored)
    • +
    • #{{ revision }} ({% call test_result(result) %})
    • {% endfor %}
    {% endif %} diff --git a/src/ci/citool/templates/test_suites.askama b/src/ci/citool/templates/test_suites.askama index bb3d9e36391..d36e85228e2 100644 --- a/src/ci/citool/templates/test_suites.askama +++ b/src/ci/citool/templates/test_suites.askama @@ -7,7 +7,7 @@
    Total tests: {{ test_count }}
    - To find tests that haven't been executed anywhere, click on "Open all" and search for "(0 passed". + To find tests that haven't been executed anywhere, click on "Open all" and search for "passed: 0".
    From aa9cb70190bb38e466983abf0c53c6f26afab4de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Thu, 17 Apr 2025 17:25:12 +0200 Subject: [PATCH 27/39] Print number of root tests and subdirectories --- src/ci/citool/templates/test_group.askama | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/ci/citool/templates/test_group.askama b/src/ci/citool/templates/test_group.askama index ba19d9258d8..bdf32d00f4a 100644 --- a/src/ci/citool/templates/test_group.askama +++ b/src/ci/citool/templates/test_group.askama @@ -4,7 +4,12 @@ passed: {{ r.passed.len() }}, ignored: {{ r.ignored.len() }}
  • -{{ name }} ({{ test_count() }} test{{ test_count() | pluralize }}) +{{ name }} ({{ test_count() }} test{{ test_count() | pluralize }}{% if !root_tests.is_empty() && root_tests.len() as u64 != test_count() -%} + , {{ root_tests.len() }} root test{{ root_tests.len() | pluralize }} +{%- endif %}{% if !groups.is_empty() -%} + , {{ groups.len() }} subdir{{ groups.len() | pluralize }} +{%- endif %}) + {% if !groups.is_empty() %}
      From 08cb187d263ec91e8e6d35b4b235eee4ecad3357 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Thu, 17 Apr 2025 17:26:40 +0200 Subject: [PATCH 28/39] Turn `test_dashboard` into a file --- src/ci/citool/src/{test_dashboard/mod.rs => test_dashboard.rs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/ci/citool/src/{test_dashboard/mod.rs => test_dashboard.rs} (100%) diff --git a/src/ci/citool/src/test_dashboard/mod.rs b/src/ci/citool/src/test_dashboard.rs similarity index 100% rename from src/ci/citool/src/test_dashboard/mod.rs rename to src/ci/citool/src/test_dashboard.rs From cecf16785f193c247c47e42524322aeccc96c8a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Thu, 17 Apr 2025 17:38:15 +0200 Subject: [PATCH 29/39] Add a note about the test dashboard to the post-merge report --- src/ci/citool/src/main.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/ci/citool/src/main.rs b/src/ci/citool/src/main.rs index a7a289fc3d4..f4e671b609f 100644 --- a/src/ci/citool/src/main.rs +++ b/src/ci/citool/src/main.rs @@ -24,7 +24,7 @@ use crate::github::JobInfoResolver; use crate::jobs::RunType; use crate::metrics::{JobMetrics, download_auto_job_metrics, download_job_metrics, load_metrics}; use crate::test_dashboard::generate_test_dashboard; -use crate::utils::load_env_var; +use crate::utils::{load_env_var, output_details}; const CI_DIRECTORY: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/.."); const DOCKER_DIRECTORY: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../docker"); @@ -188,6 +188,20 @@ fn post_merge_report(db: JobDatabase, current: String, parent: String) -> anyhow let mut job_info_resolver = JobInfoResolver::new(); output_test_diffs(&metrics, &mut job_info_resolver); + + output_details("Test dashboard", || { + println!( + r#"\nRun + +```bash +cargo run --manifest-path src/ci/citool/Cargo.toml -- \ + test-dashboard {current} --output-dir test-dashboard +``` +And then open `test-dashboard/index.html` in your browser to see an overview of all executed tests. +"# + ); + }); + output_largest_duration_changes(&metrics, &mut job_info_resolver); Ok(()) From e882ff4e7ebc4653cdc69e57bc656fe558a4af88 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Thu, 17 Apr 2025 19:06:46 +0000 Subject: [PATCH 30/39] Don't assemble non-env/bound candidates if projection is rigid --- .../src/solve/assembly/mod.rs | 87 ++++++++++++------- .../src/solve/effect_goals.rs | 3 +- .../src/solve/normalizes_to/mod.rs | 3 +- .../src/solve/trait_goals.rs | 4 +- tests/ui/impl-unused-tps.stderr | 34 ++++---- .../next-solver/rpitit-cycle-due-to-rigid.rs | 32 +++++++ 6 files changed, 107 insertions(+), 56 deletions(-) create mode 100644 tests/ui/traits/next-solver/rpitit-cycle-due-to-rigid.rs diff --git a/compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs b/compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs index 83b2465d05a..ecb57cc0ad7 100644 --- a/compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs +++ b/compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs @@ -288,6 +288,21 @@ where ) -> Vec>; } +/// Allows callers of `assemble_and_evaluate_candidates` to choose whether to limit +/// candidate assembly to param-env and alias-bound candidates. +/// +/// On top of being a micro-optimization, as it avoids doing unnecessary work when +/// a param-env trait bound candidate shadows impls for normalization, this is also +/// required to prevent query cycles due to RPITIT inference. See the issue at: +/// . +pub(super) enum AssembleCandidatesFrom { + All, + /// Only assemble candidates from the environment and alias bounds, ignoring + /// user-written and built-in impls. We only expect `ParamEnv` and `AliasBound` + /// candidates to be assembled. + EnvAndBounds, +} + impl EvalCtxt<'_, D> where D: SolverDelegate, @@ -296,6 +311,7 @@ where pub(super) fn assemble_and_evaluate_candidates>( &mut self, goal: Goal, + assemble_from: AssembleCandidatesFrom, ) -> Vec> { let Ok(normalized_self_ty) = self.structurally_normalize_ty(goal.param_env, goal.predicate.self_ty()) @@ -322,16 +338,18 @@ where } } - self.assemble_impl_candidates(goal, &mut candidates); - - self.assemble_builtin_impl_candidates(goal, &mut candidates); - self.assemble_alias_bound_candidates(goal, &mut candidates); - - self.assemble_object_bound_candidates(goal, &mut candidates); - self.assemble_param_env_candidates(goal, &mut candidates); + match assemble_from { + AssembleCandidatesFrom::All => { + self.assemble_impl_candidates(goal, &mut candidates); + self.assemble_builtin_impl_candidates(goal, &mut candidates); + self.assemble_object_bound_candidates(goal, &mut candidates); + } + AssembleCandidatesFrom::EnvAndBounds => {} + } + candidates } @@ -754,6 +772,9 @@ where }) } + /// Assemble and merge candidates for goals which are related to an underlying trait + /// goal. Right now, this is normalizes-to and host effect goals. + /// /// We sadly can't simply take all possible candidates for normalization goals /// and check whether they result in the same constraints. We want to make sure /// that trying to normalize an alias doesn't result in constraints which aren't @@ -782,47 +803,44 @@ where /// /// See trait-system-refactor-initiative#124 for more details. #[instrument(level = "debug", skip(self, inject_normalize_to_rigid_candidate), ret)] - pub(super) fn merge_candidates( + pub(super) fn assemble_and_merge_candidates>( &mut self, proven_via: Option, - candidates: Vec>, + goal: Goal, inject_normalize_to_rigid_candidate: impl FnOnce(&mut EvalCtxt<'_, D>) -> QueryResult, ) -> QueryResult { let Some(proven_via) = proven_via else { // We don't care about overflow. If proving the trait goal overflowed, then // it's enough to report an overflow error for that, we don't also have to // overflow during normalization. - return Ok(self.make_ambiguous_response_no_constraints(MaybeCause::Ambiguity)); + // + // We use `forced_ambiguity` here over `make_ambiguous_response_no_constraints` + // because the former will also record a built-in candidate in the inspector. + return self.forced_ambiguity(MaybeCause::Ambiguity).map(|cand| cand.result); }; match proven_via { TraitGoalProvenVia::ParamEnv | TraitGoalProvenVia::AliasBound => { - let mut considered_candidates = Vec::new(); - considered_candidates.extend( - candidates - .iter() - .filter(|c| matches!(c.source, CandidateSource::ParamEnv(_))) - .map(|c| c.result), - ); - // Even when a trait bound has been proven using a where-bound, we // still need to consider alias-bounds for normalization, see - // tests/ui/next-solver/alias-bound-shadowed-by-env.rs. - // + // `tests/ui/next-solver/alias-bound-shadowed-by-env.rs`. + let candidates_from_env_and_bounds: Vec<_> = self + .assemble_and_evaluate_candidates(goal, AssembleCandidatesFrom::EnvAndBounds); + // We still need to prefer where-bounds over alias-bounds however. - // See tests/ui/winnowing/norm-where-bound-gt-alias-bound.rs. - // - // FIXME(const_trait_impl): should this behavior also be used by - // constness checking. Doing so is *at least theoretically* breaking, - // see github.com/rust-lang/rust/issues/133044#issuecomment-2500709754 - if considered_candidates.is_empty() { - considered_candidates.extend( - candidates - .iter() - .filter(|c| matches!(c.source, CandidateSource::AliasBound)) - .map(|c| c.result), - ); - } + // See `tests/ui/winnowing/norm-where-bound-gt-alias-bound.rs`. + let mut considered_candidates: Vec<_> = if candidates_from_env_and_bounds + .iter() + .any(|c| matches!(c.source, CandidateSource::ParamEnv(_))) + { + candidates_from_env_and_bounds + .into_iter() + .filter(|c| matches!(c.source, CandidateSource::ParamEnv(_))) + .map(|c| c.result) + .collect() + } else { + candidates_from_env_and_bounds.into_iter().map(|c| c.result).collect() + }; // If the trait goal has been proven by using the environment, we want to treat // aliases as rigid if there are no applicable projection bounds in the environment. @@ -839,6 +857,9 @@ where } } TraitGoalProvenVia::Misc => { + let candidates = + self.assemble_and_evaluate_candidates(goal, AssembleCandidatesFrom::All); + // Prefer "orphaned" param-env normalization predicates, which are used // (for example, and ideally only) when proving item bounds for an impl. let candidates_from_env: Vec<_> = candidates diff --git a/compiler/rustc_next_trait_solver/src/solve/effect_goals.rs b/compiler/rustc_next_trait_solver/src/solve/effect_goals.rs index 0b61c368d8e..7752a705cd1 100644 --- a/compiler/rustc_next_trait_solver/src/solve/effect_goals.rs +++ b/compiler/rustc_next_trait_solver/src/solve/effect_goals.rs @@ -399,12 +399,11 @@ where &mut self, goal: Goal>, ) -> QueryResult { - let candidates = self.assemble_and_evaluate_candidates(goal); let (_, proven_via) = self.probe(|_| ProbeKind::ShadowedEnvProbing).enter(|ecx| { let trait_goal: Goal> = goal.with(ecx.cx(), goal.predicate.trait_ref); ecx.compute_trait_goal(trait_goal) })?; - self.merge_candidates(proven_via, candidates, |_ecx| Err(NoSolution)) + self.assemble_and_merge_candidates(proven_via, goal, |_ecx| Err(NoSolution)) } } diff --git a/compiler/rustc_next_trait_solver/src/solve/normalizes_to/mod.rs b/compiler/rustc_next_trait_solver/src/solve/normalizes_to/mod.rs index fdeb276a58e..9466901683e 100644 --- a/compiler/rustc_next_trait_solver/src/solve/normalizes_to/mod.rs +++ b/compiler/rustc_next_trait_solver/src/solve/normalizes_to/mod.rs @@ -32,14 +32,13 @@ where let cx = self.cx(); match goal.predicate.alias.kind(cx) { ty::AliasTermKind::ProjectionTy | ty::AliasTermKind::ProjectionConst => { - let candidates = self.assemble_and_evaluate_candidates(goal); let trait_ref = goal.predicate.alias.trait_ref(cx); let (_, proven_via) = self.probe(|_| ProbeKind::ShadowedEnvProbing).enter(|ecx| { let trait_goal: Goal> = goal.with(cx, trait_ref); ecx.compute_trait_goal(trait_goal) })?; - self.merge_candidates(proven_via, candidates, |ecx| { + self.assemble_and_merge_candidates(proven_via, goal, |ecx| { ecx.probe(|&result| ProbeKind::RigidAlias { result }).enter(|this| { this.structurally_instantiate_normalizes_to_term( goal, diff --git a/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs b/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs index 409af8568d7..7bd1300f34e 100644 --- a/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs +++ b/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs @@ -13,7 +13,7 @@ use tracing::{instrument, trace}; use crate::delegate::SolverDelegate; use crate::solve::assembly::structural_traits::{self, AsyncCallableRelevantTypes}; -use crate::solve::assembly::{self, Candidate}; +use crate::solve::assembly::{self, AssembleCandidatesFrom, Candidate}; use crate::solve::inspect::ProbeKind; use crate::solve::{ BuiltinImplSource, CandidateSource, Certainty, EvalCtxt, Goal, GoalSource, MaybeCause, @@ -1365,7 +1365,7 @@ where &mut self, goal: Goal>, ) -> Result<(CanonicalResponse, Option), NoSolution> { - let candidates = self.assemble_and_evaluate_candidates(goal); + let candidates = self.assemble_and_evaluate_candidates(goal, AssembleCandidatesFrom::All); self.merge_trait_candidates(goal, candidates) } } diff --git a/tests/ui/impl-unused-tps.stderr b/tests/ui/impl-unused-tps.stderr index 09c3fce641c..eff5ffff9b6 100644 --- a/tests/ui/impl-unused-tps.stderr +++ b/tests/ui/impl-unused-tps.stderr @@ -7,23 +7,6 @@ LL | impl Foo for [isize; 0] { LL | impl Foo for U { | ^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `[isize; 0]` -error[E0207]: the type parameter `U` is not constrained by the impl trait, self type, or predicates - --> $DIR/impl-unused-tps.rs:32:9 - | -LL | impl Bar for T { - | ^ unconstrained type parameter - -error[E0119]: conflicting implementations of trait `Bar` - --> $DIR/impl-unused-tps.rs:40:1 - | -LL | impl Bar for T { - | -------------------- first implementation here -... -LL | / impl Bar for T -LL | | where -LL | | T: Bar, - | |____________________^ conflicting implementation - error[E0119]: conflicting implementations of trait `Foo<[isize; 0]>` for type `[isize; 0]` --> $DIR/impl-unused-tps.rs:49:1 | @@ -52,6 +35,12 @@ error[E0207]: the type parameter `U` is not constrained by the impl trait, self LL | impl Foo for [isize; 1] { | ^ unconstrained type parameter +error[E0207]: the type parameter `U` is not constrained by the impl trait, self type, or predicates + --> $DIR/impl-unused-tps.rs:32:9 + | +LL | impl Bar for T { + | ^ unconstrained type parameter + error[E0207]: the type parameter `U` is not constrained by the impl trait, self type, or predicates --> $DIR/impl-unused-tps.rs:40:9 | @@ -70,6 +59,17 @@ error[E0207]: the type parameter `V` is not constrained by the impl trait, self LL | impl Foo for T | ^ unconstrained type parameter +error[E0119]: conflicting implementations of trait `Bar` + --> $DIR/impl-unused-tps.rs:40:1 + | +LL | impl Bar for T { + | -------------------- first implementation here +... +LL | / impl Bar for T +LL | | where +LL | | T: Bar, + | |____________________^ conflicting implementation + error: aborting due to 9 previous errors Some errors have detailed explanations: E0119, E0207. diff --git a/tests/ui/traits/next-solver/rpitit-cycle-due-to-rigid.rs b/tests/ui/traits/next-solver/rpitit-cycle-due-to-rigid.rs new file mode 100644 index 00000000000..ec3d710ef37 --- /dev/null +++ b/tests/ui/traits/next-solver/rpitit-cycle-due-to-rigid.rs @@ -0,0 +1,32 @@ +//@ compile-flags: -Znext-solver +//@ check-pass +//@ edition: 2024 + +// Ensure we don't end up in a query cycle due to trying to assemble an impl candidate +// for an RPITIT normalizes-to goal, even though that impl candidate would *necessarily* +// be made rigid by a where clause. This query cycle is thus avoidable by not assembling +// that impl candidate which we *know* we are going to throw away anyways. + +use std::future::Future; + +pub trait ReactiveFunction: Send { + type Output; + + fn invoke(self) -> Self::Output; +} + +trait AttributeValue { + fn resolve(self) -> impl Future + Send; +} + +impl AttributeValue for F +where + F: ReactiveFunction, + V: AttributeValue, +{ + async fn resolve(self) { + self.invoke().resolve().await + } +} + +fn main() {} From 65ce38a4c96da2f950fb6b181b2b83b0320d103d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Fri, 18 Apr 2025 12:32:19 +0200 Subject: [PATCH 31/39] Add a note that explains the counts --- src/ci/citool/templates/test_suites.askama | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ci/citool/templates/test_suites.askama b/src/ci/citool/templates/test_suites.askama index d36e85228e2..4997f6a3f1c 100644 --- a/src/ci/citool/templates/test_suites.askama +++ b/src/ci/citool/templates/test_suites.askama @@ -2,6 +2,10 @@ {% block content %}

      Rust CI test dashboard

      +
      +Here's how to interpret the "passed" and "ignored" counts: +the count includes all combinations of "stage" x "target" x "CI job where the test was executed or ignored". +
      From b18e37305304780530224736ad55145063c7e8a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Fri, 18 Apr 2025 12:44:39 +0200 Subject: [PATCH 32/39] Reduce duplicated test prefixes in nested subdirectories `assembly/asm` contained a test named `asm/aarch64-el2vmsa.rs`, while it should have been only `arch64-el2vmsa.rs`. --- src/ci/citool/src/test_dashboard.rs | 36 +++++++++-------------- src/ci/citool/templates/test_group.askama | 6 ++-- 2 files changed, 17 insertions(+), 25 deletions(-) diff --git a/src/ci/citool/src/test_dashboard.rs b/src/ci/citool/src/test_dashboard.rs index c16385baa3b..8fbd0d3f200 100644 --- a/src/ci/citool/src/test_dashboard.rs +++ b/src/ci/citool/src/test_dashboard.rs @@ -64,9 +64,10 @@ fn gather_test_suites(job_metrics: &HashMap) -> TestSuites Some((name, variant)) => (name.to_string(), variant.to_string()), None => (test_name, "".to_string()), }; - let test_entry = suite_entry.tests.entry(test_name.clone()).or_insert_with(|| { - Test { name: test_name.clone(), revisions: Default::default() } - }); + let test_entry = suite_entry + .tests + .entry(test_name.clone()) + .or_insert_with(|| Test { revisions: Default::default() }); let variant_entry = test_entry .revisions .entry(variant_name) @@ -91,16 +92,12 @@ fn gather_test_suites(job_metrics: &HashMap) -> TestSuites let mut suites = suites.into_iter().collect::>(); suites.sort_by(|a, b| a.0.cmp(&b.0)); - let mut target_suites = vec![]; - for (suite_name, suite) in suites { - let suite = TestSuite { - name: suite_name.clone(), - group: build_test_group(&suite_name, suite.tests), - }; - target_suites.push(suite); - } + let suites = suites + .into_iter() + .map(|(suite_name, suite)| TestSuite { group: build_test_group(&suite_name, suite.tests) }) + .collect(); - TestSuites { suites: target_suites } + TestSuites { suites } } /// Recursively expand a test group based on filesystem hierarchy. @@ -115,7 +112,7 @@ fn build_test_group<'a>(name: &str, tests: BTreeMap>) -> TestGr if components.peek().is_none() { // This is a root test - root_tests.push(test); + root_tests.push((name, test)); } else { // This is a test in a nested directory let subdir_tests = @@ -148,7 +145,6 @@ fn normalize_test_name(name: &str, suite_name: &str) -> String { name.trim_start_matches("/").to_string() } -#[derive(serde::Serialize)] struct TestSuites<'a> { suites: Vec>, } @@ -159,21 +155,16 @@ impl<'a> TestSuites<'a> { } } -#[derive(serde::Serialize)] struct TestSuite<'a> { - name: String, group: TestGroup<'a>, } -#[derive(Debug, serde::Serialize)] struct TestResults<'a> { passed: Vec>, ignored: Vec>, } -#[derive(Debug, serde::Serialize)] struct Test<'a> { - name: String, revisions: BTreeMap>, } @@ -189,7 +180,8 @@ impl<'a> Test<'a> { } } -#[derive(Clone, Copy, Debug, serde::Serialize)] +#[derive(Clone, Copy)] +#[allow(dead_code)] struct TestMetadata<'a> { job: &'a str, stage: u32, @@ -198,13 +190,13 @@ struct TestMetadata<'a> { // We have to use a template for the TestGroup instead of a macro, because // macros cannot be recursive in askama at the moment. -#[derive(Template, serde::Serialize)] +#[derive(Template)] #[template(path = "test_group.askama")] /// Represents a group of tests struct TestGroup<'a> { name: String, /// Tests located directly in this directory - root_tests: Vec>, + root_tests: Vec<(String, Test<'a>)>, /// Nested directories with additional tests groups: Vec<(String, TestGroup<'a>)>, } diff --git a/src/ci/citool/templates/test_group.askama b/src/ci/citool/templates/test_group.askama index bdf32d00f4a..95731103f3b 100644 --- a/src/ci/citool/templates/test_group.askama +++ b/src/ci/citool/templates/test_group.askama @@ -21,12 +21,12 @@ passed: {{ r.passed.len() }}, ignored: {{ r.ignored.len() }} {% if !root_tests.is_empty() %}
        - {% for test in root_tests %} + {% for (name, test) in root_tests %}
      • {% if let Some(result) = test.single_test() %} - {{ test.name }} ({% call test_result(result) %}) + {{ name }} ({% call test_result(result) %}) {% else %} - {{ test.name }} ({{ test.revisions.len() }} revision{{ test.revisions.len() | pluralize }}) + {{ name }} ({{ test.revisions.len() }} revision{{ test.revisions.len() | pluralize }})
          {% for (revision, result) in test.revisions %}
        • #{{ revision }} ({% call test_result(result) %})
        • From ac7d1be031ac08cc0910efa3ec2f4186d7943792 Mon Sep 17 00:00:00 2001 From: onur-ozkan Date: Wed, 16 Apr 2025 20:54:15 +0300 Subject: [PATCH 33/39] add coverage on config include logic Signed-off-by: onur-ozkan --- src/bootstrap/src/core/config/tests.rs | 211 ++++++++++++++++++++++++- 1 file changed, 209 insertions(+), 2 deletions(-) diff --git a/src/bootstrap/src/core/config/tests.rs b/src/bootstrap/src/core/config/tests.rs index d8002ba8467..c8a12c9072c 100644 --- a/src/bootstrap/src/core/config/tests.rs +++ b/src/bootstrap/src/core/config/tests.rs @@ -1,8 +1,8 @@ use std::collections::BTreeSet; -use std::env; use std::fs::{File, remove_file}; use std::io::Write; -use std::path::Path; +use std::path::{Path, PathBuf}; +use std::{env, fs}; use build_helper::ci::CiEnv; use clap::CommandFactory; @@ -23,6 +23,27 @@ pub(crate) fn parse(config: &str) -> Config { ) } +fn get_toml(file: &Path) -> Result { + let contents = std::fs::read_to_string(file).unwrap(); + toml::from_str(&contents).and_then(|table: toml::Value| TomlConfig::deserialize(table)) +} + +/// Helps with debugging by using consistent test-specific directories instead of +/// random temporary directories. +fn prepare_test_specific_dir() -> PathBuf { + let current = std::thread::current(); + // Replace "::" with "_" to make it safe for directory names on Windows systems + let test_path = current.name().unwrap().replace("::", "_"); + + let testdir = parse("").tempdir().join(test_path); + + // clean up any old test files + let _ = fs::remove_dir_all(&testdir); + let _ = fs::create_dir_all(&testdir); + + testdir +} + #[test] fn download_ci_llvm() { let config = parse("llvm.download-ci-llvm = false"); @@ -539,3 +560,189 @@ fn test_ci_flag() { let config = Config::parse_inner(Flags::parse(&["check".into()]), |&_| toml::from_str("")); assert_eq!(config.is_running_on_ci, CiEnv::is_ci()); } + +#[test] +fn test_precedence_of_includes() { + let testdir = prepare_test_specific_dir(); + + let root_config = testdir.join("config.toml"); + let root_config_content = br#" + include = ["./extension.toml"] + + [llvm] + link-jobs = 2 + "#; + File::create(&root_config).unwrap().write_all(root_config_content).unwrap(); + + let extension = testdir.join("extension.toml"); + let extension_content = br#" + change-id=543 + include = ["./extension2.toml"] + "#; + File::create(extension).unwrap().write_all(extension_content).unwrap(); + + let extension = testdir.join("extension2.toml"); + let extension_content = br#" + change-id=742 + + [llvm] + link-jobs = 10 + + [build] + description = "Some creative description" + "#; + File::create(extension).unwrap().write_all(extension_content).unwrap(); + + let config = Config::parse_inner( + Flags::parse(&["check".to_owned(), format!("--config={}", root_config.to_str().unwrap())]), + get_toml, + ); + + assert_eq!(config.change_id.unwrap(), ChangeId::Id(543)); + assert_eq!(config.llvm_link_jobs.unwrap(), 2); + assert_eq!(config.description.unwrap(), "Some creative description"); +} + +#[test] +#[should_panic(expected = "Cyclic inclusion detected")] +fn test_cyclic_include_direct() { + let testdir = prepare_test_specific_dir(); + + let root_config = testdir.join("config.toml"); + let root_config_content = br#" + include = ["./extension.toml"] + "#; + File::create(&root_config).unwrap().write_all(root_config_content).unwrap(); + + let extension = testdir.join("extension.toml"); + let extension_content = br#" + include = ["./config.toml"] + "#; + File::create(extension).unwrap().write_all(extension_content).unwrap(); + + let config = Config::parse_inner( + Flags::parse(&["check".to_owned(), format!("--config={}", root_config.to_str().unwrap())]), + get_toml, + ); +} + +#[test] +#[should_panic(expected = "Cyclic inclusion detected")] +fn test_cyclic_include_indirect() { + let testdir = prepare_test_specific_dir(); + + let root_config = testdir.join("config.toml"); + let root_config_content = br#" + include = ["./extension.toml"] + "#; + File::create(&root_config).unwrap().write_all(root_config_content).unwrap(); + + let extension = testdir.join("extension.toml"); + let extension_content = br#" + include = ["./extension2.toml"] + "#; + File::create(extension).unwrap().write_all(extension_content).unwrap(); + + let extension = testdir.join("extension2.toml"); + let extension_content = br#" + include = ["./extension3.toml"] + "#; + File::create(extension).unwrap().write_all(extension_content).unwrap(); + + let extension = testdir.join("extension3.toml"); + let extension_content = br#" + include = ["./extension.toml"] + "#; + File::create(extension).unwrap().write_all(extension_content).unwrap(); + + let config = Config::parse_inner( + Flags::parse(&["check".to_owned(), format!("--config={}", root_config.to_str().unwrap())]), + get_toml, + ); +} + +#[test] +fn test_include_absolute_paths() { + let testdir = prepare_test_specific_dir(); + + let extension = testdir.join("extension.toml"); + File::create(&extension).unwrap().write_all(&[]).unwrap(); + + let root_config = testdir.join("config.toml"); + let extension_absolute_path = + extension.canonicalize().unwrap().to_str().unwrap().replace('\\', r"\\"); + let root_config_content = format!(r#"include = ["{}"]"#, extension_absolute_path); + File::create(&root_config).unwrap().write_all(root_config_content.as_bytes()).unwrap(); + + let config = Config::parse_inner( + Flags::parse(&["check".to_owned(), format!("--config={}", root_config.to_str().unwrap())]), + get_toml, + ); +} + +#[test] +fn test_include_relative_paths() { + let testdir = prepare_test_specific_dir(); + + let _ = fs::create_dir_all(&testdir.join("subdir/another_subdir")); + + let root_config = testdir.join("config.toml"); + let root_config_content = br#" + include = ["./subdir/extension.toml"] + "#; + File::create(&root_config).unwrap().write_all(root_config_content).unwrap(); + + let extension = testdir.join("subdir/extension.toml"); + let extension_content = br#" + include = ["../extension2.toml"] + "#; + File::create(extension).unwrap().write_all(extension_content).unwrap(); + + let extension = testdir.join("extension2.toml"); + let extension_content = br#" + include = ["./subdir/another_subdir/extension3.toml"] + "#; + File::create(extension).unwrap().write_all(extension_content).unwrap(); + + let extension = testdir.join("subdir/another_subdir/extension3.toml"); + let extension_content = br#" + include = ["../../extension4.toml"] + "#; + File::create(extension).unwrap().write_all(extension_content).unwrap(); + + let extension = testdir.join("extension4.toml"); + File::create(extension).unwrap().write_all(&[]).unwrap(); + + let config = Config::parse_inner( + Flags::parse(&["check".to_owned(), format!("--config={}", root_config.to_str().unwrap())]), + get_toml, + ); +} + +#[test] +fn test_include_precedence_over_profile() { + let testdir = prepare_test_specific_dir(); + + let root_config = testdir.join("config.toml"); + let root_config_content = br#" + profile = "dist" + include = ["./extension.toml"] + "#; + File::create(&root_config).unwrap().write_all(root_config_content).unwrap(); + + let extension = testdir.join("extension.toml"); + let extension_content = br#" + [rust] + channel = "dev" + "#; + File::create(extension).unwrap().write_all(extension_content).unwrap(); + + let config = Config::parse_inner( + Flags::parse(&["check".to_owned(), format!("--config={}", root_config.to_str().unwrap())]), + get_toml, + ); + + // "dist" profile would normally set the channel to "auto-detect", but includes should + // override profile settings, so we expect this to be "dev" here. + assert_eq!(config.channel, "dev"); +} From 41ddf8672231c1f8cfa0fc754c1653f151030b19 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Sat, 29 Mar 2025 17:30:11 +0100 Subject: [PATCH 34/39] Make `#[naked]` an unsafe attribute --- compiler/rustc_builtin_macros/messages.ftl | 4 +- .../example/mini_core_hello_world.rs | 6 +- .../src/error_codes/E0736.md | 2 +- .../src/error_codes/E0787.md | 2 +- compiler/rustc_feature/src/builtin_attrs.rs | 2 +- .../rustc_mir_build/src/check_unsafety.rs | 8 ++- compiler/rustc_parse/src/validate_attr.rs | 6 -- compiler/rustc_passes/messages.ftl | 8 +-- .../src/compiler-flags/sanitizer.md | 37 +++++------ .../aarch64-naked-fn-no-bti-prolog.rs | 4 +- tests/assembly/naked-functions/aix.rs | 4 +- tests/assembly/naked-functions/wasm32.rs | 60 +++++++++--------- .../x86_64-naked-fn-no-cet-prolog.rs | 4 +- tests/codegen/cffi/c-variadic-naked.rs | 6 +- tests/codegen/naked-asan.rs | 4 +- tests/codegen/naked-fn/aligned.rs | 6 +- tests/codegen/naked-fn/generics.rs | 46 +++++++------- tests/codegen/naked-fn/instruction-set.rs | 12 ++-- .../naked-fn/min-function-alignment.rs | 16 ++--- tests/codegen/naked-fn/naked-functions.rs | 16 ++--- .../naked-symbol-visibility/a_rust_dylib.rs | 20 +++--- tests/ui/asm/naked-asm-outside-naked-fn.rs | 16 ++--- .../ui/asm/naked-asm-outside-naked-fn.stderr | 24 +++---- tests/ui/asm/naked-functions-ffi.rs | 6 +- tests/ui/asm/naked-functions-inline.rs | 20 +++--- tests/ui/asm/naked-functions-inline.stderr | 32 +++++----- .../ui/asm/naked-functions-instruction-set.rs | 8 +-- tests/ui/asm/naked-functions-rustic-abi.rs | 12 ++-- .../ui/asm/naked-functions-target-feature.rs | 12 ++-- tests/ui/asm/naked-functions-testattrs.rs | 16 ++--- tests/ui/asm/naked-functions-testattrs.stderr | 24 +++---- tests/ui/asm/naked-functions-unused.rs | 30 +++------ tests/ui/asm/naked-functions.rs | 62 +++++++++---------- tests/ui/asm/naked-functions.stderr | 50 +++++++-------- tests/ui/asm/naked-invalid-attr.rs | 28 ++++----- tests/ui/asm/naked-invalid-attr.stderr | 20 +++--- tests/ui/asm/naked-with-invalid-repr-attr.rs | 20 +++--- .../asm/naked-with-invalid-repr-attr.stderr | 12 ++-- tests/ui/asm/named-asm-labels.rs | 16 ++--- tests/ui/asm/named-asm-labels.stderr | 18 +++--- .../feature-gate-naked_functions.rs | 6 +- .../feature-gate-naked_functions.stderr | 45 ++++++++------ ...feature-gate-naked_functions_rustic_abi.rs | 6 +- ...ure-gate-naked_functions_target_feature.rs | 2 +- .../rfc-2091-track-caller/error-with-naked.rs | 4 +- .../error-with-naked.stderr | 16 ++--- 46 files changed, 375 insertions(+), 403 deletions(-) diff --git a/compiler/rustc_builtin_macros/messages.ftl b/compiler/rustc_builtin_macros/messages.ftl index 5316e90847a..73be954cefd 100644 --- a/compiler/rustc_builtin_macros/messages.ftl +++ b/compiler/rustc_builtin_macros/messages.ftl @@ -247,9 +247,9 @@ builtin_macros_multiple_defaults = multiple declared defaults .suggestion = make `{$ident}` default builtin_macros_naked_functions_testing_attribute = - cannot use `#[naked]` with testing attributes + cannot use `#[unsafe(naked)]` with testing attributes .label = function marked with testing attribute here - .naked_attribute = `#[naked]` is incompatible with testing attributes + .naked_attribute = `#[unsafe(naked)]` is incompatible with testing attributes builtin_macros_no_default_variant = `#[derive(Default)]` on enum with no `#[default]` .label = this enum needs a unit variant marked with `#[default]` diff --git a/compiler/rustc_codegen_cranelift/example/mini_core_hello_world.rs b/compiler/rustc_codegen_cranelift/example/mini_core_hello_world.rs index 09d5b73fd3d..0b3a7281d5a 100644 --- a/compiler/rustc_codegen_cranelift/example/mini_core_hello_world.rs +++ b/compiler/rustc_codegen_cranelift/example/mini_core_hello_world.rs @@ -387,11 +387,9 @@ global_asm! { } #[cfg(all(not(jit), target_arch = "x86_64"))] -#[naked] +#[unsafe(naked)] extern "C" fn naked_test() { - unsafe { - naked_asm!("ret"); - } + naked_asm!("ret") } #[repr(C)] diff --git a/compiler/rustc_error_codes/src/error_codes/E0736.md b/compiler/rustc_error_codes/src/error_codes/E0736.md index cb7633b7068..66d5fbb80cf 100644 --- a/compiler/rustc_error_codes/src/error_codes/E0736.md +++ b/compiler/rustc_error_codes/src/error_codes/E0736.md @@ -11,7 +11,7 @@ Erroneous code example: ```compile_fail,E0736 #[inline] -#[naked] +#[unsafe(naked)] fn foo() {} ``` diff --git a/compiler/rustc_error_codes/src/error_codes/E0787.md b/compiler/rustc_error_codes/src/error_codes/E0787.md index f5c5faa066b..47b56ac17f4 100644 --- a/compiler/rustc_error_codes/src/error_codes/E0787.md +++ b/compiler/rustc_error_codes/src/error_codes/E0787.md @@ -5,7 +5,7 @@ Erroneous code example: ```compile_fail,E0787 #![feature(naked_functions)] -#[naked] +#[unsafe(naked)] pub extern "C" fn f() -> u32 { 42 } diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs index 3b441729d75..713e460e507 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -517,7 +517,7 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ // Linking: gated!( - naked, Normal, template!(Word), WarnFollowing, EncodeCrossCrate::No, + unsafe naked, Normal, template!(Word), WarnFollowing, EncodeCrossCrate::No, naked_functions, experimental!(naked) ), diff --git a/compiler/rustc_mir_build/src/check_unsafety.rs b/compiler/rustc_mir_build/src/check_unsafety.rs index b6a856a6eb4..adfce99a9b5 100644 --- a/compiler/rustc_mir_build/src/check_unsafety.rs +++ b/compiler/rustc_mir_build/src/check_unsafety.rs @@ -564,13 +564,17 @@ impl<'a, 'tcx> Visitor<'a, 'tcx> for UnsafetyVisitor<'a, 'tcx> { } } ExprKind::InlineAsm(box InlineAsmExpr { - asm_macro: AsmMacro::Asm | AsmMacro::NakedAsm, + asm_macro: asm_macro @ (AsmMacro::Asm | AsmMacro::NakedAsm), ref operands, template: _, options: _, line_spans: _, }) => { - self.requires_unsafe(expr.span, UseOfInlineAssembly); + // The `naked` attribute and the `naked_asm!` block form one atomic unit of + // unsafety, and `naked_asm!` does not itself need to be wrapped in an unsafe block. + if let AsmMacro::Asm = asm_macro { + self.requires_unsafe(expr.span, UseOfInlineAssembly); + } // For inline asm, do not use `walk_expr`, since we want to handle the label block // specially. diff --git a/compiler/rustc_parse/src/validate_attr.rs b/compiler/rustc_parse/src/validate_attr.rs index b518fca7a65..6a1c2af48ed 100644 --- a/compiler/rustc_parse/src/validate_attr.rs +++ b/compiler/rustc_parse/src/validate_attr.rs @@ -194,12 +194,6 @@ pub fn check_attribute_safety(psess: &ParseSess, safety: AttributeSafety, attr: } } } else if let Safety::Unsafe(unsafe_span) = attr_item.unsafety { - // Allow (but don't require) `#[unsafe(naked)]` so that compiler-builtins can upgrade to it. - // FIXME(#139797): remove this special case when compiler-builtins has upgraded. - if attr.has_name(sym::naked) { - return; - } - psess.dcx().emit_err(errors::InvalidAttrUnsafe { span: unsafe_span, name: attr_item.path.clone(), diff --git a/compiler/rustc_passes/messages.ftl b/compiler/rustc_passes/messages.ftl index 99789b74488..413726ddd82 100644 --- a/compiler/rustc_passes/messages.ftl +++ b/compiler/rustc_passes/messages.ftl @@ -508,7 +508,7 @@ passes_must_use_no_effect = `#[must_use]` has no effect when applied to {$article} {$target} passes_naked_asm_outside_naked_fn = - the `naked_asm!` macro can only be used in functions marked with `#[naked]` + the `naked_asm!` macro can only be used in functions marked with `#[unsafe(naked)]` passes_naked_functions_asm_block = naked functions must contain a single `naked_asm!` invocation @@ -516,9 +516,9 @@ passes_naked_functions_asm_block = .label_non_asm = not allowed in naked functions passes_naked_functions_incompatible_attribute = - attribute incompatible with `#[naked]` - .label = the `{$attr}` attribute is incompatible with `#[naked]` - .naked_attribute = function marked with `#[naked]` here + attribute incompatible with `#[unsafe(naked)]` + .label = the `{$attr}` attribute is incompatible with `#[unsafe(naked)]` + .naked_attribute = function marked with `#[unsafe(naked)]` here passes_naked_functions_must_naked_asm = the `asm!` macro is not allowed in naked functions diff --git a/src/doc/unstable-book/src/compiler-flags/sanitizer.md b/src/doc/unstable-book/src/compiler-flags/sanitizer.md index 4679acf0a6a..f2e1bb80cb2 100644 --- a/src/doc/unstable-book/src/compiler-flags/sanitizer.md +++ b/src/doc/unstable-book/src/compiler-flags/sanitizer.md @@ -247,34 +247,31 @@ See the [Clang ControlFlowIntegrity documentation][clang-cfi] for more details. ```rust,ignore (making doc tests pass cross-platform is hard) #![feature(naked_functions)] -use std::arch::asm; +use std::arch::naked_asm; use std::mem; fn add_one(x: i32) -> i32 { x + 1 } -#[naked] +#[unsafe(naked)] pub extern "C" fn add_two(x: i32) { // x + 2 preceded by a landing pad/nop block - unsafe { - asm!( - " - nop - nop - nop - nop - nop - nop - nop - nop - nop - lea eax, [rdi+2] - ret - ", - options(noreturn) - ); - } + naked_asm!( + " + nop + nop + nop + nop + nop + nop + nop + nop + nop + lea eax, [rdi+2] + ret + " + ); } fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 { diff --git a/tests/assembly/naked-functions/aarch64-naked-fn-no-bti-prolog.rs b/tests/assembly/naked-functions/aarch64-naked-fn-no-bti-prolog.rs index 46e627eaa00..46acf7c6501 100644 --- a/tests/assembly/naked-functions/aarch64-naked-fn-no-bti-prolog.rs +++ b/tests/assembly/naked-functions/aarch64-naked-fn-no-bti-prolog.rs @@ -13,8 +13,8 @@ use std::arch::naked_asm; // LLVM implements this via making sure of that, even for functions with the naked attribute. // So, we must emit an appropriate instruction instead! #[no_mangle] -#[naked] -pub unsafe extern "C" fn _hlt() -> ! { +#[unsafe(naked)] +pub extern "C" fn _hlt() -> ! { // CHECK-NOT: hint #34 // CHECK: hlt #0x1 naked_asm!("hlt #1") diff --git a/tests/assembly/naked-functions/aix.rs b/tests/assembly/naked-functions/aix.rs index cc0825b3738..9aa9edc39e7 100644 --- a/tests/assembly/naked-functions/aix.rs +++ b/tests/assembly/naked-functions/aix.rs @@ -29,7 +29,7 @@ use minicore::*; // CHECK-LABEL: blr: // CHECK: blr #[no_mangle] -#[naked] -unsafe extern "C" fn blr() { +#[unsafe(naked)] +extern "C" fn blr() { naked_asm!("blr") } diff --git a/tests/assembly/naked-functions/wasm32.rs b/tests/assembly/naked-functions/wasm32.rs index 4911a6bd08f..c114cb385be 100644 --- a/tests/assembly/naked-functions/wasm32.rs +++ b/tests/assembly/naked-functions/wasm32.rs @@ -22,8 +22,8 @@ use minicore::*; // CHECK-NOT: .size // CHECK: end_function #[no_mangle] -#[naked] -unsafe extern "C" fn nop() { +#[unsafe(naked)] +extern "C" fn nop() { naked_asm!("nop") } @@ -34,11 +34,11 @@ unsafe extern "C" fn nop() { // CHECK-NOT: .size // CHECK: end_function #[no_mangle] -#[naked] +#[unsafe(naked)] #[linkage = "weak"] // wasm functions cannot be aligned, so this has no effect #[repr(align(32))] -unsafe extern "C" fn weak_aligned_nop() { +extern "C" fn weak_aligned_nop() { naked_asm!("nop") } @@ -51,48 +51,48 @@ unsafe extern "C" fn weak_aligned_nop() { // // CHECK-NEXT: end_function #[no_mangle] -#[naked] -unsafe extern "C" fn fn_i8_i8(num: i8) -> i8 { +#[unsafe(naked)] +extern "C" fn fn_i8_i8(num: i8) -> i8 { naked_asm!("local.get 0", "local.get 0", "i32.mul") } // CHECK-LABEL: fn_i8_i8_i8: // CHECK: .functype fn_i8_i8_i8 (i32, i32) -> (i32) #[no_mangle] -#[naked] -unsafe extern "C" fn fn_i8_i8_i8(a: i8, b: i8) -> i8 { +#[unsafe(naked)] +extern "C" fn fn_i8_i8_i8(a: i8, b: i8) -> i8 { naked_asm!("local.get 1", "local.get 0", "i32.mul") } // CHECK-LABEL: fn_unit_i8: // CHECK: .functype fn_unit_i8 () -> (i32) #[no_mangle] -#[naked] -unsafe extern "C" fn fn_unit_i8() -> i8 { +#[unsafe(naked)] +extern "C" fn fn_unit_i8() -> i8 { naked_asm!("i32.const 42") } // CHECK-LABEL: fn_i8_unit: // CHECK: .functype fn_i8_unit (i32) -> () #[no_mangle] -#[naked] -unsafe extern "C" fn fn_i8_unit(_: i8) { +#[unsafe(naked)] +extern "C" fn fn_i8_unit(_: i8) { naked_asm!("nop") } // CHECK-LABEL: fn_i32_i32: // CHECK: .functype fn_i32_i32 (i32) -> (i32) #[no_mangle] -#[naked] -unsafe extern "C" fn fn_i32_i32(num: i32) -> i32 { +#[unsafe(naked)] +extern "C" fn fn_i32_i32(num: i32) -> i32 { naked_asm!("local.get 0", "local.get 0", "i32.mul") } // CHECK-LABEL: fn_i64_i64: // CHECK: .functype fn_i64_i64 (i64) -> (i64) #[no_mangle] -#[naked] -unsafe extern "C" fn fn_i64_i64(num: i64) -> i64 { +#[unsafe(naked)] +extern "C" fn fn_i64_i64(num: i64) -> i64 { naked_asm!("local.get 0", "local.get 0", "i64.mul") } @@ -101,8 +101,8 @@ unsafe extern "C" fn fn_i64_i64(num: i64) -> i64 { // wasm64-unknown: .functype fn_i128_i128 (i64, i64, i64) -> () #[allow(improper_ctypes_definitions)] #[no_mangle] -#[naked] -unsafe extern "C" fn fn_i128_i128(num: i128) -> i128 { +#[unsafe(naked)] +extern "C" fn fn_i128_i128(num: i128) -> i128 { naked_asm!( "local.get 0", "local.get 2", @@ -117,8 +117,8 @@ unsafe extern "C" fn fn_i128_i128(num: i128) -> i128 { // wasm32-wasip1: .functype fn_f128_f128 (i32, i64, i64) -> () // wasm64-unknown: .functype fn_f128_f128 (i64, i64, i64) -> () #[no_mangle] -#[naked] -unsafe extern "C" fn fn_f128_f128(num: f128) -> f128 { +#[unsafe(naked)] +extern "C" fn fn_f128_f128(num: f128) -> f128 { naked_asm!( "local.get 0", "local.get 2", @@ -139,8 +139,8 @@ struct Compound { // wasm32-wasip1: .functype fn_compound_compound (i32, i32) -> () // wasm64-unknown: .functype fn_compound_compound (i64, i64) -> () #[no_mangle] -#[naked] -unsafe extern "C" fn fn_compound_compound(_: Compound) -> Compound { +#[unsafe(naked)] +extern "C" fn fn_compound_compound(_: Compound) -> Compound { // this is the wasm32-wasip1 assembly naked_asm!( "local.get 0", @@ -160,8 +160,8 @@ struct WrapperI32(i32); // CHECK-LABEL: fn_wrapperi32_wrapperi32: // CHECK: .functype fn_wrapperi32_wrapperi32 (i32) -> (i32) #[no_mangle] -#[naked] -unsafe extern "C" fn fn_wrapperi32_wrapperi32(_: WrapperI32) -> WrapperI32 { +#[unsafe(naked)] +extern "C" fn fn_wrapperi32_wrapperi32(_: WrapperI32) -> WrapperI32 { naked_asm!("local.get 0") } @@ -171,8 +171,8 @@ struct WrapperI64(i64); // CHECK-LABEL: fn_wrapperi64_wrapperi64: // CHECK: .functype fn_wrapperi64_wrapperi64 (i64) -> (i64) #[no_mangle] -#[naked] -unsafe extern "C" fn fn_wrapperi64_wrapperi64(_: WrapperI64) -> WrapperI64 { +#[unsafe(naked)] +extern "C" fn fn_wrapperi64_wrapperi64(_: WrapperI64) -> WrapperI64 { naked_asm!("local.get 0") } @@ -182,8 +182,8 @@ struct WrapperF32(f32); // CHECK-LABEL: fn_wrapperf32_wrapperf32: // CHECK: .functype fn_wrapperf32_wrapperf32 (f32) -> (f32) #[no_mangle] -#[naked] -unsafe extern "C" fn fn_wrapperf32_wrapperf32(_: WrapperF32) -> WrapperF32 { +#[unsafe(naked)] +extern "C" fn fn_wrapperf32_wrapperf32(_: WrapperF32) -> WrapperF32 { naked_asm!("local.get 0") } @@ -193,7 +193,7 @@ struct WrapperF64(f64); // CHECK-LABEL: fn_wrapperf64_wrapperf64: // CHECK: .functype fn_wrapperf64_wrapperf64 (f64) -> (f64) #[no_mangle] -#[naked] -unsafe extern "C" fn fn_wrapperf64_wrapperf64(_: WrapperF64) -> WrapperF64 { +#[unsafe(naked)] +extern "C" fn fn_wrapperf64_wrapperf64(_: WrapperF64) -> WrapperF64 { naked_asm!("local.get 0") } diff --git a/tests/assembly/naked-functions/x86_64-naked-fn-no-cet-prolog.rs b/tests/assembly/naked-functions/x86_64-naked-fn-no-cet-prolog.rs index 54e1d93c68b..df6a2e91c51 100644 --- a/tests/assembly/naked-functions/x86_64-naked-fn-no-cet-prolog.rs +++ b/tests/assembly/naked-functions/x86_64-naked-fn-no-cet-prolog.rs @@ -13,8 +13,8 @@ use std::arch::naked_asm; // works by using an instruction for each possible landing site, // and LLVM implements this via making sure of that. #[no_mangle] -#[naked] -pub unsafe extern "sysv64" fn will_halt() -> ! { +#[unsafe(naked)] +pub extern "sysv64" fn will_halt() -> ! { // CHECK-NOT: endbr{{32|64}} // CHECK: hlt naked_asm!("hlt") diff --git a/tests/codegen/cffi/c-variadic-naked.rs b/tests/codegen/cffi/c-variadic-naked.rs index 24b69c5f59e..05d48e52dc0 100644 --- a/tests/codegen/cffi/c-variadic-naked.rs +++ b/tests/codegen/cffi/c-variadic-naked.rs @@ -8,11 +8,9 @@ #![feature(naked_functions)] #![no_std] -#[naked] +#[unsafe(naked)] pub unsafe extern "C" fn c_variadic(_: usize, _: ...) { // CHECK-NOT: va_start // CHECK-NOT: alloca - core::arch::naked_asm! { - "ret", - } + core::arch::naked_asm!("ret") } diff --git a/tests/codegen/naked-asan.rs b/tests/codegen/naked-asan.rs index 8efedab6ea5..52b3e709cd3 100644 --- a/tests/codegen/naked-asan.rs +++ b/tests/codegen/naked-asan.rs @@ -13,10 +13,10 @@ pub fn caller() { } // CHECK: declare x86_intrcc void @page_fault_handler(ptr {{.*}}, i64{{.*}}){{.*}}#[[ATTRS:[0-9]+]] -#[naked] +#[unsafe(naked)] #[no_mangle] pub extern "x86-interrupt" fn page_fault_handler(_: u64, _: u64) { - unsafe { core::arch::naked_asm!("ud2") }; + core::arch::naked_asm!("ud2") } // CHECK: #[[ATTRS]] = diff --git a/tests/codegen/naked-fn/aligned.rs b/tests/codegen/naked-fn/aligned.rs index d9dcd7f6c3e..6183461feda 100644 --- a/tests/codegen/naked-fn/aligned.rs +++ b/tests/codegen/naked-fn/aligned.rs @@ -10,8 +10,8 @@ use std::arch::naked_asm; // CHECK-LABEL: naked_empty: #[repr(align(16))] #[no_mangle] -#[naked] -pub unsafe extern "C" fn naked_empty() { +#[unsafe(naked)] +pub extern "C" fn naked_empty() { // CHECK: ret - naked_asm!("ret"); + naked_asm!("ret") } diff --git a/tests/codegen/naked-fn/generics.rs b/tests/codegen/naked-fn/generics.rs index 64998df64dd..44275867771 100644 --- a/tests/codegen/naked-fn/generics.rs +++ b/tests/codegen/naked-fn/generics.rs @@ -28,21 +28,19 @@ fn test(x: u64) { // CHECK: add rax, 1 // CHECK: add rax, 42 -#[naked] +#[unsafe(naked)] pub extern "C" fn using_const_generics(x: u64) -> u64 { const M: u64 = 42; - unsafe { - naked_asm!( - "xor rax, rax", - "add rax, rdi", - "add rax, {}", - "add rax, {}", - "ret", - const N, - const M, - ) - } + naked_asm!( + "xor rax, rax", + "add rax, rdi", + "add rax, {}", + "add rax, {}", + "ret", + const N, + const M, + ) } trait Invert { @@ -60,16 +58,14 @@ impl Invert for i64 { // CHECK: call // CHECK: ret -#[naked] +#[unsafe(naked)] #[no_mangle] pub extern "C" fn generic_function(x: i64) -> i64 { - unsafe { - naked_asm!( - "call {}", - "ret", - sym ::invert, - ) - } + naked_asm!( + "call {}", + "ret", + sym ::invert, + ) } #[derive(Copy, Clone)] @@ -81,10 +77,10 @@ struct Foo(u64); // CHECK: mov rax, rdi impl Foo { - #[naked] + #[unsafe(naked)] #[no_mangle] extern "C" fn method(self) -> u64 { - unsafe { naked_asm!("mov rax, rdi", "ret") } + naked_asm!("mov rax, rdi", "ret") } } @@ -97,10 +93,10 @@ trait Bar { } impl Bar for Foo { - #[naked] + #[unsafe(naked)] #[no_mangle] extern "C" fn trait_method(self) -> u64 { - unsafe { naked_asm!("mov rax, rdi", "ret") } + naked_asm!("mov rax, rdi", "ret") } } @@ -109,7 +105,7 @@ impl Bar for Foo { // CHECK: lea rax, [rdi + rsi] // this previously ICE'd, see https://github.com/rust-lang/rust/issues/124375 -#[naked] +#[unsafe(naked)] #[no_mangle] pub unsafe extern "C" fn naked_with_args_and_return(a: isize, b: isize) -> isize { naked_asm!("lea rax, [rdi + rsi]", "ret"); diff --git a/tests/codegen/naked-fn/instruction-set.rs b/tests/codegen/naked-fn/instruction-set.rs index a7b4c22a59b..2ccd47d6458 100644 --- a/tests/codegen/naked-fn/instruction-set.rs +++ b/tests/codegen/naked-fn/instruction-set.rs @@ -20,8 +20,8 @@ use minicore::*; // arm-mode: .arm // thumb-mode: .thumb #[no_mangle] -#[naked] -unsafe extern "C" fn test_unspecified() { +#[unsafe(naked)] +extern "C" fn test_unspecified() { naked_asm!("bx lr"); } @@ -33,9 +33,9 @@ unsafe extern "C" fn test_unspecified() { // arm-mode: .arm // thumb-mode: .thumb #[no_mangle] -#[naked] +#[unsafe(naked)] #[instruction_set(arm::t32)] -unsafe extern "C" fn test_thumb() { +extern "C" fn test_thumb() { naked_asm!("bx lr"); } @@ -46,8 +46,8 @@ unsafe extern "C" fn test_thumb() { // arm-mode: .arm // thumb-mode: .thumb #[no_mangle] -#[naked] +#[unsafe(naked)] #[instruction_set(arm::a32)] -unsafe extern "C" fn test_arm() { +extern "C" fn test_arm() { naked_asm!("bx lr"); } diff --git a/tests/codegen/naked-fn/min-function-alignment.rs b/tests/codegen/naked-fn/min-function-alignment.rs index 1330d796d39..4a914228824 100644 --- a/tests/codegen/naked-fn/min-function-alignment.rs +++ b/tests/codegen/naked-fn/min-function-alignment.rs @@ -9,24 +9,24 @@ // // CHECK: .balign 16 #[no_mangle] -#[naked] -pub unsafe extern "C" fn naked_no_explicit_align() { +#[unsafe(naked)] +pub extern "C" fn naked_no_explicit_align() { core::arch::naked_asm!("ret") } // CHECK: .balign 16 #[no_mangle] #[repr(align(8))] -#[naked] -pub unsafe extern "C" fn naked_lower_align() { +#[unsafe(naked)] +pub extern "C" fn naked_lower_align() { core::arch::naked_asm!("ret") } // CHECK: .balign 32 #[no_mangle] #[repr(align(32))] -#[naked] -pub unsafe extern "C" fn naked_higher_align() { +#[unsafe(naked)] +pub extern "C" fn naked_higher_align() { core::arch::naked_asm!("ret") } @@ -38,7 +38,7 @@ pub unsafe extern "C" fn naked_higher_align() { // CHECK: .balign 16 #[no_mangle] #[cold] -#[naked] -pub unsafe extern "C" fn no_explicit_align_cold() { +#[unsafe(naked)] +pub extern "C" fn no_explicit_align_cold() { core::arch::naked_asm!("ret") } diff --git a/tests/codegen/naked-fn/naked-functions.rs b/tests/codegen/naked-fn/naked-functions.rs index 3fe795178b7..1bcdd6de373 100644 --- a/tests/codegen/naked-fn/naked-functions.rs +++ b/tests/codegen/naked-fn/naked-functions.rs @@ -60,8 +60,8 @@ use minicore::*; // linux,win: .att_syntax #[no_mangle] -#[naked] -pub unsafe extern "C" fn naked_empty() { +#[unsafe(naked)] +pub extern "C" fn naked_empty() { #[cfg(not(all(target_arch = "arm", target_feature = "thumb-mode")))] naked_asm!("ret"); @@ -114,8 +114,8 @@ pub unsafe extern "C" fn naked_empty() { // linux,win: .att_syntax #[no_mangle] -#[naked] -pub unsafe extern "C" fn naked_with_args_and_return(a: isize, b: isize) -> isize { +#[unsafe(naked)] +pub extern "C" fn naked_with_args_and_return(a: isize, b: isize) -> isize { #[cfg(any(target_os = "windows", target_os = "linux"))] { naked_asm!("lea rax, [rdi + rsi]", "ret") @@ -138,9 +138,9 @@ pub unsafe extern "C" fn naked_with_args_and_return(a: isize, b: isize) -> isize // thumb: .pushsection .text.some_different_name,\22ax\22, %progbits // CHECK-LABEL: test_link_section: #[no_mangle] -#[naked] +#[unsafe(naked)] #[link_section = ".text.some_different_name"] -pub unsafe extern "C" fn test_link_section() { +pub extern "C" fn test_link_section() { #[cfg(not(all(target_arch = "arm", target_feature = "thumb-mode")))] naked_asm!("ret"); @@ -159,7 +159,7 @@ pub unsafe extern "C" fn test_link_section() { // win_i686-LABEL: @fastcall_cc@4: #[cfg(target_os = "windows")] #[no_mangle] -#[naked] -pub unsafe extern "fastcall" fn fastcall_cc(x: i32) -> i32 { +#[unsafe(naked)] +pub extern "fastcall" fn fastcall_cc(x: i32) -> i32 { naked_asm!("ret"); } diff --git a/tests/run-make/naked-symbol-visibility/a_rust_dylib.rs b/tests/run-make/naked-symbol-visibility/a_rust_dylib.rs index f98a2036544..ae755195253 100644 --- a/tests/run-make/naked-symbol-visibility/a_rust_dylib.rs +++ b/tests/run-make/naked-symbol-visibility/a_rust_dylib.rs @@ -26,9 +26,9 @@ extern "C" fn private_vanilla() -> u32 { 42 } -#[naked] +#[unsafe(naked)] extern "C" fn private_naked() -> u32 { - unsafe { naked_asm!("mov rax, 42", "ret") } + naked_asm!("mov rax, 42", "ret") } #[no_mangle] @@ -36,19 +36,19 @@ pub extern "C" fn public_vanilla() -> u32 { 42 } -#[naked] +#[unsafe(naked)] #[no_mangle] pub extern "C" fn public_naked_nongeneric() -> u32 { - unsafe { naked_asm!("mov rax, 42", "ret") } + naked_asm!("mov rax, 42", "ret") } pub extern "C" fn public_vanilla_generic() -> u32 { T::COUNT } -#[naked] +#[unsafe(naked)] pub extern "C" fn public_naked_generic() -> u32 { - unsafe { naked_asm!("mov rax, {}", "ret", const T::COUNT) } + naked_asm!("mov rax, {}", "ret", const T::COUNT) } #[linkage = "external"] @@ -56,10 +56,10 @@ extern "C" fn vanilla_external_linkage() -> u32 { 42 } -#[naked] +#[unsafe(naked)] #[linkage = "external"] extern "C" fn naked_external_linkage() -> u32 { - unsafe { naked_asm!("mov rax, 42", "ret") } + naked_asm!("mov rax, 42", "ret") } #[cfg(not(windows))] @@ -68,11 +68,11 @@ extern "C" fn vanilla_weak_linkage() -> u32 { 42 } -#[naked] +#[unsafe(naked)] #[cfg(not(windows))] #[linkage = "weak"] extern "C" fn naked_weak_linkage() -> u32 { - unsafe { naked_asm!("mov rax, 42", "ret") } + naked_asm!("mov rax, 42", "ret") } // functions that are declared in an `extern "C"` block are currently not exported diff --git a/tests/ui/asm/naked-asm-outside-naked-fn.rs b/tests/ui/asm/naked-asm-outside-naked-fn.rs index 1696008f339..a7680cc63ae 100644 --- a/tests/ui/asm/naked-asm-outside-naked-fn.rs +++ b/tests/ui/asm/naked-asm-outside-naked-fn.rs @@ -12,24 +12,24 @@ fn main() { test1(); } -#[naked] +#[unsafe(naked)] extern "C" fn test1() { - unsafe { naked_asm!("") } + naked_asm!("") } extern "C" fn test2() { - unsafe { naked_asm!("") } - //~^ ERROR the `naked_asm!` macro can only be used in functions marked with `#[naked]` + naked_asm!("") + //~^ ERROR the `naked_asm!` macro can only be used in functions marked with `#[unsafe(naked)]` } extern "C" fn test3() { - unsafe { (|| naked_asm!(""))() } - //~^ ERROR the `naked_asm!` macro can only be used in functions marked with `#[naked]` + (|| naked_asm!(""))() + //~^ ERROR the `naked_asm!` macro can only be used in functions marked with `#[unsafe(naked)]` } fn test4() { async move { - unsafe { naked_asm!("") } ; - //~^ ERROR the `naked_asm!` macro can only be used in functions marked with `#[naked]` + naked_asm!(""); + //~^ ERROR the `naked_asm!` macro can only be used in functions marked with `#[unsafe(naked)]` }; } diff --git a/tests/ui/asm/naked-asm-outside-naked-fn.stderr b/tests/ui/asm/naked-asm-outside-naked-fn.stderr index 6e91359669c..85a50a49fec 100644 --- a/tests/ui/asm/naked-asm-outside-naked-fn.stderr +++ b/tests/ui/asm/naked-asm-outside-naked-fn.stderr @@ -1,20 +1,20 @@ -error: the `naked_asm!` macro can only be used in functions marked with `#[naked]` - --> $DIR/naked-asm-outside-naked-fn.rs:21:14 +error: the `naked_asm!` macro can only be used in functions marked with `#[unsafe(naked)]` + --> $DIR/naked-asm-outside-naked-fn.rs:21:5 | -LL | unsafe { naked_asm!("") } - | ^^^^^^^^^^^^^^ +LL | naked_asm!("") + | ^^^^^^^^^^^^^^ -error: the `naked_asm!` macro can only be used in functions marked with `#[naked]` - --> $DIR/naked-asm-outside-naked-fn.rs:26:18 +error: the `naked_asm!` macro can only be used in functions marked with `#[unsafe(naked)]` + --> $DIR/naked-asm-outside-naked-fn.rs:26:9 | -LL | unsafe { (|| naked_asm!(""))() } - | ^^^^^^^^^^^^^^ +LL | (|| naked_asm!(""))() + | ^^^^^^^^^^^^^^ -error: the `naked_asm!` macro can only be used in functions marked with `#[naked]` - --> $DIR/naked-asm-outside-naked-fn.rs:32:19 +error: the `naked_asm!` macro can only be used in functions marked with `#[unsafe(naked)]` + --> $DIR/naked-asm-outside-naked-fn.rs:32:9 | -LL | unsafe { naked_asm!("") } ; - | ^^^^^^^^^^^^^^ +LL | naked_asm!(""); + | ^^^^^^^^^^^^^^ error: aborting due to 3 previous errors diff --git a/tests/ui/asm/naked-functions-ffi.rs b/tests/ui/asm/naked-functions-ffi.rs index b78d1e6a0d1..8fd0da01d72 100644 --- a/tests/ui/asm/naked-functions-ffi.rs +++ b/tests/ui/asm/naked-functions-ffi.rs @@ -5,11 +5,9 @@ use std::arch::naked_asm; -#[naked] +#[unsafe(naked)] pub extern "C" fn naked(p: char) -> u128 { //~^ WARN uses type `char` //~| WARN uses type `u128` - unsafe { - naked_asm!(""); - } + naked_asm!("") } diff --git a/tests/ui/asm/naked-functions-inline.rs b/tests/ui/asm/naked-functions-inline.rs index 74049e8ecbc..261401be645 100644 --- a/tests/ui/asm/naked-functions-inline.rs +++ b/tests/ui/asm/naked-functions-inline.rs @@ -4,35 +4,35 @@ use std::arch::naked_asm; -#[naked] -pub unsafe extern "C" fn inline_none() { +#[unsafe(naked)] +pub extern "C" fn inline_none() { naked_asm!(""); } -#[naked] +#[unsafe(naked)] #[inline] //~^ ERROR [E0736] -pub unsafe extern "C" fn inline_hint() { +pub extern "C" fn inline_hint() { naked_asm!(""); } -#[naked] +#[unsafe(naked)] #[inline(always)] //~^ ERROR [E0736] -pub unsafe extern "C" fn inline_always() { +pub extern "C" fn inline_always() { naked_asm!(""); } -#[naked] +#[unsafe(naked)] #[inline(never)] //~^ ERROR [E0736] -pub unsafe extern "C" fn inline_never() { +pub extern "C" fn inline_never() { naked_asm!(""); } -#[naked] +#[unsafe(naked)] #[cfg_attr(all(), inline(never))] //~^ ERROR [E0736] -pub unsafe extern "C" fn conditional_inline_never() { +pub extern "C" fn conditional_inline_never() { naked_asm!(""); } diff --git a/tests/ui/asm/naked-functions-inline.stderr b/tests/ui/asm/naked-functions-inline.stderr index 84a688f6f53..6df5b08ae85 100644 --- a/tests/ui/asm/naked-functions-inline.stderr +++ b/tests/ui/asm/naked-functions-inline.stderr @@ -1,34 +1,34 @@ -error[E0736]: attribute incompatible with `#[naked]` +error[E0736]: attribute incompatible with `#[unsafe(naked)]` --> $DIR/naked-functions-inline.rs:13:1 | -LL | #[naked] - | -------- function marked with `#[naked]` here +LL | #[unsafe(naked)] + | ---------------- function marked with `#[unsafe(naked)]` here LL | #[inline] - | ^^^^^^^^^ the `inline` attribute is incompatible with `#[naked]` + | ^^^^^^^^^ the `inline` attribute is incompatible with `#[unsafe(naked)]` -error[E0736]: attribute incompatible with `#[naked]` +error[E0736]: attribute incompatible with `#[unsafe(naked)]` --> $DIR/naked-functions-inline.rs:20:1 | -LL | #[naked] - | -------- function marked with `#[naked]` here +LL | #[unsafe(naked)] + | ---------------- function marked with `#[unsafe(naked)]` here LL | #[inline(always)] - | ^^^^^^^^^^^^^^^^^ the `inline` attribute is incompatible with `#[naked]` + | ^^^^^^^^^^^^^^^^^ the `inline` attribute is incompatible with `#[unsafe(naked)]` -error[E0736]: attribute incompatible with `#[naked]` +error[E0736]: attribute incompatible with `#[unsafe(naked)]` --> $DIR/naked-functions-inline.rs:27:1 | -LL | #[naked] - | -------- function marked with `#[naked]` here +LL | #[unsafe(naked)] + | ---------------- function marked with `#[unsafe(naked)]` here LL | #[inline(never)] - | ^^^^^^^^^^^^^^^^ the `inline` attribute is incompatible with `#[naked]` + | ^^^^^^^^^^^^^^^^ the `inline` attribute is incompatible with `#[unsafe(naked)]` -error[E0736]: attribute incompatible with `#[naked]` +error[E0736]: attribute incompatible with `#[unsafe(naked)]` --> $DIR/naked-functions-inline.rs:34:19 | -LL | #[naked] - | -------- function marked with `#[naked]` here +LL | #[unsafe(naked)] + | ---------------- function marked with `#[unsafe(naked)]` here LL | #[cfg_attr(all(), inline(never))] - | ^^^^^^^^^^^^^ the `inline` attribute is incompatible with `#[naked]` + | ^^^^^^^^^^^^^ the `inline` attribute is incompatible with `#[unsafe(naked)]` error: aborting due to 4 previous errors diff --git a/tests/ui/asm/naked-functions-instruction-set.rs b/tests/ui/asm/naked-functions-instruction-set.rs index 28241badf5f..6fd34b035ed 100644 --- a/tests/ui/asm/naked-functions-instruction-set.rs +++ b/tests/ui/asm/naked-functions-instruction-set.rs @@ -12,15 +12,15 @@ extern crate minicore; use minicore::*; #[no_mangle] -#[naked] +#[unsafe(naked)] #[instruction_set(arm::t32)] -unsafe extern "C" fn test_thumb() { +extern "C" fn test_thumb() { naked_asm!("bx lr"); } #[no_mangle] -#[naked] +#[unsafe(naked)] #[instruction_set(arm::a32)] -unsafe extern "C" fn test_arm() { +extern "C" fn test_arm() { naked_asm!("bx lr"); } diff --git a/tests/ui/asm/naked-functions-rustic-abi.rs b/tests/ui/asm/naked-functions-rustic-abi.rs index b654d38ccc1..99b8d2e19fe 100644 --- a/tests/ui/asm/naked-functions-rustic-abi.rs +++ b/tests/ui/asm/naked-functions-rustic-abi.rs @@ -11,17 +11,17 @@ use std::arch::{asm, naked_asm}; -#[naked] -pub unsafe fn rust_implicit() { +#[unsafe(naked)] +pub fn rust_implicit() { naked_asm!("ret"); } -#[naked] -pub unsafe extern "Rust" fn rust_explicit() { +#[unsafe(naked)] +pub extern "Rust" fn rust_explicit() { naked_asm!("ret"); } -#[naked] -pub unsafe extern "rust-cold" fn rust_cold() { +#[unsafe(naked)] +pub extern "rust-cold" fn rust_cold() { naked_asm!("ret"); } diff --git a/tests/ui/asm/naked-functions-target-feature.rs b/tests/ui/asm/naked-functions-target-feature.rs index afe1a389147..d8dc2104c76 100644 --- a/tests/ui/asm/naked-functions-target-feature.rs +++ b/tests/ui/asm/naked-functions-target-feature.rs @@ -8,14 +8,14 @@ use std::arch::{asm, naked_asm}; #[cfg(target_arch = "x86_64")] #[target_feature(enable = "sse2")] -#[naked] -pub unsafe extern "C" fn compatible_target_feature() { - naked_asm!(""); +#[unsafe(naked)] +pub extern "C" fn compatible_target_feature() { + naked_asm!("ret"); } #[cfg(target_arch = "aarch64")] #[target_feature(enable = "neon")] -#[naked] -pub unsafe extern "C" fn compatible_target_feature() { - naked_asm!(""); +#[unsafe(naked)] +pub extern "C" fn compatible_target_feature() { + naked_asm!("ret"); } diff --git a/tests/ui/asm/naked-functions-testattrs.rs b/tests/ui/asm/naked-functions-testattrs.rs index ad31876a77a..c8539e80640 100644 --- a/tests/ui/asm/naked-functions-testattrs.rs +++ b/tests/ui/asm/naked-functions-testattrs.rs @@ -8,31 +8,31 @@ use std::arch::naked_asm; #[test] -#[naked] +#[unsafe(naked)] //~^ ERROR [E0736] extern "C" fn test_naked() { - unsafe { naked_asm!("") }; + naked_asm!("") } #[should_panic] #[test] -#[naked] +#[unsafe(naked)] //~^ ERROR [E0736] extern "C" fn test_naked_should_panic() { - unsafe { naked_asm!("") }; + naked_asm!("") } #[ignore] #[test] -#[naked] +#[unsafe(naked)] //~^ ERROR [E0736] extern "C" fn test_naked_ignore() { - unsafe { naked_asm!("") }; + naked_asm!("") } #[bench] -#[naked] +#[unsafe(naked)] //~^ ERROR [E0736] extern "C" fn bench_naked() { - unsafe { naked_asm!("") }; + naked_asm!("") } diff --git a/tests/ui/asm/naked-functions-testattrs.stderr b/tests/ui/asm/naked-functions-testattrs.stderr index 0f0bb91b954..ad2041ec118 100644 --- a/tests/ui/asm/naked-functions-testattrs.stderr +++ b/tests/ui/asm/naked-functions-testattrs.stderr @@ -1,34 +1,34 @@ -error[E0736]: cannot use `#[naked]` with testing attributes +error[E0736]: cannot use `#[unsafe(naked)]` with testing attributes --> $DIR/naked-functions-testattrs.rs:11:1 | LL | #[test] | ------- function marked with testing attribute here -LL | #[naked] - | ^^^^^^^^ `#[naked]` is incompatible with testing attributes +LL | #[unsafe(naked)] + | ^^^^^^^^^^^^^^^^ `#[unsafe(naked)]` is incompatible with testing attributes -error[E0736]: cannot use `#[naked]` with testing attributes +error[E0736]: cannot use `#[unsafe(naked)]` with testing attributes --> $DIR/naked-functions-testattrs.rs:19:1 | LL | #[test] | ------- function marked with testing attribute here -LL | #[naked] - | ^^^^^^^^ `#[naked]` is incompatible with testing attributes +LL | #[unsafe(naked)] + | ^^^^^^^^^^^^^^^^ `#[unsafe(naked)]` is incompatible with testing attributes -error[E0736]: cannot use `#[naked]` with testing attributes +error[E0736]: cannot use `#[unsafe(naked)]` with testing attributes --> $DIR/naked-functions-testattrs.rs:27:1 | LL | #[test] | ------- function marked with testing attribute here -LL | #[naked] - | ^^^^^^^^ `#[naked]` is incompatible with testing attributes +LL | #[unsafe(naked)] + | ^^^^^^^^^^^^^^^^ `#[unsafe(naked)]` is incompatible with testing attributes -error[E0736]: cannot use `#[naked]` with testing attributes +error[E0736]: cannot use `#[unsafe(naked)]` with testing attributes --> $DIR/naked-functions-testattrs.rs:34:1 | LL | #[bench] | -------- function marked with testing attribute here -LL | #[naked] - | ^^^^^^^^ `#[naked]` is incompatible with testing attributes +LL | #[unsafe(naked)] + | ^^^^^^^^^^^^^^^^ `#[unsafe(naked)]` is incompatible with testing attributes error: aborting due to 4 previous errors diff --git a/tests/ui/asm/naked-functions-unused.rs b/tests/ui/asm/naked-functions-unused.rs index c27037819a4..67c05984be7 100644 --- a/tests/ui/asm/naked-functions-unused.rs +++ b/tests/ui/asm/naked-functions-unused.rs @@ -64,44 +64,34 @@ pub mod normal { pub mod naked { use std::arch::naked_asm; - #[naked] + #[unsafe(naked)] pub extern "C" fn function(a: usize, b: usize) -> usize { - unsafe { - naked_asm!(""); - } + naked_asm!("") } pub struct Naked; impl Naked { - #[naked] + #[unsafe(naked)] pub extern "C" fn associated(a: usize, b: usize) -> usize { - unsafe { - naked_asm!(""); - } + naked_asm!("") } - #[naked] + #[unsafe(naked)] pub extern "C" fn method(&self, a: usize, b: usize) -> usize { - unsafe { - naked_asm!(""); - } + naked_asm!("") } } impl super::Trait for Naked { - #[naked] + #[unsafe(naked)] extern "C" fn trait_associated(a: usize, b: usize) -> usize { - unsafe { - naked_asm!(""); - } + naked_asm!("") } - #[naked] + #[unsafe(naked)] extern "C" fn trait_method(&self, a: usize, b: usize) -> usize { - unsafe { - naked_asm!(""); - } + naked_asm!("") } } } diff --git a/tests/ui/asm/naked-functions.rs b/tests/ui/asm/naked-functions.rs index 8ba0eecb7b5..b433c1b5389 100644 --- a/tests/ui/asm/naked-functions.rs +++ b/tests/ui/asm/naked-functions.rs @@ -9,8 +9,8 @@ use std::arch::{asm, naked_asm}; #[unsafe(naked)] -pub unsafe extern "C" fn inline_asm_macro() { - asm!("", options(raw)); +pub extern "C" fn inline_asm_macro() { + unsafe { asm!("", options(raw)) }; //~^ERROR the `asm!` macro is not allowed in naked functions } @@ -21,7 +21,7 @@ pub struct P { } #[unsafe(naked)] -pub unsafe extern "C" fn patterns( +pub extern "C" fn patterns( mut a: u32, //~^ ERROR patterns not allowed in naked function parameters &b: &i32, @@ -35,7 +35,7 @@ pub unsafe extern "C" fn patterns( } #[unsafe(naked)] -pub unsafe extern "C" fn inc(a: u32) -> u32 { +pub extern "C" fn inc(a: u32) -> u32 { //~^ ERROR naked functions must contain a single `naked_asm!` invocation a + 1 //~^ ERROR referencing function parameters is not allowed in naked functions @@ -43,19 +43,19 @@ pub unsafe extern "C" fn inc(a: u32) -> u32 { #[unsafe(naked)] #[allow(asm_sub_register)] -pub unsafe extern "C" fn inc_asm(a: u32) -> u32 { +pub extern "C" fn inc_asm(a: u32) -> u32 { naked_asm!("/* {0} */", in(reg) a) //~^ ERROR the `in` operand cannot be used with `naked_asm!` } #[unsafe(naked)] -pub unsafe extern "C" fn inc_closure(a: u32) -> u32 { +pub extern "C" fn inc_closure(a: u32) -> u32 { //~^ ERROR naked functions must contain a single `naked_asm!` invocation (|| a + 1)() } #[unsafe(naked)] -pub unsafe extern "C" fn unsupported_operands() { +pub extern "C" fn unsupported_operands() { //~^ ERROR naked functions must contain a single `naked_asm!` invocation let mut a = 0usize; let mut b = 0usize; @@ -84,11 +84,10 @@ pub extern "C" fn missing_assembly() { #[unsafe(naked)] pub extern "C" fn too_many_asm_blocks() { //~^ ERROR naked functions must contain a single `naked_asm!` invocation - unsafe { - naked_asm!("", options(noreturn)); - //~^ ERROR the `noreturn` option cannot be used with `naked_asm!` - naked_asm!(""); - } + + naked_asm!("", options(noreturn)); + //~^ ERROR the `noreturn` option cannot be used with `naked_asm!` + naked_asm!(""); } pub fn outer(x: u32) -> extern "C" fn(usize) -> usize { @@ -124,49 +123,44 @@ unsafe extern "C" fn invalid_may_unwind() { #[unsafe(naked)] pub extern "C" fn valid_a() -> T { - unsafe { - naked_asm!(""); - } + naked_asm!(""); } #[unsafe(naked)] pub extern "C" fn valid_b() { - unsafe { + { { - { - naked_asm!(""); - }; + naked_asm!(""); }; - } + }; } #[unsafe(naked)] -pub unsafe extern "C" fn valid_c() { +pub extern "C" fn valid_c() { naked_asm!(""); } #[cfg(target_arch = "x86_64")] #[unsafe(naked)] -pub unsafe extern "C" fn valid_att_syntax() { +pub extern "C" fn valid_att_syntax() { naked_asm!("", options(att_syntax)); } #[unsafe(naked)] -#[unsafe(naked)] -pub unsafe extern "C" fn allow_compile_error(a: u32) -> u32 { +pub extern "C" fn allow_compile_error(a: u32) -> u32 { compile_error!("this is a user specified error") //~^ ERROR this is a user specified error } #[unsafe(naked)] -pub unsafe extern "C" fn allow_compile_error_and_asm(a: u32) -> u32 { +pub extern "C" fn allow_compile_error_and_asm(a: u32) -> u32 { compile_error!("this is a user specified error"); //~^ ERROR this is a user specified error naked_asm!("") } #[unsafe(naked)] -pub unsafe extern "C" fn invalid_asm_syntax(a: u32) -> u32 { +pub extern "C" fn invalid_asm_syntax(a: u32) -> u32 { naked_asm!(invalid_syntax) //~^ ERROR asm template must be a string literal } @@ -174,7 +168,7 @@ pub unsafe extern "C" fn invalid_asm_syntax(a: u32) -> u32 { #[cfg(target_arch = "x86_64")] #[cfg_attr(target_pointer_width = "64", no_mangle)] #[unsafe(naked)] -pub unsafe extern "C" fn compatible_cfg_attributes() { +pub extern "C" fn compatible_cfg_attributes() { naked_asm!("", options(att_syntax)); } @@ -183,20 +177,20 @@ pub unsafe extern "C" fn compatible_cfg_attributes() { #[deny(dead_code)] #[forbid(dead_code)] #[unsafe(naked)] -pub unsafe extern "C" fn compatible_diagnostic_attributes() { +pub extern "C" fn compatible_diagnostic_attributes() { naked_asm!("", options(raw)); } #[deprecated = "test"] #[unsafe(naked)] -pub unsafe extern "C" fn compatible_deprecated_attributes() { +pub extern "C" fn compatible_deprecated_attributes() { naked_asm!("", options(raw)); } #[cfg(target_arch = "x86_64")] #[must_use] #[unsafe(naked)] -pub unsafe extern "C" fn compatible_must_use_attributes() -> u64 { +pub extern "C" fn compatible_must_use_attributes() -> u64 { naked_asm!( " mov rax, 42 @@ -208,13 +202,13 @@ pub unsafe extern "C" fn compatible_must_use_attributes() -> u64 { #[export_name = "exported_function_name"] #[link_section = ".custom_section"] #[unsafe(naked)] -pub unsafe extern "C" fn compatible_ffi_attributes_1() { +pub extern "C" fn compatible_ffi_attributes_1() { naked_asm!("", options(raw)); } #[cold] #[unsafe(naked)] -pub unsafe extern "C" fn compatible_codegen_attributes() { +pub extern "C" fn compatible_codegen_attributes() { naked_asm!("", options(raw)); } @@ -223,12 +217,12 @@ pub unsafe extern "C" fn compatible_codegen_attributes() { // a normal comment #[doc(alias = "ADocAlias")] #[unsafe(naked)] -pub unsafe extern "C" fn compatible_doc_attributes() { +pub extern "C" fn compatible_doc_attributes() { naked_asm!("", options(raw)); } #[linkage = "external"] #[unsafe(naked)] -pub unsafe extern "C" fn compatible_linkage() { +pub extern "C" fn compatible_linkage() { naked_asm!("", options(raw)); } diff --git a/tests/ui/asm/naked-functions.stderr b/tests/ui/asm/naked-functions.stderr index 0a55bb9cd83..2b67c3aecd7 100644 --- a/tests/ui/asm/naked-functions.stderr +++ b/tests/ui/asm/naked-functions.stderr @@ -11,70 +11,70 @@ LL | in(reg) a, | ^^ the `in` operand is not meaningful for global-scoped inline assembly, remove it error: the `noreturn` option cannot be used with `naked_asm!` - --> $DIR/naked-functions.rs:88:32 + --> $DIR/naked-functions.rs:88:28 | -LL | naked_asm!("", options(noreturn)); - | ^^^^^^^^ the `noreturn` option is not meaningful for global-scoped inline assembly +LL | naked_asm!("", options(noreturn)); + | ^^^^^^^^ the `noreturn` option is not meaningful for global-scoped inline assembly error: the `nomem` option cannot be used with `naked_asm!` - --> $DIR/naked-functions.rs:106:28 + --> $DIR/naked-functions.rs:105:28 | LL | naked_asm!("", options(nomem, preserves_flags)); | ^^^^^ the `nomem` option is not meaningful for global-scoped inline assembly error: the `preserves_flags` option cannot be used with `naked_asm!` - --> $DIR/naked-functions.rs:106:35 + --> $DIR/naked-functions.rs:105:35 | LL | naked_asm!("", options(nomem, preserves_flags)); | ^^^^^^^^^^^^^^^ the `preserves_flags` option is not meaningful for global-scoped inline assembly error: the `readonly` option cannot be used with `naked_asm!` - --> $DIR/naked-functions.rs:113:28 + --> $DIR/naked-functions.rs:112:28 | LL | naked_asm!("", options(readonly, nostack), options(pure)); | ^^^^^^^^ the `readonly` option is not meaningful for global-scoped inline assembly error: the `nostack` option cannot be used with `naked_asm!` - --> $DIR/naked-functions.rs:113:38 + --> $DIR/naked-functions.rs:112:38 | LL | naked_asm!("", options(readonly, nostack), options(pure)); | ^^^^^^^ the `nostack` option is not meaningful for global-scoped inline assembly error: the `pure` option cannot be used with `naked_asm!` - --> $DIR/naked-functions.rs:113:56 + --> $DIR/naked-functions.rs:112:56 | LL | naked_asm!("", options(readonly, nostack), options(pure)); | ^^^^ the `pure` option is not meaningful for global-scoped inline assembly error: the `may_unwind` option cannot be used with `naked_asm!` - --> $DIR/naked-functions.rs:121:28 + --> $DIR/naked-functions.rs:120:28 | LL | naked_asm!("", options(may_unwind)); | ^^^^^^^^^^ the `may_unwind` option is not meaningful for global-scoped inline assembly error: this is a user specified error - --> $DIR/naked-functions.rs:157:5 + --> $DIR/naked-functions.rs:151:5 | LL | compile_error!("this is a user specified error") | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: this is a user specified error - --> $DIR/naked-functions.rs:163:5 + --> $DIR/naked-functions.rs:157:5 | LL | compile_error!("this is a user specified error"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: asm template must be a string literal - --> $DIR/naked-functions.rs:170:16 + --> $DIR/naked-functions.rs:164:16 | LL | naked_asm!(invalid_syntax) | ^^^^^^^^^^^^^^ error[E0787]: the `asm!` macro is not allowed in naked functions - --> $DIR/naked-functions.rs:13:5 + --> $DIR/naked-functions.rs:13:14 | -LL | asm!("", options(raw)); - | ^^^^^^^^^^^^^^^^^^^^^^ consider using the `naked_asm!` macro instead +LL | unsafe { asm!("", options(raw)) }; + | ^^^^^^^^^^^^^^^^^^^^^^ consider using the `naked_asm!` macro instead error: patterns not allowed in naked function parameters --> $DIR/naked-functions.rs:25:5 @@ -111,8 +111,8 @@ LL | a + 1 error[E0787]: naked functions must contain a single `naked_asm!` invocation --> $DIR/naked-functions.rs:38:1 | -LL | pub unsafe extern "C" fn inc(a: u32) -> u32 { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | pub extern "C" fn inc(a: u32) -> u32 { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ LL | LL | a + 1 | ----- not allowed in naked functions @@ -120,8 +120,8 @@ LL | a + 1 error[E0787]: naked functions must contain a single `naked_asm!` invocation --> $DIR/naked-functions.rs:52:1 | -LL | pub unsafe extern "C" fn inc_closure(a: u32) -> u32 { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | pub extern "C" fn inc_closure(a: u32) -> u32 { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ LL | LL | (|| a + 1)() | ------------ not allowed in naked functions @@ -129,8 +129,8 @@ LL | (|| a + 1)() error[E0787]: naked functions must contain a single `naked_asm!` invocation --> $DIR/naked-functions.rs:58:1 | -LL | pub unsafe extern "C" fn unsupported_operands() { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | pub extern "C" fn unsupported_operands() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ LL | LL | let mut a = 0usize; | ------------------- not allowed in naked functions @@ -155,11 +155,11 @@ error[E0787]: naked functions must contain a single `naked_asm!` invocation LL | pub extern "C" fn too_many_asm_blocks() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ... -LL | naked_asm!(""); - | -------------- multiple `naked_asm!` invocations are not allowed in naked functions +LL | naked_asm!(""); + | -------------- multiple `naked_asm!` invocations are not allowed in naked functions error: referencing function parameters is not allowed in naked functions - --> $DIR/naked-functions.rs:98:11 + --> $DIR/naked-functions.rs:97:11 | LL | *&y | ^ @@ -167,7 +167,7 @@ LL | *&y = help: follow the calling convention in asm block to use parameters error[E0787]: naked functions must contain a single `naked_asm!` invocation - --> $DIR/naked-functions.rs:96:5 + --> $DIR/naked-functions.rs:95:5 | LL | pub extern "C" fn inner(y: usize) -> usize { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/asm/naked-invalid-attr.rs b/tests/ui/asm/naked-invalid-attr.rs index 4053c58fb51..6c5fdbe74d8 100644 --- a/tests/ui/asm/naked-invalid-attr.rs +++ b/tests/ui/asm/naked-invalid-attr.rs @@ -1,17 +1,17 @@ -// Checks that #[naked] attribute can be placed on function definitions only. +// Checks that #[unsafe(naked)] attribute can be placed on function definitions only. // //@ needs-asm-support #![feature(naked_functions)] -#![naked] //~ ERROR should be applied to a function definition +#![unsafe(naked)] //~ ERROR should be applied to a function definition use std::arch::naked_asm; extern "C" { - #[naked] //~ ERROR should be applied to a function definition + #[unsafe(naked)] //~ ERROR should be applied to a function definition fn f(); } -#[naked] //~ ERROR should be applied to a function definition +#[unsafe(naked)] //~ ERROR should be applied to a function definition #[repr(C)] struct S { a: u32, @@ -19,35 +19,35 @@ struct S { } trait Invoke { - #[naked] //~ ERROR should be applied to a function definition + #[unsafe(naked)] //~ ERROR should be applied to a function definition extern "C" fn invoke(&self); } impl Invoke for S { - #[naked] + #[unsafe(naked)] extern "C" fn invoke(&self) { - unsafe { naked_asm!("") } + naked_asm!("") } } -#[naked] +#[unsafe(naked)] extern "C" fn ok() { - unsafe { naked_asm!("") } + naked_asm!("") } impl S { - #[naked] + #[unsafe(naked)] extern "C" fn g() { - unsafe { naked_asm!("") } + naked_asm!("") } - #[naked] + #[unsafe(naked)] extern "C" fn h(&self) { - unsafe { naked_asm!("") } + naked_asm!("") } } fn main() { - #[naked] //~ ERROR should be applied to a function definition + #[unsafe(naked)] //~ ERROR should be applied to a function definition || {}; } diff --git a/tests/ui/asm/naked-invalid-attr.stderr b/tests/ui/asm/naked-invalid-attr.stderr index 640f9d9510d..6e2746b5684 100644 --- a/tests/ui/asm/naked-invalid-attr.stderr +++ b/tests/ui/asm/naked-invalid-attr.stderr @@ -1,8 +1,8 @@ error: attribute should be applied to a function definition --> $DIR/naked-invalid-attr.rs:14:1 | -LL | #[naked] - | ^^^^^^^^ +LL | #[unsafe(naked)] + | ^^^^^^^^^^^^^^^^ LL | #[repr(C)] LL | / struct S { LL | | a: u32, @@ -13,32 +13,32 @@ LL | | } error: attribute should be applied to a function definition --> $DIR/naked-invalid-attr.rs:51:5 | -LL | #[naked] - | ^^^^^^^^ +LL | #[unsafe(naked)] + | ^^^^^^^^^^^^^^^^ LL | || {}; | ----- not a function definition error: attribute should be applied to a function definition --> $DIR/naked-invalid-attr.rs:22:5 | -LL | #[naked] - | ^^^^^^^^ +LL | #[unsafe(naked)] + | ^^^^^^^^^^^^^^^^ LL | extern "C" fn invoke(&self); | ---------------------------- not a function definition error: attribute should be applied to a function definition --> $DIR/naked-invalid-attr.rs:10:5 | -LL | #[naked] - | ^^^^^^^^ +LL | #[unsafe(naked)] + | ^^^^^^^^^^^^^^^^ LL | fn f(); | ------- not a function definition error: attribute should be applied to a function definition --> $DIR/naked-invalid-attr.rs:5:1 | -LL | #![naked] - | ^^^^^^^^^ cannot be applied to crates +LL | #![unsafe(naked)] + | ^^^^^^^^^^^^^^^^^ cannot be applied to crates error: aborting due to 5 previous errors diff --git a/tests/ui/asm/naked-with-invalid-repr-attr.rs b/tests/ui/asm/naked-with-invalid-repr-attr.rs index 18b9c1014c3..c9f335ea950 100644 --- a/tests/ui/asm/naked-with-invalid-repr-attr.rs +++ b/tests/ui/asm/naked-with-invalid-repr-attr.rs @@ -6,43 +6,43 @@ use std::arch::naked_asm; #[repr(C)] //~^ ERROR attribute should be applied to a struct, enum, or union [E0517] -#[naked] +#[unsafe(naked)] extern "C" fn example1() { //~^ NOTE not a struct, enum, or union - unsafe { naked_asm!("") } + naked_asm!("") } #[repr(transparent)] //~^ ERROR attribute should be applied to a struct, enum, or union [E0517] -#[naked] +#[unsafe(naked)] extern "C" fn example2() { //~^ NOTE not a struct, enum, or union - unsafe { naked_asm!("") } + naked_asm!("") } #[repr(align(16), C)] //~^ ERROR attribute should be applied to a struct, enum, or union [E0517] -#[naked] +#[unsafe(naked)] extern "C" fn example3() { //~^ NOTE not a struct, enum, or union - unsafe { naked_asm!("") } + naked_asm!("") } // note: two errors because of packed and C #[repr(C, packed)] //~^ ERROR attribute should be applied to a struct or union [E0517] //~| ERROR attribute should be applied to a struct, enum, or union [E0517] -#[naked] +#[unsafe(naked)] extern "C" fn example4() { //~^ NOTE not a struct, enum, or union //~| NOTE not a struct or union - unsafe { naked_asm!("") } + naked_asm!("") } #[repr(u8)] //~^ ERROR attribute should be applied to an enum [E0517] -#[naked] +#[unsafe(naked)] extern "C" fn example5() { //~^ NOTE not an enum - unsafe { naked_asm!("") } + naked_asm!("") } diff --git a/tests/ui/asm/naked-with-invalid-repr-attr.stderr b/tests/ui/asm/naked-with-invalid-repr-attr.stderr index 8248a8c1657..219e32473be 100644 --- a/tests/ui/asm/naked-with-invalid-repr-attr.stderr +++ b/tests/ui/asm/naked-with-invalid-repr-attr.stderr @@ -6,7 +6,7 @@ LL | #[repr(C)] ... LL | / extern "C" fn example1() { LL | | -LL | | unsafe { naked_asm!("") } +LL | | naked_asm!("") LL | | } | |_- not a struct, enum, or union @@ -18,7 +18,7 @@ LL | #[repr(transparent)] ... LL | / extern "C" fn example2() { LL | | -LL | | unsafe { naked_asm!("") } +LL | | naked_asm!("") LL | | } | |_- not a struct, enum, or union @@ -30,7 +30,7 @@ LL | #[repr(align(16), C)] ... LL | / extern "C" fn example3() { LL | | -LL | | unsafe { naked_asm!("") } +LL | | naked_asm!("") LL | | } | |_- not a struct, enum, or union @@ -43,7 +43,7 @@ LL | #[repr(C, packed)] LL | / extern "C" fn example4() { LL | | LL | | -LL | | unsafe { naked_asm!("") } +LL | | naked_asm!("") LL | | } | |_- not a struct, enum, or union @@ -56,7 +56,7 @@ LL | #[repr(C, packed)] LL | / extern "C" fn example4() { LL | | LL | | -LL | | unsafe { naked_asm!("") } +LL | | naked_asm!("") LL | | } | |_- not a struct or union @@ -68,7 +68,7 @@ LL | #[repr(u8)] ... LL | / extern "C" fn example5() { LL | | -LL | | unsafe { naked_asm!("") } +LL | | naked_asm!("") LL | | } | |_- not an enum diff --git a/tests/ui/asm/named-asm-labels.rs b/tests/ui/asm/named-asm-labels.rs index 77831e679ed..d5c194452d7 100644 --- a/tests/ui/asm/named-asm-labels.rs +++ b/tests/ui/asm/named-asm-labels.rs @@ -175,9 +175,9 @@ fn main() { // Trigger on naked fns too, even though they can't be inlined, reusing a // label or LTO can cause labels to break -#[naked] +#[unsafe(naked)] pub extern "C" fn foo() -> i32 { - unsafe { naked_asm!(".Lfoo: mov rax, {}; ret;", "nop", const 1) } + naked_asm!(".Lfoo: mov rax, {}; ret;", "nop", const 1) //~^ ERROR avoid using named labels } @@ -188,21 +188,21 @@ pub extern "C" fn bar() { //~^ ERROR avoid using named labels } -#[naked] +#[unsafe(naked)] pub extern "C" fn aaa() { fn _local() {} - unsafe { naked_asm!(".Laaa: nop; ret;") } //~ ERROR avoid using named labels + naked_asm!(".Laaa: nop; ret;") //~ ERROR avoid using named labels } pub fn normal() { fn _local1() {} - #[naked] + #[unsafe(naked)] pub extern "C" fn bbb() { fn _very_local() {} - unsafe { naked_asm!(".Lbbb: nop; ret;") } //~ ERROR avoid using named labels + naked_asm!(".Lbbb: nop; ret;") //~ ERROR avoid using named labels } fn _local2() {} @@ -219,8 +219,8 @@ fn closures() { }; || { - #[naked] - unsafe extern "C" fn _nested() { + #[unsafe(naked)] + extern "C" fn _nested() { naked_asm!("ret;"); } diff --git a/tests/ui/asm/named-asm-labels.stderr b/tests/ui/asm/named-asm-labels.stderr index 44ce358c62b..0120d4948d5 100644 --- a/tests/ui/asm/named-asm-labels.stderr +++ b/tests/ui/asm/named-asm-labels.stderr @@ -475,10 +475,10 @@ LL | #[warn(named_asm_labels)] | ^^^^^^^^^^^^^^^^ error: avoid using named labels in inline assembly - --> $DIR/named-asm-labels.rs:180:26 + --> $DIR/named-asm-labels.rs:180:17 | -LL | unsafe { naked_asm!(".Lfoo: mov rax, {}; ret;", "nop", const 1) } - | ^^^^^ +LL | naked_asm!(".Lfoo: mov rax, {}; ret;", "nop", const 1) + | ^^^^^ | = help: only local labels of the form `:` should be used in inline asm = note: see the asm section of Rust By Example for more information @@ -493,19 +493,19 @@ LL | unsafe { asm!(".Lbar: mov rax, {}; ret;", "nop", const 1, options(noret = note: see the asm section of Rust By Example for more information error: avoid using named labels in inline assembly - --> $DIR/named-asm-labels.rs:195:26 + --> $DIR/named-asm-labels.rs:195:17 | -LL | unsafe { naked_asm!(".Laaa: nop; ret;") } - | ^^^^^ +LL | naked_asm!(".Laaa: nop; ret;") + | ^^^^^ | = help: only local labels of the form `:` should be used in inline asm = note: see the asm section of Rust By Example for more information error: avoid using named labels in inline assembly - --> $DIR/named-asm-labels.rs:205:30 + --> $DIR/named-asm-labels.rs:205:21 | -LL | unsafe { naked_asm!(".Lbbb: nop; ret;") } - | ^^^^^ +LL | naked_asm!(".Lbbb: nop; ret;") + | ^^^^^ | = help: only local labels of the form `:` should be used in inline asm = note: see the asm section of Rust By Example for more information diff --git a/tests/ui/feature-gates/feature-gate-naked_functions.rs b/tests/ui/feature-gates/feature-gate-naked_functions.rs index 77a67e0696e..d940decd561 100644 --- a/tests/ui/feature-gates/feature-gate-naked_functions.rs +++ b/tests/ui/feature-gates/feature-gate-naked_functions.rs @@ -3,20 +3,18 @@ use std::arch::naked_asm; //~^ ERROR use of unstable library feature `naked_functions` -#[naked] +#[naked] //~ ERROR unsafe attribute used without unsafe //~^ ERROR the `#[naked]` attribute is an experimental feature extern "C" fn naked() { naked_asm!("") //~^ ERROR use of unstable library feature `naked_functions` - //~| ERROR: requires unsafe } -#[naked] +#[naked] //~ ERROR unsafe attribute used without unsafe //~^ ERROR the `#[naked]` attribute is an experimental feature extern "C" fn naked_2() -> isize { naked_asm!("") //~^ ERROR use of unstable library feature `naked_functions` - //~| ERROR: requires unsafe } fn main() {} diff --git a/tests/ui/feature-gates/feature-gate-naked_functions.stderr b/tests/ui/feature-gates/feature-gate-naked_functions.stderr index 9bfb9275bb2..ea765db7d94 100644 --- a/tests/ui/feature-gates/feature-gate-naked_functions.stderr +++ b/tests/ui/feature-gates/feature-gate-naked_functions.stderr @@ -9,7 +9,7 @@ LL | naked_asm!("") = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0658]: use of unstable library feature `naked_functions` - --> $DIR/feature-gate-naked_functions.rs:17:5 + --> $DIR/feature-gate-naked_functions.rs:16:5 | LL | naked_asm!("") | ^^^^^^^^^ @@ -18,6 +18,28 @@ LL | naked_asm!("") = help: add `#![feature(naked_functions)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date +error: unsafe attribute used without unsafe + --> $DIR/feature-gate-naked_functions.rs:6:3 + | +LL | #[naked] + | ^^^^^ usage of unsafe attribute + | +help: wrap the attribute in `unsafe(...)` + | +LL | #[unsafe(naked)] + | +++++++ + + +error: unsafe attribute used without unsafe + --> $DIR/feature-gate-naked_functions.rs:13:3 + | +LL | #[naked] + | ^^^^^ usage of unsafe attribute + | +help: wrap the attribute in `unsafe(...)` + | +LL | #[unsafe(naked)] + | +++++++ + + error[E0658]: the `#[naked]` attribute is an experimental feature --> $DIR/feature-gate-naked_functions.rs:6:1 | @@ -29,7 +51,7 @@ LL | #[naked] = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0658]: the `#[naked]` attribute is an experimental feature - --> $DIR/feature-gate-naked_functions.rs:14:1 + --> $DIR/feature-gate-naked_functions.rs:13:1 | LL | #[naked] | ^^^^^^^^ @@ -48,23 +70,6 @@ LL | use std::arch::naked_asm; = help: add `#![feature(naked_functions)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date -error[E0133]: use of inline assembly is unsafe and requires unsafe function or block - --> $DIR/feature-gate-naked_functions.rs:9:5 - | -LL | naked_asm!("") - | ^^^^^^^^^^^^^^ use of inline assembly - | - = note: inline assembly is entirely unchecked and can cause undefined behavior - -error[E0133]: use of inline assembly is unsafe and requires unsafe function or block - --> $DIR/feature-gate-naked_functions.rs:17:5 - | -LL | naked_asm!("") - | ^^^^^^^^^^^^^^ use of inline assembly - | - = note: inline assembly is entirely unchecked and can cause undefined behavior - error: aborting due to 7 previous errors -Some errors have detailed explanations: E0133, E0658. -For more information about an error, try `rustc --explain E0133`. +For more information about this error, try `rustc --explain E0658`. diff --git a/tests/ui/feature-gates/feature-gate-naked_functions_rustic_abi.rs b/tests/ui/feature-gates/feature-gate-naked_functions_rustic_abi.rs index c91d8339944..cc5b4f0e88b 100644 --- a/tests/ui/feature-gates/feature-gate-naked_functions_rustic_abi.rs +++ b/tests/ui/feature-gates/feature-gate-naked_functions_rustic_abi.rs @@ -5,19 +5,19 @@ use std::arch::naked_asm; -#[naked] +#[unsafe(naked)] pub unsafe fn rust_implicit() { //~^ ERROR `#[naked]` is currently unstable on `extern "Rust"` functions naked_asm!("ret"); } -#[naked] +#[unsafe(naked)] pub unsafe extern "Rust" fn rust_explicit() { //~^ ERROR `#[naked]` is currently unstable on `extern "Rust"` functions naked_asm!("ret"); } -#[naked] +#[unsafe(naked)] pub unsafe extern "rust-cold" fn rust_cold() { //~^ ERROR `#[naked]` is currently unstable on `extern "rust-cold"` functions naked_asm!("ret"); diff --git a/tests/ui/feature-gates/feature-gate-naked_functions_target_feature.rs b/tests/ui/feature-gates/feature-gate-naked_functions_target_feature.rs index 0d3af4c5fe0..b2e102f1db4 100644 --- a/tests/ui/feature-gates/feature-gate-naked_functions_target_feature.rs +++ b/tests/ui/feature-gates/feature-gate-naked_functions_target_feature.rs @@ -5,7 +5,7 @@ use std::arch::naked_asm; -#[naked] +#[unsafe(naked)] #[target_feature(enable = "avx2")] //~^ ERROR: `#[target_feature(/* ... */)]` is currently unstable on `#[naked]` functions extern "C" fn naked() { diff --git a/tests/ui/rfcs/rfc-2091-track-caller/error-with-naked.rs b/tests/ui/rfcs/rfc-2091-track-caller/error-with-naked.rs index 0e85515fd10..ce6d10bf33c 100644 --- a/tests/ui/rfcs/rfc-2091-track-caller/error-with-naked.rs +++ b/tests/ui/rfcs/rfc-2091-track-caller/error-with-naked.rs @@ -5,7 +5,7 @@ use std::arch::naked_asm; #[track_caller] //~ ERROR [E0736] //~^ ERROR `#[track_caller]` requires Rust ABI -#[naked] +#[unsafe(naked)] extern "C" fn f() { unsafe { naked_asm!(""); @@ -17,7 +17,7 @@ struct S; impl S { #[track_caller] //~ ERROR [E0736] //~^ ERROR `#[track_caller]` requires Rust ABI - #[naked] + #[unsafe(naked)] extern "C" fn g() { unsafe { naked_asm!(""); diff --git a/tests/ui/rfcs/rfc-2091-track-caller/error-with-naked.stderr b/tests/ui/rfcs/rfc-2091-track-caller/error-with-naked.stderr index 0625ed1183b..f89d94b67d8 100644 --- a/tests/ui/rfcs/rfc-2091-track-caller/error-with-naked.stderr +++ b/tests/ui/rfcs/rfc-2091-track-caller/error-with-naked.stderr @@ -1,20 +1,20 @@ -error[E0736]: attribute incompatible with `#[naked]` +error[E0736]: attribute incompatible with `#[unsafe(naked)]` --> $DIR/error-with-naked.rs:6:1 | LL | #[track_caller] - | ^^^^^^^^^^^^^^^ the `track_caller` attribute is incompatible with `#[naked]` + | ^^^^^^^^^^^^^^^ the `track_caller` attribute is incompatible with `#[unsafe(naked)]` LL | -LL | #[naked] - | -------- function marked with `#[naked]` here +LL | #[unsafe(naked)] + | ---------------- function marked with `#[unsafe(naked)]` here -error[E0736]: attribute incompatible with `#[naked]` +error[E0736]: attribute incompatible with `#[unsafe(naked)]` --> $DIR/error-with-naked.rs:18:5 | LL | #[track_caller] - | ^^^^^^^^^^^^^^^ the `track_caller` attribute is incompatible with `#[naked]` + | ^^^^^^^^^^^^^^^ the `track_caller` attribute is incompatible with `#[unsafe(naked)]` LL | -LL | #[naked] - | -------- function marked with `#[naked]` here +LL | #[unsafe(naked)] + | ---------------- function marked with `#[unsafe(naked)]` here error[E0737]: `#[track_caller]` requires Rust ABI --> $DIR/error-with-naked.rs:6:1 From e0d9244472b50631f07af8a425c1dbed65161d25 Mon Sep 17 00:00:00 2001 From: Thalia Archibald Date: Tue, 15 Apr 2025 05:29:38 -0700 Subject: [PATCH 35/39] Sort Unix env constants alphabetically by target_os They were roughly grouped into Linux, Apple, BSD, and everything else, roughly in alphabetical order. Alphabetically order them to make it easier to maintain and discard the Unix-specific groups to generalize it to all platforms. --- library/std/src/sys/pal/unix/env.rs | 292 ++++++++++++++-------------- 1 file changed, 147 insertions(+), 145 deletions(-) diff --git a/library/std/src/sys/pal/unix/env.rs b/library/std/src/sys/pal/unix/env.rs index c6609298f4b..1a5e23f06b8 100644 --- a/library/std/src/sys/pal/unix/env.rs +++ b/library/std/src/sys/pal/unix/env.rs @@ -1,106 +1,20 @@ -#[cfg(target_os = "linux")] +// Keep entries sorted alphabetically. + +#[cfg(target_os = "aix")] pub mod os { pub const FAMILY: &str = "unix"; - pub const OS: &str = "linux"; + pub const OS: &str = "aix"; pub const DLL_PREFIX: &str = "lib"; - pub const DLL_SUFFIX: &str = ".so"; - pub const DLL_EXTENSION: &str = "so"; + pub const DLL_SUFFIX: &str = ".a"; + pub const DLL_EXTENSION: &str = "a"; pub const EXE_SUFFIX: &str = ""; pub const EXE_EXTENSION: &str = ""; } -#[cfg(target_os = "macos")] +#[cfg(target_os = "android")] pub mod os { pub const FAMILY: &str = "unix"; - pub const OS: &str = "macos"; - pub const DLL_PREFIX: &str = "lib"; - pub const DLL_SUFFIX: &str = ".dylib"; - pub const DLL_EXTENSION: &str = "dylib"; - pub const EXE_SUFFIX: &str = ""; - pub const EXE_EXTENSION: &str = ""; -} - -#[cfg(target_os = "ios")] -pub mod os { - pub const FAMILY: &str = "unix"; - pub const OS: &str = "ios"; - pub const DLL_PREFIX: &str = "lib"; - pub const DLL_SUFFIX: &str = ".dylib"; - pub const DLL_EXTENSION: &str = "dylib"; - pub const EXE_SUFFIX: &str = ""; - pub const EXE_EXTENSION: &str = ""; -} - -#[cfg(target_os = "tvos")] -pub mod os { - pub const FAMILY: &str = "unix"; - pub const OS: &str = "tvos"; - pub const DLL_PREFIX: &str = "lib"; - pub const DLL_SUFFIX: &str = ".dylib"; - pub const DLL_EXTENSION: &str = "dylib"; - pub const EXE_SUFFIX: &str = ""; - pub const EXE_EXTENSION: &str = ""; -} - -#[cfg(target_os = "watchos")] -pub mod os { - pub const FAMILY: &str = "unix"; - pub const OS: &str = "watchos"; - pub const DLL_PREFIX: &str = "lib"; - pub const DLL_SUFFIX: &str = ".dylib"; - pub const DLL_EXTENSION: &str = "dylib"; - pub const EXE_SUFFIX: &str = ""; - pub const EXE_EXTENSION: &str = ""; -} - -#[cfg(target_os = "visionos")] -pub mod os { - pub const FAMILY: &str = "unix"; - pub const OS: &str = "visionos"; - pub const DLL_PREFIX: &str = "lib"; - pub const DLL_SUFFIX: &str = ".dylib"; - pub const DLL_EXTENSION: &str = "dylib"; - pub const EXE_SUFFIX: &str = ""; - pub const EXE_EXTENSION: &str = ""; -} - -#[cfg(target_os = "freebsd")] -pub mod os { - pub const FAMILY: &str = "unix"; - pub const OS: &str = "freebsd"; - pub const DLL_PREFIX: &str = "lib"; - pub const DLL_SUFFIX: &str = ".so"; - pub const DLL_EXTENSION: &str = "so"; - pub const EXE_SUFFIX: &str = ""; - pub const EXE_EXTENSION: &str = ""; -} - -#[cfg(target_os = "dragonfly")] -pub mod os { - pub const FAMILY: &str = "unix"; - pub const OS: &str = "dragonfly"; - pub const DLL_PREFIX: &str = "lib"; - pub const DLL_SUFFIX: &str = ".so"; - pub const DLL_EXTENSION: &str = "so"; - pub const EXE_SUFFIX: &str = ""; - pub const EXE_EXTENSION: &str = ""; -} - -#[cfg(target_os = "netbsd")] -pub mod os { - pub const FAMILY: &str = "unix"; - pub const OS: &str = "netbsd"; - pub const DLL_PREFIX: &str = "lib"; - pub const DLL_SUFFIX: &str = ".so"; - pub const DLL_EXTENSION: &str = "so"; - pub const EXE_SUFFIX: &str = ""; - pub const EXE_EXTENSION: &str = ""; -} - -#[cfg(target_os = "openbsd")] -pub mod os { - pub const FAMILY: &str = "unix"; - pub const OS: &str = "openbsd"; + pub const OS: &str = "android"; pub const DLL_PREFIX: &str = "lib"; pub const DLL_SUFFIX: &str = ".so"; pub const DLL_EXTENSION: &str = "so"; @@ -119,10 +33,10 @@ pub mod os { pub const EXE_EXTENSION: &str = "exe"; } -#[cfg(target_os = "android")] +#[cfg(target_os = "dragonfly")] pub mod os { pub const FAMILY: &str = "unix"; - pub const OS: &str = "android"; + pub const OS: &str = "dragonfly"; pub const DLL_PREFIX: &str = "lib"; pub const DLL_SUFFIX: &str = ".so"; pub const DLL_EXTENSION: &str = "so"; @@ -130,10 +44,21 @@ pub mod os { pub const EXE_EXTENSION: &str = ""; } -#[cfg(target_os = "solaris")] +#[cfg(all(target_os = "emscripten", target_arch = "wasm32"))] pub mod os { pub const FAMILY: &str = "unix"; - pub const OS: &str = "solaris"; + pub const OS: &str = "emscripten"; + pub const DLL_PREFIX: &str = "lib"; + pub const DLL_SUFFIX: &str = ".so"; + pub const DLL_EXTENSION: &str = "so"; + pub const EXE_SUFFIX: &str = ".js"; + pub const EXE_EXTENSION: &str = "js"; +} + +#[cfg(target_os = "espidf")] +pub mod os { + pub const FAMILY: &str = "unix"; + pub const OS: &str = "espidf"; pub const DLL_PREFIX: &str = "lib"; pub const DLL_SUFFIX: &str = ".so"; pub const DLL_EXTENSION: &str = "so"; @@ -141,10 +66,21 @@ pub mod os { pub const EXE_EXTENSION: &str = ""; } -#[cfg(target_os = "illumos")] +#[cfg(target_os = "freebsd")] pub mod os { pub const FAMILY: &str = "unix"; - pub const OS: &str = "illumos"; + pub const OS: &str = "freebsd"; + pub const DLL_PREFIX: &str = "lib"; + pub const DLL_SUFFIX: &str = ".so"; + pub const DLL_EXTENSION: &str = "so"; + pub const EXE_SUFFIX: &str = ""; + pub const EXE_EXTENSION: &str = ""; +} + +#[cfg(target_os = "fuchsia")] +pub mod os { + pub const FAMILY: &str = "unix"; + pub const OS: &str = "fuchsia"; pub const DLL_PREFIX: &str = "lib"; pub const DLL_SUFFIX: &str = ".so"; pub const DLL_EXTENSION: &str = "so"; @@ -185,35 +121,24 @@ pub mod os { pub const EXE_EXTENSION: &str = ""; } -#[cfg(target_os = "vita")] +#[cfg(target_os = "illumos")] pub mod os { pub const FAMILY: &str = "unix"; - pub const OS: &str = "vita"; + pub const OS: &str = "illumos"; pub const DLL_PREFIX: &str = "lib"; pub const DLL_SUFFIX: &str = ".so"; pub const DLL_EXTENSION: &str = "so"; - pub const EXE_SUFFIX: &str = ".elf"; - pub const EXE_EXTENSION: &str = "elf"; + pub const EXE_SUFFIX: &str = ""; + pub const EXE_EXTENSION: &str = ""; } -#[cfg(all(target_os = "emscripten", target_arch = "wasm32"))] +#[cfg(target_os = "ios")] pub mod os { pub const FAMILY: &str = "unix"; - pub const OS: &str = "emscripten"; + pub const OS: &str = "ios"; pub const DLL_PREFIX: &str = "lib"; - pub const DLL_SUFFIX: &str = ".so"; - pub const DLL_EXTENSION: &str = "so"; - pub const EXE_SUFFIX: &str = ".js"; - pub const EXE_EXTENSION: &str = "js"; -} - -#[cfg(target_os = "fuchsia")] -pub mod os { - pub const FAMILY: &str = "unix"; - pub const OS: &str = "fuchsia"; - pub const DLL_PREFIX: &str = "lib"; - pub const DLL_SUFFIX: &str = ".so"; - pub const DLL_EXTENSION: &str = "so"; + pub const DLL_SUFFIX: &str = ".dylib"; + pub const DLL_EXTENSION: &str = "dylib"; pub const EXE_SUFFIX: &str = ""; pub const EXE_EXTENSION: &str = ""; } @@ -229,6 +154,39 @@ pub mod os { pub const EXE_EXTENSION: &str = ""; } +#[cfg(target_os = "linux")] +pub mod os { + pub const FAMILY: &str = "unix"; + pub const OS: &str = "linux"; + pub const DLL_PREFIX: &str = "lib"; + pub const DLL_SUFFIX: &str = ".so"; + pub const DLL_EXTENSION: &str = "so"; + pub const EXE_SUFFIX: &str = ""; + pub const EXE_EXTENSION: &str = ""; +} + +#[cfg(target_os = "macos")] +pub mod os { + pub const FAMILY: &str = "unix"; + pub const OS: &str = "macos"; + pub const DLL_PREFIX: &str = "lib"; + pub const DLL_SUFFIX: &str = ".dylib"; + pub const DLL_EXTENSION: &str = "dylib"; + pub const EXE_SUFFIX: &str = ""; + pub const EXE_EXTENSION: &str = ""; +} + +#[cfg(target_os = "netbsd")] +pub mod os { + pub const FAMILY: &str = "unix"; + pub const OS: &str = "netbsd"; + pub const DLL_PREFIX: &str = "lib"; + pub const DLL_SUFFIX: &str = ".so"; + pub const DLL_EXTENSION: &str = "so"; + pub const EXE_SUFFIX: &str = ""; + pub const EXE_EXTENSION: &str = ""; +} + #[cfg(target_os = "nto")] pub mod os { pub const FAMILY: &str = "unix"; @@ -240,6 +198,28 @@ pub mod os { pub const EXE_EXTENSION: &str = ""; } +#[cfg(target_os = "nuttx")] +pub mod os { + pub const FAMILY: &str = "unix"; + pub const OS: &str = "nuttx"; + pub const DLL_PREFIX: &str = "lib"; + pub const DLL_SUFFIX: &str = ".so"; + pub const DLL_EXTENSION: &str = "so"; + pub const EXE_SUFFIX: &str = ""; + pub const EXE_EXTENSION: &str = ""; +} + +#[cfg(target_os = "openbsd")] +pub mod os { + pub const FAMILY: &str = "unix"; + pub const OS: &str = "openbsd"; + pub const DLL_PREFIX: &str = "lib"; + pub const DLL_SUFFIX: &str = ".so"; + pub const DLL_EXTENSION: &str = "so"; + pub const EXE_SUFFIX: &str = ""; + pub const EXE_EXTENSION: &str = ""; +} + #[cfg(target_os = "redox")] pub mod os { pub const FAMILY: &str = "unix"; @@ -262,6 +242,50 @@ pub mod os { pub const EXE_EXTENSION: &str = ""; } +#[cfg(target_os = "solaris")] +pub mod os { + pub const FAMILY: &str = "unix"; + pub const OS: &str = "solaris"; + pub const DLL_PREFIX: &str = "lib"; + pub const DLL_SUFFIX: &str = ".so"; + pub const DLL_EXTENSION: &str = "so"; + pub const EXE_SUFFIX: &str = ""; + pub const EXE_EXTENSION: &str = ""; +} + +#[cfg(target_os = "tvos")] +pub mod os { + pub const FAMILY: &str = "unix"; + pub const OS: &str = "tvos"; + pub const DLL_PREFIX: &str = "lib"; + pub const DLL_SUFFIX: &str = ".dylib"; + pub const DLL_EXTENSION: &str = "dylib"; + pub const EXE_SUFFIX: &str = ""; + pub const EXE_EXTENSION: &str = ""; +} + +#[cfg(target_os = "visionos")] +pub mod os { + pub const FAMILY: &str = "unix"; + pub const OS: &str = "visionos"; + pub const DLL_PREFIX: &str = "lib"; + pub const DLL_SUFFIX: &str = ".dylib"; + pub const DLL_EXTENSION: &str = "dylib"; + pub const EXE_SUFFIX: &str = ""; + pub const EXE_EXTENSION: &str = ""; +} + +#[cfg(target_os = "vita")] +pub mod os { + pub const FAMILY: &str = "unix"; + pub const OS: &str = "vita"; + pub const DLL_PREFIX: &str = "lib"; + pub const DLL_SUFFIX: &str = ".so"; + pub const DLL_EXTENSION: &str = "so"; + pub const EXE_SUFFIX: &str = ".elf"; + pub const EXE_EXTENSION: &str = "elf"; +} + #[cfg(target_os = "vxworks")] pub mod os { pub const FAMILY: &str = "unix"; @@ -273,35 +297,13 @@ pub mod os { pub const EXE_EXTENSION: &str = ""; } -#[cfg(target_os = "espidf")] +#[cfg(target_os = "watchos")] pub mod os { pub const FAMILY: &str = "unix"; - pub const OS: &str = "espidf"; + pub const OS: &str = "watchos"; pub const DLL_PREFIX: &str = "lib"; - pub const DLL_SUFFIX: &str = ".so"; - pub const DLL_EXTENSION: &str = "so"; - pub const EXE_SUFFIX: &str = ""; - pub const EXE_EXTENSION: &str = ""; -} - -#[cfg(target_os = "aix")] -pub mod os { - pub const FAMILY: &str = "unix"; - pub const OS: &str = "aix"; - pub const DLL_PREFIX: &str = "lib"; - pub const DLL_SUFFIX: &str = ".a"; - pub const DLL_EXTENSION: &str = "a"; - pub const EXE_SUFFIX: &str = ""; - pub const EXE_EXTENSION: &str = ""; -} - -#[cfg(target_os = "nuttx")] -pub mod os { - pub const FAMILY: &str = "unix"; - pub const OS: &str = "nuttx"; - pub const DLL_PREFIX: &str = "lib"; - pub const DLL_SUFFIX: &str = ".so"; - pub const DLL_EXTENSION: &str = "so"; + pub const DLL_SUFFIX: &str = ".dylib"; + pub const DLL_EXTENSION: &str = "dylib"; pub const EXE_SUFFIX: &str = ""; pub const EXE_EXTENSION: &str = ""; } From 37712cc01604633fec6aac9bc720210207fa4b54 Mon Sep 17 00:00:00 2001 From: Thalia Archibald Date: Tue, 15 Apr 2025 05:48:27 -0700 Subject: [PATCH 36/39] Combine env consts into std::sys::env_consts --- library/std/src/env.rs | 2 +- .../sys/{pal/unix/env.rs => env_consts.rs} | 70 ++++++++++++++++++- library/std/src/sys/mod.rs | 1 + library/std/src/sys/pal/hermit/env.rs | 9 --- library/std/src/sys/pal/hermit/mod.rs | 1 - library/std/src/sys/pal/sgx/env.rs | 9 --- library/std/src/sys/pal/sgx/mod.rs | 1 - library/std/src/sys/pal/solid/env.rs | 9 --- library/std/src/sys/pal/solid/mod.rs | 1 - library/std/src/sys/pal/teeos/mod.rs | 3 - library/std/src/sys/pal/trusty/mod.rs | 2 - library/std/src/sys/pal/uefi/env.rs | 9 --- library/std/src/sys/pal/uefi/mod.rs | 1 - library/std/src/sys/pal/unix/mod.rs | 1 - library/std/src/sys/pal/unsupported/env.rs | 9 --- library/std/src/sys/pal/unsupported/mod.rs | 1 - library/std/src/sys/pal/wasi/env.rs | 11 --- library/std/src/sys/pal/wasi/mod.rs | 1 - library/std/src/sys/pal/wasip2/mod.rs | 2 - library/std/src/sys/pal/wasm/env.rs | 9 --- library/std/src/sys/pal/wasm/mod.rs | 1 - library/std/src/sys/pal/windows/env.rs | 9 --- library/std/src/sys/pal/windows/mod.rs | 1 - library/std/src/sys/pal/xous/mod.rs | 2 - library/std/src/sys/pal/zkvm/mod.rs | 1 - 25 files changed, 71 insertions(+), 95 deletions(-) rename library/std/src/sys/{pal/unix/env.rs => env_consts.rs} (81%) delete mode 100644 library/std/src/sys/pal/hermit/env.rs delete mode 100644 library/std/src/sys/pal/sgx/env.rs delete mode 100644 library/std/src/sys/pal/solid/env.rs delete mode 100644 library/std/src/sys/pal/uefi/env.rs delete mode 100644 library/std/src/sys/pal/unsupported/env.rs delete mode 100644 library/std/src/sys/pal/wasi/env.rs delete mode 100644 library/std/src/sys/pal/wasm/env.rs delete mode 100644 library/std/src/sys/pal/windows/env.rs diff --git a/library/std/src/env.rs b/library/std/src/env.rs index 05bd4345ea8..c84a72c4fad 100644 --- a/library/std/src/env.rs +++ b/library/std/src/env.rs @@ -950,7 +950,7 @@ impl fmt::Debug for ArgsOs { /// Constants associated with the current target #[stable(feature = "env", since = "1.0.0")] pub mod consts { - use crate::sys::env::os; + use crate::sys::env_consts::os; /// A string describing the architecture of the CPU that is currently in use. /// An example value may be: `"x86"`, `"arm"` or `"riscv64"`. diff --git a/library/std/src/sys/pal/unix/env.rs b/library/std/src/sys/env_consts.rs similarity index 81% rename from library/std/src/sys/pal/unix/env.rs rename to library/std/src/sys/env_consts.rs index 1a5e23f06b8..f439aa5a905 100644 --- a/library/std/src/sys/pal/unix/env.rs +++ b/library/std/src/sys/env_consts.rs @@ -1,3 +1,5 @@ +//! Constants associated with each target. + // Keep entries sorted alphabetically. #[cfg(target_os = "aix")] @@ -44,7 +46,7 @@ pub mod os { pub const EXE_EXTENSION: &str = ""; } -#[cfg(all(target_os = "emscripten", target_arch = "wasm32"))] +#[cfg(target_os = "emscripten")] pub mod os { pub const FAMILY: &str = "unix"; pub const OS: &str = "emscripten"; @@ -99,6 +101,17 @@ pub mod os { pub const EXE_EXTENSION: &str = ""; } +#[cfg(target_os = "hermit")] +pub mod os { + pub const FAMILY: &str = ""; + pub const OS: &str = "hermit"; + pub const DLL_PREFIX: &str = ""; + pub const DLL_SUFFIX: &str = ""; + pub const DLL_EXTENSION: &str = ""; + pub const EXE_SUFFIX: &str = ""; + pub const EXE_EXTENSION: &str = ""; +} + #[cfg(target_os = "horizon")] pub mod os { pub const FAMILY: &str = "unix"; @@ -242,6 +255,17 @@ pub mod os { pub const EXE_EXTENSION: &str = ""; } +#[cfg(all(target_vendor = "fortanix", target_env = "sgx"))] +pub mod os { + pub const FAMILY: &str = ""; + pub const OS: &str = ""; + pub const DLL_PREFIX: &str = ""; + pub const DLL_SUFFIX: &str = ".sgxs"; + pub const DLL_EXTENSION: &str = "sgxs"; + pub const EXE_SUFFIX: &str = ".sgxs"; + pub const EXE_EXTENSION: &str = "sgxs"; +} + #[cfg(target_os = "solaris")] pub mod os { pub const FAMILY: &str = "unix"; @@ -253,6 +277,17 @@ pub mod os { pub const EXE_EXTENSION: &str = ""; } +#[cfg(target_os = "solid_asp3")] +pub mod os { + pub const FAMILY: &str = "itron"; + pub const OS: &str = "solid"; + pub const DLL_PREFIX: &str = ""; + pub const DLL_SUFFIX: &str = ".so"; + pub const DLL_EXTENSION: &str = "so"; + pub const EXE_SUFFIX: &str = ""; + pub const EXE_EXTENSION: &str = ""; +} + #[cfg(target_os = "tvos")] pub mod os { pub const FAMILY: &str = "unix"; @@ -264,6 +299,17 @@ pub mod os { pub const EXE_EXTENSION: &str = ""; } +#[cfg(target_os = "uefi")] +pub mod os { + pub const FAMILY: &str = ""; + pub const OS: &str = "uefi"; + pub const DLL_PREFIX: &str = ""; + pub const DLL_SUFFIX: &str = ""; + pub const DLL_EXTENSION: &str = ""; + pub const EXE_SUFFIX: &str = ".efi"; + pub const EXE_EXTENSION: &str = "efi"; +} + #[cfg(target_os = "visionos")] pub mod os { pub const FAMILY: &str = "unix"; @@ -297,6 +343,17 @@ pub mod os { pub const EXE_EXTENSION: &str = ""; } +#[cfg(all(target_family = "wasm", not(any(target_os = "emscripten", target_os = "linux"))))] +pub mod os { + pub const FAMILY: &str = ""; + pub const OS: &str = ""; + pub const DLL_PREFIX: &str = ""; + pub const DLL_SUFFIX: &str = ".wasm"; + pub const DLL_EXTENSION: &str = "wasm"; + pub const EXE_SUFFIX: &str = ".wasm"; + pub const EXE_EXTENSION: &str = "wasm"; +} + #[cfg(target_os = "watchos")] pub mod os { pub const FAMILY: &str = "unix"; @@ -307,3 +364,14 @@ pub mod os { pub const EXE_SUFFIX: &str = ""; pub const EXE_EXTENSION: &str = ""; } + +#[cfg(target_os = "windows")] +pub mod os { + pub const FAMILY: &str = "windows"; + pub const OS: &str = "windows"; + pub const DLL_PREFIX: &str = ""; + pub const DLL_SUFFIX: &str = ".dll"; + pub const DLL_EXTENSION: &str = "dll"; + pub const EXE_SUFFIX: &str = ".exe"; + pub const EXE_EXTENSION: &str = "exe"; +} diff --git a/library/std/src/sys/mod.rs b/library/std/src/sys/mod.rs index bc4bf11cb74..e7b631999e0 100644 --- a/library/std/src/sys/mod.rs +++ b/library/std/src/sys/mod.rs @@ -12,6 +12,7 @@ pub mod anonymous_pipe; pub mod args; pub mod backtrace; pub mod cmath; +pub mod env_consts; pub mod exit_guard; pub mod fd; pub mod fs; diff --git a/library/std/src/sys/pal/hermit/env.rs b/library/std/src/sys/pal/hermit/env.rs deleted file mode 100644 index 7a0fcb31ef2..00000000000 --- a/library/std/src/sys/pal/hermit/env.rs +++ /dev/null @@ -1,9 +0,0 @@ -pub mod os { - pub const FAMILY: &str = ""; - pub const OS: &str = "hermit"; - pub const DLL_PREFIX: &str = ""; - pub const DLL_SUFFIX: &str = ""; - pub const DLL_EXTENSION: &str = ""; - pub const EXE_SUFFIX: &str = ""; - pub const EXE_EXTENSION: &str = ""; -} diff --git a/library/std/src/sys/pal/hermit/mod.rs b/library/std/src/sys/pal/hermit/mod.rs index 821836824e2..70636760a83 100644 --- a/library/std/src/sys/pal/hermit/mod.rs +++ b/library/std/src/sys/pal/hermit/mod.rs @@ -18,7 +18,6 @@ use crate::os::raw::c_char; -pub mod env; pub mod futex; pub mod os; #[path = "../unsupported/pipe.rs"] diff --git a/library/std/src/sys/pal/sgx/env.rs b/library/std/src/sys/pal/sgx/env.rs deleted file mode 100644 index 8043b7c5213..00000000000 --- a/library/std/src/sys/pal/sgx/env.rs +++ /dev/null @@ -1,9 +0,0 @@ -pub mod os { - pub const FAMILY: &str = ""; - pub const OS: &str = ""; - pub const DLL_PREFIX: &str = ""; - pub const DLL_SUFFIX: &str = ".sgxs"; - pub const DLL_EXTENSION: &str = "sgxs"; - pub const EXE_SUFFIX: &str = ".sgxs"; - pub const EXE_EXTENSION: &str = "sgxs"; -} diff --git a/library/std/src/sys/pal/sgx/mod.rs b/library/std/src/sys/pal/sgx/mod.rs index 8a87e7a7ae1..99735947e2c 100644 --- a/library/std/src/sys/pal/sgx/mod.rs +++ b/library/std/src/sys/pal/sgx/mod.rs @@ -9,7 +9,6 @@ use crate::io::ErrorKind; use crate::sync::atomic::{AtomicBool, Ordering}; pub mod abi; -pub mod env; mod libunwind_integration; pub mod os; #[path = "../unsupported/pipe.rs"] diff --git a/library/std/src/sys/pal/solid/env.rs b/library/std/src/sys/pal/solid/env.rs deleted file mode 100644 index 6855c113b28..00000000000 --- a/library/std/src/sys/pal/solid/env.rs +++ /dev/null @@ -1,9 +0,0 @@ -pub mod os { - pub const FAMILY: &str = "itron"; - pub const OS: &str = "solid"; - pub const DLL_PREFIX: &str = ""; - pub const DLL_SUFFIX: &str = ".so"; - pub const DLL_EXTENSION: &str = "so"; - pub const EXE_SUFFIX: &str = ""; - pub const EXE_EXTENSION: &str = ""; -} diff --git a/library/std/src/sys/pal/solid/mod.rs b/library/std/src/sys/pal/solid/mod.rs index c41dc848a1b..0011cf256df 100644 --- a/library/std/src/sys/pal/solid/mod.rs +++ b/library/std/src/sys/pal/solid/mod.rs @@ -16,7 +16,6 @@ pub mod itron { use super::unsupported; } -pub mod env; // `error` is `pub(crate)` so that it can be accessed by `itron/error.rs` as // `crate::sys::error` pub(crate) mod error; diff --git a/library/std/src/sys/pal/teeos/mod.rs b/library/std/src/sys/pal/teeos/mod.rs index b8095cec3e9..c7b17777258 100644 --- a/library/std/src/sys/pal/teeos/mod.rs +++ b/library/std/src/sys/pal/teeos/mod.rs @@ -6,9 +6,6 @@ #![allow(unused_variables)] #![allow(dead_code)] -#[path = "../unsupported/env.rs"] -pub mod env; -//pub mod fd; pub mod os; #[path = "../unsupported/pipe.rs"] pub mod pipe; diff --git a/library/std/src/sys/pal/trusty/mod.rs b/library/std/src/sys/pal/trusty/mod.rs index 04e6b4c8186..275f6062463 100644 --- a/library/std/src/sys/pal/trusty/mod.rs +++ b/library/std/src/sys/pal/trusty/mod.rs @@ -3,8 +3,6 @@ #[path = "../unsupported/common.rs"] #[deny(unsafe_op_in_unsafe_fn)] mod common; -#[path = "../unsupported/env.rs"] -pub mod env; #[path = "../unsupported/os.rs"] pub mod os; #[path = "../unsupported/pipe.rs"] diff --git a/library/std/src/sys/pal/uefi/env.rs b/library/std/src/sys/pal/uefi/env.rs deleted file mode 100644 index c106d5fed3e..00000000000 --- a/library/std/src/sys/pal/uefi/env.rs +++ /dev/null @@ -1,9 +0,0 @@ -pub mod os { - pub const FAMILY: &str = ""; - pub const OS: &str = "uefi"; - pub const DLL_PREFIX: &str = ""; - pub const DLL_SUFFIX: &str = ""; - pub const DLL_EXTENSION: &str = ""; - pub const EXE_SUFFIX: &str = ".efi"; - pub const EXE_EXTENSION: &str = "efi"; -} diff --git a/library/std/src/sys/pal/uefi/mod.rs b/library/std/src/sys/pal/uefi/mod.rs index cd901f48b76..bd6a36021f4 100644 --- a/library/std/src/sys/pal/uefi/mod.rs +++ b/library/std/src/sys/pal/uefi/mod.rs @@ -13,7 +13,6 @@ //! [`OsString`]: crate::ffi::OsString #![forbid(unsafe_op_in_unsafe_fn)] -pub mod env; pub mod helpers; pub mod os; #[path = "../unsupported/pipe.rs"] diff --git a/library/std/src/sys/pal/unix/mod.rs b/library/std/src/sys/pal/unix/mod.rs index 3a790d9c868..a4702ae1b18 100644 --- a/library/std/src/sys/pal/unix/mod.rs +++ b/library/std/src/sys/pal/unix/mod.rs @@ -6,7 +6,6 @@ use crate::io::ErrorKind; #[macro_use] pub mod weak; -pub mod env; #[cfg(target_os = "fuchsia")] pub mod fuchsia; pub mod futex; diff --git a/library/std/src/sys/pal/unsupported/env.rs b/library/std/src/sys/pal/unsupported/env.rs deleted file mode 100644 index d2efec506c5..00000000000 --- a/library/std/src/sys/pal/unsupported/env.rs +++ /dev/null @@ -1,9 +0,0 @@ -pub mod os { - pub const FAMILY: &str = ""; - pub const OS: &str = ""; - pub const DLL_PREFIX: &str = ""; - pub const DLL_SUFFIX: &str = ""; - pub const DLL_EXTENSION: &str = ""; - pub const EXE_SUFFIX: &str = ""; - pub const EXE_EXTENSION: &str = ""; -} diff --git a/library/std/src/sys/pal/unsupported/mod.rs b/library/std/src/sys/pal/unsupported/mod.rs index dea42a95dcc..5e3295b1331 100644 --- a/library/std/src/sys/pal/unsupported/mod.rs +++ b/library/std/src/sys/pal/unsupported/mod.rs @@ -1,6 +1,5 @@ #![deny(unsafe_op_in_unsafe_fn)] -pub mod env; pub mod os; pub mod pipe; pub mod thread; diff --git a/library/std/src/sys/pal/wasi/env.rs b/library/std/src/sys/pal/wasi/env.rs deleted file mode 100644 index 8d444982673..00000000000 --- a/library/std/src/sys/pal/wasi/env.rs +++ /dev/null @@ -1,11 +0,0 @@ -#![forbid(unsafe_op_in_unsafe_fn)] - -pub mod os { - pub const FAMILY: &str = ""; - pub const OS: &str = ""; - pub const DLL_PREFIX: &str = ""; - pub const DLL_SUFFIX: &str = ".wasm"; - pub const DLL_EXTENSION: &str = "wasm"; - pub const EXE_SUFFIX: &str = ".wasm"; - pub const EXE_EXTENSION: &str = "wasm"; -} diff --git a/library/std/src/sys/pal/wasi/mod.rs b/library/std/src/sys/pal/wasi/mod.rs index 4ea42b1082b..61dd1c3f98b 100644 --- a/library/std/src/sys/pal/wasi/mod.rs +++ b/library/std/src/sys/pal/wasi/mod.rs @@ -13,7 +13,6 @@ //! compiling for wasm. That way it's a compile time error for something that's //! guaranteed to be a runtime error! -pub mod env; #[allow(unused)] #[path = "../wasm/atomics/futex.rs"] pub mod futex; diff --git a/library/std/src/sys/pal/wasip2/mod.rs b/library/std/src/sys/pal/wasip2/mod.rs index 6445bf2cc0d..47fe3221c90 100644 --- a/library/std/src/sys/pal/wasip2/mod.rs +++ b/library/std/src/sys/pal/wasip2/mod.rs @@ -6,8 +6,6 @@ //! To begin with, this target mirrors the wasi target 1 to 1, but over //! time this will change significantly. -#[path = "../wasi/env.rs"] -pub mod env; #[allow(unused)] #[path = "../wasm/atomics/futex.rs"] pub mod futex; diff --git a/library/std/src/sys/pal/wasm/env.rs b/library/std/src/sys/pal/wasm/env.rs deleted file mode 100644 index 730e356d7fe..00000000000 --- a/library/std/src/sys/pal/wasm/env.rs +++ /dev/null @@ -1,9 +0,0 @@ -pub mod os { - pub const FAMILY: &str = ""; - pub const OS: &str = ""; - pub const DLL_PREFIX: &str = ""; - pub const DLL_SUFFIX: &str = ".wasm"; - pub const DLL_EXTENSION: &str = "wasm"; - pub const EXE_SUFFIX: &str = ".wasm"; - pub const EXE_EXTENSION: &str = "wasm"; -} diff --git a/library/std/src/sys/pal/wasm/mod.rs b/library/std/src/sys/pal/wasm/mod.rs index af370020d96..37cb46a8f6b 100644 --- a/library/std/src/sys/pal/wasm/mod.rs +++ b/library/std/src/sys/pal/wasm/mod.rs @@ -16,7 +16,6 @@ #![deny(unsafe_op_in_unsafe_fn)] -pub mod env; #[path = "../unsupported/os.rs"] pub mod os; #[path = "../unsupported/pipe.rs"] diff --git a/library/std/src/sys/pal/windows/env.rs b/library/std/src/sys/pal/windows/env.rs deleted file mode 100644 index f0a99d6200c..00000000000 --- a/library/std/src/sys/pal/windows/env.rs +++ /dev/null @@ -1,9 +0,0 @@ -pub mod os { - pub const FAMILY: &str = "windows"; - pub const OS: &str = "windows"; - pub const DLL_PREFIX: &str = ""; - pub const DLL_SUFFIX: &str = ".dll"; - pub const DLL_EXTENSION: &str = "dll"; - pub const EXE_SUFFIX: &str = ".exe"; - pub const EXE_EXTENSION: &str = "exe"; -} diff --git a/library/std/src/sys/pal/windows/mod.rs b/library/std/src/sys/pal/windows/mod.rs index 3c0a5c2de26..4f18c4009ab 100644 --- a/library/std/src/sys/pal/windows/mod.rs +++ b/library/std/src/sys/pal/windows/mod.rs @@ -15,7 +15,6 @@ pub mod compat; pub mod api; pub mod c; -pub mod env; #[cfg(not(target_vendor = "win7"))] pub mod futex; pub mod handle; diff --git a/library/std/src/sys/pal/xous/mod.rs b/library/std/src/sys/pal/xous/mod.rs index 4f652d3f130..383d031ed43 100644 --- a/library/std/src/sys/pal/xous/mod.rs +++ b/library/std/src/sys/pal/xous/mod.rs @@ -1,7 +1,5 @@ #![forbid(unsafe_op_in_unsafe_fn)] -#[path = "../unsupported/env.rs"] -pub mod env; pub mod os; #[path = "../unsupported/pipe.rs"] pub mod pipe; diff --git a/library/std/src/sys/pal/zkvm/mod.rs b/library/std/src/sys/pal/zkvm/mod.rs index ebd7b036779..e1efa240685 100644 --- a/library/std/src/sys/pal/zkvm/mod.rs +++ b/library/std/src/sys/pal/zkvm/mod.rs @@ -11,7 +11,6 @@ pub const WORD_SIZE: usize = size_of::(); pub mod abi; -pub mod env; pub mod os; #[path = "../unsupported/pipe.rs"] pub mod pipe; From 670ff84d1c70146a2556b41650d3e417143c6c60 Mon Sep 17 00:00:00 2001 From: Thalia Archibald Date: Tue, 15 Apr 2025 06:42:39 -0700 Subject: [PATCH 37/39] Handle unsupported fallback --- library/std/src/sys/env_consts.rs | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/library/std/src/sys/env_consts.rs b/library/std/src/sys/env_consts.rs index f439aa5a905..018d7954db2 100644 --- a/library/std/src/sys/env_consts.rs +++ b/library/std/src/sys/env_consts.rs @@ -1,6 +1,19 @@ //! Constants associated with each target. -// Keep entries sorted alphabetically. +// Replaces the #[else] gate with #[cfg(not(any(…)))] of all the other gates. +// This ensures that they must be mutually exclusive and do not have precedence +// like cfg_if!. +macro cfg_unordered( + $(#[cfg($cfg:meta)] $os:item)* + #[else] $fallback:item +) { + $(#[cfg($cfg)] $os)* + #[cfg(not(any($($cfg),*)))] $fallback +} + +// Keep entries sorted alphabetically and mutually exclusive. + +cfg_unordered! { #[cfg(target_os = "aix")] pub mod os { @@ -375,3 +388,17 @@ pub mod os { pub const EXE_SUFFIX: &str = ".exe"; pub const EXE_EXTENSION: &str = "exe"; } + +// The fallback when none of the other gates match. +#[else] +pub mod os { + pub const FAMILY: &str = ""; + pub const OS: &str = ""; + pub const DLL_PREFIX: &str = ""; + pub const DLL_SUFFIX: &str = ""; + pub const DLL_EXTENSION: &str = ""; + pub const EXE_SUFFIX: &str = ""; + pub const EXE_EXTENSION: &str = ""; +} + +} From 93fa96cfba126d542ebd33624e5f774d397dbf3f Mon Sep 17 00:00:00 2001 From: Thalia Archibald Date: Fri, 18 Apr 2025 16:45:26 -0700 Subject: [PATCH 38/39] Use struct update syntax for some TargetOptions --- compiler/rustc_target/src/spec/base/linux_musl.rs | 15 +++++++-------- compiler/rustc_target/src/spec/base/linux_ohos.rs | 15 +++++++-------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/compiler/rustc_target/src/spec/base/linux_musl.rs b/compiler/rustc_target/src/spec/base/linux_musl.rs index 1a854fe362d..1bef602404e 100644 --- a/compiler/rustc_target/src/spec/base/linux_musl.rs +++ b/compiler/rustc_target/src/spec/base/linux_musl.rs @@ -1,12 +1,11 @@ use crate::spec::{LinkSelfContainedDefault, TargetOptions, base, crt_objects}; pub(crate) fn opts() -> TargetOptions { - let mut base = base::linux::opts(); - - base.env = "musl".into(); - base.pre_link_objects_self_contained = crt_objects::pre_musl_self_contained(); - base.post_link_objects_self_contained = crt_objects::post_musl_self_contained(); - base.link_self_contained = LinkSelfContainedDefault::InferredForMusl; - - base + TargetOptions { + env: "musl".into(), + pre_link_objects_self_contained: crt_objects::pre_musl_self_contained(), + post_link_objects_self_contained: crt_objects::post_musl_self_contained(), + link_self_contained: LinkSelfContainedDefault::InferredForMusl, + ..base::linux::opts() + } } diff --git a/compiler/rustc_target/src/spec/base/linux_ohos.rs b/compiler/rustc_target/src/spec/base/linux_ohos.rs index 6f4d69a996c..1b7f1e19666 100644 --- a/compiler/rustc_target/src/spec/base/linux_ohos.rs +++ b/compiler/rustc_target/src/spec/base/linux_ohos.rs @@ -1,12 +1,11 @@ use crate::spec::{TargetOptions, TlsModel, base}; pub(crate) fn opts() -> TargetOptions { - let mut base = base::linux::opts(); - - base.env = "ohos".into(); - base.crt_static_default = false; - base.tls_model = TlsModel::Emulated; - base.has_thread_local = false; - - base + TargetOptions { + env: "ohos".into(), + crt_static_default: false, + tls_model: TlsModel::Emulated, + has_thread_local: false, + ..base::linux::opts() + } } From 3eaa4b93684f10765ce9b8004fac461008ae678a Mon Sep 17 00:00:00 2001 From: Spencer Date: Tue, 15 Apr 2025 21:06:27 -0600 Subject: [PATCH 39/39] Cleaned up 4 tests in `tests/ui/issues` --- src/tools/tidy/src/issues.txt | 5 -- src/tools/tidy/src/ui_tests.rs | 2 +- tests/ui/argument-suggestions/exotic-calls.rs | 10 +++ .../argument-suggestions/exotic-calls.stderr | 35 ++++++++--- .../ref-dyn-trait-in-structs-and-enums.rs | 54 ++++++++++++++++ tests/ui/issues/auxiliary/issue-14421.rs | 25 -------- tests/ui/issues/issue-14421.rs | 15 ----- tests/ui/issues/issue-16939.rs | 8 --- tests/ui/issues/issue-16939.stderr | 20 ------ tests/ui/issues/issue-23808.rs | 59 ------------------ tests/ui/issues/issue-9719.rs | 40 ------------ ...ead-code-reexported-types-across-crates.rs | 33 ++++++++++ .../no-dead-code-for-static-trait-impl.rs | 61 +++++++++++++++++++ ...ead-code-reexported-types-across-crates.rs | 17 ++++++ 14 files changed, 202 insertions(+), 182 deletions(-) create mode 100644 tests/ui/codegen/ref-dyn-trait-in-structs-and-enums.rs delete mode 100644 tests/ui/issues/auxiliary/issue-14421.rs delete mode 100644 tests/ui/issues/issue-14421.rs delete mode 100644 tests/ui/issues/issue-16939.rs delete mode 100644 tests/ui/issues/issue-16939.stderr delete mode 100644 tests/ui/issues/issue-23808.rs delete mode 100644 tests/ui/issues/issue-9719.rs create mode 100644 tests/ui/lint/dead-code/auxiliary/no-dead-code-reexported-types-across-crates.rs create mode 100644 tests/ui/lint/dead-code/no-dead-code-for-static-trait-impl.rs create mode 100644 tests/ui/lint/dead-code/no-dead-code-reexported-types-across-crates.rs diff --git a/src/tools/tidy/src/issues.txt b/src/tools/tidy/src/issues.txt index d2ae9b1f6ef..1df95e91594 100644 --- a/src/tools/tidy/src/issues.txt +++ b/src/tools/tidy/src/issues.txt @@ -1397,7 +1397,6 @@ ui/issues/auxiliary/issue-13620-1.rs ui/issues/auxiliary/issue-13620-2.rs ui/issues/auxiliary/issue-14344-1.rs ui/issues/auxiliary/issue-14344-2.rs -ui/issues/auxiliary/issue-14421.rs ui/issues/auxiliary/issue-14422.rs ui/issues/auxiliary/issue-15562.rs ui/issues/auxiliary/issue-16643.rs @@ -1564,7 +1563,6 @@ ui/issues/issue-14366.rs ui/issues/issue-14382.rs ui/issues/issue-14393.rs ui/issues/issue-14399.rs -ui/issues/issue-14421.rs ui/issues/issue-14422.rs ui/issues/issue-14541.rs ui/issues/issue-14721.rs @@ -1629,7 +1627,6 @@ ui/issues/issue-16774.rs ui/issues/issue-16783.rs ui/issues/issue-16819.rs ui/issues/issue-16922-rpass.rs -ui/issues/issue-16939.rs ui/issues/issue-16966.rs ui/issues/issue-16994.rs ui/issues/issue-17001.rs @@ -1867,7 +1864,6 @@ ui/issues/issue-23550.rs ui/issues/issue-23589.rs ui/issues/issue-23699.rs ui/issues/issue-2380-b.rs -ui/issues/issue-23808.rs ui/issues/issue-2383.rs ui/issues/issue-23891.rs ui/issues/issue-23898.rs @@ -2607,7 +2603,6 @@ ui/issues/issue-9249.rs ui/issues/issue-9259.rs ui/issues/issue-92741.rs ui/issues/issue-9446.rs -ui/issues/issue-9719.rs ui/issues/issue-9725.rs ui/issues/issue-9737.rs ui/issues/issue-9814.rs diff --git a/src/tools/tidy/src/ui_tests.rs b/src/tools/tidy/src/ui_tests.rs index 61728d0553f..2e069af23d6 100644 --- a/src/tools/tidy/src/ui_tests.rs +++ b/src/tools/tidy/src/ui_tests.rs @@ -17,7 +17,7 @@ use ignore::Walk; const ENTRY_LIMIT: u32 = 901; // FIXME: The following limits should be reduced eventually. -const ISSUES_ENTRY_LIMIT: u32 = 1631; +const ISSUES_ENTRY_LIMIT: u32 = 1626; const EXPECTED_TEST_FILE_EXTENSIONS: &[&str] = &[ "rs", // test source files diff --git a/tests/ui/argument-suggestions/exotic-calls.rs b/tests/ui/argument-suggestions/exotic-calls.rs index 569a39a2b45..765b4bc536c 100644 --- a/tests/ui/argument-suggestions/exotic-calls.rs +++ b/tests/ui/argument-suggestions/exotic-calls.rs @@ -1,8 +1,18 @@ +//! Checks variations of E0057, which is the incorrect number of agruments passed into a closure + +//@ check-fail + fn foo(t: T) { t(1i32); //~^ ERROR function takes 0 arguments but 1 argument was supplied } +/// Regression test for +fn foo2(f: T) { + |t| f(t); + //~^ ERROR function takes 0 arguments but 1 argument was supplied +} + fn bar(t: impl Fn()) { t(1i32); //~^ ERROR function takes 0 arguments but 1 argument was supplied diff --git a/tests/ui/argument-suggestions/exotic-calls.stderr b/tests/ui/argument-suggestions/exotic-calls.stderr index aca3b8a3433..e78871c19cb 100644 --- a/tests/ui/argument-suggestions/exotic-calls.stderr +++ b/tests/ui/argument-suggestions/exotic-calls.stderr @@ -1,11 +1,11 @@ error[E0057]: this function takes 0 arguments but 1 argument was supplied - --> $DIR/exotic-calls.rs:2:5 + --> $DIR/exotic-calls.rs:6:5 | LL | t(1i32); | ^ ---- unexpected argument of type `i32` | note: callable defined here - --> $DIR/exotic-calls.rs:1:11 + --> $DIR/exotic-calls.rs:5:11 | LL | fn foo(t: T) { | ^^^^ @@ -16,13 +16,30 @@ LL + t(); | error[E0057]: this function takes 0 arguments but 1 argument was supplied - --> $DIR/exotic-calls.rs:7:5 + --> $DIR/exotic-calls.rs:12:9 + | +LL | |t| f(t); + | ^ - unexpected argument + | +note: callable defined here + --> $DIR/exotic-calls.rs:11:12 + | +LL | fn foo2(f: T) { + | ^^^^ +help: remove the extra argument + | +LL - |t| f(t); +LL + |t| f(); + | + +error[E0057]: this function takes 0 arguments but 1 argument was supplied + --> $DIR/exotic-calls.rs:17:5 | LL | t(1i32); | ^ ---- unexpected argument of type `i32` | note: type parameter defined here - --> $DIR/exotic-calls.rs:6:11 + --> $DIR/exotic-calls.rs:16:11 | LL | fn bar(t: impl Fn()) { | ^^^^^^^^^ @@ -33,13 +50,13 @@ LL + t(); | error[E0057]: this function takes 0 arguments but 1 argument was supplied - --> $DIR/exotic-calls.rs:16:5 + --> $DIR/exotic-calls.rs:26:5 | LL | baz()(1i32) | ^^^^^ ---- unexpected argument of type `i32` | note: opaque type defined here - --> $DIR/exotic-calls.rs:11:13 + --> $DIR/exotic-calls.rs:21:13 | LL | fn baz() -> impl Fn() { | ^^^^^^^^^ @@ -50,13 +67,13 @@ LL + baz()() | error[E0057]: this function takes 0 arguments but 1 argument was supplied - --> $DIR/exotic-calls.rs:22:5 + --> $DIR/exotic-calls.rs:32:5 | LL | x(1i32); | ^ ---- unexpected argument of type `i32` | note: closure defined here - --> $DIR/exotic-calls.rs:21:13 + --> $DIR/exotic-calls.rs:31:13 | LL | let x = || {}; | ^^ @@ -66,6 +83,6 @@ LL - x(1i32); LL + x(); | -error: aborting due to 4 previous errors +error: aborting due to 5 previous errors For more information about this error, try `rustc --explain E0057`. diff --git a/tests/ui/codegen/ref-dyn-trait-in-structs-and-enums.rs b/tests/ui/codegen/ref-dyn-trait-in-structs-and-enums.rs new file mode 100644 index 00000000000..04548817773 --- /dev/null +++ b/tests/ui/codegen/ref-dyn-trait-in-structs-and-enums.rs @@ -0,0 +1,54 @@ +//! Regression test for an LLVM assertion that used to be hit when: +//! +//! - There's a generic enum contained within a tuple struct +//! - When the tuple struct is parameterized by some lifetime `'a` +//! - The enum is concretized with its type argument being a reference to a trait object (of +//! lifetime `'a`) +//! +//! Issue: + +//@ build-pass + +// Dummy trait implemented for `isize` to use in the test cases +pub trait MyTrait { + fn dummy(&self) {} +} +impl MyTrait for isize {} + +// `&dyn MyTrait` contained in enum variant +pub struct EnumRefDynTrait<'a>(Enum<&'a (dyn MyTrait + 'a)>); +pub enum Enum { + Variant(T), +} + +fn enum_dyn_trait() { + let x: isize = 42; + let y = EnumRefDynTrait(Enum::Variant(&x as &dyn MyTrait)); + let _ = y; +} + +// `&dyn MyTrait` contained behind `Option` in named field of struct +struct RefDynTraitNamed<'a> { + x: Option<&'a (dyn MyTrait + 'a)>, +} + +fn named_option_dyn_trait() { + let x: isize = 42; + let y = RefDynTraitNamed { x: Some(&x as &dyn MyTrait) }; + let _ = y; +} + +// `&dyn MyTrait` contained behind `Option` in unnamed field of struct +pub struct RefDynTraitUnnamed<'a>(Option<&'a (dyn MyTrait + 'a)>); + +fn unnamed_option_dyn_trait() { + let x: isize = 42; + let y = RefDynTraitUnnamed(Some(&x as &dyn MyTrait)); + let _ = y; +} + +pub fn main() { + enum_dyn_trait(); + named_option_dyn_trait(); + unnamed_option_dyn_trait(); +} diff --git a/tests/ui/issues/auxiliary/issue-14421.rs b/tests/ui/issues/auxiliary/issue-14421.rs deleted file mode 100644 index 5fe4b24cf17..00000000000 --- a/tests/ui/issues/auxiliary/issue-14421.rs +++ /dev/null @@ -1,25 +0,0 @@ -#![crate_type="lib"] -#![deny(warnings)] -#![allow(dead_code)] - -pub use src::aliases::B; -pub use src::hidden_core::make; - -mod src { - pub mod aliases { - use super::hidden_core::A; - pub type B = A; - } - - pub mod hidden_core { - use super::aliases::B; - - pub struct A { t: T } - - pub fn make() -> B { A { t: 1.0 } } - - impl A { - pub fn foo(&mut self) { println!("called foo"); } - } - } -} diff --git a/tests/ui/issues/issue-14421.rs b/tests/ui/issues/issue-14421.rs deleted file mode 100644 index b7038584fce..00000000000 --- a/tests/ui/issues/issue-14421.rs +++ /dev/null @@ -1,15 +0,0 @@ -//@ run-pass -#![allow(non_snake_case)] - -//@ aux-build:issue-14421.rs - - -extern crate issue_14421 as bug_lib; - -use bug_lib::B; -use bug_lib::make; - -pub fn main() { - let mut an_A: B = make(); - an_A.foo(); -} diff --git a/tests/ui/issues/issue-16939.rs b/tests/ui/issues/issue-16939.rs deleted file mode 100644 index ad724834391..00000000000 --- a/tests/ui/issues/issue-16939.rs +++ /dev/null @@ -1,8 +0,0 @@ -// Make sure we don't ICE when making an overloaded call with the -// wrong arity. - -fn _foo (f: F) { - |t| f(t); //~ ERROR E0057 -} - -fn main() {} diff --git a/tests/ui/issues/issue-16939.stderr b/tests/ui/issues/issue-16939.stderr deleted file mode 100644 index 6e0889b8963..00000000000 --- a/tests/ui/issues/issue-16939.stderr +++ /dev/null @@ -1,20 +0,0 @@ -error[E0057]: this function takes 0 arguments but 1 argument was supplied - --> $DIR/issue-16939.rs:5:9 - | -LL | |t| f(t); - | ^ - unexpected argument - | -note: callable defined here - --> $DIR/issue-16939.rs:4:12 - | -LL | fn _foo (f: F) { - | ^^^^ -help: remove the extra argument - | -LL - |t| f(t); -LL + |t| f(); - | - -error: aborting due to 1 previous error - -For more information about this error, try `rustc --explain E0057`. diff --git a/tests/ui/issues/issue-23808.rs b/tests/ui/issues/issue-23808.rs deleted file mode 100644 index 6af0bd422e3..00000000000 --- a/tests/ui/issues/issue-23808.rs +++ /dev/null @@ -1,59 +0,0 @@ -//@ run-pass - -#![deny(dead_code)] - -// use different types / traits to test all combinations - -trait Const { - const C: (); -} - -trait StaticFn { - fn sfn(); -} - -struct ConstStruct; -struct StaticFnStruct; - -enum ConstEnum {} -enum StaticFnEnum {} - -struct AliasedConstStruct; -struct AliasedStaticFnStruct; - -enum AliasedConstEnum {} -enum AliasedStaticFnEnum {} - -type AliasConstStruct = AliasedConstStruct; -type AliasStaticFnStruct = AliasedStaticFnStruct; -type AliasConstEnum = AliasedConstEnum; -type AliasStaticFnEnum = AliasedStaticFnEnum; - -macro_rules! impl_Const {($($T:ident),*) => {$( - impl Const for $T { - const C: () = (); - } -)*}} - -macro_rules! impl_StaticFn {($($T:ident),*) => {$( - impl StaticFn for $T { - fn sfn() {} - } -)*}} - -impl_Const!(ConstStruct, ConstEnum, AliasedConstStruct, AliasedConstEnum); -impl_StaticFn!(StaticFnStruct, StaticFnEnum, AliasedStaticFnStruct, AliasedStaticFnEnum); - -fn main() { - let () = ConstStruct::C; - let () = ConstEnum::C; - - StaticFnStruct::sfn(); - StaticFnEnum::sfn(); - - let () = AliasConstStruct::C; - let () = AliasConstEnum::C; - - AliasStaticFnStruct::sfn(); - AliasStaticFnEnum::sfn(); -} diff --git a/tests/ui/issues/issue-9719.rs b/tests/ui/issues/issue-9719.rs deleted file mode 100644 index 904768c9341..00000000000 --- a/tests/ui/issues/issue-9719.rs +++ /dev/null @@ -1,40 +0,0 @@ -//@ build-pass -#![allow(dead_code)] - -mod a { - pub enum Enum { - A(T), - } - - pub trait X { - fn dummy(&self) { } - } - impl X for isize {} - - pub struct Z<'a>(Enum<&'a (dyn X + 'a)>); - fn foo() { let x: isize = 42; let z = Z(Enum::A(&x as &dyn X)); let _ = z; } -} - -mod b { - trait X { - fn dummy(&self) { } - } - impl X for isize {} - struct Y<'a>{ - x:Option<&'a (dyn X + 'a)>, - } - - fn bar() { - let x: isize = 42; - let _y = Y { x: Some(&x as &dyn X) }; - } -} - -mod c { - pub trait X { fn f(&self); } - impl X for isize { fn f(&self) {} } - pub struct Z<'a>(Option<&'a (dyn X + 'a)>); - fn main() { let x: isize = 42; let z = Z(Some(&x as &dyn X)); let _ = z; } -} - -pub fn main() {} diff --git a/tests/ui/lint/dead-code/auxiliary/no-dead-code-reexported-types-across-crates.rs b/tests/ui/lint/dead-code/auxiliary/no-dead-code-reexported-types-across-crates.rs new file mode 100644 index 00000000000..5f328d1abf6 --- /dev/null +++ b/tests/ui/lint/dead-code/auxiliary/no-dead-code-reexported-types-across-crates.rs @@ -0,0 +1,33 @@ +//! Auxilary file for testing `dead_code` lint. This crate is compiled as a library and exposes +//! aliased types. When used externally, there should not be warnings of `dead_code` +//! +//! Issue: + +// Expose internal types to be used in external test +pub use src::aliases::ExposedType; +pub use src::hidden_core::new; + +mod src { + pub mod aliases { + use super::hidden_core::InternalStruct; + pub type ExposedType = InternalStruct; + } + + pub mod hidden_core { + use super::aliases::ExposedType; + + pub struct InternalStruct { + _x: T, + } + + pub fn new() -> ExposedType { + InternalStruct { _x: 1.0 } + } + + impl InternalStruct { + pub fn foo(&mut self) { + println!("called foo"); + } + } + } +} diff --git a/tests/ui/lint/dead-code/no-dead-code-for-static-trait-impl.rs b/tests/ui/lint/dead-code/no-dead-code-for-static-trait-impl.rs new file mode 100644 index 00000000000..8d54eda6bca --- /dev/null +++ b/tests/ui/lint/dead-code/no-dead-code-for-static-trait-impl.rs @@ -0,0 +1,61 @@ +//! Regression test to ensure false positive `dead_code` diagnostic warnings are not triggered for +//! structs and enums that implement static trait functions or use associated constants. +//! +//! Aliased versions of all cases are also tested +//! +//! Issue: + +//@ check-pass +#![deny(dead_code)] + +trait Const { + const C: (); +} + +trait StaticFn { + fn sfn(); +} + +macro_rules! impl_const {($($T:ident),*) => {$( + impl Const for $T { + const C: () = (); + } +)*}} + +macro_rules! impl_static_fn {($($T:ident),*) => {$( + impl StaticFn for $T { + fn sfn() {} + } +)*}} + +struct ConstStruct; +enum ConstEnum {} +struct AliasedConstStruct; +type AliasConstStruct = AliasedConstStruct; +enum AliasedConstEnum {} +type AliasConstEnum = AliasedConstEnum; + +impl_const!(ConstStruct, ConstEnum, AliasedConstStruct, AliasedConstEnum); + +struct StaticFnStruct; +enum StaticFnEnum {} +struct AliasedStaticFnStruct; +type AliasStaticFnStruct = AliasedStaticFnStruct; +enum AliasedStaticFnEnum {} +type AliasStaticFnEnum = AliasedStaticFnEnum; + +impl_static_fn!(StaticFnStruct, StaticFnEnum, AliasedStaticFnStruct, AliasedStaticFnEnum); + +fn main() { + // Use the associated constant for all the types, they should be considered "used" + let () = ConstStruct::C; + let () = ConstEnum::C; + let () = AliasConstStruct::C; + let () = AliasConstEnum::C; + + // Use the associated static function for all the types, they should be considered "used" + StaticFnStruct::sfn(); + StaticFnEnum::sfn(); + AliasStaticFnStruct::sfn(); + AliasStaticFnEnum::sfn(); +} diff --git a/tests/ui/lint/dead-code/no-dead-code-reexported-types-across-crates.rs b/tests/ui/lint/dead-code/no-dead-code-reexported-types-across-crates.rs new file mode 100644 index 00000000000..11082f772ff --- /dev/null +++ b/tests/ui/lint/dead-code/no-dead-code-reexported-types-across-crates.rs @@ -0,0 +1,17 @@ +//! Regression test to ensure that `dead_code` warning does not get triggered when using re-exported +//! types that are exposed from a different crate +//! +//! Issue: + +//@ check-pass +//@ aux-build:no-dead-code-reexported-types-across-crates.rs + +extern crate no_dead_code_reexported_types_across_crates as bug_lib; + +use bug_lib::ExposedType; +use bug_lib::new; + +pub fn main() { + let mut x: ExposedType = new(); + x.foo(); +}