diff --git a/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs b/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs index 8abcaeb5fbf53..e63baf77c0852 100644 --- a/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs +++ b/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs @@ -3,8 +3,8 @@ use std::ops::Range; use rustc_errors::E0232; use rustc_hir::AttrPath; use rustc_hir::attrs::diagnostic::{ - AppendConstMessage, Directive, FilterFormatString, Flag, FormatArg, FormatString, LitOrArg, - Name, NameValue, OnUnimplementedCondition, Piece, Predicate, + Directive, FilterFormatString, Flag, FormatArg, FormatString, LitOrArg, Name, NameValue, + OnUnimplementedCondition, Piece, Predicate, }; use rustc_hir::lints::{AttributeLintKind, FormatWarning}; use rustc_macros::Diagnostic; @@ -92,7 +92,6 @@ fn parse_directive_items<'p, S: Stage>( let mut notes = ThinVec::new(); let mut parent_label = None; let mut subcommands = ThinVec::new(); - let mut append_const_msg = None; for item in items { let span = item.span(); @@ -131,7 +130,6 @@ fn parse_directive_items<'p, S: Stage>( let Some(ret) = (||{ Some($($code)*) })() else { - malformed!() }; ret @@ -159,8 +157,13 @@ fn parse_directive_items<'p, S: Stage>( let item: &MetaItemParser = or_malformed!(item.meta_item()?); let name = or_malformed!(item.ident()?).name; - // Some things like `message = "message"` must have a value. - // But with things like `append_const_msg` that is optional. + // Currently, as of April 2026, all arguments of all diagnostic attrs + // must have a value, like `message = "message"`. Thus in a well-formed + // diagnostic attribute this is never `None`. + // + // But we don't assert its presence yet because we don't want to mention it + // if someone does something like `#[diagnostic::on_unimplemented(doesnt_exist)]`. + // That happens in the big `match` below. let value: Option = match item.args().name_value() { Some(nv) => Some(or_malformed!(nv.value_as_ident()?)), None => None, @@ -223,14 +226,6 @@ fn parse_directive_items<'p, S: Stage>( let value = or_malformed!(value?); notes.push(parse_format(value)) } - - (Mode::RustcOnUnimplemented, sym::append_const_msg) => { - append_const_msg = if let Some(msg) = value { - Some(AppendConstMessage::Custom(msg.name, item.span())) - } else { - Some(AppendConstMessage::Default) - } - } (Mode::RustcOnUnimplemented, sym::parent_label) => { let value = or_malformed!(value?); if parent_label.is_none() { @@ -290,7 +285,6 @@ fn parse_directive_items<'p, S: Stage>( label, notes, parent_label, - append_const_msg, }) } diff --git a/compiler/rustc_hir/src/attrs/diagnostic.rs b/compiler/rustc_hir/src/attrs/diagnostic.rs index 7c66b3f844691..c700ca142759e 100644 --- a/compiler/rustc_hir/src/attrs/diagnostic.rs +++ b/compiler/rustc_hir/src/attrs/diagnostic.rs @@ -19,7 +19,6 @@ pub struct Directive { pub label: Option<(Span, FormatString)>, pub notes: ThinVec, pub parent_label: Option, - pub append_const_msg: Option, } impl Directive { @@ -63,7 +62,6 @@ impl Directive { let mut label = None; let mut notes = Vec::new(); let mut parent_label = None; - let mut append_const_msg = None; info!( "evaluate_directive({:?}, trait_ref={:?}, options={:?}, args ={:?})", self, trait_name, condition_options, args @@ -91,8 +89,6 @@ impl Directive { if let Some(ref parent_label_) = command.parent_label { parent_label = Some(parent_label_.clone()); } - - append_const_msg = command.append_const_msg; } OnUnimplementedNote { @@ -100,7 +96,6 @@ impl Directive { message: message.map(|m| m.1.format(args)), notes: notes.into_iter().map(|n| n.format(args)).collect(), parent_label: parent_label.map(|e_s| e_s.format(args)), - append_const_msg, } } } @@ -111,17 +106,6 @@ pub struct OnUnimplementedNote { pub label: Option, pub notes: Vec, pub parent_label: Option, - // If none, should fall back to a generic message - pub append_const_msg: Option, -} - -/// Append a message for `[const] Trait` errors. -#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)] -#[derive(HashStable_Generic, Encodable, Decodable, PrintAttribute)] -pub enum AppendConstMessage { - #[default] - Default, - Custom(Symbol, Span), } /// Like [std::fmt::Arguments] this is a string that has been parsed into "pieces", diff --git a/compiler/rustc_hir_typeck/src/method/suggest.rs b/compiler/rustc_hir_typeck/src/method/suggest.rs index c5b3d7065fa92..85bcc2745254d 100644 --- a/compiler/rustc_hir_typeck/src/method/suggest.rs +++ b/compiler/rustc_hir_typeck/src/method/suggest.rs @@ -1214,6 +1214,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { unsatisfied_predicates, ) }; + if let SelfSource::MethodCall(rcvr_expr) = source { + self.err_ctxt().note_field_shadowed_by_private_candidate( + &mut err, + rcvr_expr.hir_id, + self.param_env, + ); + } self.set_label_for_method_error( &mut err, diff --git a/compiler/rustc_hir_typeck/src/op.rs b/compiler/rustc_hir_typeck/src/op.rs index cf61728f7c2a3..f21db0bf85550 100644 --- a/compiler/rustc_hir_typeck/src/op.rs +++ b/compiler/rustc_hir_typeck/src/op.rs @@ -249,6 +249,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { rhs_ty_var, Some(lhs_expr), |err, ty| { + self.err_ctxt().note_field_shadowed_by_private_candidate( + err, + rhs_expr.hir_id, + self.param_env, + ); if let Op::BinOp(binop) = op && binop.node == hir::BinOpKind::Eq { @@ -331,6 +336,17 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { lhs_expr.span, format!("cannot use `{}` on type `{}`", s, lhs_ty_str), ); + let err_ctxt = self.err_ctxt(); + err_ctxt.note_field_shadowed_by_private_candidate( + &mut err, + lhs_expr.hir_id, + self.param_env, + ); + err_ctxt.note_field_shadowed_by_private_candidate( + &mut err, + rhs_expr.hir_id, + self.param_env, + ); self.note_unmet_impls_on_type(&mut err, &errors, false); (err, None) } @@ -391,6 +407,17 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { err.span_label(lhs_expr.span, lhs_ty_str.clone()); err.span_label(rhs_expr.span, rhs_ty_str); } + let err_ctxt = self.err_ctxt(); + err_ctxt.note_field_shadowed_by_private_candidate( + &mut err, + lhs_expr.hir_id, + self.param_env, + ); + err_ctxt.note_field_shadowed_by_private_candidate( + &mut err, + rhs_expr.hir_id, + self.param_env, + ); let suggest_derive = self.can_eq(self.param_env, lhs_ty, rhs_ty); self.note_unmet_impls_on_type(&mut err, &errors, suggest_derive); (err, output_def_id) diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 53e2527057bc2..c6df4a6c38cc3 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -399,7 +399,6 @@ symbols! { anon_assoc, anonymous_lifetime_in_impl_trait, any, - append_const_msg, apx_target_feature, arbitrary_enum_discriminant, arbitrary_self_types, diff --git a/compiler/rustc_trait_selection/src/error_reporting/infer/mod.rs b/compiler/rustc_trait_selection/src/error_reporting/infer/mod.rs index d9ea2e0057895..f7486a23bf731 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/infer/mod.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/infer/mod.rs @@ -1528,6 +1528,10 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { label_or_note(span, terr.to_string(self.tcx)); } + if let Some(param_env) = param_env { + self.note_field_shadowed_by_private_candidate_in_cause(diag, cause, param_env); + } + if self.check_and_note_conflicting_crates(diag, terr) { return; } diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs index d0358b03af197..79007f64a632e 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs @@ -14,7 +14,7 @@ use rustc_errors::{ Applicability, Diag, ErrorGuaranteed, Level, MultiSpan, StashKey, StringPart, Suggestions, msg, pluralize, struct_span_code_err, }; -use rustc_hir::attrs::diagnostic::{AppendConstMessage, OnUnimplementedNote}; +use rustc_hir::attrs::diagnostic::OnUnimplementedNote; use rustc_hir::def_id::{DefId, LOCAL_CRATE, LocalDefId}; use rustc_hir::intravisit::Visitor; use rustc_hir::{self as hir, LangItem, Node, find_attr}; @@ -193,10 +193,16 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { label, notes, parent_label, - append_const_msg, } = self.on_unimplemented_note(main_trait_predicate, main_obligation, &mut long_ty_file); let have_alt_message = message.is_some() || label.is_some(); + + let message = message.unwrap_or_else(|| self.get_standard_error_message( + main_trait_predicate, + None, + post_message, + &mut long_ty_file, + )); let is_try_conversion = self.is_try_conversion(span, main_trait_predicate.def_id()); let is_question_mark = matches!( root_obligation.cause.code().peel_derives(), @@ -210,16 +216,15 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { let question_mark_message = "the question mark operation (`?`) implicitly \ performs a conversion on the error value \ using the `From` trait"; - let (message, notes, append_const_msg) = if is_try_conversion { + let (message, notes) = if is_try_conversion { let ty = self.tcx.short_string( main_trait_predicate.skip_binder().self_ty(), &mut long_ty_file, ); // We have a `-> Result<_, E1>` and `gives_E2()?`. ( - Some(format!("`?` couldn't convert the error to `{ty}`")), + format!("`?` couldn't convert the error to `{ty}`"), vec![question_mark_message.to_owned()], - Some(AppendConstMessage::Default), ) } else if is_question_mark { let main_trait_predicate = @@ -228,26 +233,16 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { // trait object: `-> Result<_, Box` and `gives_E()?` when // `E: Error` isn't met. ( - Some(format!( + format!( "`?` couldn't convert the error: `{main_trait_predicate}` is \ not satisfied", - )), + ), vec![question_mark_message.to_owned()], - Some(AppendConstMessage::Default), ) } else { - (message, notes, append_const_msg) + (message, notes) }; - let default_err_msg = || self.get_standard_error_message( - main_trait_predicate, - message, - None, - append_const_msg, - post_message, - &mut long_ty_file, - ); - let (err_msg, safe_transmute_explanation) = if self.tcx.is_lang_item( main_trait_predicate.def_id(), LangItem::TransmuteTrait, @@ -271,7 +266,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { ); } GetSafeTransmuteErrorAndReason::Default => { - (default_err_msg(), None) + (message, None) } GetSafeTransmuteErrorAndReason::Error { err_msg, @@ -279,7 +274,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { } => (err_msg, safe_transmute_explanation), } } else { - (default_err_msg(), None) + (message, None) }; let mut err = struct_span_code_err!(self.dcx(), span, E0277, "{}", err_msg); @@ -393,7 +388,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { if let Some(s) = label { // If it has a custom `#[rustc_on_unimplemented]` // error message, let's display it as the label! - err.span_label(span, s.as_str().to_owned()); + err.span_label(span, s); if !matches!(leaf_trait_predicate.skip_binder().self_ty().kind(), ty::Param(_)) // When the self type is a type param We don't need to "the trait // `std::marker::Sized` is not implemented for `T`" as we will point @@ -559,6 +554,11 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { ); } + self.note_field_shadowed_by_private_candidate_in_cause( + &mut err, + &obligation.cause, + obligation.param_env, + ); self.try_to_add_help_message( &root_obligation, &obligation, @@ -857,9 +857,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { let err_msg = self.get_standard_error_message( trait_ref, - None, Some(predicate.constness()), - None, String::new(), &mut file, ); @@ -919,7 +917,6 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { label, notes, parent_label, - append_const_msg: _, } = note; if let Some(message) = message { @@ -2836,40 +2833,17 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { fn get_standard_error_message( &self, trait_predicate: ty::PolyTraitPredicate<'tcx>, - message: Option, predicate_constness: Option, - append_const_msg: Option, post_message: String, long_ty_path: &mut Option, ) -> String { - message - .and_then(|cannot_do_this| { - match (predicate_constness, append_const_msg) { - // do nothing if predicate is not const - (None, _) => Some(cannot_do_this), - // suggested using default post message - ( - Some(ty::BoundConstness::Const | ty::BoundConstness::Maybe), - Some(AppendConstMessage::Default), - ) => Some(format!("{cannot_do_this} in const contexts")), - // overridden post message - ( - Some(ty::BoundConstness::Const | ty::BoundConstness::Maybe), - Some(AppendConstMessage::Custom(custom_msg, _)), - ) => Some(format!("{cannot_do_this}{custom_msg}")), - // fallback to generic message - (Some(ty::BoundConstness::Const | ty::BoundConstness::Maybe), None) => None, - } - }) - .unwrap_or_else(|| { - format!( - "the trait bound `{}` is not satisfied{post_message}", - self.tcx.short_string( - trait_predicate.print_with_bound_constness(predicate_constness), - long_ty_path, - ), - ) - }) + format!( + "the trait bound `{}` is not satisfied{post_message}", + self.tcx.short_string( + trait_predicate.print_with_bound_constness(predicate_constness), + long_ty_path, + ), + ) } fn select_transmute_obligation_for_reporting( diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs index 2d9574ea8c546..4912d5f4582e7 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs @@ -22,8 +22,10 @@ use rustc_hir::{ expr_needs_parens, }; use rustc_infer::infer::{BoundRegionConversionTime, DefineOpaqueTypes, InferCtxt, InferOk}; +use rustc_infer::traits::ImplSource; use rustc_middle::middle::privacy::Level; use rustc_middle::traits::IsConstable; +use rustc_middle::ty::adjustment::{Adjust, DerefAdjustKind}; use rustc_middle::ty::error::TypeError; use rustc_middle::ty::print::{ PrintPolyTraitPredicateExt as _, PrintPolyTraitRefExt, PrintTraitPredicateExt as _, @@ -49,7 +51,7 @@ use crate::error_reporting::TypeErrCtxt; use crate::errors; use crate::infer::InferCtxtExt as _; use crate::traits::query::evaluate_obligation::InferCtxtExt as _; -use crate::traits::{ImplDerivedCause, NormalizeExt, ObligationCtxt}; +use crate::traits::{ImplDerivedCause, NormalizeExt, ObligationCtxt, SelectionContext}; #[derive(Debug)] pub enum CoroutineInteriorOrUpvar { @@ -242,6 +244,213 @@ pub fn suggest_restriction<'tcx, G: EmissionGuarantee>( } impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { + pub fn note_field_shadowed_by_private_candidate_in_cause( + &self, + err: &mut Diag<'_>, + cause: &ObligationCause<'tcx>, + param_env: ty::ParamEnv<'tcx>, + ) { + let mut hir_ids = FxHashSet::default(); + // Walk the parent chain so we can recover + // the source expression from whichever layer carries them. + let mut next_code = Some(cause.code()); + while let Some(cause_code) = next_code { + match cause_code { + ObligationCauseCode::BinOp { lhs_hir_id, rhs_hir_id, .. } => { + hir_ids.insert(*lhs_hir_id); + hir_ids.insert(*rhs_hir_id); + } + ObligationCauseCode::FunctionArg { arg_hir_id, .. } + | ObligationCauseCode::ReturnValue(arg_hir_id) + | ObligationCauseCode::AwaitableExpr(arg_hir_id) + | ObligationCauseCode::BlockTailExpression(arg_hir_id, _) + | ObligationCauseCode::UnOp { hir_id: arg_hir_id } => { + hir_ids.insert(*arg_hir_id); + } + ObligationCauseCode::OpaqueReturnType(Some((_, hir_id))) => { + hir_ids.insert(*hir_id); + } + _ => {} + } + next_code = cause_code.parent(); + } + + if !cause.span.is_dummy() + && let Some(body) = self.tcx.hir_maybe_body_owned_by(cause.body_id) + { + let mut expr_finder = FindExprBySpan::new(cause.span, self.tcx); + expr_finder.visit_body(body); + if let Some(expr) = expr_finder.result { + hir_ids.insert(expr.hir_id); + } + } + + // we will sort immediately by source order before emitting any diagnostics + #[allow(rustc::potential_query_instability)] + let mut hir_ids: Vec<_> = hir_ids.into_iter().collect(); + let source_map = self.tcx.sess.source_map(); + hir_ids.sort_by_cached_key(|hir_id| { + let span = self.tcx.hir_span(*hir_id); + let lo = source_map.lookup_byte_offset(span.lo()); + let hi = source_map.lookup_byte_offset(span.hi()); + (lo.sf.name.prefer_remapped_unconditionally().to_string(), lo.pos.0, hi.pos.0) + }); + + for hir_id in hir_ids { + self.note_field_shadowed_by_private_candidate(err, hir_id, param_env); + } + } + + pub fn note_field_shadowed_by_private_candidate( + &self, + err: &mut Diag<'_>, + hir_id: hir::HirId, + param_env: ty::ParamEnv<'tcx>, + ) { + let Some(typeck_results) = &self.typeck_results else { + return; + }; + let Node::Expr(expr) = self.tcx.hir_node(hir_id) else { + return; + }; + let hir::ExprKind::Field(base_expr, field_ident) = expr.kind else { + return; + }; + + let Some(base_ty) = typeck_results.expr_ty_opt(base_expr) else { + return; + }; + let base_ty = self.resolve_vars_if_possible(base_ty); + if base_ty.references_error() { + return; + } + + let fn_body_hir_id = self.tcx.local_def_id_to_hir_id(typeck_results.hir_owner.def_id); + let mut private_candidate: Option<(Ty<'tcx>, Ty<'tcx>, Span)> = None; + + for (deref_base_ty, _) in (self.autoderef_steps)(base_ty) { + let ty::Adt(base_def, args) = deref_base_ty.kind() else { + continue; + }; + + if base_def.is_enum() { + continue; + } + + let (adjusted_ident, def_scope) = + self.tcx.adjust_ident_and_get_scope(field_ident, base_def.did(), fn_body_hir_id); + + let Some((_, field_def)) = + base_def.non_enum_variant().fields.iter_enumerated().find(|(_, field)| { + field.ident(self.tcx).normalize_to_macros_2_0() == adjusted_ident + }) + else { + continue; + }; + let field_span = self + .tcx + .def_ident_span(field_def.did) + .unwrap_or_else(|| self.tcx.def_span(field_def.did)); + + if field_def.vis.is_accessible_from(def_scope, self.tcx) { + let accessible_field_ty = field_def.ty(self.tcx, args); + if let Some((private_base_ty, private_field_ty, private_field_span)) = + private_candidate + && !self.can_eq(param_env, private_field_ty, accessible_field_ty) + { + let private_struct_span = match private_base_ty.kind() { + ty::Adt(private_base_def, _) => self + .tcx + .def_ident_span(private_base_def.did()) + .unwrap_or_else(|| self.tcx.def_span(private_base_def.did())), + _ => DUMMY_SP, + }; + let accessible_struct_span = self + .tcx + .def_ident_span(base_def.did()) + .unwrap_or_else(|| self.tcx.def_span(base_def.did())); + let deref_impl_span = (typeck_results + .expr_adjustments(base_expr) + .iter() + .filter(|adj| { + matches!(adj.kind, Adjust::Deref(DerefAdjustKind::Overloaded(_))) + }) + .count() + == 1) + .then(|| { + self.probe(|_| { + let deref_trait_did = + self.tcx.require_lang_item(LangItem::Deref, DUMMY_SP); + let trait_ref = + ty::TraitRef::new(self.tcx, deref_trait_did, [private_base_ty]); + let obligation: Obligation<'tcx, ty::Predicate<'tcx>> = + Obligation::new( + self.tcx, + ObligationCause::dummy(), + param_env, + trait_ref, + ); + let Ok(Some(ImplSource::UserDefined(impl_data))) = + SelectionContext::new(self) + .select(&obligation.with(self.tcx, trait_ref)) + else { + return None; + }; + Some(self.tcx.def_span(impl_data.impl_def_id)) + }) + }) + .flatten(); + + let mut note_spans: MultiSpan = private_struct_span.into(); + if private_struct_span != DUMMY_SP { + note_spans.push_span_label(private_struct_span, "in this struct"); + } + if private_field_span != DUMMY_SP { + note_spans.push_span_label( + private_field_span, + "if this field wasn't private, it would be accessible", + ); + } + if accessible_struct_span != DUMMY_SP { + note_spans.push_span_label( + accessible_struct_span, + "this struct is accessible through auto-deref", + ); + } + if field_span != DUMMY_SP { + note_spans + .push_span_label(field_span, "this is the field that was accessed"); + } + if let Some(deref_impl_span) = deref_impl_span + && deref_impl_span != DUMMY_SP + { + note_spans.push_span_label( + deref_impl_span, + "the field was accessed through this `Deref`", + ); + } + + err.span_note( + note_spans, + format!( + "there is a field `{field_ident}` on `{private_base_ty}` with type `{private_field_ty}` but it is private; `{field_ident}` from `{deref_base_ty}` was accessed through auto-deref instead" + ), + ); + } + + // we finally get to the accessible field, + // so we can return early without checking the rest of the autoderef candidates + return; + } + + private_candidate.get_or_insert(( + deref_base_ty, + field_def.ty(self.tcx, args), + field_span, + )); + } + } + pub fn suggest_restricting_param_bound( &self, err: &mut Diag<'_>, diff --git a/library/core/src/cmp.rs b/library/core/src/cmp.rs index 49d7487c2803b..2051a806af642 100644 --- a/library/core/src/cmp.rs +++ b/library/core/src/cmp.rs @@ -241,10 +241,9 @@ use crate::ops::ControlFlow; #[stable(feature = "rust1", since = "1.0.0")] #[doc(alias = "==")] #[doc(alias = "!=")] -#[rustc_on_unimplemented( +#[diagnostic::on_unimplemented( message = "can't compare `{Self}` with `{Rhs}`", - label = "no implementation for `{Self} == {Rhs}`", - append_const_msg + label = "no implementation for `{Self} == {Rhs}`" )] #[rustc_diagnostic_item = "PartialEq"] #[rustc_const_unstable(feature = "const_cmp", issue = "143800")] @@ -1356,10 +1355,9 @@ pub macro Ord($item:item) { #[doc(alias = "<")] #[doc(alias = "<=")] #[doc(alias = ">=")] -#[rustc_on_unimplemented( +#[diagnostic::on_unimplemented( message = "can't compare `{Self}` with `{Rhs}`", - label = "no implementation for `{Self} < {Rhs}` and `{Self} > {Rhs}`", - append_const_msg + label = "no implementation for `{Self} < {Rhs}` and `{Self} > {Rhs}`" )] #[rustc_diagnostic_item = "PartialOrd"] #[allow(multiple_supertrait_upcastable)] // FIXME(sized_hierarchy): remove this diff --git a/library/core/src/iter/range.rs b/library/core/src/iter/range.rs index 951bb5d0029f6..695e1f27eff41 100644 --- a/library/core/src/iter/range.rs +++ b/library/core/src/iter/range.rs @@ -21,7 +21,7 @@ unsafe_impl_trusted_step![AsciiChar char i8 i16 i32 i64 i128 isize u8 u16 u32 u6 /// The *successor* operation moves towards values that compare greater. /// The *predecessor* operation moves towards values that compare lesser. #[rustc_diagnostic_item = "range_step"] -#[rustc_on_unimplemented( +#[diagnostic::on_unimplemented( message = "`std::ops::Range<{Self}>` is not an iterator", label = "`Range<{Self}>` is not an iterator", note = "`Range` only implements `Iterator` for select types in the standard library, \ diff --git a/library/core/src/marker.rs b/library/core/src/marker.rs index c79e8fc4060c1..3ca6466d122a4 100644 --- a/library/core/src/marker.rs +++ b/library/core/src/marker.rs @@ -1054,7 +1054,7 @@ marker_impls! { #[unstable(feature = "const_destruct", issue = "133214")] #[rustc_const_unstable(feature = "const_destruct", issue = "133214")] #[lang = "destruct"] -#[rustc_on_unimplemented(message = "can't drop `{Self}`", append_const_msg)] +#[diagnostic::on_unimplemented(message = "can't drop `{Self}`")] #[rustc_deny_explicit_impl] #[rustc_dyn_incompatible_trait] pub const trait Destruct: PointeeSized {} diff --git a/library/core/src/ops/arith.rs b/library/core/src/ops/arith.rs index aec52424af3c4..afb814dbc6b33 100644 --- a/library/core/src/ops/arith.rs +++ b/library/core/src/ops/arith.rs @@ -70,8 +70,7 @@ on(all(Self = "{integer}", Rhs = "{float}"), message = "cannot add a float to an integer",), on(all(Self = "{float}", Rhs = "{integer}"), message = "cannot add an integer to a float",), message = "cannot add `{Rhs}` to `{Self}`", - label = "no implementation for `{Self} + {Rhs}`", - append_const_msg + label = "no implementation for `{Self} + {Rhs}`" )] #[doc(alias = "+")] pub const trait Add { @@ -181,10 +180,9 @@ add_impl! { usize u8 u16 u32 u64 u128 isize i8 i16 i32 i64 i128 f16 f32 f64 f128 #[lang = "sub"] #[stable(feature = "rust1", since = "1.0.0")] #[rustc_const_unstable(feature = "const_ops", issue = "143802")] -#[rustc_on_unimplemented( +#[diagnostic::on_unimplemented( message = "cannot subtract `{Rhs}` from `{Self}`", - label = "no implementation for `{Self} - {Rhs}`", - append_const_msg + label = "no implementation for `{Self} - {Rhs}`" )] #[doc(alias = "-")] pub const trait Sub { diff --git a/tests/auxiliary/minicore.rs b/tests/auxiliary/minicore.rs index d84357edd0423..5c6eb54832437 100644 --- a/tests/auxiliary/minicore.rs +++ b/tests/auxiliary/minicore.rs @@ -63,7 +63,7 @@ pub trait MetaSized: PointeeSized {} pub trait Sized: MetaSized {} #[lang = "destruct"] -#[rustc_on_unimplemented(message = "can't drop `{Self}`", append_const_msg)] +#[diagnostic::on_unimplemented(message = "can't drop `{Self}`")] pub trait Destruct: PointeeSized {} #[lang = "legacy_receiver"] diff --git a/tests/ui/privacy/private-field-deref-confusion-issue-149546.rs b/tests/ui/privacy/private-field-deref-confusion-issue-149546.rs new file mode 100644 index 0000000000000..ec26c879d4eb3 --- /dev/null +++ b/tests/ui/privacy/private-field-deref-confusion-issue-149546.rs @@ -0,0 +1,88 @@ +// Field lookup still resolves to the public field on the Deref target, but +// follow-up diagnostics should explain that the original type has a same-named +// private field with a different type. +//@ dont-require-annotations: ERROR + +mod structs { + pub struct A { + field: usize, + b: B, + } + + pub struct B { + pub field: bool, + } + + impl std::ops::Deref for A { + type Target = B; + + fn deref(&self) -> &Self::Target { + &self.b + } + } +} + +use structs::A; + +fn takes_usize(_: usize) {} + +trait Marker {} + +impl Marker for usize {} + +struct Wrapper(i32); + +impl std::ops::Add for Wrapper { + type Output = (); + + fn add(self, _: T) {} +} + +fn by_value(a: A) { + a.field + 5; +} + +fn by_ref(a: &A) { + a.field + 5; +} + +fn rhs_by_value(a: A) { + 5 + a.field; +} + +fn rhs_by_ref(a: &A) { + 5 + a.field; +} + +fn rhs_assign_op_by_value(a: A) { + let mut n = 5; + n += a.field; +} + +fn rhs_assign_op_by_ref(a: &A) { + let mut n = 5; + n += a.field; +} + +fn rhs_nested_obligation(a: A) { + Wrapper(5) + a.field; +} + +fn method_call(a: A) { + a.field.count_ones(); +} + +fn type_mismatch(a: A) { + let value: usize = a.field; + eprintln!("value: {value}"); +} + +fn function_arg(a: A) { + takes_usize(a.field); +} + +fn return_value(a: &A) -> usize { + a.field +} + +fn main() {} diff --git a/tests/ui/privacy/private-field-deref-confusion-issue-149546.stderr b/tests/ui/privacy/private-field-deref-confusion-issue-149546.stderr new file mode 100644 index 0000000000000..9f2bb523913c8 --- /dev/null +++ b/tests/ui/privacy/private-field-deref-confusion-issue-149546.stderr @@ -0,0 +1,317 @@ +error[E0369]: cannot add `{integer}` to `bool` + --> $DIR/private-field-deref-confusion-issue-149546.rs:42:13 + | +LL | a.field + 5; + | ------- ^ - {integer} + | | + | bool + | +note: there is a field `field` on `A` with type `usize` but it is private; `field` from `B` was accessed through auto-deref instead + --> $DIR/private-field-deref-confusion-issue-149546.rs:7:16 + | +LL | pub struct A { + | ^ in this struct +LL | field: usize, + | ----- if this field wasn't private, it would be accessible +... +LL | pub struct B { + | - this struct is accessible through auto-deref +LL | pub field: bool, + | ----- this is the field that was accessed +... +LL | impl std::ops::Deref for A { + | -------------------------- the field was accessed through this `Deref` + +error[E0369]: cannot add `{integer}` to `bool` + --> $DIR/private-field-deref-confusion-issue-149546.rs:46:13 + | +LL | a.field + 5; + | ------- ^ - {integer} + | | + | bool + | +note: there is a field `field` on `A` with type `usize` but it is private; `field` from `B` was accessed through auto-deref instead + --> $DIR/private-field-deref-confusion-issue-149546.rs:7:16 + | +LL | pub struct A { + | ^ in this struct +LL | field: usize, + | ----- if this field wasn't private, it would be accessible +... +LL | pub struct B { + | - this struct is accessible through auto-deref +LL | pub field: bool, + | ----- this is the field that was accessed +... +LL | impl std::ops::Deref for A { + | -------------------------- the field was accessed through this `Deref` + +error[E0277]: cannot add `bool` to `{integer}` + --> $DIR/private-field-deref-confusion-issue-149546.rs:50:7 + | +LL | 5 + a.field; + | ^ no implementation for `{integer} + bool` + | + = help: the trait `Add` is not implemented for `{integer}` +note: there is a field `field` on `A` with type `usize` but it is private; `field` from `B` was accessed through auto-deref instead + --> $DIR/private-field-deref-confusion-issue-149546.rs:7:16 + | +LL | pub struct A { + | ^ in this struct +LL | field: usize, + | ----- if this field wasn't private, it would be accessible +... +LL | pub struct B { + | - this struct is accessible through auto-deref +LL | pub field: bool, + | ----- this is the field that was accessed +... +LL | impl std::ops::Deref for A { + | -------------------------- the field was accessed through this `Deref` + = help: the following other types implement trait `Add`: + `&f128` implements `Add` + `&f128` implements `Add` + `&f16` implements `Add` + `&f16` implements `Add` + `&f32` implements `Add` + `&f32` implements `Add` + `&f64` implements `Add` + `&f64` implements `Add` + and 56 others + +error[E0277]: cannot add `bool` to `{integer}` + --> $DIR/private-field-deref-confusion-issue-149546.rs:54:7 + | +LL | 5 + a.field; + | ^ no implementation for `{integer} + bool` + | + = help: the trait `Add` is not implemented for `{integer}` +note: there is a field `field` on `A` with type `usize` but it is private; `field` from `B` was accessed through auto-deref instead + --> $DIR/private-field-deref-confusion-issue-149546.rs:7:16 + | +LL | pub struct A { + | ^ in this struct +LL | field: usize, + | ----- if this field wasn't private, it would be accessible +... +LL | pub struct B { + | - this struct is accessible through auto-deref +LL | pub field: bool, + | ----- this is the field that was accessed +... +LL | impl std::ops::Deref for A { + | -------------------------- the field was accessed through this `Deref` + = help: the following other types implement trait `Add`: + `&f128` implements `Add` + `&f128` implements `Add` + `&f16` implements `Add` + `&f16` implements `Add` + `&f32` implements `Add` + `&f32` implements `Add` + `&f64` implements `Add` + `&f64` implements `Add` + and 56 others + +error[E0277]: cannot add-assign `bool` to `{integer}` + --> $DIR/private-field-deref-confusion-issue-149546.rs:59:7 + | +LL | n += a.field; + | ^^ no implementation for `{integer} += bool` + | + = help: the trait `AddAssign` is not implemented for `{integer}` +note: there is a field `field` on `A` with type `usize` but it is private; `field` from `B` was accessed through auto-deref instead + --> $DIR/private-field-deref-confusion-issue-149546.rs:7:16 + | +LL | pub struct A { + | ^ in this struct +LL | field: usize, + | ----- if this field wasn't private, it would be accessible +... +LL | pub struct B { + | - this struct is accessible through auto-deref +LL | pub field: bool, + | ----- this is the field that was accessed +... +LL | impl std::ops::Deref for A { + | -------------------------- the field was accessed through this `Deref` + = help: the following other types implement trait `AddAssign`: + `f128` implements `AddAssign<&f128>` + `f128` implements `AddAssign` + `f16` implements `AddAssign<&f16>` + `f16` implements `AddAssign` + `f32` implements `AddAssign<&f32>` + `f32` implements `AddAssign` + `f64` implements `AddAssign<&f64>` + `f64` implements `AddAssign` + and 24 others + +error[E0277]: cannot add-assign `bool` to `{integer}` + --> $DIR/private-field-deref-confusion-issue-149546.rs:64:7 + | +LL | n += a.field; + | ^^ no implementation for `{integer} += bool` + | + = help: the trait `AddAssign` is not implemented for `{integer}` +note: there is a field `field` on `A` with type `usize` but it is private; `field` from `B` was accessed through auto-deref instead + --> $DIR/private-field-deref-confusion-issue-149546.rs:7:16 + | +LL | pub struct A { + | ^ in this struct +LL | field: usize, + | ----- if this field wasn't private, it would be accessible +... +LL | pub struct B { + | - this struct is accessible through auto-deref +LL | pub field: bool, + | ----- this is the field that was accessed +... +LL | impl std::ops::Deref for A { + | -------------------------- the field was accessed through this `Deref` + = help: the following other types implement trait `AddAssign`: + `f128` implements `AddAssign<&f128>` + `f128` implements `AddAssign` + `f16` implements `AddAssign<&f16>` + `f16` implements `AddAssign` + `f32` implements `AddAssign<&f32>` + `f32` implements `AddAssign` + `f64` implements `AddAssign<&f64>` + `f64` implements `AddAssign` + and 24 others + +error[E0277]: the trait bound `bool: Marker` is not satisfied + --> $DIR/private-field-deref-confusion-issue-149546.rs:68:16 + | +LL | Wrapper(5) + a.field; + | ^ the trait `Marker` is not implemented for `bool` + | +note: there is a field `field` on `A` with type `usize` but it is private; `field` from `B` was accessed through auto-deref instead + --> $DIR/private-field-deref-confusion-issue-149546.rs:7:16 + | +LL | pub struct A { + | ^ in this struct +LL | field: usize, + | ----- if this field wasn't private, it would be accessible +... +LL | pub struct B { + | - this struct is accessible through auto-deref +LL | pub field: bool, + | ----- this is the field that was accessed +... +LL | impl std::ops::Deref for A { + | -------------------------- the field was accessed through this `Deref` +help: the trait `Marker` is implemented for `usize` + --> $DIR/private-field-deref-confusion-issue-149546.rs:31:1 + | +LL | impl Marker for usize {} + | ^^^^^^^^^^^^^^^^^^^^^ +note: required for `Wrapper` to implement `Add` + --> $DIR/private-field-deref-confusion-issue-149546.rs:35:17 + | +LL | impl std::ops::Add for Wrapper { + | ------ ^^^^^^^^^^^^^^^^ ^^^^^^^ + | | + | unsatisfied trait bound introduced here + +error[E0599]: no method named `count_ones` found for type `bool` in the current scope + --> $DIR/private-field-deref-confusion-issue-149546.rs:72:13 + | +LL | a.field.count_ones(); + | ^^^^^^^^^^ method not found in `bool` + | +note: there is a field `field` on `A` with type `usize` but it is private; `field` from `B` was accessed through auto-deref instead + --> $DIR/private-field-deref-confusion-issue-149546.rs:7:16 + | +LL | pub struct A { + | ^ in this struct +LL | field: usize, + | ----- if this field wasn't private, it would be accessible +... +LL | pub struct B { + | - this struct is accessible through auto-deref +LL | pub field: bool, + | ----- this is the field that was accessed +... +LL | impl std::ops::Deref for A { + | -------------------------- the field was accessed through this `Deref` + +error[E0308]: mismatched types + --> $DIR/private-field-deref-confusion-issue-149546.rs:76:24 + | +LL | let value: usize = a.field; + | ----- ^^^^^^^ expected `usize`, found `bool` + | | + | expected due to this + | +note: there is a field `field` on `A` with type `usize` but it is private; `field` from `B` was accessed through auto-deref instead + --> $DIR/private-field-deref-confusion-issue-149546.rs:7:16 + | +LL | pub struct A { + | ^ in this struct +LL | field: usize, + | ----- if this field wasn't private, it would be accessible +... +LL | pub struct B { + | - this struct is accessible through auto-deref +LL | pub field: bool, + | ----- this is the field that was accessed +... +LL | impl std::ops::Deref for A { + | -------------------------- the field was accessed through this `Deref` + +error[E0308]: mismatched types + --> $DIR/private-field-deref-confusion-issue-149546.rs:81:17 + | +LL | takes_usize(a.field); + | ----------- ^^^^^^^ expected `usize`, found `bool` + | | + | arguments to this function are incorrect + | +note: there is a field `field` on `A` with type `usize` but it is private; `field` from `B` was accessed through auto-deref instead + --> $DIR/private-field-deref-confusion-issue-149546.rs:7:16 + | +LL | pub struct A { + | ^ in this struct +LL | field: usize, + | ----- if this field wasn't private, it would be accessible +... +LL | pub struct B { + | - this struct is accessible through auto-deref +LL | pub field: bool, + | ----- this is the field that was accessed +... +LL | impl std::ops::Deref for A { + | -------------------------- the field was accessed through this `Deref` +note: function defined here + --> $DIR/private-field-deref-confusion-issue-149546.rs:27:4 + | +LL | fn takes_usize(_: usize) {} + | ^^^^^^^^^^^ -------- + +error[E0308]: mismatched types + --> $DIR/private-field-deref-confusion-issue-149546.rs:85:5 + | +LL | fn return_value(a: &A) -> usize { + | ----- expected `usize` because of return type +LL | a.field + | ^^^^^^^ expected `usize`, found `bool` + | +note: there is a field `field` on `A` with type `usize` but it is private; `field` from `B` was accessed through auto-deref instead + --> $DIR/private-field-deref-confusion-issue-149546.rs:7:16 + | +LL | pub struct A { + | ^ in this struct +LL | field: usize, + | ----- if this field wasn't private, it would be accessible +... +LL | pub struct B { + | - this struct is accessible through auto-deref +LL | pub field: bool, + | ----- this is the field that was accessed +... +LL | impl std::ops::Deref for A { + | -------------------------- the field was accessed through this `Deref` + +error: aborting due to 11 previous errors + +Some errors have detailed explanations: E0277, E0308, E0369, E0599. +For more information about an error, try `rustc --explain E0277`.