From b7d67670b460f269600c389ef390642748db0fcf Mon Sep 17 00:00:00 2001 From: Yuki Okushi Date: Thu, 5 Feb 2026 21:14:54 +0900 Subject: [PATCH 01/39] Normalize capture place `ty`s to prevent ICE --- compiler/rustc_middle/src/ty/closure.rs | 92 ++++++++++++------- compiler/rustc_mir_build/src/builder/mod.rs | 4 +- .../normalize-capture-place-151579.rs | 30 ++++++ .../normalize-capture-place-151579.stderr | 23 +++++ 4 files changed, 115 insertions(+), 34 deletions(-) create mode 100644 tests/ui/traits/next-solver/normalize-capture-place-151579.rs create mode 100644 tests/ui/traits/next-solver/normalize-capture-place-151579.stderr diff --git a/compiler/rustc_middle/src/ty/closure.rs b/compiler/rustc_middle/src/ty/closure.rs index ce6f87668beff..fd59a5649fa24 100644 --- a/compiler/rustc_middle/src/ty/closure.rs +++ b/compiler/rustc_middle/src/ty/closure.rs @@ -13,6 +13,7 @@ use crate::hir::place::{ Place as HirPlace, PlaceBase as HirPlaceBase, ProjectionKind as HirProjectionKind, }; use crate::query::Providers; +use crate::ty::Ty; use crate::{mir, ty}; /// Captures are represented using fields inside a structure. @@ -96,26 +97,30 @@ impl<'tcx> CapturedPlace<'tcx> { } /// Returns a symbol of the captured upvar, which looks like `name__field1__field2`. - pub fn to_symbol(&self) -> Symbol { + pub fn to_symbol(&self, tcx: TyCtxt<'tcx>) -> Symbol { let mut symbol = self.var_ident.to_string(); + let typing_env = typing_env_for_place(tcx, &self.place); let mut ty = self.place.base_ty; for proj in self.place.projections.iter() { match proj.kind { - HirProjectionKind::Field(idx, variant) => match ty.kind() { - ty::Tuple(_) => write!(&mut symbol, "__{}", idx.index()).unwrap(), - ty::Adt(def, ..) => { - write!( - &mut symbol, - "__{}", - def.variant(variant).fields[idx].name.as_str(), - ) - .unwrap(); + HirProjectionKind::Field(idx, variant) => { + let ty = normalize_place_ty(tcx, typing_env, ty); + match ty.kind() { + ty::Tuple(_) => write!(&mut symbol, "__{}", idx.index()).unwrap(), + ty::Adt(def, ..) => { + write!( + &mut symbol, + "__{}", + def.variant(variant).fields[idx].name.as_str(), + ) + .unwrap(); + } + ty => { + bug!("Unexpected type {:?} for `Field` projection", ty) + } } - ty => { - bug!("Unexpected type {:?} for `Field` projection", ty) - } - }, + } HirProjectionKind::UnwrapUnsafeBinder => { write!(&mut symbol, "__unwrap").unwrap(); @@ -304,29 +309,34 @@ pub fn place_to_string_for_capture<'tcx>(tcx: TyCtxt<'tcx>, place: &HirPlace<'tc _ => bug!("Capture_information should only contain upvars"), }; - for (i, proj) in place.projections.iter().enumerate() { + let typing_env = typing_env_for_place(tcx, place); + let mut ty = place.base_ty; + for proj in place.projections.iter() { match proj.kind { HirProjectionKind::Deref => { curr_string = format!("*{curr_string}"); } - HirProjectionKind::Field(idx, variant) => match place.ty_before_projection(i).kind() { - ty::Adt(def, ..) => { - curr_string = format!( - "{}.{}", - curr_string, - def.variant(variant).fields[idx].name.as_str() - ); - } - ty::Tuple(_) => { - curr_string = format!("{}.{}", curr_string, idx.index()); - } - _ => { - bug!( - "Field projection applied to a type other than Adt or Tuple: {:?}.", - place.ty_before_projection(i).kind() - ) + HirProjectionKind::Field(idx, variant) => { + let ty = normalize_place_ty(tcx, typing_env, ty); + match ty.kind() { + ty::Adt(def, ..) => { + curr_string = format!( + "{}.{}", + curr_string, + def.variant(variant).fields[idx].name.as_str() + ); + } + ty::Tuple(_) => { + curr_string = format!("{}.{}", curr_string, idx.index()); + } + ty => { + bug!( + "Field projection applied to a type other than Adt or Tuple: {:?}.", + ty + ) + } } - }, + } HirProjectionKind::UnwrapUnsafeBinder => { curr_string = format!("unwrap_binder!({curr_string})"); } @@ -334,11 +344,29 @@ pub fn place_to_string_for_capture<'tcx>(tcx: TyCtxt<'tcx>, place: &HirPlace<'tc HirProjectionKind::OpaqueCast => {} proj => bug!("{:?} unexpected because it isn't captured", proj), } + ty = proj.ty; } curr_string } +fn typing_env_for_place<'tcx>(tcx: TyCtxt<'tcx>, place: &HirPlace<'tcx>) -> ty::TypingEnv<'tcx> { + match place.base { + HirPlaceBase::Upvar(upvar_id) => { + ty::TypingEnv::post_analysis(tcx, upvar_id.closure_expr_id) + } + _ => ty::TypingEnv::fully_monomorphized(), + } +} + +fn normalize_place_ty<'tcx>( + tcx: TyCtxt<'tcx>, + typing_env: ty::TypingEnv<'tcx>, + ty: Ty<'tcx>, +) -> Ty<'tcx> { + tcx.normalize_erasing_regions(typing_env, ty) +} + #[derive(Eq, Clone, PartialEq, Debug, TyEncodable, TyDecodable, Copy, HashStable, Hash)] #[derive(TypeFoldable, TypeVisitable)] pub enum BorrowKind { diff --git a/compiler/rustc_mir_build/src/builder/mod.rs b/compiler/rustc_mir_build/src/builder/mod.rs index fbd7aa90f49c4..de39f5b69100c 100644 --- a/compiler/rustc_mir_build/src/builder/mod.rs +++ b/compiler/rustc_mir_build/src/builder/mod.rs @@ -55,7 +55,7 @@ pub(crate) fn closure_saved_names_of_captured_variables<'tcx>( tcx.closure_captures(def_id) .iter() .map(|captured_place| { - let name = captured_place.to_symbol(); + let name = captured_place.to_symbol(tcx); match captured_place.info.capture_kind { ty::UpvarCapture::ByValue | ty::UpvarCapture::ByUse => name, ty::UpvarCapture::ByRef(..) => Symbol::intern(&format!("_ref__{name}")), @@ -995,7 +995,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { .zip_eq(capture_tys) .enumerate() .map(|(i, (captured_place, ty))| { - let name = captured_place.to_symbol(); + let name = captured_place.to_symbol(tcx); let capture = captured_place.info.capture_kind; let var_id = match captured_place.place.base { diff --git a/tests/ui/traits/next-solver/normalize-capture-place-151579.rs b/tests/ui/traits/next-solver/normalize-capture-place-151579.rs new file mode 100644 index 0000000000000..d5dd9acc1011f --- /dev/null +++ b/tests/ui/traits/next-solver/normalize-capture-place-151579.rs @@ -0,0 +1,30 @@ +// Regression test for #151579 +//@ compile-flags: -Znext-solver=globally +//@ edition:2018 + +#![deny(rust_2021_incompatible_closure_captures)] + +struct Dummy; + +trait Trait { + type Assoc; +} + +impl Trait for Dummy { + type Assoc = (*mut i32,); +} + +struct SyncPointer(::Assoc); +unsafe impl Sync for SyncPointer {} + +fn test_assoc_capture(a: SyncPointer) { + let _ = move || { + //~^ ERROR: changes to closure capture + let _x = a.0.0; + }; +} + +fn main() { + let ptr = SyncPointer((std::ptr::null_mut(),)); + test_assoc_capture(ptr); +} diff --git a/tests/ui/traits/next-solver/normalize-capture-place-151579.stderr b/tests/ui/traits/next-solver/normalize-capture-place-151579.stderr new file mode 100644 index 0000000000000..ee2500ae907ab --- /dev/null +++ b/tests/ui/traits/next-solver/normalize-capture-place-151579.stderr @@ -0,0 +1,23 @@ +error: changes to closure capture in Rust 2021 will affect which traits the closure implements + --> $DIR/normalize-capture-place-151579.rs:21:13 + | +LL | let _ = move || { + | ^^^^^^^ in Rust 2018, this closure implements `Sync` as `a` implements `Sync`, but in Rust 2021, this closure will no longer implement `Sync` because `a` is not fully captured and `a.0.0` does not implement `Sync` +LL | +LL | let _x = a.0.0; + | ----- in Rust 2018, this closure captures all of `a`, but in Rust 2021, it will only capture `a.0.0` + | + = note: for more information, see +note: the lint level is defined here + --> $DIR/normalize-capture-place-151579.rs:5:9 + | +LL | #![deny(rust_2021_incompatible_closure_captures)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: add a dummy let to cause `a` to be fully captured + | +LL ~ let _ = move || { +LL + let _ = &a; + | + +error: aborting due to 1 previous error + From 0d9f020c621cc5233b093ba05b2fe2838752f6ff Mon Sep 17 00:00:00 2001 From: Yuki Okushi Date: Wed, 11 Feb 2026 19:53:30 +0900 Subject: [PATCH 02/39] call `try_structurally_resolve_type` before emitting diag --- compiler/rustc_hir_typeck/src/upvar.rs | 18 +++- compiler/rustc_middle/src/ty/closure.rs | 92 +++++++-------------- compiler/rustc_mir_build/src/builder/mod.rs | 4 +- 3 files changed, 50 insertions(+), 64 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/upvar.rs b/compiler/rustc_hir_typeck/src/upvar.rs index 767913ba5261a..204aca18966fa 100644 --- a/compiler/rustc_hir_typeck/src/upvar.rs +++ b/compiler/rustc_hir_typeck/src/upvar.rs @@ -368,6 +368,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // the projections. origin.1.projections.clear() } + self.try_structurally_resolve_place_ty(origin.0, &mut origin.1); self.typeck_results .borrow_mut() @@ -1127,6 +1128,19 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } + fn try_structurally_resolve_place_ty(&self, span: Span, place: &mut Place<'tcx>) { + place.base_ty = self.try_structurally_resolve_type(span, place.base_ty); + for proj in &mut place.projections { + proj.ty = self.try_structurally_resolve_type(span, proj.ty); + } + } + + fn place_to_string_for_migration(&self, capture: &ty::CapturedPlace<'tcx>) -> String { + let mut place = capture.place.clone(); + self.try_structurally_resolve_place_ty(capture.get_path_span(self.tcx), &mut place); + ty::place_to_string_for_capture(self.tcx, &place) + } + /// Combines all the reasons for 2229 migrations fn compute_2229_migrations_reasons( &self, @@ -1229,7 +1243,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { problematic_captures.insert( UpvarMigrationInfo::CapturingPrecise { source_expr: capture.info.path_expr_id, - var_name: capture.to_string(self.tcx), + var_name: self.place_to_string_for_migration(capture), }, capture_problems, ); @@ -1312,7 +1326,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { projections_list.push(captured_place.place.projections.as_slice()); diagnostics_info.insert(UpvarMigrationInfo::CapturingPrecise { source_expr: captured_place.info.path_expr_id, - var_name: captured_place.to_string(self.tcx), + var_name: self.place_to_string_for_migration(captured_place), }); } ty::UpvarCapture::ByRef(..) => {} diff --git a/compiler/rustc_middle/src/ty/closure.rs b/compiler/rustc_middle/src/ty/closure.rs index fd59a5649fa24..ce6f87668beff 100644 --- a/compiler/rustc_middle/src/ty/closure.rs +++ b/compiler/rustc_middle/src/ty/closure.rs @@ -13,7 +13,6 @@ use crate::hir::place::{ Place as HirPlace, PlaceBase as HirPlaceBase, ProjectionKind as HirProjectionKind, }; use crate::query::Providers; -use crate::ty::Ty; use crate::{mir, ty}; /// Captures are represented using fields inside a structure. @@ -97,30 +96,26 @@ impl<'tcx> CapturedPlace<'tcx> { } /// Returns a symbol of the captured upvar, which looks like `name__field1__field2`. - pub fn to_symbol(&self, tcx: TyCtxt<'tcx>) -> Symbol { + pub fn to_symbol(&self) -> Symbol { let mut symbol = self.var_ident.to_string(); - let typing_env = typing_env_for_place(tcx, &self.place); let mut ty = self.place.base_ty; for proj in self.place.projections.iter() { match proj.kind { - HirProjectionKind::Field(idx, variant) => { - let ty = normalize_place_ty(tcx, typing_env, ty); - match ty.kind() { - ty::Tuple(_) => write!(&mut symbol, "__{}", idx.index()).unwrap(), - ty::Adt(def, ..) => { - write!( - &mut symbol, - "__{}", - def.variant(variant).fields[idx].name.as_str(), - ) - .unwrap(); - } - ty => { - bug!("Unexpected type {:?} for `Field` projection", ty) - } + HirProjectionKind::Field(idx, variant) => match ty.kind() { + ty::Tuple(_) => write!(&mut symbol, "__{}", idx.index()).unwrap(), + ty::Adt(def, ..) => { + write!( + &mut symbol, + "__{}", + def.variant(variant).fields[idx].name.as_str(), + ) + .unwrap(); } - } + ty => { + bug!("Unexpected type {:?} for `Field` projection", ty) + } + }, HirProjectionKind::UnwrapUnsafeBinder => { write!(&mut symbol, "__unwrap").unwrap(); @@ -309,34 +304,29 @@ pub fn place_to_string_for_capture<'tcx>(tcx: TyCtxt<'tcx>, place: &HirPlace<'tc _ => bug!("Capture_information should only contain upvars"), }; - let typing_env = typing_env_for_place(tcx, place); - let mut ty = place.base_ty; - for proj in place.projections.iter() { + for (i, proj) in place.projections.iter().enumerate() { match proj.kind { HirProjectionKind::Deref => { curr_string = format!("*{curr_string}"); } - HirProjectionKind::Field(idx, variant) => { - let ty = normalize_place_ty(tcx, typing_env, ty); - match ty.kind() { - ty::Adt(def, ..) => { - curr_string = format!( - "{}.{}", - curr_string, - def.variant(variant).fields[idx].name.as_str() - ); - } - ty::Tuple(_) => { - curr_string = format!("{}.{}", curr_string, idx.index()); - } - ty => { - bug!( - "Field projection applied to a type other than Adt or Tuple: {:?}.", - ty - ) - } + HirProjectionKind::Field(idx, variant) => match place.ty_before_projection(i).kind() { + ty::Adt(def, ..) => { + curr_string = format!( + "{}.{}", + curr_string, + def.variant(variant).fields[idx].name.as_str() + ); } - } + ty::Tuple(_) => { + curr_string = format!("{}.{}", curr_string, idx.index()); + } + _ => { + bug!( + "Field projection applied to a type other than Adt or Tuple: {:?}.", + place.ty_before_projection(i).kind() + ) + } + }, HirProjectionKind::UnwrapUnsafeBinder => { curr_string = format!("unwrap_binder!({curr_string})"); } @@ -344,29 +334,11 @@ pub fn place_to_string_for_capture<'tcx>(tcx: TyCtxt<'tcx>, place: &HirPlace<'tc HirProjectionKind::OpaqueCast => {} proj => bug!("{:?} unexpected because it isn't captured", proj), } - ty = proj.ty; } curr_string } -fn typing_env_for_place<'tcx>(tcx: TyCtxt<'tcx>, place: &HirPlace<'tcx>) -> ty::TypingEnv<'tcx> { - match place.base { - HirPlaceBase::Upvar(upvar_id) => { - ty::TypingEnv::post_analysis(tcx, upvar_id.closure_expr_id) - } - _ => ty::TypingEnv::fully_monomorphized(), - } -} - -fn normalize_place_ty<'tcx>( - tcx: TyCtxt<'tcx>, - typing_env: ty::TypingEnv<'tcx>, - ty: Ty<'tcx>, -) -> Ty<'tcx> { - tcx.normalize_erasing_regions(typing_env, ty) -} - #[derive(Eq, Clone, PartialEq, Debug, TyEncodable, TyDecodable, Copy, HashStable, Hash)] #[derive(TypeFoldable, TypeVisitable)] pub enum BorrowKind { diff --git a/compiler/rustc_mir_build/src/builder/mod.rs b/compiler/rustc_mir_build/src/builder/mod.rs index de39f5b69100c..fbd7aa90f49c4 100644 --- a/compiler/rustc_mir_build/src/builder/mod.rs +++ b/compiler/rustc_mir_build/src/builder/mod.rs @@ -55,7 +55,7 @@ pub(crate) fn closure_saved_names_of_captured_variables<'tcx>( tcx.closure_captures(def_id) .iter() .map(|captured_place| { - let name = captured_place.to_symbol(tcx); + let name = captured_place.to_symbol(); match captured_place.info.capture_kind { ty::UpvarCapture::ByValue | ty::UpvarCapture::ByUse => name, ty::UpvarCapture::ByRef(..) => Symbol::intern(&format!("_ref__{name}")), @@ -995,7 +995,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { .zip_eq(capture_tys) .enumerate() .map(|(i, (captured_place, ty))| { - let name = captured_place.to_symbol(tcx); + let name = captured_place.to_symbol(); let capture = captured_place.info.capture_kind; let var_id = match captured_place.place.base { From 1d395f14267b53f564763f1de2f8d61f8e29b922 Mon Sep 17 00:00:00 2001 From: Yuki Okushi Date: Thu, 12 Feb 2026 22:00:55 +0900 Subject: [PATCH 03/39] Deeply normalize on next solver --- compiler/rustc_hir_typeck/src/upvar.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/compiler/rustc_hir_typeck/src/upvar.rs b/compiler/rustc_hir_typeck/src/upvar.rs index 204aca18966fa..a4ea7ec78dcc0 100644 --- a/compiler/rustc_hir_typeck/src/upvar.rs +++ b/compiler/rustc_hir_typeck/src/upvar.rs @@ -51,6 +51,7 @@ use rustc_middle::{bug, span_bug}; use rustc_session::lint; use rustc_span::{BytePos, Pos, Span, Symbol, sym}; use rustc_trait_selection::infer::InferCtxtExt; +use rustc_trait_selection::solve; use tracing::{debug, instrument}; use super::FnCtxt; @@ -1133,6 +1134,23 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { for proj in &mut place.projections { proj.ty = self.try_structurally_resolve_type(span, proj.ty); } + + if self.next_trait_solver() { + let cause = self.misc(span); + let at = self.at(&cause, self.param_env); + let deeply_normalize = |ty| match solve::deeply_normalize(at, ty) { + Ok(ty) => ty, + Err(errors) => { + let guar = self.err_ctxt().report_fulfillment_errors(errors); + Ty::new_error(self.tcx, guar) + } + }; + + place.base_ty = deeply_normalize(place.base_ty); + for proj in &mut place.projections { + proj.ty = deeply_normalize(proj.ty); + } + } } fn place_to_string_for_migration(&self, capture: &ty::CapturedPlace<'tcx>) -> String { From a8c0441c4be7a120f5908a9f35df4f448211c240 Mon Sep 17 00:00:00 2001 From: Yuki Okushi Date: Fri, 13 Feb 2026 07:25:54 +0900 Subject: [PATCH 04/39] improve normalization way --- compiler/rustc_hir_typeck/src/upvar.rs | 33 ++++++++++---------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/upvar.rs b/compiler/rustc_hir_typeck/src/upvar.rs index a4ea7ec78dcc0..5e7a6aabe58e9 100644 --- a/compiler/rustc_hir_typeck/src/upvar.rs +++ b/compiler/rustc_hir_typeck/src/upvar.rs @@ -51,7 +51,7 @@ use rustc_middle::{bug, span_bug}; use rustc_session::lint; use rustc_span::{BytePos, Pos, Span, Symbol, sym}; use rustc_trait_selection::infer::InferCtxtExt; -use rustc_trait_selection::solve; +use rustc_trait_selection::traits::ObligationCtxt; use tracing::{debug, instrument}; use super::FnCtxt; @@ -1130,26 +1130,19 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } fn try_structurally_resolve_place_ty(&self, span: Span, place: &mut Place<'tcx>) { - place.base_ty = self.try_structurally_resolve_type(span, place.base_ty); - for proj in &mut place.projections { - proj.ty = self.try_structurally_resolve_type(span, proj.ty); - } - - if self.next_trait_solver() { - let cause = self.misc(span); - let at = self.at(&cause, self.param_env); - let deeply_normalize = |ty| match solve::deeply_normalize(at, ty) { - Ok(ty) => ty, - Err(errors) => { - let guar = self.err_ctxt().report_fulfillment_errors(errors); - Ty::new_error(self.tcx, guar) - } - }; - - place.base_ty = deeply_normalize(place.base_ty); - for proj in &mut place.projections { - proj.ty = deeply_normalize(proj.ty); + let cause = self.misc(span); + let ocx = ObligationCtxt::new_with_diagnostics(&self.infcx); + let deeply_normalize = |ty| match ocx.deeply_normalize(&cause, self.param_env, ty) { + Ok(ty) => ty, + Err(errors) => { + let guar = self.err_ctxt().report_fulfillment_errors(errors); + Ty::new_error(self.tcx, guar) } + }; + + place.base_ty = deeply_normalize(place.base_ty); + for proj in &mut place.projections { + proj.ty = deeply_normalize(proj.ty); } } From 52cf4ba62fcc8eff8ab121acacf268c82bc5133f Mon Sep 17 00:00:00 2001 From: Yuki Okushi Date: Sat, 14 Feb 2026 20:43:56 +0900 Subject: [PATCH 05/39] Move normalization forward up --- compiler/rustc_hir_typeck/src/upvar.rs | 27 +++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/upvar.rs b/compiler/rustc_hir_typeck/src/upvar.rs index 5e7a6aabe58e9..ee9b5e3030b36 100644 --- a/compiler/rustc_hir_typeck/src/upvar.rs +++ b/compiler/rustc_hir_typeck/src/upvar.rs @@ -50,6 +50,7 @@ use rustc_middle::ty::{ use rustc_middle::{bug, span_bug}; use rustc_session::lint; use rustc_span::{BytePos, Pos, Span, Symbol, sym}; +use rustc_trait_selection::error_reporting::InferCtxtErrorExt as _; use rustc_trait_selection::infer::InferCtxtExt; use rustc_trait_selection::traits::ObligationCtxt; use tracing::{debug, instrument}; @@ -780,6 +781,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { typeck_results.closure_min_captures.remove(&closure_def_id).unwrap_or_default(); for (mut place, capture_info) in capture_information.into_iter() { + let span = capture_info.path_expr_id.map_or(closure_span, |e| self.tcx.hir_span(e)); + self.try_structurally_resolve_place_ty(span, &mut place); + let var_hir_id = match place.base { PlaceBase::Upvar(upvar_id) => upvar_id.var_path.hir_id, base => bug!("Expected upvar, found={:?}", base), @@ -1132,11 +1136,14 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { fn try_structurally_resolve_place_ty(&self, span: Span, place: &mut Place<'tcx>) { let cause = self.misc(span); let ocx = ObligationCtxt::new_with_diagnostics(&self.infcx); - let deeply_normalize = |ty| match ocx.deeply_normalize(&cause, self.param_env, ty) { - Ok(ty) => ty, - Err(errors) => { - let guar = self.err_ctxt().report_fulfillment_errors(errors); - Ty::new_error(self.tcx, guar) + let deeply_normalize = |ty| { + let ty = self.resolve_vars_if_possible(ty); + match ocx.deeply_normalize(&cause, self.param_env, ty) { + Ok(ty) => ty, + Err(errors) => { + let guar = self.infcx.err_ctxt().report_fulfillment_errors(errors); + Ty::new_error(self.tcx, guar) + } } }; @@ -1146,12 +1153,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } - fn place_to_string_for_migration(&self, capture: &ty::CapturedPlace<'tcx>) -> String { - let mut place = capture.place.clone(); - self.try_structurally_resolve_place_ty(capture.get_path_span(self.tcx), &mut place); - ty::place_to_string_for_capture(self.tcx, &place) - } - /// Combines all the reasons for 2229 migrations fn compute_2229_migrations_reasons( &self, @@ -1254,7 +1255,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { problematic_captures.insert( UpvarMigrationInfo::CapturingPrecise { source_expr: capture.info.path_expr_id, - var_name: self.place_to_string_for_migration(capture), + var_name: capture.to_string(self.tcx), }, capture_problems, ); @@ -1337,7 +1338,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { projections_list.push(captured_place.place.projections.as_slice()); diagnostics_info.insert(UpvarMigrationInfo::CapturingPrecise { source_expr: captured_place.info.path_expr_id, - var_name: self.place_to_string_for_migration(captured_place), + var_name: captured_place.to_string(self.tcx), }); } ty::UpvarCapture::ByRef(..) => {} From 82b09a177c75a10c3a946a3299eb4a52e155ccf8 Mon Sep 17 00:00:00 2001 From: Yuki Okushi Date: Sun, 15 Feb 2026 13:10:58 +0900 Subject: [PATCH 06/39] Normalize the whole `Place` --- compiler/rustc_hir_typeck/src/upvar.rs | 27 ++++++++----------- tests/crashes/120911.rs | 26 ------------------ ...closure-capture-hrtb-gat-no-ice-120811.rs} | 12 ++++++++- 3 files changed, 22 insertions(+), 43 deletions(-) delete mode 100644 tests/crashes/120911.rs rename tests/{crashes/120811.rs => ui/closures/closure-capture-hrtb-gat-no-ice-120811.rs} (83%) diff --git a/compiler/rustc_hir_typeck/src/upvar.rs b/compiler/rustc_hir_typeck/src/upvar.rs index ee9b5e3030b36..9f6bbb8fe9aa7 100644 --- a/compiler/rustc_hir_typeck/src/upvar.rs +++ b/compiler/rustc_hir_typeck/src/upvar.rs @@ -370,7 +370,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // the projections. origin.1.projections.clear() } - self.try_structurally_resolve_place_ty(origin.0, &mut origin.1); + self.deeply_normalize_place(origin.0, &mut origin.1); self.typeck_results .borrow_mut() @@ -782,7 +782,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { for (mut place, capture_info) in capture_information.into_iter() { let span = capture_info.path_expr_id.map_or(closure_span, |e| self.tcx.hir_span(e)); - self.try_structurally_resolve_place_ty(span, &mut place); + self.deeply_normalize_place(span, &mut place); let var_hir_id = match place.base { PlaceBase::Upvar(upvar_id) => upvar_id.var_path.hir_id, @@ -1132,24 +1132,19 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ); } } - - fn try_structurally_resolve_place_ty(&self, span: Span, place: &mut Place<'tcx>) { + fn deeply_normalize_place(&self, span: Span, place: &mut Place<'tcx>) { let cause = self.misc(span); let ocx = ObligationCtxt::new_with_diagnostics(&self.infcx); - let deeply_normalize = |ty| { - let ty = self.resolve_vars_if_possible(ty); - match ocx.deeply_normalize(&cause, self.param_env, ty) { - Ok(ty) => ty, - Err(errors) => { - let guar = self.infcx.err_ctxt().report_fulfillment_errors(errors); - Ty::new_error(self.tcx, guar) + let resolved_place = self.resolve_vars_if_possible(place.clone()); + match ocx.deeply_normalize(&cause, self.param_env, resolved_place) { + Ok(normalized) => *place = normalized, + Err(errors) => { + let guar = self.infcx.err_ctxt().report_fulfillment_errors(errors); + place.base_ty = Ty::new_error(self.tcx, guar); + for proj in &mut place.projections { + proj.ty = Ty::new_error(self.tcx, guar); } } - }; - - place.base_ty = deeply_normalize(place.base_ty); - for proj in &mut place.projections { - proj.ty = deeply_normalize(proj.ty); } } diff --git a/tests/crashes/120911.rs b/tests/crashes/120911.rs deleted file mode 100644 index 9bd2bf681420e..0000000000000 --- a/tests/crashes/120911.rs +++ /dev/null @@ -1,26 +0,0 @@ -//@ known-bug: #120911 -trait Container { - type Item<'a>; -} -impl Container for () { - type Item<'a> = (); -} -struct Exchange { - _marker: std::marker::PhantomData<(C, F)>, -} -fn exchange(_: F) -> Exchange -where - C: Container, - for<'a> F: FnMut(&C::Item<'a>), -{ - unimplemented!() -} -trait Parallelization {} -impl Parallelization for Exchange {} -fn unary_frontier>(_: P) {} -fn main() { - let exchange = exchange(|_| ()); - let _ = || { - unary_frontier(exchange); - }; -} diff --git a/tests/crashes/120811.rs b/tests/ui/closures/closure-capture-hrtb-gat-no-ice-120811.rs similarity index 83% rename from tests/crashes/120811.rs rename to tests/ui/closures/closure-capture-hrtb-gat-no-ice-120811.rs index 6b368fe681abb..4acb4840d8685 100644 --- a/tests/crashes/120811.rs +++ b/tests/ui/closures/closure-capture-hrtb-gat-no-ice-120811.rs @@ -1,14 +1,20 @@ -//@ known-bug: #120811 +//@ check-pass + +// Regression test for #120811. +// Used to ICE during closure capture analysis. trait Container { type Item<'a>; } + impl Container for () { type Item<'a> = (); } + struct Exchange { _marker: std::marker::PhantomData<(C, F)>, } + fn exchange(_: F) -> Exchange where C: Container, @@ -16,9 +22,13 @@ where { unimplemented!() } + trait Parallelization {} + impl Parallelization for Exchange {} + fn unary_frontier>(_: P) {} + fn main() { let exchange = exchange(|_| ()); let _ = || { From a4e7e0ccc4240fec69fb7e3ff7941895da9d5f1d Mon Sep 17 00:00:00 2001 From: Yuki Okushi Date: Mon, 16 Feb 2026 21:22:18 +0900 Subject: [PATCH 07/39] deeply normalize only on next solver --- compiler/rustc_hir_typeck/src/upvar.rs | 118 +++++++++++++++++-------- 1 file changed, 79 insertions(+), 39 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/upvar.rs b/compiler/rustc_hir_typeck/src/upvar.rs index 9f6bbb8fe9aa7..0a0068072d718 100644 --- a/compiler/rustc_hir_typeck/src/upvar.rs +++ b/compiler/rustc_hir_typeck/src/upvar.rs @@ -52,7 +52,7 @@ use rustc_session::lint; use rustc_span::{BytePos, Pos, Span, Symbol, sym}; use rustc_trait_selection::error_reporting::InferCtxtErrorExt as _; use rustc_trait_selection::infer::InferCtxtExt; -use rustc_trait_selection::traits::ObligationCtxt; +use rustc_trait_selection::solve; use tracing::{debug, instrument}; use super::FnCtxt; @@ -199,17 +199,18 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let closure_def_id = closure_def_id.expect_local(); assert_eq!(self.tcx.hir_body_owner_def_id(body.id()), closure_def_id); + + // Used by `ExprUseVisitor` when collecting closure capture information. + let closure_fcx = FnCtxt::new(self, self.tcx.param_env(closure_def_id), closure_def_id); + let mut delegate = InferBorrowKind { + fcx: &closure_fcx, closure_def_id, capture_information: Default::default(), fake_reads: Default::default(), }; - let _ = euv::ExprUseVisitor::new( - &FnCtxt::new(self, self.tcx.param_env(closure_def_id), closure_def_id), - &mut delegate, - ) - .consume_body(body); + let _ = euv::ExprUseVisitor::new(&closure_fcx, &mut delegate).consume_body(body); // There are several curious situations with coroutine-closures where // analysis is too aggressive with borrows when the coroutine-closure is @@ -289,7 +290,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let hir::def::Res::Local(local_id) = path.res else { bug!(); }; - let place = self.place_for_root_variable(closure_def_id, local_id); + let place = closure_fcx.place_for_root_variable(closure_def_id, local_id); delegate.capture_information.push(( place, ty::CaptureInfo { @@ -328,7 +329,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { if let Some(upvars) = self.tcx.upvars_mentioned(closure_def_id) { for var_hir_id in upvars.keys() { - let place = self.place_for_root_variable(closure_def_id, *var_hir_id); + let place = closure_fcx.place_for_root_variable(closure_def_id, *var_hir_id); debug!("seed place {:?}", place); @@ -370,7 +371,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // the projections. origin.1.projections.clear() } - self.deeply_normalize_place(origin.0, &mut origin.1); self.typeck_results .borrow_mut() @@ -563,17 +563,17 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { bug!(); }; + let coroutine_fcx = + FnCtxt::new(self, self.tcx.param_env(coroutine_def_id), coroutine_def_id); + let mut delegate = InferBorrowKind { + fcx: &coroutine_fcx, closure_def_id: coroutine_def_id, capture_information: Default::default(), fake_reads: Default::default(), }; - let _ = euv::ExprUseVisitor::new( - &FnCtxt::new(self, self.tcx.param_env(coroutine_def_id), coroutine_def_id), - &mut delegate, - ) - .consume_expr(body); + let _ = euv::ExprUseVisitor::new(&coroutine_fcx, &mut delegate).consume_expr(body); let (_, kind, _) = self.process_collected_capture_information( hir::CaptureBy::Ref, @@ -781,9 +781,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { typeck_results.closure_min_captures.remove(&closure_def_id).unwrap_or_default(); for (mut place, capture_info) in capture_information.into_iter() { - let span = capture_info.path_expr_id.map_or(closure_span, |e| self.tcx.hir_span(e)); - self.deeply_normalize_place(span, &mut place); - let var_hir_id = match place.base { PlaceBase::Upvar(upvar_id) => upvar_id.var_path.hir_id, base => bug!("Expected upvar, found={:?}", base), @@ -1132,19 +1129,42 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ); } } - fn deeply_normalize_place(&self, span: Span, place: &mut Place<'tcx>) { - let cause = self.misc(span); - let ocx = ObligationCtxt::new_with_diagnostics(&self.infcx); - let resolved_place = self.resolve_vars_if_possible(place.clone()); - match ocx.deeply_normalize(&cause, self.param_env, resolved_place) { - Ok(normalized) => *place = normalized, - Err(errors) => { - let guar = self.infcx.err_ctxt().report_fulfillment_errors(errors); - place.base_ty = Ty::new_error(self.tcx, guar); - for proj in &mut place.projections { - proj.ty = Ty::new_error(self.tcx, guar); + fn normalize_capture_place(&self, span: Span, place: &mut Place<'tcx>) { + *place = self.resolve_vars_if_possible(place.clone()); + + // In the new solver, types in HIR `Place`s can contain unnormalized aliases, + // which can ICE later (e.g. when projecting fields for diagnostics). + if self.next_trait_solver() { + let cause = self.misc(span); + let at = self.at(&cause, self.param_env); + match solve::deeply_normalize_with_skipped_universes_and_ambiguous_coroutine_goals( + at, + place.clone(), + vec![], + ) { + Ok((normalized, goals)) => { + *place = normalized; + if !goals.is_empty() { + let mut typeck_results = self.typeck_results.borrow_mut(); + typeck_results.coroutine_stalled_predicates.extend( + goals + .into_iter() + // FIXME: throwing away the param-env :( + .map(|goal| (goal.predicate, self.misc(span))), + ); + } + } + Err(errors) => { + let guar = self.infcx.err_ctxt().report_fulfillment_errors(errors); + place.base_ty = Ty::new_error(self.tcx, guar); + for proj in &mut place.projections { + proj.ty = Ty::new_error(self.tcx, guar); + } } } + } else { + // For the old solver we can rely on `normalize` to eagerly normalize aliases. + *place = self.normalize(span, place.clone()); } } @@ -1756,11 +1776,16 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ) -> Place<'tcx> { let upvar_id = ty::UpvarId::new(var_hir_id, closure_def_id); - Place { + let mut place = Place { base_ty: self.node_ty(var_hir_id), base: PlaceBase::Upvar(upvar_id), projections: Default::default(), - } + }; + + // Normalize eagerly when inserting into `capture_information`, so all downstream + // capture analysis can assume a normalized `Place`. + self.normalize_capture_place(self.tcx.hir_span(var_hir_id), &mut place); + place } fn should_log_capture_analysis(&self, closure_def_id: LocalDefId) -> bool { @@ -2020,7 +2045,8 @@ fn drop_location_span(tcx: TyCtxt<'_>, hir_id: HirId) -> Span { tcx.sess.source_map().end_point(owner_span) } -struct InferBorrowKind<'tcx> { +struct InferBorrowKind<'fcx, 'a, 'tcx> { + fcx: &'fcx FnCtxt<'a, 'tcx>, // The def-id of the closure whose kind and upvar accesses are being inferred. closure_def_id: LocalDefId, @@ -2054,7 +2080,7 @@ struct InferBorrowKind<'tcx> { fake_reads: Vec<(Place<'tcx>, FakeReadCause, HirId)>, } -impl<'tcx> euv::Delegate<'tcx> for InferBorrowKind<'tcx> { +impl<'fcx, 'a, 'tcx> euv::Delegate<'tcx> for InferBorrowKind<'fcx, 'a, 'tcx> { #[instrument(skip(self), level = "debug")] fn fake_read( &mut self, @@ -2068,8 +2094,11 @@ impl<'tcx> euv::Delegate<'tcx> for InferBorrowKind<'tcx> { // such as deref of a raw pointer. let dummy_capture_kind = ty::UpvarCapture::ByRef(ty::BorrowKind::Immutable); - let (place, _) = - restrict_capture_precision(place_with_id.place.clone(), dummy_capture_kind); + let span = self.fcx.tcx.hir_span(diag_expr_id); + let mut place = place_with_id.place.clone(); + self.fcx.normalize_capture_place(span, &mut place); + + let (place, _) = restrict_capture_precision(place, dummy_capture_kind); let (place, _) = restrict_repr_packed_field_ref_capture(place, dummy_capture_kind); self.fake_reads.push((place, cause, diag_expr_id)); @@ -2080,8 +2109,12 @@ impl<'tcx> euv::Delegate<'tcx> for InferBorrowKind<'tcx> { let PlaceBase::Upvar(upvar_id) = place_with_id.place.base else { return }; assert_eq!(self.closure_def_id, upvar_id.closure_expr_id); + let span = self.fcx.tcx.hir_span(diag_expr_id); + let mut place = place_with_id.place.clone(); + self.fcx.normalize_capture_place(span, &mut place); + self.capture_information.push(( - place_with_id.place.clone(), + place, ty::CaptureInfo { capture_kind_expr_id: Some(diag_expr_id), path_expr_id: Some(diag_expr_id), @@ -2095,8 +2128,12 @@ impl<'tcx> euv::Delegate<'tcx> for InferBorrowKind<'tcx> { let PlaceBase::Upvar(upvar_id) = place_with_id.place.base else { return }; assert_eq!(self.closure_def_id, upvar_id.closure_expr_id); + let span = self.fcx.tcx.hir_span(diag_expr_id); + let mut place = place_with_id.place.clone(); + self.fcx.normalize_capture_place(span, &mut place); + self.capture_information.push(( - place_with_id.place.clone(), + place, ty::CaptureInfo { capture_kind_expr_id: Some(diag_expr_id), path_expr_id: Some(diag_expr_id), @@ -2118,14 +2155,17 @@ impl<'tcx> euv::Delegate<'tcx> for InferBorrowKind<'tcx> { // The region here will get discarded/ignored let capture_kind = ty::UpvarCapture::ByRef(bk); + let span = self.fcx.tcx.hir_span(diag_expr_id); + let mut place = place_with_id.place.clone(); + self.fcx.normalize_capture_place(span, &mut place); + // We only want repr packed restriction to be applied to reading references into a packed // struct, and not when the data is being moved. Therefore we call this method here instead // of in `restrict_capture_precision`. - let (place, mut capture_kind) = - restrict_repr_packed_field_ref_capture(place_with_id.place.clone(), capture_kind); + let (place, mut capture_kind) = restrict_repr_packed_field_ref_capture(place, capture_kind); // Raw pointers don't inherit mutability - if place_with_id.place.deref_tys().any(Ty::is_raw_ptr) { + if place.deref_tys().any(Ty::is_raw_ptr) { capture_kind = ty::UpvarCapture::ByRef(ty::BorrowKind::Immutable); } From d79fe527ebb161d47e398164ba9e881f4da6a47d Mon Sep 17 00:00:00 2001 From: Yuki Okushi Date: Mon, 16 Feb 2026 21:34:26 +0900 Subject: [PATCH 08/39] Remove comment about ExprUseVisitor --- compiler/rustc_hir_typeck/src/upvar.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/compiler/rustc_hir_typeck/src/upvar.rs b/compiler/rustc_hir_typeck/src/upvar.rs index 0a0068072d718..ee508434bc628 100644 --- a/compiler/rustc_hir_typeck/src/upvar.rs +++ b/compiler/rustc_hir_typeck/src/upvar.rs @@ -200,7 +200,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { assert_eq!(self.tcx.hir_body_owner_def_id(body.id()), closure_def_id); - // Used by `ExprUseVisitor` when collecting closure capture information. let closure_fcx = FnCtxt::new(self, self.tcx.param_env(closure_def_id), closure_def_id); let mut delegate = InferBorrowKind { From dddbf96f9176ba38e27ef96509d802ee45c67d2b Mon Sep 17 00:00:00 2001 From: Yuki Okushi Date: Tue, 17 Feb 2026 20:29:11 +0900 Subject: [PATCH 09/39] simplify `normalize_capture_place` --- compiler/rustc_hir_typeck/src/upvar.rs | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/upvar.rs b/compiler/rustc_hir_typeck/src/upvar.rs index ee508434bc628..8e2d202215a52 100644 --- a/compiler/rustc_hir_typeck/src/upvar.rs +++ b/compiler/rustc_hir_typeck/src/upvar.rs @@ -1128,8 +1128,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ); } } - fn normalize_capture_place(&self, span: Span, place: &mut Place<'tcx>) { - *place = self.resolve_vars_if_possible(place.clone()); + fn normalize_capture_place(&self, span: Span, place: Place<'tcx>) -> Place<'tcx> { + let mut place = self.resolve_vars_if_possible(place); // In the new solver, types in HIR `Place`s can contain unnormalized aliases, // which can ICE later (e.g. when projecting fields for diagnostics). @@ -1142,7 +1142,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { vec![], ) { Ok((normalized, goals)) => { - *place = normalized; if !goals.is_empty() { let mut typeck_results = self.typeck_results.borrow_mut(); typeck_results.coroutine_stalled_predicates.extend( @@ -1152,6 +1151,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { .map(|goal| (goal.predicate, self.misc(span))), ); } + normalized } Err(errors) => { let guar = self.infcx.err_ctxt().report_fulfillment_errors(errors); @@ -1159,11 +1159,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { for proj in &mut place.projections { proj.ty = Ty::new_error(self.tcx, guar); } + place } } } else { // For the old solver we can rely on `normalize` to eagerly normalize aliases. - *place = self.normalize(span, place.clone()); + self.normalize(span, place) } } @@ -1775,7 +1776,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ) -> Place<'tcx> { let upvar_id = ty::UpvarId::new(var_hir_id, closure_def_id); - let mut place = Place { + let place = Place { base_ty: self.node_ty(var_hir_id), base: PlaceBase::Upvar(upvar_id), projections: Default::default(), @@ -1783,8 +1784,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // Normalize eagerly when inserting into `capture_information`, so all downstream // capture analysis can assume a normalized `Place`. - self.normalize_capture_place(self.tcx.hir_span(var_hir_id), &mut place); - place + self.normalize_capture_place(self.tcx.hir_span(var_hir_id), place) } fn should_log_capture_analysis(&self, closure_def_id: LocalDefId) -> bool { @@ -2094,8 +2094,7 @@ impl<'fcx, 'a, 'tcx> euv::Delegate<'tcx> for InferBorrowKind<'fcx, 'a, 'tcx> { let dummy_capture_kind = ty::UpvarCapture::ByRef(ty::BorrowKind::Immutable); let span = self.fcx.tcx.hir_span(diag_expr_id); - let mut place = place_with_id.place.clone(); - self.fcx.normalize_capture_place(span, &mut place); + let place = self.fcx.normalize_capture_place(span, place_with_id.place.clone()); let (place, _) = restrict_capture_precision(place, dummy_capture_kind); @@ -2109,8 +2108,7 @@ impl<'fcx, 'a, 'tcx> euv::Delegate<'tcx> for InferBorrowKind<'fcx, 'a, 'tcx> { assert_eq!(self.closure_def_id, upvar_id.closure_expr_id); let span = self.fcx.tcx.hir_span(diag_expr_id); - let mut place = place_with_id.place.clone(); - self.fcx.normalize_capture_place(span, &mut place); + let place = self.fcx.normalize_capture_place(span, place_with_id.place.clone()); self.capture_information.push(( place, @@ -2128,8 +2126,7 @@ impl<'fcx, 'a, 'tcx> euv::Delegate<'tcx> for InferBorrowKind<'fcx, 'a, 'tcx> { assert_eq!(self.closure_def_id, upvar_id.closure_expr_id); let span = self.fcx.tcx.hir_span(diag_expr_id); - let mut place = place_with_id.place.clone(); - self.fcx.normalize_capture_place(span, &mut place); + let place = self.fcx.normalize_capture_place(span, place_with_id.place.clone()); self.capture_information.push(( place, @@ -2155,8 +2152,7 @@ impl<'fcx, 'a, 'tcx> euv::Delegate<'tcx> for InferBorrowKind<'fcx, 'a, 'tcx> { let capture_kind = ty::UpvarCapture::ByRef(bk); let span = self.fcx.tcx.hir_span(diag_expr_id); - let mut place = place_with_id.place.clone(); - self.fcx.normalize_capture_place(span, &mut place); + let place = self.fcx.normalize_capture_place(span, place_with_id.place.clone()); // We only want repr packed restriction to be applied to reading references into a packed // struct, and not when the data is being moved. Therefore we call this method here instead From 547c53a65eb09020390cdfdbc4ab4d6bfb717b9f Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Wed, 18 Feb 2026 19:18:57 +0530 Subject: [PATCH 10/39] add span_parent method in ProcMacroClientInterface --- src/tools/rust-analyzer/crates/proc-macro-srv/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/lib.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/lib.rs index 6b770e440c7bb..65de804404831 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/lib.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv/src/lib.rs @@ -121,6 +121,7 @@ pub trait ProcMacroClientInterface { fn byte_range(&mut self, span: Span) -> Range; fn span_source(&mut self, span: Span) -> Span; + fn span_parent(&mut self, span: Span) -> Option; } const EXPANDER_STACK_SIZE: usize = 8 * 1024 * 1024; From fc97d7e5527083cca4f1378ed68585f995d00b00 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Wed, 18 Feb 2026 19:19:46 +0530 Subject: [PATCH 11/39] update span_parent implementation to call span_parent in callback --- .../proc-macro-srv/src/server_impl/rust_analyzer_span.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs index 0890e72c4fb1a..f9764f160de39 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs @@ -168,8 +168,10 @@ impl server::Server for RaSpanServer<'_> { self.callback.as_mut()?.source_text(span) } - fn span_parent(&mut self, _span: Self::Span) -> Option { - // FIXME requires db, looks up the parent call site + fn span_parent(&mut self, span: Self::Span) -> Option { + if let Some(ref mut callback) = self.callback { + return callback.span_parent(span); + } None } fn span_source(&mut self, span: Self::Span) -> Self::Span { From 93e0d9e82cb24b11ed4512b3ba4a9a8fd67643ad Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Wed, 18 Feb 2026 19:20:22 +0530 Subject: [PATCH 12/39] add span_parent in MockCallback --- .../rust-analyzer/crates/proc-macro-srv/src/tests/utils.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/tests/utils.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/tests/utils.rs index 28d826d01ea73..31beca20d61ee 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/tests/utils.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv/src/tests/utils.rs @@ -146,6 +146,10 @@ impl ProcMacroClientInterface for MockCallback<'_> { fn span_source(&mut self, span: Span) -> Span { span } + + fn span_parent(&mut self, _span: Span) -> Option { + None + } } pub fn assert_expand_with_callback( From 3efdde1ef03b2c72a70f224d56912950c186ef28 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Wed, 18 Feb 2026 19:21:00 +0530 Subject: [PATCH 13/39] update ProcMacroClientHandle implementation --- .../proc-macro-srv-cli/src/main_loop.rs | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv-cli/src/main_loop.rs b/src/tools/rust-analyzer/crates/proc-macro-srv-cli/src/main_loop.rs index 2c54b18077bb0..c525ed848b2fc 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv-cli/src/main_loop.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv-cli/src/main_loop.rs @@ -309,6 +309,40 @@ impl proc_macro_srv::ProcMacroClientInterface for ProcMacroClientHandle<'_> { other => handle_failure(other), } } + + fn span_parent( + &mut self, + proc_macro_srv::span::Span { range, anchor, ctx }: proc_macro_srv::span::Span, + ) -> Option { + let response = self.roundtrip(bidirectional::SubRequest::SpanParent { + file_id: anchor.file_id.as_u32(), + ast_id: anchor.ast_id.into_raw(), + start: range.start().into(), + end: range.end().into(), + ctx: ctx.into_u32(), + }); + + match response { + Ok(bidirectional::SubResponse::SpanParentResult { parent_span }) => { + parent_span.map(|bidirectional::ParentSpan { file_id, ast_id, start, end, ctx }| { + proc_macro_srv::span::Span { + range: proc_macro_srv::span::TextRange::new( + proc_macro_srv::span::TextSize::new(start), + proc_macro_srv::span::TextSize::new(end), + ), + anchor: proc_macro_srv::span::SpanAnchor { + file_id: proc_macro_srv::span::EditionedFileId::from_raw(file_id), + ast_id: proc_macro_srv::span::ErasedFileAstId::from_raw(ast_id), + }, + // SAFETY: spans originate from the server. If the protocol is violated, + // undefined behavior is the caller’s responsibility. + ctx: unsafe { proc_macro_srv::span::SyntaxContext::from_u32(ctx) }, + } + }) + } + other => handle_failure(other), + } + } } fn handle_expand_ra( From 6cb58583dd51cac596ae9ec193992f58961f7f60 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Wed, 18 Feb 2026 19:22:50 +0530 Subject: [PATCH 14/39] add SpanParentResult and SpanParent --- .../src/bidirectional_protocol/msg.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/bidirectional_protocol/msg.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/bidirectional_protocol/msg.rs index 10a8d66677ac4..ab4bed81e6118 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-api/src/bidirectional_protocol/msg.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/bidirectional_protocol/msg.rs @@ -22,6 +22,7 @@ pub enum SubRequest { LineColumn { file_id: u32, ast_id: u32, offset: u32 }, ByteRange { file_id: u32, ast_id: u32, start: u32, end: u32 }, SpanSource { file_id: u32, ast_id: u32, start: u32, end: u32, ctx: u32 }, + SpanParent { file_id: u32, ast_id: u32, start: u32, end: u32, ctx: u32 }, } #[derive(Debug, Serialize, Deserialize)] @@ -50,11 +51,23 @@ pub enum SubResponse { end: u32, ctx: u32, }, + SpanParentResult { + parent_span: Option, + }, Cancel { reason: String, }, } +#[derive(Debug, Serialize, Deserialize)] +pub struct ParentSpan { + pub file_id: u32, + pub ast_id: u32, + pub start: u32, + pub end: u32, + pub ctx: u32, +} + #[derive(Debug, Serialize, Deserialize)] pub enum BidirectionalMessage { Request(Request), From 2116aaa68332ba71e80005dc9f20eb0e4a7fc142 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Wed, 18 Feb 2026 19:23:26 +0530 Subject: [PATCH 15/39] add SpanParent implementation in bidirectional variant --- .../crates/load-cargo/src/lib.rs | 40 ++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/crates/load-cargo/src/lib.rs b/src/tools/rust-analyzer/crates/load-cargo/src/lib.rs index 654ff4f75b0e6..b8ce3a8da4a23 100644 --- a/src/tools/rust-analyzer/crates/load-cargo/src/lib.rs +++ b/src/tools/rust-analyzer/crates/load-cargo/src/lib.rs @@ -26,7 +26,7 @@ use ide_db::{ use itertools::Itertools; use proc_macro_api::{ MacroDylib, ProcMacroClient, - bidirectional_protocol::msg::{SubRequest, SubResponse}, + bidirectional_protocol::msg::{ParentSpan, SubRequest, SubResponse}, }; use project_model::{CargoConfig, PackageRoot, ProjectManifest, ProjectWorkspace}; use span::{Span, SpanAnchor, SyntaxContext}; @@ -659,6 +659,44 @@ impl ProcMacroExpander for Expander { ctx: current_span.ctx.into_u32(), }) } + SubRequest::SpanParent { file_id, ast_id, start, end, ctx } => { + let span = Span { + range: TextRange::new(TextSize::from(start), TextSize::from(end)), + anchor: SpanAnchor { + file_id: span::EditionedFileId::from_raw(file_id), + ast_id: span::ErasedFileAstId::from_raw(ast_id), + }, + // SAFETY: We only receive spans from the server. If someone mess up the communication UB can happen, + // but that will be their problem. + ctx: unsafe { SyntaxContext::from_u32(ctx) }, + }; + + if let Some(macro_call_id) = span.ctx.outer_expn(db) { + let macro_call_loc = db.lookup_intern_macro_call(macro_call_id.into()); + + let call_site_file = macro_call_loc.kind.file_id(); + let call_site_ast_id = macro_call_loc.kind.erased_ast_id(); + + if let Some(editioned_file_id) = call_site_file.file_id() { + let range = db + .ast_id_map(editioned_file_id.into()) + .get_erased(call_site_ast_id) + .text_range(); + + let parent_span = Some(ParentSpan { + file_id: editioned_file_id.editioned_file_id(db).as_u32(), + ast_id: span::ROOT_ERASED_FILE_AST_ID.into_raw(), + start: u32::from(range.start()), + end: u32::from(range.end()), + ctx: macro_call_loc.ctxt.into_u32(), + }); + + return Ok(SubResponse::SpanParentResult { parent_span }); + } + } + + Ok(SubResponse::SpanParentResult { parent_span: None }) + } }; match self.0.expand( subtree.view(), From b081bc85bf69751425a016eb7312b712fb0ce9b8 Mon Sep 17 00:00:00 2001 From: Benno Lossin Date: Fri, 20 Feb 2026 15:12:40 +0100 Subject: [PATCH 16/39] rustc_abi: stabilize VariantIdx and FieldIdx --- compiler/rustc_abi/src/layout.rs | 59 ++++++++++++++++++++++++++++- compiler/rustc_abi/src/layout/ty.rs | 55 +-------------------------- compiler/rustc_abi/src/lib.rs | 4 +- 3 files changed, 61 insertions(+), 57 deletions(-) diff --git a/compiler/rustc_abi/src/layout.rs b/compiler/rustc_abi/src/layout.rs index 4f1594d02a82e..edeb3038a21b4 100644 --- a/compiler/rustc_abi/src/layout.rs +++ b/compiler/rustc_abi/src/layout.rs @@ -21,7 +21,64 @@ mod simple; mod ty; #[cfg(feature = "nightly")] -pub use ty::{FIRST_VARIANT, FieldIdx, Layout, TyAbiInterface, TyAndLayout, VariantIdx}; +pub use ty::{Layout, TyAbiInterface, TyAndLayout}; + +rustc_index::newtype_index! { + /// The *source-order* index of a field in a variant. + /// + /// This is how most code after type checking refers to fields, rather than + /// using names (as names have hygiene complications and more complex lookup). + /// + /// Particularly for `repr(Rust)` types, this may not be the same as *layout* order. + /// (It is for `repr(C)` `struct`s, however.) + /// + /// For example, in the following types, + /// ```rust + /// # enum Never {} + /// # #[repr(u16)] + /// enum Demo1 { + /// Variant0 { a: Never, b: i32 } = 100, + /// Variant1 { c: u8, d: u64 } = 10, + /// } + /// struct Demo2 { e: u8, f: u16, g: u8 } + /// ``` + /// `b` is `FieldIdx(1)` in `VariantIdx(0)`, + /// `d` is `FieldIdx(1)` in `VariantIdx(1)`, and + /// `f` is `FieldIdx(1)` in `VariantIdx(0)`. + #[cfg_attr(feature = "nightly", derive(rustc_macros::HashStable_Generic))] + #[encodable] + #[orderable] + #[gate_rustc_only] + pub struct FieldIdx {} +} + +impl FieldIdx { + /// The second field, at index 1. + /// + /// For use alongside [`FieldIdx::ZERO`], particularly with scalar pairs. + pub const ONE: FieldIdx = FieldIdx::from_u32(1); +} + +rustc_index::newtype_index! { + /// The *source-order* index of a variant in a type. + /// + /// For enums, these are always `0..variant_count`, regardless of any + /// custom discriminants that may have been defined, and including any + /// variants that may end up uninhabited due to field types. (Some of the + /// variants may not be present in a monomorphized ABI [`Variants`], but + /// those skipped variants are always counted when determining the *index*.) + /// + /// `struct`s, `tuples`, and `unions`s are considered to have a single variant + /// with variant index zero, aka [`FIRST_VARIANT`]. + #[cfg_attr(feature = "nightly", derive(rustc_macros::HashStable_Generic))] + #[encodable] + #[orderable] + #[gate_rustc_only] + pub struct VariantIdx { + /// Equivalent to `VariantIdx(0)`. + const FIRST_VARIANT = 0; + } +} // A variant is absent if it's uninhabited and only has ZST fields. // Present uninhabited variants only require space for their fields, diff --git a/compiler/rustc_abi/src/layout/ty.rs b/compiler/rustc_abi/src/layout/ty.rs index aafb124986e14..7698a40629daf 100644 --- a/compiler/rustc_abi/src/layout/ty.rs +++ b/compiler/rustc_abi/src/layout/ty.rs @@ -4,6 +4,7 @@ use std::ops::Deref; use rustc_data_structures::intern::Interned; use rustc_macros::HashStable_Generic; +use crate::layout::{FieldIdx, VariantIdx}; use crate::{ AbiAlign, Align, BackendRepr, FieldsShape, Float, HasDataLayout, LayoutData, Niche, PointeeInfo, Primitive, Size, Variants, @@ -11,60 +12,6 @@ use crate::{ // Explicitly import `Float` to avoid ambiguity with `Primitive::Float`. -rustc_index::newtype_index! { - /// The *source-order* index of a field in a variant. - /// - /// This is how most code after type checking refers to fields, rather than - /// using names (as names have hygiene complications and more complex lookup). - /// - /// Particularly for `repr(Rust)` types, this may not be the same as *layout* order. - /// (It is for `repr(C)` `struct`s, however.) - /// - /// For example, in the following types, - /// ```rust - /// # enum Never {} - /// # #[repr(u16)] - /// enum Demo1 { - /// Variant0 { a: Never, b: i32 } = 100, - /// Variant1 { c: u8, d: u64 } = 10, - /// } - /// struct Demo2 { e: u8, f: u16, g: u8 } - /// ``` - /// `b` is `FieldIdx(1)` in `VariantIdx(0)`, - /// `d` is `FieldIdx(1)` in `VariantIdx(1)`, and - /// `f` is `FieldIdx(1)` in `VariantIdx(0)`. - #[derive(HashStable_Generic)] - #[encodable] - #[orderable] - pub struct FieldIdx {} -} - -impl FieldIdx { - /// The second field, at index 1. - /// - /// For use alongside [`FieldIdx::ZERO`], particularly with scalar pairs. - pub const ONE: FieldIdx = FieldIdx::from_u32(1); -} - -rustc_index::newtype_index! { - /// The *source-order* index of a variant in a type. - /// - /// For enums, these are always `0..variant_count`, regardless of any - /// custom discriminants that may have been defined, and including any - /// variants that may end up uninhabited due to field types. (Some of the - /// variants may not be present in a monomorphized ABI [`Variants`], but - /// those skipped variants are always counted when determining the *index*.) - /// - /// `struct`s, `tuples`, and `unions`s are considered to have a single variant - /// with variant index zero, aka [`FIRST_VARIANT`]. - #[derive(HashStable_Generic)] - #[encodable] - #[orderable] - pub struct VariantIdx { - /// Equivalent to `VariantIdx(0)`. - const FIRST_VARIANT = 0; - } -} #[derive(Copy, Clone, PartialEq, Eq, Hash, HashStable_Generic)] #[rustc_pass_by_value] pub struct Layout<'a>(pub Interned<'a, LayoutData>); diff --git a/compiler/rustc_abi/src/lib.rs b/compiler/rustc_abi/src/lib.rs index 2a783cd73d9de..7f65982c550a9 100644 --- a/compiler/rustc_abi/src/lib.rs +++ b/compiler/rustc_abi/src/lib.rs @@ -64,9 +64,9 @@ pub use canon_abi::{ArmCall, CanonAbi, InterruptKind, X86Call}; #[cfg(feature = "nightly")] pub use extern_abi::CVariadicStatus; pub use extern_abi::{ExternAbi, all_names}; +pub use layout::{FIRST_VARIANT, FieldIdx, LayoutCalculator, LayoutCalculatorError, VariantIdx}; #[cfg(feature = "nightly")] -pub use layout::{FIRST_VARIANT, FieldIdx, Layout, TyAbiInterface, TyAndLayout, VariantIdx}; -pub use layout::{LayoutCalculator, LayoutCalculatorError}; +pub use layout::{Layout, TyAbiInterface, TyAndLayout}; /// Requirements for a `StableHashingContext` to be used in this crate. /// This is a hack to allow using the `HashStable_Generic` derive macro From 4eaf448fae00fa7971dac07826987228c5ab1f34 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Tue, 24 Feb 2026 18:50:21 +0800 Subject: [PATCH 17/39] fix: Fix scrutinee expr indent for replace_if_let_with_match - Remove some redundant `.clone_for_update()` and `.indent(IndentLevel::single())` Example --- ```rust fn main() { if true { $0if true && false { foo() } } } ``` **Before this PR** ```rust fn main() { if true { match true && false { true => foo(), _ => (), } } } ``` **After this PR** ```rust fn main() { if true { match true && false { true => foo(), _ => (), } } } ``` --- .../src/handlers/replace_if_let_with_match.rs | 74 ++++++++++++++----- 1 file changed, 55 insertions(+), 19 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_if_let_with_match.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_if_let_with_match.rs index d2452f28c4d78..dcadb5368d525 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_if_let_with_match.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_if_let_with_match.rs @@ -57,7 +57,7 @@ pub(crate) fn replace_if_let_with_match(acc: &mut Assists, ctx: &AssistContext<' let if_exprs = successors(Some(if_expr.clone()), |expr| match expr.else_branch()? { ast::ElseBranch::IfExpr(expr) => Some(expr), ast::ElseBranch::Block(block) => { - let block = unwrap_trivial_block(block).clone_for_update(); + let block = unwrap_trivial_block(block); else_block = Some(block.reset_indent().indent(IndentLevel(1))); None } @@ -91,7 +91,7 @@ pub(crate) fn replace_if_let_with_match(acc: &mut Assists, ctx: &AssistContext<' guard }; - let body = if_expr.then_branch()?.clone_for_update().indent(IndentLevel(1)); + let body = if_expr.then_branch()?.indent(IndentLevel(1)); cond_bodies.push((cond, guard, body)); } @@ -114,7 +114,7 @@ pub(crate) fn replace_if_let_with_match(acc: &mut Assists, ctx: &AssistContext<' let make_match_arm = |(pat, guard, body): (_, Option, ast::BlockExpr)| { // Dedent from original position, then indent for match arm - let body = body.dedent(indent).indent(IndentLevel::single()); + let body = body.dedent(indent); let body = unwrap_trivial_block(body); match (pat, guard.map(|it| make.match_guard(it))) { (Some(pat), guard) => make.match_arm(pat, guard, body), @@ -127,8 +127,8 @@ pub(crate) fn replace_if_let_with_match(acc: &mut Assists, ctx: &AssistContext<' } }; let arms = cond_bodies.into_iter().map(make_match_arm).chain([else_arm]); - let match_expr = - make.expr_match(scrutinee_to_be_expr, make.match_arm_list(arms)).indent(indent); + let expr = scrutinee_to_be_expr.reset_indent(); + let match_expr = make.expr_match(expr, make.match_arm_list(arms)).indent(indent); match_expr.into() }; @@ -246,7 +246,7 @@ pub(crate) fn replace_match_with_if_let(acc: &mut Assists, ctx: &AssistContext<' first_arm.guard(), second_arm.guard(), )?; - let scrutinee = match_expr.expr()?; + let scrutinee = match_expr.expr()?.reset_indent(); let guard = guard.and_then(|it| it.condition()); let let_ = match &if_let_pat { @@ -293,10 +293,8 @@ pub(crate) fn replace_match_with_if_let(acc: &mut Assists, ctx: &AssistContext<' } else { condition }; - let then_expr = - then_expr.clone_for_update().reset_indent().indent(IndentLevel::single()); - let else_expr = - else_expr.clone_for_update().reset_indent().indent(IndentLevel::single()); + let then_expr = then_expr.reset_indent(); + let else_expr = else_expr.reset_indent(); let then_block = make_block_expr(then_expr); let else_expr = if is_empty_expr(&else_expr) { None } else { Some(else_expr) }; let if_let_expr = make @@ -956,7 +954,9 @@ fn foo(x: Result) { r#" fn main() { if true { - $0if let Ok(rel_path) = path.strip_prefix(root_path) { + $0if let Ok(rel_path) = path.strip_prefix(root_path) + .and(x) + { let rel_path = RelativePathBuf::from_path(rel_path) .ok()?; Some((*id, rel_path)) @@ -971,7 +971,8 @@ fn main() { r#" fn main() { if true { - match path.strip_prefix(root_path) { + match path.strip_prefix(root_path) + .and(x) { Ok(rel_path) => { let rel_path = RelativePathBuf::from_path(rel_path) .ok()?; @@ -993,7 +994,9 @@ fn main() { r#" fn main() { if true { - $0if let Ok(rel_path) = path.strip_prefix(root_path) { + $0if let Ok(rel_path) = path.strip_prefix(root_path) + .and(x) + { Foo { x: 1 } @@ -1008,7 +1011,8 @@ fn main() { r#" fn main() { if true { - match path.strip_prefix(root_path) { + match path.strip_prefix(root_path) + .and(x) { Ok(rel_path) => { Foo { x: 1 @@ -1023,7 +1027,33 @@ fn main() { } } "#, - ) + ); + + check_assist( + replace_if_let_with_match, + r#" +fn main() { + if true { + $0if true + && false + { + foo() + } + } +} +"#, + r#" +fn main() { + if true { + match true + && false { + true => foo(), + false => (), + } + } +} +"#, + ); } #[test] @@ -1878,7 +1908,9 @@ fn foo(x: Result) { r#" fn main() { if true { - $0match path.strip_prefix(root_path) { + $0match path.strip_prefix(root_path) + .and(x) + { Ok(rel_path) => Foo { x: 2 } @@ -1892,7 +1924,8 @@ fn main() { r#" fn main() { if true { - if let Ok(rel_path) = path.strip_prefix(root_path) { + if let Ok(rel_path) = path.strip_prefix(root_path) + .and(x) { Foo { x: 2 } @@ -1911,7 +1944,9 @@ fn main() { r#" fn main() { if true { - $0match path.strip_prefix(root_path) { + $0match path.strip_prefix(root_path) + .and(x) + { Ok(rel_path) => { let rel_path = RelativePathBuf::from_path(rel_path) .ok()?; @@ -1929,7 +1964,8 @@ fn main() { r#" fn main() { if true { - if let Ok(rel_path) = path.strip_prefix(root_path) { + if let Ok(rel_path) = path.strip_prefix(root_path) + .and(x) { let rel_path = RelativePathBuf::from_path(rel_path) .ok()?; Some((*id, rel_path)) From 87e4a3dad1321d26ce25f223e89fd6511ce52b71 Mon Sep 17 00:00:00 2001 From: Albab-Hasan Date: Tue, 24 Feb 2026 18:56:08 +0600 Subject: [PATCH 18/39] fix: detect E0804 when casting raw ptr-to-dyn adds auto traits MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit the `check_ptr_ptr_cast` function had an early return when the source and destination dyn trait objects shared the same principal trait def id: if src_principal == dst_principal { return Ok(()); } this bypassed all three remaining checks that the code comment explicitly listed as required: generic argument equality, projection equality, and the auto-trait superset check. as a result, casts like `*mut dyn Trait as *mut (dyn Trait + Send)` were silently accepted instead of emitting E0804. the fix removes the early return. `CastError::PtrPtrAddingAutoTraits` and its diagnostic were already implemented — they just couldn't be reached. the supertrait case (`trait Trait: Send`) continues to work correctly because the auto-trait check already accounts for implied auto traits via supertrait elaboration. --- src/tools/rust-analyzer/crates/hir-ty/src/infer/cast.rs | 6 +----- .../crates/ide-diagnostics/src/handlers/invalid_cast.rs | 2 ++ 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/cast.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/cast.rs index d69b00adb7f7b..fc38361d7e41e 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/cast.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/cast.rs @@ -328,11 +328,7 @@ impl<'db> CastCheck<'db> { // // Note that trait upcasting goes through a different mechanism (`coerce_unsized`) // and is unaffected by this check. - (Some(src_principal), Some(dst_principal)) => { - if src_principal == dst_principal { - return Ok(()); - } - + (Some(src_principal), Some(_)) => { // We need to reconstruct trait object types. // `m_src` and `m_dst` won't work for us here because they will potentially // contain wrappers, which we do not care about. diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/invalid_cast.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/invalid_cast.rs index 7479f8147d2eb..405d8df6854df 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/invalid_cast.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/invalid_cast.rs @@ -517,11 +517,13 @@ trait Trait<'a> {} fn add_auto<'a>(x: *mut dyn Trait<'a>) -> *mut (dyn Trait<'a> + Send) { x as _ + //^^^^^^ error: cannot add auto trait to dyn bound via pointer cast } // (to test diagnostic list formatting) fn add_multiple_auto<'a>(x: *mut dyn Trait<'a>) -> *mut (dyn Trait<'a> + Send + Sync + Unpin) { x as _ + //^^^^^^ error: cannot add auto trait to dyn bound via pointer cast } "#, ); From 81bcf97e0638e8274045e85eeb88e3725358fc0e Mon Sep 17 00:00:00 2001 From: Wilfred Hughes Date: Thu, 19 Feb 2026 17:46:41 +0000 Subject: [PATCH 19/39] internal: Skip rustfmt test if nightly toolchain is missing Currently the rustfmt slow test fails if you don't have a nightly toolchain installed. We install a nightly toolchain on CI, but it's a little confusing that tests can fail on a fresh checkout on a new laptop. Instead, skip this test if there's no nightly toolchain. --- .../crates/rust-analyzer/tests/slow-tests/main.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/main.rs b/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/main.rs index 26ba549a29be1..fcdc8bb7cdd0d 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/main.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/main.rs @@ -22,6 +22,7 @@ mod testdir; use std::{collections::HashMap, path::PathBuf, time::Instant}; +use ide_db::FxHashMap; use lsp_types::{ CodeActionContext, CodeActionParams, CompletionParams, DidOpenTextDocumentParams, DocumentFormattingParams, DocumentRangeFormattingParams, FileRename, FormattingOptions, @@ -673,6 +674,17 @@ fn test_format_document_range() { return; } + // This test requires a nightly toolchain, so skip if it's not available. + let cwd = std::env::current_dir().unwrap_or_default(); + let has_nightly_rustfmt = toolchain::command("rustfmt", cwd, &FxHashMap::default()) + .args(["+nightly", "--version"]) + .output() + .is_ok_and(|out| out.status.success()); + if !has_nightly_rustfmt { + tracing::warn!("skipping test_format_document_range: nightly rustfmt not available"); + return; + } + let server = Project::with_fixture( r#" //- /Cargo.toml From 54e9a6a994986ba1e6ef779dbe0bc67222d75f1b Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sat, 14 Feb 2026 14:00:45 +0100 Subject: [PATCH 20/39] refactor 'valid for read/write' definition: exclude null --- library/core/src/ptr/mod.rs | 61 +++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 29 deletions(-) diff --git a/library/core/src/ptr/mod.rs b/library/core/src/ptr/mod.rs index cb75cd9a2a578..48e1e206a313e 100644 --- a/library/core/src/ptr/mod.rs +++ b/library/core/src/ptr/mod.rs @@ -15,22 +15,19 @@ //! The precise rules for validity are not determined yet. The guarantees that are //! provided at this point are very minimal: //! -//! * For memory accesses of [size zero][zst], *every* pointer is valid, including the [null] -//! pointer. The following points are only concerned with non-zero-sized accesses. -//! * A [null] pointer is *never* valid. -//! * For a pointer to be valid, it is necessary, but not always sufficient, that the pointer be -//! *dereferenceable*. The [provenance] of the pointer is used to determine which [allocation] -//! it is derived from; a pointer is dereferenceable if the memory range of the given size -//! starting at the pointer is entirely contained within the bounds of that allocation. Note +//! * A [null] pointer is *never* valid for reads/writes. +//! * For memory accesses of [size zero][zst], *every* non-null pointer is valid for reads/writes. +//! The following points are only concerned with non-zero-sized accesses. +//! * For a pointer to be valid for reads/writes, it is necessary, but not always sufficient, that +//! the pointer be *dereferenceable*. The [provenance] of the pointer is used to determine which +//! [allocation] it is derived from; a pointer is dereferenceable if the memory range of the given +//! size starting at the pointer is entirely contained within the bounds of that allocation. Note //! that in Rust, every (stack-allocated) variable is considered a separate allocation. //! * All accesses performed by functions in this module are *non-atomic* in the sense //! of [atomic operations] used to synchronize between threads. This means it is //! undefined behavior to perform two concurrent accesses to the same location from different -//! threads unless both accesses only read from memory. Notice that this explicitly -//! includes [`read_volatile`] and [`write_volatile`]: Volatile accesses cannot -//! be used for inter-thread synchronization, regardless of whether they are acting on -//! Rust memory or not. -//! * The result of casting a reference to a pointer is valid for as long as the +//! threads unless both accesses only read from memory. +//! * The result of casting a reference to a pointer is valid for reads/writes for as long as the //! underlying allocation is live and no reference (just raw pointers) is used to //! access the same memory. That is, reference and pointer accesses cannot be //! interleaved. @@ -41,6 +38,13 @@ //! information, see the [book] as well as the section in the reference devoted //! to [undefined behavior][ub]. //! +//! Note that some operations such as [`read`] and [`write`][`write()`] do allow null pointers if +//! the total size of the access is zero. However, other operations internally convert pointers into +//! references. Therefore, the general notion of "valid for reads/writes" excludes null pointers, +//! and the specific operations that permit null pointers mention that as an exception. Furthermore, +//! [`read_volatile`] and [`write_volatile`] can be used in even more situations; see their +//! documentation for details. +//! //! We say that a pointer is "dangling" if it is not valid for any non-zero-sized accesses. This //! means out-of-bounds pointers, pointers to freed memory, null pointers, and pointers created with //! [`NonNull::dangling`] are all dangling. @@ -450,9 +454,9 @@ mod mut_ptr; /// /// Behavior is undefined if any of the following conditions are violated: /// -/// * `src` must be [valid] for reads of `count * size_of::()` bytes. +/// * `src` must be [valid] for reads of `count * size_of::()` bytes or that number must be 0. /// -/// * `dst` must be [valid] for writes of `count * size_of::()` bytes. +/// * `dst` must be [valid] for writes of `count * size_of::()` bytes or that number must be 0. /// /// * Both `src` and `dst` must be properly aligned. /// @@ -568,11 +572,11 @@ pub const unsafe fn copy_nonoverlapping(src: *const T, dst: *mut T, count: us /// /// Behavior is undefined if any of the following conditions are violated: /// -/// * `src` must be [valid] for reads of `count * size_of::()` bytes. +/// * `src` must be [valid] for reads of `count * size_of::()` bytes or that number must be 0. /// -/// * `dst` must be [valid] for writes of `count * size_of::()` bytes, and must remain valid even -/// when `src` is read for `count * size_of::()` bytes. (This means if the memory ranges -/// overlap, the `dst` pointer must not be invalidated by `src` reads.) +/// * `dst` must be [valid] for writes of `count * size_of::()` bytes or that number must be 0, +/// and `dst` must remain valid even when `src` is read for `count * size_of::()` bytes. (This +/// means if the memory ranges overlap, the `dst` pointer must not be invalidated by `src` reads.) /// /// * Both `src` and `dst` must be properly aligned. /// @@ -1508,7 +1512,7 @@ unsafe fn swap_nonoverlapping_bytes(x: *mut u8, y: *mut u8, bytes: NonZero(dst: *mut T, src: T) -> T { ) => ub_checks::maybe_is_aligned_and_not_null(addr, align, is_zst) ); if T::IS_ZST { - // `dst` may be valid for read and writes while also being null, in which case we cannot - // call `mem::replace`. However, we also don't have to actually do anything since there - // isn't actually any data to be copied anyway. All values of type `T` are - // bit-identical, so we can just return `src` here. + // If `T` is a ZST, `dst` is allowed to be null. However, we also don't have to actually + // do anything since there isn't actually any data to be copied anyway. All values of + // type `T` are bit-identical, so we can just return `src` here. return src; } mem::replace(&mut *dst, src) @@ -1572,7 +1575,7 @@ pub const unsafe fn replace(dst: *mut T, src: T) -> T { /// /// Behavior is undefined if any of the following conditions are violated: /// -/// * `src` must be [valid] for reads. +/// * `src` must be [valid] for reads or `T` must be a ZST. /// /// * `src` must be properly aligned. Use [`read_unaligned`] if this is not the /// case. @@ -1824,7 +1827,7 @@ pub const unsafe fn read_unaligned(src: *const T) -> T { /// /// Behavior is undefined if any of the following conditions are violated: /// -/// * `dst` must be [valid] for writes. +/// * `dst` must be [valid] for writes or `T` must be a ZST. /// /// * `dst` must be properly aligned. Use [`write_unaligned`] if this is not the /// case. @@ -2047,8 +2050,8 @@ pub const unsafe fn write_unaligned(dst: *mut T, src: T) { /// /// Behavior is undefined if any of the following conditions are violated: /// -/// * `src` must be either [valid] for reads, or it must point to memory outside of all Rust -/// allocations and reading from that memory must: +/// * `src` must be either [valid] for reads, or `T` must be a ZST, or `src` must point to memory +/// outside of all Rust allocations and reading from that memory must: /// - not trap, and /// - not cause any memory inside a Rust allocation to be modified. /// @@ -2135,8 +2138,8 @@ pub unsafe fn read_volatile(src: *const T) -> T { /// /// Behavior is undefined if any of the following conditions are violated: /// -/// * `dst` must be either [valid] for writes, or it must point to memory outside of all Rust -/// allocations and writing to that memory must: +/// * `dst` must be either [valid] for writes, or `T` must be a ZST, or `dst` must point to memory +/// outside of all Rust allocations and writing to that memory must: /// - not trap, and /// - not cause any memory inside a Rust allocation to be modified. /// From ff62e9a2a6cb3b89c70f49a7e6c8ecb3becd71dc Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Tue, 24 Feb 2026 20:33:28 +0800 Subject: [PATCH 21/39] fix: no complete enum variant qualifier in pat MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Example --- ```rust enum Enum { TupleV(u32), RecordV { field: u32 }, UnitV } use Enum::*; fn func() { if let $0 = unknown {} } ``` **Before this PR** ```text ... ev RecordV ev TupleV ev UnitV bn RecordV {…} RecordV { field$1 }$0 bn TupleV(…) TupleV($1)$0 bn UnitV UnitV$0 kw mut kw ref ``` **After this PR** ```text ... bn RecordV {…} RecordV { field$1 }$0 bn TupleV(…) TupleV($1)$0 bn UnitV UnitV$0 kw mut kw ref ``` --- .../ide-completion/src/completions/pattern.rs | 2 +- .../ide-completion/src/tests/pattern.rs | 32 ++++++++++++++++--- .../crates/ide-completion/src/tests/record.rs | 6 ---- 3 files changed, 29 insertions(+), 11 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/pattern.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/pattern.rs index eeb2c65e48449..6e9328165d957 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/pattern.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/pattern.rs @@ -95,7 +95,7 @@ pub(crate) fn complete_pattern( if refutable || single_variant_enum(variant.parent_enum(ctx.db)) => { acc.add_variant_pat(ctx, pattern_ctx, None, variant, Some(name.clone())); - true + false } hir::ModuleDef::Adt(hir::Adt::Enum(e)) => refutable || single_variant_enum(e), hir::ModuleDef::Const(..) => refutable, diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/pattern.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/pattern.rs index b8728028bb160..0d85f2e9ad627 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/tests/pattern.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/pattern.rs @@ -122,7 +122,6 @@ fn foo() { st Record st Tuple st Unit - ev TupleV bn Record {…} Record { field$1 }$0 bn Tuple(…) Tuple($1)$0 bn TupleV(…) TupleV($1)$0 @@ -159,8 +158,6 @@ fn foo(foo: Foo) { match foo { Foo { x: $0 } } } expect![[r#" en Bar st Foo - ev Nil - ev Value bn Foo {…} Foo { x$1 }$0 bn Nil Nil$0 bn Value Value$0 @@ -189,7 +186,6 @@ fn foo() { st Record st Tuple st Unit - ev Variant bn Record {…} Record { field$1 }$0 bn Tuple(…) Tuple($1)$0 bn Variant Variant$0 @@ -354,6 +350,34 @@ fn func() { ); } +#[test] +fn enum_unqualified() { + check_with_base_items( + r#" +use Enum::*; +fn func() { + if let $0 = unknown {} +} +"#, + expect![[r#" + ct CONST + en Enum + ma makro!(…) macro_rules! makro + md module + st Record + st Tuple + st Unit + bn Record {…} Record { field$1 }$0 + bn RecordV {…} RecordV { field$1 }$0 + bn Tuple(…) Tuple($1)$0 + bn TupleV(…) TupleV($1)$0 + bn UnitV UnitV$0 + kw mut + kw ref + "#]], + ); +} + #[test] fn completes_in_record_field_pat() { check( diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/record.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/record.rs index 045b2d03b0515..c1274f6640274 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/tests/record.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/record.rs @@ -61,8 +61,6 @@ fn foo(baz: Baz) { en Baz en Result md core - ev Err - ev Ok bn Baz::Bar Baz::Bar$0 bn Baz::Foo Baz::Foo$0 bn Err(…) Err($1)$0 @@ -89,10 +87,6 @@ fn foo(baz: Baz) { en Baz en Result md core - ev Bar - ev Err - ev Foo - ev Ok bn Bar Bar$0 bn Err(…) Err($1)$0 bn Foo Foo$0 From d173db9edb9626dcb31c09dabc014c1845fca019 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lauren=C8=9Biu=20Nicola?= Date: Wed, 25 Feb 2026 21:06:33 +0200 Subject: [PATCH 22/39] Don't panic on invalid notifications --- .../crates/rust-analyzer/src/handlers/dispatch.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/dispatch.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/dispatch.rs index 90deae2d902e5..67bd643fcef16 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/dispatch.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/dispatch.rs @@ -414,7 +414,8 @@ impl NotificationDispatcher<'_> { let params = match not.extract::(N::METHOD) { Ok(it) => it, Err(ExtractError::JsonError { method, error }) => { - panic!("Invalid request\nMethod: {method}\n error: {error}",) + tracing::error!(method = %method, error = %error, "invalid notification"); + return self; } Err(ExtractError::MethodMismatch(not)) => { self.not = Some(not); From 526c1afb5cf2da2f5c4cf94a3cf18c18705fc259 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Thu, 26 Feb 2026 16:05:31 +0100 Subject: [PATCH 23/39] Correctly handle `#[doc(alias = "...")]` attribute on inlined reexports --- src/librustdoc/clean/mod.rs | 3 ++- tests/rustdoc-js/auxiliary/reexport-alias.rs | 2 ++ tests/rustdoc-js/reexport-alias.js | 12 ++++++++++++ tests/rustdoc-js/reexport-alias.rs | 8 ++++++++ 4 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 tests/rustdoc-js/auxiliary/reexport-alias.rs create mode 100644 tests/rustdoc-js/reexport-alias.js create mode 100644 tests/rustdoc-js/reexport-alias.rs diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index 880d2f7f37ccd..d109dd31bb855 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -2701,7 +2701,7 @@ fn add_without_unwanted_attributes<'hir>( } hir::Attribute::Parsed(AttributeKind::Doc(box d)) => { // Remove attributes from `normal` that should not be inherited by `use` re-export. - let DocAttribute { hidden, inline, cfg, .. } = d; + let DocAttribute { hidden, inline, cfg, aliases, .. } = d; let mut attr = DocAttribute::default(); if is_inline { attr.cfg = cfg.clone(); @@ -2709,6 +2709,7 @@ fn add_without_unwanted_attributes<'hir>( attr.inline = inline.clone(); attr.hidden = hidden.clone(); } + attr.aliases = aliases.clone(); attrs.push(( Cow::Owned(hir::Attribute::Parsed(AttributeKind::Doc(Box::new(attr)))), import_parent, diff --git a/tests/rustdoc-js/auxiliary/reexport-alias.rs b/tests/rustdoc-js/auxiliary/reexport-alias.rs new file mode 100644 index 0000000000000..ba935f6edbdb9 --- /dev/null +++ b/tests/rustdoc-js/auxiliary/reexport-alias.rs @@ -0,0 +1,2 @@ +#[doc(alias = "answer")] +pub fn number() {} diff --git a/tests/rustdoc-js/reexport-alias.js b/tests/rustdoc-js/reexport-alias.js new file mode 100644 index 0000000000000..0df4a614eed56 --- /dev/null +++ b/tests/rustdoc-js/reexport-alias.js @@ -0,0 +1,12 @@ +// exact-check + +// This test ensures that inlined reexport items keep the `#[doc(alias = "...")]` +// information. +// This is a regression test for . + +const EXPECTED = { + 'query': 'answer', + 'others': [ + { 'path': 'foo', 'name': 'number', 'is_alias': true }, + ], +}; diff --git a/tests/rustdoc-js/reexport-alias.rs b/tests/rustdoc-js/reexport-alias.rs new file mode 100644 index 0000000000000..e716fe9c7b9df --- /dev/null +++ b/tests/rustdoc-js/reexport-alias.rs @@ -0,0 +1,8 @@ +//@ aux-crate:priv:reexport_alias=reexport-alias.rs +//@ compile-flags: -Zunstable-options --extern equivalent + +#![crate_name = "foo"] + +extern crate reexport_alias; + +pub use reexport_alias::number; From 148f0b1acfaf51879ee1957a608e963db9859222 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lauren=C8=9Biu=20Nicola?= Date: Fri, 27 Feb 2026 08:53:39 +0200 Subject: [PATCH 24/39] Use -Zlockfile-path on every 1.95 nightly --- .../project-model/src/cargo_config_file.rs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/tools/rust-analyzer/crates/project-model/src/cargo_config_file.rs b/src/tools/rust-analyzer/crates/project-model/src/cargo_config_file.rs index ae36deb71f02a..9c7f109a626c3 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/cargo_config_file.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/cargo_config_file.rs @@ -158,14 +158,15 @@ pub(crate) fn make_lockfile_copy( build: semver::BuildMetadata::EMPTY, }; - const MINIMUM_TOOLCHAIN_VERSION_SUPPORTING_LOCKFILE_PATH_ENV: semver::Version = - semver::Version { - major: 1, - minor: 95, - patch: 0, - pre: semver::Prerelease::EMPTY, - build: semver::BuildMetadata::EMPTY, - }; + // TODO: turn this into a const and remove pre once 1.95 is stable + #[allow(non_snake_case)] + let MINIMUM_TOOLCHAIN_VERSION_SUPPORTING_LOCKFILE_PATH_ENV: semver::Version = semver::Version { + major: 1, + minor: 95, + patch: 0, + pre: semver::Prerelease::new("nightly").unwrap(), + build: semver::BuildMetadata::EMPTY, + }; let usage = if *toolchain_version >= MINIMUM_TOOLCHAIN_VERSION_SUPPORTING_LOCKFILE_PATH_ENV { LockfileUsage::WithEnvVar From 171dae6725c64b18b43d179b1df3a8903da119dd Mon Sep 17 00:00:00 2001 From: SpiZeak Date: Wed, 25 Feb 2026 23:23:38 +0100 Subject: [PATCH 25/39] feat: migrate fix_visibility assist to SyntaxEditor --- .../src/handlers/fix_visibility.rs | 43 ++++++++++++++----- .../src/ast/syntax_factory/constructors.rs | 6 ++- 2 files changed, 38 insertions(+), 11 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/fix_visibility.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/fix_visibility.rs index 0fd8057a39585..5134b98f1b2fd 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/fix_visibility.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/fix_visibility.rs @@ -2,7 +2,7 @@ use hir::{HasSource, HasVisibility, ModuleDef, PathResolution, ScopeDef, db::Hir use ide_db::FileId; use syntax::{ AstNode, TextRange, - ast::{self, HasVisibility as _, edit_in_place::HasVisibilityEdit, make}, + ast::{self, HasVisibility as _, syntax_factory::SyntaxFactory}, }; use crate::{AssistContext, AssistId, Assists}; @@ -59,10 +59,12 @@ fn add_vis_to_referenced_module_def(acc: &mut Assists, ctx: &AssistContext<'_>) let (vis_owner, target, target_file, target_name) = target_data_for_def(ctx.db(), def)?; + let make = SyntaxFactory::without_mappings(); + let missing_visibility = if current_module.krate(ctx.db()) == target_module.krate(ctx.db()) { - make::visibility_pub_crate() + make.visibility_pub_crate() } else { - make::visibility_pub() + make.visibility_pub() }; let assist_label = match target_name { @@ -75,15 +77,36 @@ fn add_vis_to_referenced_module_def(acc: &mut Assists, ctx: &AssistContext<'_>) } }; - acc.add(AssistId::quick_fix("fix_visibility"), assist_label, target, |edit| { - edit.edit_file(target_file); - - let vis_owner = edit.make_mut(vis_owner); - vis_owner.set_visibility(Some(missing_visibility.clone_for_update())); + acc.add(AssistId::quick_fix("fix_visibility"), assist_label, target, |builder| { + let mut editor = builder.make_editor(vis_owner.syntax()); + + if let Some(current_visibility) = vis_owner.visibility() { + editor.replace(current_visibility.syntax(), missing_visibility.syntax()); + } else { + let vis_before = vis_owner + .syntax() + .children_with_tokens() + .find(|it| { + !matches!( + it.kind(), + syntax::SyntaxKind::WHITESPACE + | syntax::SyntaxKind::COMMENT + | syntax::SyntaxKind::ATTR + ) + }) + .unwrap_or_else(|| vis_owner.syntax().first_child_or_token().unwrap()); + + editor.insert_all( + syntax::syntax_editor::Position::before(vis_before), + vec![missing_visibility.syntax().clone().into(), make.whitespace(" ").into()], + ); + } - if let Some((cap, vis)) = ctx.config.snippet_cap.zip(vis_owner.visibility()) { - edit.add_tabstop_before(cap, vis); + if let Some(cap) = ctx.config.snippet_cap { + editor.add_annotation(missing_visibility.syntax(), builder.make_tabstop_before(cap)); } + + builder.add_file_edits(target_file, editor); }) } diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs index 6e17d262a79dd..27182191c3dae 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs @@ -79,8 +79,12 @@ impl SyntaxFactory { make::path_concat(first, second).clone_for_update() } + pub fn visibility_pub_crate(&self) -> ast::Visibility { + make::visibility_pub_crate().clone_for_update() + } + pub fn visibility_pub(&self) -> ast::Visibility { - make::visibility_pub() + make::visibility_pub().clone_for_update() } pub fn struct_( From 8f25f8ca50bd22097b9af769cf1ab22fd82bf316 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= Date: Fri, 27 Feb 2026 10:36:51 +0100 Subject: [PATCH 26/39] fix comment about placeholders --- compiler/rustc_next_trait_solver/src/placeholder.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_next_trait_solver/src/placeholder.rs b/compiler/rustc_next_trait_solver/src/placeholder.rs index 31fce0601697b..83f3bdf01dc83 100644 --- a/compiler/rustc_next_trait_solver/src/placeholder.rs +++ b/compiler/rustc_next_trait_solver/src/placeholder.rs @@ -32,8 +32,10 @@ where Infcx: InferCtxtLike, I: Interner, { - /// Returns `Some` if we *were* able to replace bound vars. If there are any bound vars that - /// use a binding level above `universe_indices.len()`, we fail. + /// Returns a type with all bound vars replaced by placeholders, + /// together with mappings from the new placeholders back to the original variable. + /// + /// Panics if there are any bound vars that use a binding level above `universe_indices.len()`. pub fn replace_bound_vars>( infcx: &'a Infcx, universe_indices: &'a mut Vec>, From ea2a15fb8937de31829da8f405bb9e520dae4ca7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= Date: Fri, 27 Feb 2026 10:46:31 +0100 Subject: [PATCH 27/39] and some typos --- compiler/rustc_next_trait_solver/src/solve/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_next_trait_solver/src/solve/mod.rs b/compiler/rustc_next_trait_solver/src/solve/mod.rs index 52a1479d70a15..58bd7cf663d98 100644 --- a/compiler/rustc_next_trait_solver/src/solve/mod.rs +++ b/compiler/rustc_next_trait_solver/src/solve/mod.rs @@ -39,9 +39,9 @@ use crate::solve::assembly::Candidate; /// /// We previously used `cx.recursion_limit().0.checked_ilog2().unwrap_or(0)` for this. /// However, it feels unlikely that uncreasing the recursion limit by a power of two -/// to get one more iteration is every useful or desirable. We now instead used a constant +/// to get one more iteration is ever useful or desirable. We now instead used a constant /// here. If there ever ends up some use-cases where a bigger number of fixpoint iterations -/// is required, we can add a new attribute for that or revert this to be dependant on the +/// is required, we can add a new attribute for that or revert this to be dependent on the /// recursion limit again. However, this feels very unlikely. const FIXPOINT_STEP_LIMIT: usize = 8; From 287903328011e92558dd188029991ea2e8b646ce Mon Sep 17 00:00:00 2001 From: SpiZeak Date: Fri, 27 Feb 2026 11:09:44 +0100 Subject: [PATCH 28/39] fix: migrate to SyntaxEditor in generate_derive assist --- .../src/handlers/generate_derive.rs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_derive.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_derive.rs index 06fef4af22382..3ef68f06e499d 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_derive.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_derive.rs @@ -1,7 +1,7 @@ use syntax::{ SyntaxKind::{ATTR, COMMENT, WHITESPACE}, T, - ast::{self, AstNode, HasAttrs, edit::IndentLevel, make}, + ast::{self, AstNode, HasAttrs, edit::IndentLevel, syntax_factory::SyntaxFactory}, syntax_editor::{Element, Position}, }; @@ -42,13 +42,15 @@ pub(crate) fn generate_derive(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opt }; acc.add(AssistId::generate("generate_derive"), "Add `#[derive]`", target, |edit| { + let make = SyntaxFactory::without_mappings(); + match derive_attr { None => { - let derive = make::attr_outer(make::meta_token_tree( - make::ext::ident_path("derive"), - make::token_tree(T!['('], vec![]).clone_for_update(), - )) - .clone_for_update(); + let derive = + make.attr_outer(make.meta_token_tree( + make.ident_path("derive"), + make.token_tree(T!['('], vec![]), + )); let mut editor = edit.make_editor(nominal.syntax()); let indent = IndentLevel::from_node(nominal.syntax()); @@ -57,11 +59,12 @@ pub(crate) fn generate_derive(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opt .children_with_tokens() .find(|it| !matches!(it.kind(), WHITESPACE | COMMENT | ATTR)) .map_or(Position::first_child_of(nominal.syntax()), Position::before); + editor.insert_all( after_attrs_and_comments, vec![ derive.syntax().syntax_element(), - make::tokens::whitespace(&format!("\n{indent}")).syntax_element(), + make.whitespace(&format!("\n{indent}")).syntax_element(), ], ); @@ -72,7 +75,9 @@ pub(crate) fn generate_derive(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opt .expect("failed to get token tree out of Meta") .r_paren_token() .expect("make::attr_outer was expected to have a R_PAREN"); + let tabstop_before = edit.make_tabstop_before(cap); + editor.add_annotation(delimiter, tabstop_before); edit.add_file_edits(ctx.vfs_file_id(), editor); } From 6df216934cbf91fc7e268111eb58f990447ea959 Mon Sep 17 00:00:00 2001 From: Guillaume Date: Wed, 25 Feb 2026 22:12:55 +0100 Subject: [PATCH 29/39] Fix LegacyKeyValueFormat report from docker build: aarch64-gnu-debug --- .../host-aarch64/aarch64-gnu-debug/Dockerfile | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/ci/docker/host-aarch64/aarch64-gnu-debug/Dockerfile b/src/ci/docker/host-aarch64/aarch64-gnu-debug/Dockerfile index 04b46226acfa4..7bc91243a5e2f 100644 --- a/src/ci/docker/host-aarch64/aarch64-gnu-debug/Dockerfile +++ b/src/ci/docker/host-aarch64/aarch64-gnu-debug/Dockerfile @@ -29,19 +29,18 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ COPY scripts/sccache.sh /scripts/ RUN sh /scripts/sccache.sh -ENV RUSTBUILD_FORCE_CLANG_BASED_TESTS 1 +ENV RUSTBUILD_FORCE_CLANG_BASED_TESTS="1" # llvm.use-linker conflicts with downloading CI LLVM -ENV NO_DOWNLOAD_CI_LLVM 1 +ENV NO_DOWNLOAD_CI_LLVM="1" -ENV RUST_CONFIGURE_ARGS \ - --build=aarch64-unknown-linux-gnu \ +ENV RUST_CONFIGURE_ARGS="--build=aarch64-unknown-linux-gnu \ --enable-debug \ --enable-lld \ --set llvm.use-linker=lld \ --set target.aarch64-unknown-linux-gnu.linker=clang \ --set target.aarch64-unknown-linux-gnu.cc=clang \ - --set target.aarch64-unknown-linux-gnu.cxx=clang++ + --set target.aarch64-unknown-linux-gnu.cxx=clang++" # This job appears to be checking two separate things: # - That we can build the compiler with `--enable-debug` @@ -52,6 +51,5 @@ ENV RUST_CONFIGURE_ARGS \ # Currently we only run the subset of tests with "clang" in their name. # - See also FIXME(#132034) -ENV SCRIPT \ - python3 ../x.py --stage 2 build && \ - python3 ../x.py --stage 2 test tests/run-make tests/run-make-cargo +ENV SCRIPT="python3 ../x.py --stage 2 build && \ + python3 ../x.py --stage 2 test tests/run-make tests/run-make-cargo" From 54c505fff04763acc13f95acf7df73536228206b Mon Sep 17 00:00:00 2001 From: is57primenumber <58158444+is57primenumber@users.noreply.github.com> Date: Fri, 27 Feb 2026 22:54:10 +0900 Subject: [PATCH 30/39] Fix ICE when macro-expanded extern crate shadows std --- compiler/rustc_resolve/src/imports.rs | 8 ++++-- .../resolve/ice-on-shadowing-std-with-attr.rs | 14 ++++++++++ .../ice-on-shadowing-std-with-attr.stderr | 28 +++++++++++++++++++ .../ice-on-shadowing-std-with-macro.rs | 19 +++++++++++++ .../ice-on-shadowing-std-with-macro.stderr | 27 ++++++++++++++++++ 5 files changed, 94 insertions(+), 2 deletions(-) create mode 100644 tests/ui/resolve/ice-on-shadowing-std-with-attr.rs create mode 100644 tests/ui/resolve/ice-on-shadowing-std-with-attr.stderr create mode 100644 tests/ui/resolve/ice-on-shadowing-std-with-macro.rs create mode 100644 tests/ui/resolve/ice-on-shadowing-std-with-macro.stderr diff --git a/compiler/rustc_resolve/src/imports.rs b/compiler/rustc_resolve/src/imports.rs index 704c316bce650..f0527740a58c7 100644 --- a/compiler/rustc_resolve/src/imports.rs +++ b/compiler/rustc_resolve/src/imports.rs @@ -1048,7 +1048,9 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { message, } => { if no_ambiguity { - assert!(import.imported_module.get().is_none()); + if !self.issue_145575_hack_applied { + assert!(import.imported_module.get().is_none()); + } self.report_error( span, ResolutionError::FailedToResolve { @@ -1072,7 +1074,9 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { .. } => { if no_ambiguity { - assert!(import.imported_module.get().is_none()); + if !self.issue_145575_hack_applied { + assert!(import.imported_module.get().is_none()); + } let module = if let Some(ModuleOrUniformRoot::Module(m)) = module { m.opt_def_id() } else { diff --git a/tests/ui/resolve/ice-on-shadowing-std-with-attr.rs b/tests/ui/resolve/ice-on-shadowing-std-with-attr.rs new file mode 100644 index 0000000000000..67f499e774e23 --- /dev/null +++ b/tests/ui/resolve/ice-on-shadowing-std-with-attr.rs @@ -0,0 +1,14 @@ +//@ edition: 2024 +// This test ensures that `extern crate` with attribute shadowing std does not cause ICE. +// Issue link: https://github.com/rust-lang/rust/issues/152895 + +#![crate_type = "lib"] + +#[foobar] //~ ERROR cannot find attribute `foobar` in this scope +extern crate core as std; //~ ERROR macro-expanded `extern crate` items cannot shadow names passed with `--extern` +mod inner { + use std::collections::hash_map::HashMap; //~ ERROR cannot find `collections` in `std` + use std::vec::IntoIter; //~ ERROR unresolved import `std::vec` + + use crate::*; +} diff --git a/tests/ui/resolve/ice-on-shadowing-std-with-attr.stderr b/tests/ui/resolve/ice-on-shadowing-std-with-attr.stderr new file mode 100644 index 0000000000000..f04852999b325 --- /dev/null +++ b/tests/ui/resolve/ice-on-shadowing-std-with-attr.stderr @@ -0,0 +1,28 @@ +error: macro-expanded `extern crate` items cannot shadow names passed with `--extern` + --> $DIR/ice-on-shadowing-std-with-attr.rs:8:1 + | +LL | extern crate core as std; + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + +error[E0433]: cannot find `collections` in `std` + --> $DIR/ice-on-shadowing-std-with-attr.rs:10:14 + | +LL | use std::collections::hash_map::HashMap; + | ^^^^^^^^^^^ could not find `collections` in `std` + +error[E0432]: unresolved import `std::vec` + --> $DIR/ice-on-shadowing-std-with-attr.rs:11:14 + | +LL | use std::vec::IntoIter; + | ^^^ could not find `vec` in `std` + +error: cannot find attribute `foobar` in this scope + --> $DIR/ice-on-shadowing-std-with-attr.rs:7:3 + | +LL | #[foobar] + | ^^^^^^ + +error: aborting due to 4 previous errors + +Some errors have detailed explanations: E0432, E0433. +For more information about an error, try `rustc --explain E0432`. diff --git a/tests/ui/resolve/ice-on-shadowing-std-with-macro.rs b/tests/ui/resolve/ice-on-shadowing-std-with-macro.rs new file mode 100644 index 0000000000000..fdefa7e8ce582 --- /dev/null +++ b/tests/ui/resolve/ice-on-shadowing-std-with-macro.rs @@ -0,0 +1,19 @@ +//@ edition: 2024 +// This test ensures that a macro-expanded `extern crate` shadowing std does not cause ICE. +// Issue link: https://github.com/rust-lang/rust/issues/152895 + +#![crate_type = "lib"] + +mod inner { + use std::collections::hash_map::HashMap; //~ ERROR cannot find `collections` in `std` + use std::vec::IntoIter; //~ ERROR unresolved import `std::vec` + + use crate::*; +} + +macro_rules! define_other_core { + () => { + extern crate core as std; //~ ERROR macro-expanded `extern crate` items cannot shadow names passed with `--extern` + }; +} +define_other_core! {} diff --git a/tests/ui/resolve/ice-on-shadowing-std-with-macro.stderr b/tests/ui/resolve/ice-on-shadowing-std-with-macro.stderr new file mode 100644 index 0000000000000..8bdf809775516 --- /dev/null +++ b/tests/ui/resolve/ice-on-shadowing-std-with-macro.stderr @@ -0,0 +1,27 @@ +error: macro-expanded `extern crate` items cannot shadow names passed with `--extern` + --> $DIR/ice-on-shadowing-std-with-macro.rs:16:9 + | +LL | extern crate core as std; + | ^^^^^^^^^^^^^^^^^^^^^^^^^ +... +LL | define_other_core! {} + | --------------------- in this macro invocation + | + = note: this error originates in the macro `define_other_core` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0433]: cannot find `collections` in `std` + --> $DIR/ice-on-shadowing-std-with-macro.rs:8:14 + | +LL | use std::collections::hash_map::HashMap; + | ^^^^^^^^^^^ could not find `collections` in `std` + +error[E0432]: unresolved import `std::vec` + --> $DIR/ice-on-shadowing-std-with-macro.rs:9:14 + | +LL | use std::vec::IntoIter; + | ^^^ could not find `vec` in `std` + +error: aborting due to 3 previous errors + +Some errors have detailed explanations: E0432, E0433. +For more information about an error, try `rustc --explain E0432`. From d52424c4246d94ad16ba931cb2e7d2ae33ab41cd Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Fri, 27 Feb 2026 14:41:26 +0000 Subject: [PATCH 31/39] Check usages of variables in subdiagnostics --- compiler/rustc_macros/src/diagnostics/subdiagnostic.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs b/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs index 7eb170ec236a3..5907e268e91b2 100644 --- a/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs +++ b/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs @@ -535,7 +535,7 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> { let mut calls = TokenStream::new(); for (kind, messages) in kind_messages { let message = format_ident!("__message"); - let message_stream = messages.diag_message(None); + let message_stream = messages.diag_message(Some(self.variant)); calls.extend(quote! { let #message = #diag.eagerly_translate(#message_stream); }); let name = format_ident!("{}{}", if span_field.is_some() { "span_" } else { "" }, kind); From 7b428597ff078b1f900a2cb9aa6257f34369f26e Mon Sep 17 00:00:00 2001 From: Benno Lossin Date: Fri, 20 Feb 2026 15:13:17 +0100 Subject: [PATCH 32/39] add field representing types --- Cargo.lock | 1 + compiler/rustc_ast/src/ast.rs | 5 + compiler/rustc_ast/src/util/classify.rs | 1 + compiler/rustc_ast_lowering/src/lib.rs | 7 + compiler/rustc_ast_pretty/src/pprust/state.rs | 17 ++ .../src/const_eval/machine.rs | 30 +++- .../rustc_const_eval/src/interpret/mod.rs | 4 +- compiler/rustc_feature/src/unstable.rs | 4 + compiler/rustc_hir/src/hir.rs | 10 ++ compiler/rustc_hir/src/intravisit.rs | 5 + compiler/rustc_hir/src/lang_items.rs | 7 + .../rustc_hir_analysis/src/check/intrinsic.rs | 2 + compiler/rustc_hir_analysis/src/errors.rs | 9 + .../src/hir_ty_lowering/mod.rs | 166 +++++++++++++++++- compiler/rustc_hir_pretty/src/lib.rs | 13 +- compiler/rustc_hir_typeck/src/errors.rs | 9 - compiler/rustc_hir_typeck/src/expr.rs | 3 +- compiler/rustc_middle/src/ty/adt.rs | 72 +++++++- .../src/ty/context/impl_interner.rs | 3 + compiler/rustc_middle/src/ty/print/pretty.rs | 12 +- compiler/rustc_middle/src/ty/sty.rs | 33 +++- .../src/solve/assembly/mod.rs | 6 + .../src/solve/effect_goals.rs | 7 + .../src/solve/normalizes_to/mod.rs | 25 ++- .../src/solve/trait_goals.rs | 50 +++++- compiler/rustc_parse/src/parser/expr.rs | 2 +- compiler/rustc_parse/src/parser/ty.rs | 48 +++++ compiler/rustc_passes/src/input_stats.rs | 2 + compiler/rustc_span/src/symbol.rs | 8 + .../src/traits/project.rs | 17 +- .../src/traits/select/candidate_assembly.rs | 59 ++++++- .../src/traits/select/confirmation.rs | 1 + compiler/rustc_ty_utils/src/instance.rs | 16 ++ compiler/rustc_type_ir/Cargo.toml | 1 + compiler/rustc_type_ir/src/inherent.rs | 12 +- compiler/rustc_type_ir/src/lang_items.rs | 3 + compiler/rustc_type_ir/src/lib.rs | 10 ++ library/core/src/field.rs | 83 +++++++++ library/core/src/intrinsics/mod.rs | 14 ++ library/core/src/lib.rs | 3 + library/std/src/lib.rs | 2 + src/librustdoc/clean/mod.rs | 8 + src/librustdoc/clean/types.rs | 4 + src/librustdoc/html/format.rs | 5 + src/librustdoc/html/render/search_index.rs | 1 + src/librustdoc/json/conversions.rs | 2 + .../clippy/clippy_lints/src/dereference.rs | 1 + .../clippy_utils/src/check_proc_macro.rs | 1 + .../clippy/clippy_utils/src/hir_utils.rs | 9 +- src/tools/clippy/clippy_utils/src/sym.rs | 1 - src/tools/rustfmt/src/types.rs | 8 + tests/ui/README.md | 6 + .../feature-gate-field-projections.rs | 19 ++ .../feature-gate-field-projections.stderr | 83 +++++++++ ...eature-gate-field-representing-type-raw.rs | 4 + ...re-gate-field-representing-type-raw.stderr | 12 ++ .../auxiliary/extern-crate.rs | 6 + .../incoherent-impl.next.stderr | 27 +++ .../incoherent-impl.old.stderr | 27 +++ .../incoherent-impl.rs | 28 +++ .../invalid.next.stderr | 71 ++++++++ .../invalid.old.stderr | 71 ++++++++ tests/ui/field_representing_types/invalid.rs | 32 ++++ .../non-struct.next.stderr | 27 +++ .../non-struct.old.stderr | 27 +++ .../ui/field_representing_types/non-struct.rs | 27 +++ .../nonexistent.next.stderr | 124 +++++++++++++ .../nonexistent.old.stderr | 124 +++++++++++++ .../field_representing_types/nonexistent.rs | 49 ++++++ .../not-field-if-packed.next.stderr | 15 ++ .../not-field-if-packed.old.stderr | 15 ++ .../not-field-if-packed.rs | 17 ++ tests/ui/field_representing_types/offset.rs | 52 ++++++ .../privacy.next.stderr | 15 ++ .../privacy.old.stderr | 15 ++ tests/ui/field_representing_types/privacy.rs | 34 ++++ .../projections.next.stderr | 32 ++++ .../projections.old.stderr | 32 ++++ .../field_representing_types/projections.rs | 58 ++++++ .../sized.next.stderr | 51 ++++++ .../field_representing_types/sized.old.stderr | 51 ++++++ tests/ui/field_representing_types/sized.rs | 38 ++++ tests/ui/field_representing_types/traits.rs | 29 +++ .../weird-impls.next.stderr | 30 ++++ .../weird-impls.old.stderr | 30 ++++ .../field_representing_types/weird-impls.rs | 37 ++++ 86 files changed, 2105 insertions(+), 32 deletions(-) create mode 100644 library/core/src/field.rs create mode 100644 tests/ui/feature-gates/feature-gate-field-projections.rs create mode 100644 tests/ui/feature-gates/feature-gate-field-projections.stderr create mode 100644 tests/ui/feature-gates/feature-gate-field-representing-type-raw.rs create mode 100644 tests/ui/feature-gates/feature-gate-field-representing-type-raw.stderr create mode 100644 tests/ui/field_representing_types/auxiliary/extern-crate.rs create mode 100644 tests/ui/field_representing_types/incoherent-impl.next.stderr create mode 100644 tests/ui/field_representing_types/incoherent-impl.old.stderr create mode 100644 tests/ui/field_representing_types/incoherent-impl.rs create mode 100644 tests/ui/field_representing_types/invalid.next.stderr create mode 100644 tests/ui/field_representing_types/invalid.old.stderr create mode 100644 tests/ui/field_representing_types/invalid.rs create mode 100644 tests/ui/field_representing_types/non-struct.next.stderr create mode 100644 tests/ui/field_representing_types/non-struct.old.stderr create mode 100644 tests/ui/field_representing_types/non-struct.rs create mode 100644 tests/ui/field_representing_types/nonexistent.next.stderr create mode 100644 tests/ui/field_representing_types/nonexistent.old.stderr create mode 100644 tests/ui/field_representing_types/nonexistent.rs create mode 100644 tests/ui/field_representing_types/not-field-if-packed.next.stderr create mode 100644 tests/ui/field_representing_types/not-field-if-packed.old.stderr create mode 100644 tests/ui/field_representing_types/not-field-if-packed.rs create mode 100644 tests/ui/field_representing_types/offset.rs create mode 100644 tests/ui/field_representing_types/privacy.next.stderr create mode 100644 tests/ui/field_representing_types/privacy.old.stderr create mode 100644 tests/ui/field_representing_types/privacy.rs create mode 100644 tests/ui/field_representing_types/projections.next.stderr create mode 100644 tests/ui/field_representing_types/projections.old.stderr create mode 100644 tests/ui/field_representing_types/projections.rs create mode 100644 tests/ui/field_representing_types/sized.next.stderr create mode 100644 tests/ui/field_representing_types/sized.old.stderr create mode 100644 tests/ui/field_representing_types/sized.rs create mode 100644 tests/ui/field_representing_types/traits.rs create mode 100644 tests/ui/field_representing_types/weird-impls.next.stderr create mode 100644 tests/ui/field_representing_types/weird-impls.old.stderr create mode 100644 tests/ui/field_representing_types/weird-impls.rs diff --git a/Cargo.lock b/Cargo.lock index 796e987fb7264..413b5df68f76d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4748,6 +4748,7 @@ dependencies = [ "ena", "indexmap", "rustc-hash 2.1.1", + "rustc_abi", "rustc_ast_ir", "rustc_data_structures", "rustc_error_messages", diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index 026758958f04f..04bad6142b6cc 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -2553,6 +2553,11 @@ pub enum TyKind { /// Pattern types like `pattern_type!(u32 is 1..=)`, which is the same as `NonZero`, /// just as part of the type system. Pat(Box, Box), + /// A `field_of` expression (e.g., `builtin # field_of(Struct, field)`). + /// + /// Usually not written directly in user code but indirectly via the macro + /// `core::field::field_of!(...)`. + FieldOf(Box, Option, Ident), /// Sometimes we need a dummy value when no error has occurred. Dummy, /// Placeholder for a kind that has failed to be defined. diff --git a/compiler/rustc_ast/src/util/classify.rs b/compiler/rustc_ast/src/util/classify.rs index 2e494b968b6bd..43ef6897b79cf 100644 --- a/compiler/rustc_ast/src/util/classify.rs +++ b/compiler/rustc_ast/src/util/classify.rs @@ -298,6 +298,7 @@ fn type_trailing_braced_mac_call(mut ty: &ast::Ty) -> Option<&ast::MacCall> { | ast::TyKind::ImplicitSelf | ast::TyKind::CVarArgs | ast::TyKind::Pat(..) + | ast::TyKind::FieldOf(..) | ast::TyKind::Dummy | ast::TyKind::Err(..) => break None, } diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index 24a7215ddb385..34db2b92cd781 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -1496,6 +1496,13 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { TyKind::Pat(ty, pat) => { hir::TyKind::Pat(self.lower_ty_alloc(ty, itctx), self.lower_ty_pat(pat, ty.span)) } + TyKind::FieldOf(ty, variant, field) => hir::TyKind::FieldOf( + self.lower_ty_alloc(ty, itctx), + self.arena.alloc(hir::TyFieldPath { + variant: variant.map(|variant| self.lower_ident(variant)), + field: self.lower_ident(*field), + }), + ), TyKind::MacCall(_) => { span_bug!(t.span, "`TyKind::MacCall` should have been expanded by now") } diff --git a/compiler/rustc_ast_pretty/src/pprust/state.rs b/compiler/rustc_ast_pretty/src/pprust/state.rs index c8874ed99dca9..f4168301bc5df 100644 --- a/compiler/rustc_ast_pretty/src/pprust/state.rs +++ b/compiler/rustc_ast_pretty/src/pprust/state.rs @@ -1377,6 +1377,23 @@ impl<'a> State<'a> { self.word(" is "); self.print_ty_pat(pat); } + ast::TyKind::FieldOf(ty, variant, field) => { + self.word("builtin # field_of"); + self.popen(); + let ib = self.ibox(0); + self.print_type(ty); + self.word(","); + self.space(); + + if let Some(variant) = variant { + self.print_ident(*variant); + self.word("."); + } + self.print_ident(*field); + + self.end(ib); + self.pclose(); + } } self.end(ib); } diff --git a/compiler/rustc_const_eval/src/const_eval/machine.rs b/compiler/rustc_const_eval/src/const_eval/machine.rs index 3da9f2e2cdcdf..0ddc50a01598e 100644 --- a/compiler/rustc_const_eval/src/const_eval/machine.rs +++ b/compiler/rustc_const_eval/src/const_eval/machine.rs @@ -12,8 +12,8 @@ use rustc_middle::mir::AssertMessage; use rustc_middle::mir::interpret::ReportedErrorInfo; use rustc_middle::query::TyCtxtAt; use rustc_middle::ty::layout::{HasTypingEnv, TyAndLayout, ValidityRequirement}; -use rustc_middle::ty::{self, Ty, TyCtxt}; -use rustc_middle::{bug, mir}; +use rustc_middle::ty::{self, FieldInfo, Ty, TyCtxt}; +use rustc_middle::{bug, mir, span_bug}; use rustc_span::{Span, Symbol, sym}; use rustc_target::callconv::FnAbi; use tracing::debug; @@ -23,8 +23,9 @@ use crate::errors::{LongRunning, LongRunningWarn}; use crate::interpret::{ self, AllocId, AllocInit, AllocRange, ConstAllocation, CtfeProvenance, FnArg, Frame, GlobalAlloc, ImmTy, InterpCx, InterpResult, OpTy, PlaceTy, Pointer, RangeSet, Scalar, - compile_time_machine, err_inval, interp_ok, throw_exhaust, throw_inval, throw_ub, - throw_ub_custom, throw_unsup, throw_unsup_format, type_implements_dyn_trait, + compile_time_machine, ensure_monomorphic_enough, err_inval, interp_ok, throw_exhaust, + throw_inval, throw_ub, throw_ub_custom, throw_unsup, throw_unsup_format, + type_implements_dyn_trait, }; /// When hitting this many interpreted terminators we emit a deny by default lint @@ -619,6 +620,27 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> { ecx.write_type_info(ty, dest)?; } + sym::field_offset => { + let frt_ty = instance.args.type_at(0); + ensure_monomorphic_enough(ecx.tcx.tcx, frt_ty)?; + + let (ty, variant, field) = if let ty::Adt(def, args) = frt_ty.kind() + && let Some(FieldInfo { base, variant_idx, field_idx, .. }) = + def.field_representing_type_info(ecx.tcx.tcx, args) + { + (base, variant_idx, field_idx) + } else { + span_bug!(ecx.cur_span(), "expected field representing type, got {frt_ty}") + }; + let layout = ecx.layout_of(ty)?; + let cx = ty::layout::LayoutCx::new(ecx.tcx.tcx, ecx.typing_env()); + + let layout = layout.for_variant(&cx, variant); + let offset = layout.fields.offset(field.index()).bytes(); + + ecx.write_scalar(Scalar::from_target_usize(offset, ecx), dest)?; + } + _ => { // We haven't handled the intrinsic, let's see if we can use a fallback body. if ecx.tcx.intrinsic(instance.def_id()).unwrap().must_be_overridden { diff --git a/compiler/rustc_const_eval/src/interpret/mod.rs b/compiler/rustc_const_eval/src/interpret/mod.rs index 3fbb043885715..c1d32e0f7bfbe 100644 --- a/compiler/rustc_const_eval/src/interpret/mod.rs +++ b/compiler/rustc_const_eval/src/interpret/mod.rs @@ -38,6 +38,8 @@ use self::place::{MemPlace, Place}; pub use self::projection::{OffsetMode, Projectable}; pub use self::stack::{Frame, FrameInfo, LocalState, ReturnContinuation}; pub use self::util::EnteredTraceSpan; -pub(crate) use self::util::{create_static_alloc, type_implements_dyn_trait}; +pub(crate) use self::util::{ + create_static_alloc, ensure_monomorphic_enough, type_implements_dyn_trait, +}; pub use self::validity::{CtfeValidationMode, RangeSet, RefTracking}; pub use self::visitor::ValueVisitor; diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index 1d123385961aa..f36102fdc73f0 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -221,6 +221,8 @@ declare_features! ( (internal, custom_mir, "1.65.0", None), /// Implementation details of externally implementable items (internal, eii_internals, "1.94.0", None), + /// Implementation details of field representing types. + (internal, field_representing_type_raw, "CURRENT_RUSTC_VERSION", None), /// Outputs useful `assert!` messages (unstable, generic_assert, "1.63.0", None), /// Allows using the #[rustc_intrinsic] attribute. @@ -486,6 +488,8 @@ declare_features! ( (unstable, ffi_const, "1.45.0", Some(58328)), /// Allows the use of `#[ffi_pure]` on foreign functions. (unstable, ffi_pure, "1.45.0", Some(58329)), + /// Experimental field projections. + (incomplete, field_projections, "CURRENT_RUSTC_VERSION", Some(145383)), /// Allows marking trait functions as `final` to prevent overriding impls (unstable, final_associated_functions, "CURRENT_RUSTC_VERSION", Some(131179)), /// Controlling the behavior of fmt::Debug diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index 960fd206041a8..889dea461e06a 100644 --- a/compiler/rustc_hir/src/hir.rs +++ b/compiler/rustc_hir/src/hir.rs @@ -1741,6 +1741,12 @@ impl<'hir> Block<'hir> { } } +#[derive(Debug, Clone, Copy, HashStable_Generic)] +pub struct TyFieldPath { + pub variant: Option, + pub field: Ident, +} + #[derive(Debug, Clone, Copy, HashStable_Generic)] pub struct TyPat<'hir> { #[stable_hasher(ignore)] @@ -3804,6 +3810,10 @@ pub enum TyKind<'hir, Unambig = ()> { Err(rustc_span::ErrorGuaranteed), /// Pattern types (`pattern_type!(u32 is 1..)`) Pat(&'hir Ty<'hir>, &'hir TyPat<'hir>), + /// Field representing type (`field_of!(Struct, field)`). + /// + /// The optional ident is the variant when an enum is passed `field_of!(Enum, Variant.field)`. + FieldOf(&'hir Ty<'hir>, &'hir TyFieldPath), /// `TyKind::Infer` means the type should be inferred instead of it having been /// specified. This can appear anywhere in a type. /// diff --git a/compiler/rustc_hir/src/intravisit.rs b/compiler/rustc_hir/src/intravisit.rs index 21bcf53b5619f..6a69326d8c847 100644 --- a/compiler/rustc_hir/src/intravisit.rs +++ b/compiler/rustc_hir/src/intravisit.rs @@ -1047,6 +1047,11 @@ pub fn walk_ty<'v, V: Visitor<'v>>(visitor: &mut V, typ: &'v Ty<'v, AmbigArg>) - try_visit!(visitor.visit_ty_unambig(ty)); try_visit!(visitor.visit_pattern_type_pattern(pat)); } + TyKind::FieldOf(ty, TyFieldPath { variant, field }) => { + try_visit!(visitor.visit_ty_unambig(ty)); + visit_opt!(visitor, visit_ident, *variant); + try_visit!(visitor.visit_ident(*field)); + } } V::Result::output() } diff --git a/compiler/rustc_hir/src/lang_items.rs b/compiler/rustc_hir/src/lang_items.rs index f57b1a9c8dea3..9ec681c374574 100644 --- a/compiler/rustc_hir/src/lang_items.rs +++ b/compiler/rustc_hir/src/lang_items.rs @@ -435,6 +435,13 @@ language_item_table! { // Reborrowing related lang-items Reborrow, sym::reborrow, reborrow, Target::Trait, GenericRequirement::Exact(0); CoerceShared, sym::coerce_shared, coerce_shared, Target::Trait, GenericRequirement::Exact(0); + + // Field representing types. + FieldRepresentingType, sym::field_representing_type, field_representing_type, Target::Struct, GenericRequirement::Exact(3); + Field, sym::field, field, Target::Trait, GenericRequirement::Exact(0); + FieldBase, sym::field_base, field_base, Target::AssocTy, GenericRequirement::Exact(0); + FieldType, sym::field_type, field_type, Target::AssocTy, GenericRequirement::Exact(0); + FieldOffset, sym::field_offset, field_offset, Target::AssocConst, GenericRequirement::Exact(0); } /// The requirement imposed on the generics of a lang item diff --git a/compiler/rustc_hir_analysis/src/check/intrinsic.rs b/compiler/rustc_hir_analysis/src/check/intrinsic.rs index 9167116ca5717..a1c8c0150a66e 100644 --- a/compiler/rustc_hir_analysis/src/check/intrinsic.rs +++ b/compiler/rustc_hir_analysis/src/check/intrinsic.rs @@ -117,6 +117,7 @@ fn intrinsic_operation_unsafety(tcx: TyCtxt<'_>, intrinsic_id: LocalDefId) -> hi | sym::fabsf128 | sym::fadd_algebraic | sym::fdiv_algebraic + | sym::field_offset | sym::floorf16 | sym::floorf32 | sym::floorf64 @@ -297,6 +298,7 @@ pub(crate) fn check_intrinsic_type( (1, 0, vec![Ty::new_imm_ptr(tcx, param(0))], tcx.types.usize) } sym::offset_of => (1, 0, vec![tcx.types.u32, tcx.types.u32], tcx.types.usize), + sym::field_offset => (1, 0, vec![], tcx.types.usize), sym::rustc_peek => (1, 0, vec![param(0)], param(0)), sym::caller_location => (0, 0, vec![], tcx.caller_location_ty()), sym::assert_inhabited | sym::assert_zero_valid | sym::assert_mem_uninitialized_valid => { diff --git a/compiler/rustc_hir_analysis/src/errors.rs b/compiler/rustc_hir_analysis/src/errors.rs index c91b67e763811..c9edba6a87962 100644 --- a/compiler/rustc_hir_analysis/src/errors.rs +++ b/compiler/rustc_hir_analysis/src/errors.rs @@ -1446,6 +1446,15 @@ pub struct NoVariantNamed<'tcx> { pub ty: Ty<'tcx>, } +#[derive(Diagnostic)] +#[diag("no field `{$field}` on type `{$ty}`", code = E0609)] +pub struct NoFieldOnType<'tcx> { + #[primary_span] + pub span: Span, + pub ty: Ty<'tcx>, + pub field: Ident, +} + // FIXME(fmease): Deduplicate: #[derive(Diagnostic)] diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs index 3f82375308927..e3a1da25e8c3b 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs @@ -21,6 +21,7 @@ pub mod generics; use std::slice; +use rustc_abi::FIRST_VARIANT; use rustc_ast::LitKind; use rustc_data_structures::assert_matches; use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet}; @@ -50,11 +51,11 @@ use rustc_trait_selection::traits::{self, FulfillmentError}; use tracing::{debug, instrument}; use crate::check::check_abi; -use crate::check_c_variadic_abi; -use crate::errors::{AmbiguousLifetimeBound, BadReturnTypeNotation}; +use crate::errors::{AmbiguousLifetimeBound, BadReturnTypeNotation, NoFieldOnType}; use crate::hir_ty_lowering::errors::{GenericsArgsErrExtend, prohibit_assoc_item_constraint}; use crate::hir_ty_lowering::generics::{check_generic_arg_count, lower_generic_args}; use crate::middle::resolve_bound_vars as rbv; +use crate::{NoVariantNamed, check_c_variadic_abi}; /// The context in which an implied bound is being added to a item being lowered (i.e. a sizedness /// trait or a default trait) @@ -3046,6 +3047,14 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { self.record_ty(pat.hir_id, ty, pat.span); pat_ty } + hir::TyKind::FieldOf(ty, hir::TyFieldPath { variant, field }) => self.lower_field_of( + self.lower_ty(ty), + self.item_def_id(), + ty.span, + hir_ty.hir_id, + *variant, + *field, + ), hir::TyKind::Err(guar) => Ty::new_error(tcx, *guar), }; @@ -3087,6 +3096,159 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { } } + fn lower_field_of( + &self, + ty: Ty<'tcx>, + item_def_id: LocalDefId, + ty_span: Span, + hir_id: HirId, + variant: Option, + field: Ident, + ) -> Ty<'tcx> { + let dcx = self.dcx(); + let tcx = self.tcx(); + match ty.kind() { + ty::Adt(def, _) => { + let base_did = def.did(); + let kind_name = tcx.def_descr(base_did); + let (variant_idx, variant) = if def.is_enum() { + let Some(variant) = variant else { + let err = dcx + .create_err(NoVariantNamed { span: field.span, ident: field, ty }) + .with_span_help( + field.span.shrink_to_lo(), + "you might be missing a variant here: `Variant.`", + ) + .emit(); + return Ty::new_error(tcx, err); + }; + + if let Some(res) = def + .variants() + .iter_enumerated() + .find(|(_, f)| f.ident(tcx).normalize_to_macros_2_0() == variant) + { + res + } else { + let err = dcx + .create_err(NoVariantNamed { span: variant.span, ident: variant, ty }) + .emit(); + return Ty::new_error(tcx, err); + } + } else { + if let Some(variant) = variant { + let adt_path = tcx.def_path_str(base_did); + struct_span_code_err!( + dcx, + variant.span, + E0609, + "{kind_name} `{adt_path}` does not have any variants", + ) + .with_span_label(variant.span, "variant unknown") + .emit(); + } + (FIRST_VARIANT, def.non_enum_variant()) + }; + let block = tcx.local_def_id_to_hir_id(item_def_id); + let (ident, def_scope) = tcx.adjust_ident_and_get_scope(field, def.did(), block); + if let Some((field_idx, field)) = variant + .fields + .iter_enumerated() + .find(|(_, f)| f.ident(tcx).normalize_to_macros_2_0() == ident) + { + if field.vis.is_accessible_from(def_scope, tcx) { + tcx.check_stability(field.did, Some(hir_id), ident.span, None); + } else { + let adt_path = tcx.def_path_str(base_did); + struct_span_code_err!( + dcx, + ident.span, + E0616, + "field `{ident}` of {kind_name} `{adt_path}` is private", + ) + .with_span_label(ident.span, "private field") + .emit(); + } + Ty::new_field_representing_type(tcx, ty, variant_idx, field_idx) + } else { + let err = + dcx.create_err(NoFieldOnType { span: ident.span, field: ident, ty }).emit(); + Ty::new_error(tcx, err) + } + } + ty::Tuple(tys) => { + let index = match field.as_str().parse::() { + Ok(idx) => idx, + Err(_) => { + let err = + dcx.create_err(NoFieldOnType { span: field.span, field, ty }).emit(); + return Ty::new_error(tcx, err); + } + }; + if field.name != sym::integer(index) { + bug!("we parsed above, but now not equal?"); + } + if tys.get(index).is_some() { + Ty::new_field_representing_type(tcx, ty, FIRST_VARIANT, index.into()) + } else { + let err = dcx.create_err(NoFieldOnType { span: field.span, field, ty }).emit(); + Ty::new_error(tcx, err) + } + } + // FIXME(FRTs): support type aliases + /* + ty::Alias(AliasTyKind::Free, ty) => { + return self.lower_field_of( + ty, + item_def_id, + ty_span, + hir_id, + variant, + field, + ); + }*/ + ty::Alias(..) => Ty::new_error( + tcx, + dcx.span_err(ty_span, format!("could not resolve fields of `{ty}`")), + ), + ty::Error(err) => Ty::new_error(tcx, *err), + ty::Bool + | ty::Char + | ty::Int(_) + | ty::Uint(_) + | ty::Float(_) + | ty::Foreign(_) + | ty::Str + | ty::RawPtr(_, _) + | ty::Ref(_, _, _) + | ty::FnDef(_, _) + | ty::FnPtr(_, _) + | ty::UnsafeBinder(_) + | ty::Dynamic(_, _) + | ty::Closure(_, _) + | ty::CoroutineClosure(_, _) + | ty::Coroutine(_, _) + | ty::CoroutineWitness(_, _) + | ty::Never + | ty::Param(_) + | ty::Bound(_, _) + | ty::Placeholder(_) + | ty::Slice(..) => Ty::new_error( + tcx, + dcx.span_err(ty_span, format!("type `{ty}` doesn't have fields")), + ), + ty::Infer(_) => Ty::new_error( + tcx, + dcx.span_err(ty_span, format!("cannot use `{ty}` in this position")), + ), + // FIXME(FRTs): support these types? + ty::Array(..) | ty::Pat(..) => Ty::new_error( + tcx, + dcx.span_err(ty_span, format!("type `{ty}` is not yet supported in `field_of!`")), + ), + } + } + /// Lower an opaque type (i.e., an existential impl-Trait type) from the HIR. #[instrument(level = "debug", skip(self), ret)] fn lower_opaque_ty(&self, def_id: LocalDefId, in_trait: Option) -> Ty<'tcx> { diff --git a/compiler/rustc_hir_pretty/src/lib.rs b/compiler/rustc_hir_pretty/src/lib.rs index fc6cc4e672517..ec0688b64f3dd 100644 --- a/compiler/rustc_hir_pretty/src/lib.rs +++ b/compiler/rustc_hir_pretty/src/lib.rs @@ -19,7 +19,7 @@ use rustc_hir::attrs::{AttributeKind, PrintAttribute}; use rustc_hir::{ BindingMode, ByRef, ConstArg, ConstArgExprField, ConstArgKind, GenericArg, GenericBound, GenericParam, GenericParamKind, HirId, ImplicitSelfKind, LifetimeParamKind, Node, PatKind, - PreciseCapturingArg, RangeEnd, Term, TyPatKind, + PreciseCapturingArg, RangeEnd, Term, TyFieldPath, TyPatKind, }; use rustc_span::source_map::{SourceMap, Spanned}; use rustc_span::{DUMMY_SP, FileName, Ident, Span, Symbol, kw, sym}; @@ -464,6 +464,17 @@ impl<'a> State<'a> { self.word(" is "); self.print_ty_pat(pat); } + hir::TyKind::FieldOf(ty, TyFieldPath { variant, field }) => { + self.word("field_of!("); + self.print_type(ty); + self.word(", "); + if let Some(variant) = *variant { + self.print_ident(variant); + self.word("."); + } + self.print_ident(*field); + self.word(")"); + } } self.end(ib) } diff --git a/compiler/rustc_hir_typeck/src/errors.rs b/compiler/rustc_hir_typeck/src/errors.rs index 8251669ea8d2a..0f33cf0b8433b 100644 --- a/compiler/rustc_hir_typeck/src/errors.rs +++ b/compiler/rustc_hir_typeck/src/errors.rs @@ -490,15 +490,6 @@ impl HelpUseLatestEdition { } } -#[derive(Diagnostic)] -#[diag("no field `{$field}` on type `{$ty}`", code = E0609)] -pub(crate) struct NoFieldOnType<'tcx> { - #[primary_span] - pub(crate) span: Span, - pub(crate) ty: Ty<'tcx>, - pub(crate) field: Ident, -} - #[derive(Diagnostic)] #[diag("no field named `{$field}` on enum variant `{$container}::{$ident}`", code = E0609)] pub(crate) struct NoFieldOnVariant<'tcx> { diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index f3ca04c6bbe27..fd83f6f9ae1eb 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -20,6 +20,7 @@ use rustc_hir::def_id::DefId; use rustc_hir::lang_items::LangItem; use rustc_hir::{ExprKind, HirId, QPath, find_attr, is_range_literal}; use rustc_hir_analysis::NoVariantNamed; +use rustc_hir_analysis::errors::NoFieldOnType; use rustc_hir_analysis::hir_ty_lowering::HirTyLowerer as _; use rustc_infer::infer::{self, DefineOpaqueTypes, InferOk, RegionVariableOrigin}; use rustc_infer::traits::query::NoSolution; @@ -43,7 +44,7 @@ use crate::coercion::CoerceMany; use crate::errors::{ AddressOfTemporaryTaken, BaseExpressionDoubleDot, BaseExpressionDoubleDotAddExpr, BaseExpressionDoubleDotRemove, CantDereference, FieldMultiplySpecifiedInInitializer, - FunctionalRecordUpdateOnNonStruct, HelpUseLatestEdition, NakedAsmOutsideNakedFn, NoFieldOnType, + FunctionalRecordUpdateOnNonStruct, HelpUseLatestEdition, NakedAsmOutsideNakedFn, NoFieldOnVariant, ReturnLikeStatementKind, ReturnStmtOutsideOfFnBody, StructExprNonExhaustive, TypeMismatchFruTypo, YieldExprOutsideOfCoroutine, }; diff --git a/compiler/rustc_middle/src/ty/adt.rs b/compiler/rustc_middle/src/ty/adt.rs index 020669588ac5c..c141eb0311dcf 100644 --- a/compiler/rustc_middle/src/ty/adt.rs +++ b/compiler/rustc_middle/src/ty/adt.rs @@ -3,7 +3,7 @@ use std::hash::{Hash, Hasher}; use std::ops::Range; use std::str; -use rustc_abi::{FIRST_VARIANT, ReprOptions, VariantIdx}; +use rustc_abi::{FIRST_VARIANT, FieldIdx, ReprOptions, VariantIdx}; use rustc_data_structures::fingerprint::Fingerprint; use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::intern::Interned; @@ -15,6 +15,8 @@ use rustc_hir::{self as hir, LangItem, find_attr}; use rustc_index::{IndexSlice, IndexVec}; use rustc_macros::{HashStable, TyDecodable, TyEncodable}; use rustc_session::DataTypeKind; +use rustc_span::sym; +use rustc_type_ir::FieldInfo; use rustc_type_ir::solve::AdtDestructorKind; use tracing::{debug, info, trace}; @@ -23,8 +25,8 @@ use super::{ }; use crate::ich::StableHashingContext; use crate::mir::interpret::ErrorHandled; -use crate::ty; use crate::ty::util::{Discr, IntTypeExt}; +use crate::ty::{self, ConstKind}; #[derive(Clone, Copy, PartialEq, Eq, Hash, HashStable, TyEncodable, TyDecodable)] pub struct AdtFlags(u16); @@ -58,6 +60,8 @@ bitflags::bitflags! { const IS_PIN = 1 << 11; /// Indicates whether the type is `#[pin_project]`. const IS_PIN_PROJECT = 1 << 12; + /// Indicates whether the type is `FieldRepresentingType`. + const IS_FIELD_REPRESENTING_TYPE = 1 << 13; } } rustc_data_structures::external_bitflags_debug! { AdtFlags } @@ -200,6 +204,51 @@ impl<'tcx> AdtDef<'tcx> { pub fn repr(self) -> ReprOptions { self.0.0.repr } + + pub fn field_representing_type_info( + self, + tcx: TyCtxt<'tcx>, + args: ty::GenericArgsRef<'tcx>, + ) -> Option>> { + if !self.is_field_representing_type() { + return None; + } + let base = args.type_at(0); + let variant_idx = match args.const_at(1).kind() { + ConstKind::Value(v) => VariantIdx::from_u32(v.to_leaf().to_u32()), + _ => return None, + }; + let field_idx = match args.const_at(2).kind() { + ConstKind::Value(v) => FieldIdx::from_u32(v.to_leaf().to_u32()), + _ => return None, + }; + let (ty, variant, name) = match base.kind() { + ty::Adt(base_def, base_args) => { + let variant = base_def.variant(variant_idx); + let field = &variant.fields[field_idx]; + (field.ty(tcx, base_args), base_def.is_enum().then_some(variant.name), field.name) + } + ty::Tuple(tys) => { + if variant_idx != FIRST_VARIANT { + bug!("expected variant of tuple to be FIRST_VARIANT, but found {variant_idx:?}") + } + ( + if let Some(ty) = tys.get(field_idx.index()) { + *ty + } else { + bug!( + "expected valid tuple index, but got {field_idx:?}, tuple length: {}", + tys.len() + ) + }, + None, + sym::integer(field_idx.index()), + ) + } + _ => panic!(), + }; + Some(FieldInfo { base, ty, variant, variant_idx, name, field_idx }) + } } impl<'tcx> rustc_type_ir::inherent::AdtDef> for AdtDef<'tcx> { @@ -211,6 +260,10 @@ impl<'tcx> rustc_type_ir::inherent::AdtDef> for AdtDef<'tcx> { self.is_struct() } + fn is_packed(self) -> bool { + self.repr().packed() + } + fn struct_tail_ty(self, interner: TyCtxt<'tcx>) -> Option>> { Some(interner.type_of(self.non_enum_variant().tail_opt()?.did)) } @@ -223,6 +276,14 @@ impl<'tcx> rustc_type_ir::inherent::AdtDef> for AdtDef<'tcx> { self.is_manually_drop() } + fn field_representing_type_info( + self, + tcx: TyCtxt<'tcx>, + args: ty::GenericArgsRef<'tcx>, + ) -> Option>> { + self.field_representing_type_info(tcx, args) + } + fn all_field_tys( self, tcx: TyCtxt<'tcx>, @@ -321,6 +382,9 @@ impl AdtDefData { if tcx.is_lang_item(did, LangItem::Pin) { flags |= AdtFlags::IS_PIN; } + if tcx.is_lang_item(did, LangItem::FieldRepresentingType) { + flags |= AdtFlags::IS_FIELD_REPRESENTING_TYPE; + } AdtDefData { did, variants, flags, repr } } @@ -449,6 +513,10 @@ impl<'tcx> AdtDef<'tcx> { self.flags().contains(AdtFlags::IS_PIN_PROJECT) } + pub fn is_field_representing_type(self) -> bool { + self.flags().contains(AdtFlags::IS_FIELD_REPRESENTING_TYPE) + } + /// Returns `true` if this type has a destructor. pub fn has_dtor(self, tcx: TyCtxt<'tcx>) -> bool { self.destructor(tcx).is_some() diff --git a/compiler/rustc_middle/src/ty/context/impl_interner.rs b/compiler/rustc_middle/src/ty/context/impl_interner.rs index a4aeaacbf05db..d369ea1ba2453 100644 --- a/compiler/rustc_middle/src/ty/context/impl_interner.rs +++ b/compiler/rustc_middle/src/ty/context/impl_interner.rs @@ -755,6 +755,8 @@ bidirectional_lang_item_map! { CoroutineReturn, CoroutineYield, DynMetadata, + FieldBase, + FieldType, FutureOutput, Metadata, // tidy-alphabetical-end @@ -786,6 +788,7 @@ bidirectional_lang_item_map! { Destruct, DiscriminantKind, Drop, + Field, Fn, FnMut, FnOnce, diff --git a/compiler/rustc_middle/src/ty/print/pretty.rs b/compiler/rustc_middle/src/ty/print/pretty.rs index 02b804c1ab29c..a3fd19e3d8f54 100644 --- a/compiler/rustc_middle/src/ty/print/pretty.rs +++ b/compiler/rustc_middle/src/ty/print/pretty.rs @@ -17,7 +17,7 @@ use rustc_hir::limit::Limit; use rustc_macros::{Lift, extension}; use rustc_session::cstore::{ExternCrate, ExternCrateSource}; use rustc_span::{Ident, RemapPathScopeComponents, Symbol, kw, sym}; -use rustc_type_ir::{Upcast as _, elaborate}; +use rustc_type_ir::{FieldInfo, Upcast as _, elaborate}; use smallvec::SmallVec; // `pretty` is a separate module only for organization. @@ -793,6 +793,16 @@ pub trait PrettyPrinter<'tcx>: Printer<'tcx> + fmt::Write { false => write!(self, "{}", self.tcx().item_name(def_id))?, }, }, + ty::Adt(def, args) + if let Some(FieldInfo { base, variant, name, .. }) = + def.field_representing_type_info(self.tcx(), args) => + { + if let Some(variant) = variant { + write!(self, "field_of!({base}, {variant}.{name})")?; + } else { + write!(self, "field_of!({base}, {name})")?; + } + } ty::Adt(def, args) => self.print_def_path(def.did(), args)?, ty::Dynamic(data, r) => { let print_r = self.should_print_optional_region(r); diff --git a/compiler/rustc_middle/src/ty/sty.rs b/compiler/rustc_middle/src/ty/sty.rs index 4be30d8b6c918..21ecedb6480aa 100644 --- a/compiler/rustc_middle/src/ty/sty.rs +++ b/compiler/rustc_middle/src/ty/sty.rs @@ -26,8 +26,8 @@ use crate::infer::canonical::Canonical; use crate::traits::ObligationCause; use crate::ty::InferTy::*; use crate::ty::{ - self, AdtDef, Discr, GenericArg, GenericArgs, GenericArgsRef, List, ParamEnv, Region, Ty, - TyCtxt, TypeFlags, TypeSuperVisitable, TypeVisitable, TypeVisitor, UintTy, + self, AdtDef, Const, Discr, GenericArg, GenericArgs, GenericArgsRef, List, ParamEnv, Region, + Ty, TyCtxt, TypeFlags, TypeSuperVisitable, TypeVisitable, TypeVisitor, UintTy, ValTree, }; // Re-export and re-parameterize some `I = TyCtxt<'tcx>` types here @@ -487,6 +487,35 @@ impl<'tcx> Ty<'tcx> { Ty::new(tcx, Pat(base, pat)) } + #[inline] + pub fn new_field_representing_type( + tcx: TyCtxt<'tcx>, + base: Ty<'tcx>, + variant: VariantIdx, + field: FieldIdx, + ) -> Ty<'tcx> { + let Some(did) = tcx.lang_items().field_representing_type() else { + bug!("could not locate the `FieldRepresentingType` lang item") + }; + let def = tcx.adt_def(did); + let args = tcx.mk_args(&[ + base.into(), + Const::new_value( + tcx, + ValTree::from_scalar_int(tcx, variant.as_u32().into()), + tcx.types.u32, + ) + .into(), + Const::new_value( + tcx, + ValTree::from_scalar_int(tcx, field.as_u32().into()), + tcx.types.u32, + ) + .into(), + ]); + Ty::new_adt(tcx, def, args) + } + #[inline] #[instrument(level = "debug", skip(tcx))] pub fn new_opaque(tcx: TyCtxt<'tcx>, def_id: DefId, args: GenericArgsRef<'tcx>) -> Ty<'tcx> { 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 63f246db8a5fb..038168ca638b4 100644 --- a/compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs +++ b/compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs @@ -348,6 +348,11 @@ where ecx: &mut EvalCtxt<'_, D>, goal: Goal, ) -> Vec>; + + fn consider_builtin_field_candidate( + ecx: &mut EvalCtxt<'_, D>, + goal: Goal, + ) -> Result, NoSolution>; } /// Allows callers of `assemble_and_evaluate_candidates` to choose whether to limit @@ -617,6 +622,7 @@ where Some(SolverTraitLangItem::BikeshedGuaranteedNoDrop) => { G::consider_builtin_bikeshed_guaranteed_no_drop_candidate(self, goal) } + Some(SolverTraitLangItem::Field) => G::consider_builtin_field_candidate(self, goal), _ => Err(NoSolution), } }; 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 2837b8565f603..4da76b88b5de0 100644 --- a/compiler/rustc_next_trait_solver/src/solve/effect_goals.rs +++ b/compiler/rustc_next_trait_solver/src/solve/effect_goals.rs @@ -430,6 +430,13 @@ where ) -> Vec> { unreachable!("Unsize is not const") } + + fn consider_builtin_field_candidate( + _ecx: &mut EvalCtxt<'_, D>, + _goal: Goal<::Interner, Self>, + ) -> Result::Interner>, NoSolution> { + unreachable!("Field is not const") + } } impl EvalCtxt<'_, D> 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 70c28421c57ea..13f2ad8f82eba 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 @@ -7,7 +7,7 @@ use rustc_type_ir::fast_reject::DeepRejectCtxt; use rustc_type_ir::inherent::*; use rustc_type_ir::lang_items::{SolverAdtLangItem, SolverLangItem, SolverTraitLangItem}; use rustc_type_ir::solve::SizedTraitKind; -use rustc_type_ir::{self as ty, Interner, NormalizesTo, PredicateKind, Upcast as _}; +use rustc_type_ir::{self as ty, FieldInfo, Interner, NormalizesTo, PredicateKind, Upcast as _}; use tracing::instrument; use crate::delegate::SolverDelegate; @@ -950,6 +950,29 @@ where ) -> Result, NoSolution> { unreachable!("`BikeshedGuaranteedNoDrop` does not have an associated type: {:?}", goal) } + + fn consider_builtin_field_candidate( + ecx: &mut EvalCtxt<'_, D>, + goal: Goal, + ) -> Result, NoSolution> { + let self_ty = goal.predicate.self_ty(); + let ty::Adt(def, args) = self_ty.kind() else { + return Err(NoSolution); + }; + let Some(FieldInfo { base, ty, .. }) = def.field_representing_type_info(ecx.cx(), args) + else { + return Err(NoSolution); + }; + let ty = match ecx.cx().as_lang_item(goal.predicate.def_id()) { + Some(SolverLangItem::FieldBase) => base, + Some(SolverLangItem::FieldType) => ty, + _ => panic!("unexpected associated type {:?} in `Field`", goal.predicate), + }; + ecx.probe_builtin_trait_candidate(BuiltinImplSource::Misc).enter(|ecx| { + ecx.instantiate_normalizes_to_term(goal, ty.into()); + ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) + }) + } } impl EvalCtxt<'_, D> 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 dd012a5146e61..6589a12c4cc2d 100644 --- a/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs +++ b/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs @@ -8,7 +8,7 @@ use rustc_type_ir::solve::{ AliasBoundKind, CandidatePreferenceMode, CanonicalResponse, SizedTraitKind, }; use rustc_type_ir::{ - self as ty, Interner, Movability, PredicatePolarity, TraitPredicate, TraitRef, + self as ty, FieldInfo, Interner, Movability, PredicatePolarity, TraitPredicate, TraitRef, TypeVisitableExt as _, TypingMode, Upcast as _, elaborate, }; use tracing::{debug, instrument, trace}; @@ -844,6 +844,54 @@ where } }) } + + fn consider_builtin_field_candidate( + ecx: &mut EvalCtxt<'_, D>, + goal: Goal, + ) -> Result, NoSolution> { + if goal.predicate.polarity != ty::PredicatePolarity::Positive { + return Err(NoSolution); + } + if let ty::Adt(def, args) = goal.predicate.self_ty().kind() + && let Some(FieldInfo { base, ty, .. }) = + def.field_representing_type_info(ecx.cx(), args) + && { + let sized_trait = ecx.cx().require_trait_lang_item(SolverTraitLangItem::Sized); + // FIXME: add better support for builtin impls of traits that check for the bounds + // on the trait definition in std. + + // NOTE: these bounds have to be kept in sync with the definition of the `Field` + // trait in `library/core/src/field.rs` as well as the old trait solver `fn + // assemble_candidates_for_field_trait` in + // `compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs`. + ecx.add_goal( + GoalSource::ImplWhereBound, + Goal { + param_env: goal.param_env, + predicate: TraitRef::new(ecx.cx(), sized_trait, [base]).upcast(ecx.cx()), + }, + ); + ecx.add_goal( + GoalSource::ImplWhereBound, + Goal { + param_env: goal.param_env, + predicate: TraitRef::new(ecx.cx(), sized_trait, [ty]).upcast(ecx.cx()), + }, + ); + ecx.try_evaluate_added_goals()? == Certainty::Yes + } + && match base.kind() { + ty::Adt(def, _) => def.is_struct() && !def.is_packed(), + ty::Tuple(..) => true, + _ => false, + } + { + ecx.probe_builtin_trait_candidate(BuiltinImplSource::Misc) + .enter(|ecx| ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)) + } else { + Err(NoSolution) + } + } } /// Small helper function to change the `def_id` of a trait predicate - this is not normally diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index 52dcade91aeaf..8a9a5a3469f02 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -1140,7 +1140,7 @@ impl<'a> Parser<'a> { /// Parse the field access used in offset_of, matched by `$(e:expr)+`. /// Currently returns a list of idents. However, it should be possible in /// future to also do array indices, which might be arbitrary expressions. - fn parse_floating_field_access(&mut self) -> PResult<'a, Vec> { + pub(crate) fn parse_floating_field_access(&mut self) -> PResult<'a, Vec> { let mut fields = Vec::new(); let mut trailing_dot = None; diff --git a/compiler/rustc_parse/src/parser/ty.rs b/compiler/rustc_parse/src/parser/ty.rs index 6ff165eb22b71..3e1616dbe5cf1 100644 --- a/compiler/rustc_parse/src/parser/ty.rs +++ b/compiler/rustc_parse/src/parser/ty.rs @@ -329,6 +329,8 @@ impl<'a> Parser<'a> { self.parse_borrowed_pointee()? } else if self.eat_keyword_noexpect(kw::Typeof) { self.parse_typeof_ty(lo)? + } else if self.is_builtin() { + self.parse_builtin_ty()? } else if self.eat_keyword(exp!(Underscore)) { // A type to be inferred `_` TyKind::Infer @@ -803,6 +805,52 @@ impl<'a> Parser<'a> { Ok(TyKind::Err(guar)) } + fn parse_builtin_ty(&mut self) -> PResult<'a, TyKind> { + self.parse_builtin(|this, lo, ident| { + Ok(match ident.name { + sym::field_of => Some(this.parse_ty_field_of(lo)?), + _ => None, + }) + }) + } + + pub(crate) fn parse_ty_field_of(&mut self, _lo: Span) -> PResult<'a, TyKind> { + let container = self.parse_ty()?; + self.expect(exp!(Comma))?; + + let fields = self.parse_floating_field_access()?; + let trailing_comma = self.eat_noexpect(&TokenKind::Comma); + + if let Err(mut e) = self.expect_one_of(&[], &[exp!(CloseParen)]) { + if trailing_comma { + e.note("unexpected third argument to field_of"); + } else { + e.note("field_of expects dot-separated field and variant names"); + } + e.emit(); + } + + // Eat tokens until the macro call ends. + if self.may_recover() { + while !self.token.kind.is_close_delim_or_eof() { + self.bump(); + } + } + + match *fields { + [] => Err(self.dcx().struct_span_err( + self.token.span, + "`field_of!` expects dot-separated field and variant names", + )), + [field] => Ok(TyKind::FieldOf(container, None, field)), + [variant, field] => Ok(TyKind::FieldOf(container, Some(variant), field)), + _ => Err(self.dcx().struct_span_err( + fields.iter().map(|f| f.span).collect::>(), + "`field_of!` only supports a single field or a variant with a field", + )), + } + } + /// Parses a function pointer type (`TyKind::FnPtr`). /// ```ignore (illustrative) /// [unsafe] [extern "ABI"] fn (S) -> T diff --git a/compiler/rustc_passes/src/input_stats.rs b/compiler/rustc_passes/src/input_stats.rs index 23fbb9ab3a2b7..e424cc09fb607 100644 --- a/compiler/rustc_passes/src/input_stats.rs +++ b/compiler/rustc_passes/src/input_stats.rs @@ -410,6 +410,7 @@ impl<'v> hir_visit::Visitor<'v> for StatCollector<'v> { TraitObject, Infer, Pat, + FieldOf, Err ] ); @@ -688,6 +689,7 @@ impl<'v> ast_visit::Visitor<'v> for StatCollector<'v> { MacCall, CVarArgs, Dummy, + FieldOf, Err ] ); diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index f87189b4de42c..17b1fd274410c 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -939,7 +939,15 @@ symbols! { ffi_const, ffi_pure, ffi_returns_twice, + field, + field_base, field_init_shorthand, + field_of, + field_offset, + field_projections, + field_representing_type, + field_representing_type_raw, + field_type, fields, file, final_associated_functions, diff --git a/compiler/rustc_trait_selection/src/traits/project.rs b/compiler/rustc_trait_selection/src/traits/project.rs index 5bbbad0b5bbfa..3df5c9e33438a 100644 --- a/compiler/rustc_trait_selection/src/traits/project.rs +++ b/compiler/rustc_trait_selection/src/traits/project.rs @@ -13,7 +13,7 @@ use rustc_middle::traits::select::OverflowError; use rustc_middle::traits::{BuiltinImplSource, ImplSource, ImplSourceUserDefinedData}; use rustc_middle::ty::fast_reject::DeepRejectCtxt; use rustc_middle::ty::{ - self, Term, Ty, TyCtxt, TypeFoldable, TypeVisitableExt, TypingMode, Upcast, + self, FieldInfo, Term, Ty, TyCtxt, TypeFoldable, TypeVisitableExt, TypingMode, Upcast, }; use rustc_middle::{bug, span_bug}; use rustc_span::sym; @@ -987,6 +987,7 @@ fn assemble_candidates_from_impls<'cx, 'tcx>( | LangItem::Future | LangItem::Iterator | LangItem::AsyncIterator + | LangItem::Field | LangItem::Fn | LangItem::FnMut | LangItem::FnOnce @@ -1547,6 +1548,20 @@ fn confirm_builtin_candidate<'cx, 'tcx>( } }); (metadata_ty.into(), obligations) + } else if tcx.is_lang_item(trait_def_id, LangItem::Field) { + let ty::Adt(def, args) = self_ty.kind() else { + bug!("only field representing types can implement `Field`") + }; + let Some(FieldInfo { base, ty, .. }) = def.field_representing_type_info(tcx, args) else { + bug!("only field representing types can implement `Field`") + }; + if tcx.is_lang_item(item_def_id, LangItem::FieldBase) { + (base.into(), PredicateObligations::new()) + } else if tcx.is_lang_item(item_def_id, LangItem::FieldType) { + (ty.into(), PredicateObligations::new()) + } else { + bug!("unexpected associated type {:?} in `Field`", obligation.predicate); + } } else { bug!("unexpected builtin trait with associated type: {:?}", obligation.predicate); }; diff --git a/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs b/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs index 935834b832f24..b0af60d2aecfc 100644 --- a/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs +++ b/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs @@ -12,14 +12,18 @@ use hir::LangItem; use hir::def_id::DefId; use rustc_data_structures::fx::{FxHashSet, FxIndexSet}; use rustc_hir::{self as hir, CoroutineDesugaring, CoroutineKind}; -use rustc_infer::traits::{Obligation, PolyTraitObligation, SelectionError}; +use rustc_infer::traits::{Obligation, PolyTraitObligation, PredicateObligation, SelectionError}; use rustc_middle::ty::fast_reject::DeepRejectCtxt; -use rustc_middle::ty::{self, SizedTraitKind, Ty, TypeVisitableExt, TypingMode, elaborate}; +use rustc_middle::ty::{ + self, FieldInfo, SizedTraitKind, TraitRef, Ty, TypeVisitableExt, TypingMode, elaborate, +}; use rustc_middle::{bug, span_bug}; +use rustc_span::DUMMY_SP; use tracing::{debug, instrument, trace}; use super::SelectionCandidate::*; use super::{SelectionCandidateSet, SelectionContext, TraitObligationStack}; +use crate::traits::query::evaluate_obligation::InferCtxtExt; use crate::traits::util; impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { @@ -128,6 +132,9 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { &mut candidates, ); } + Some(LangItem::Field) => { + self.assemble_candidates_for_field_trait(obligation, &mut candidates); + } _ => { // We re-match here for traits that can have both builtin impls and user written impls. // After the builtin impls we need to also add user written impls, which we do not want to @@ -1439,4 +1446,52 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { } } } + + fn assemble_candidates_for_field_trait( + &mut self, + obligation: &PolyTraitObligation<'tcx>, + candidates: &mut SelectionCandidateSet<'tcx>, + ) { + if let ty::Adt(def, args) = obligation.predicate.self_ty().skip_binder().kind() + && let Some(FieldInfo { base, ty, .. }) = + def.field_representing_type_info(self.tcx(), args) + // NOTE: these bounds have to be kept in sync with the definition of the `Field` trait + // in `library/core/src/field.rs` as well as the new trait solver `fn + // consider_builtin_field_candidate` in + // `compiler/rustc_next_trait_solver/src/solve/trait_goals.rs`. + && match self.infcx.evaluate_obligation(&PredicateObligation::new( + self.tcx(), + obligation.cause.clone(), + obligation.param_env, + TraitRef::new( + self.tcx(), + self.tcx().require_lang_item(LangItem::Sized, DUMMY_SP), + [base], + ), + )) { + Ok(res) if res.must_apply_modulo_regions() => true, + _ => false, + } + && match self.infcx.evaluate_obligation(&PredicateObligation::new( + self.tcx(), + obligation.cause.clone(), + obligation.param_env, + TraitRef::new( + self.tcx(), + self.tcx().require_lang_item(LangItem::Sized, DUMMY_SP), + [ty], + ), + )) { + Ok(res) if res.must_apply_modulo_regions() => true, + _ => false, + } + && match base.kind() { + ty::Adt(def, _) => def.is_struct() && !def.repr().packed(), + ty::Tuple(..) => true, + _ => false, + } + { + candidates.vec.push(BuiltinCandidate); + } + } } diff --git a/compiler/rustc_trait_selection/src/traits/select/confirmation.rs b/compiler/rustc_trait_selection/src/traits/select/confirmation.rs index b0e045274d0d8..9e7637df26490 100644 --- a/compiler/rustc_trait_selection/src/traits/select/confirmation.rs +++ b/compiler/rustc_trait_selection/src/traits/select/confirmation.rs @@ -262,6 +262,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { Some( LangItem::Destruct | LangItem::DiscriminantKind + | LangItem::Field | LangItem::FnPtrTrait | LangItem::PointeeTrait | LangItem::Tuple diff --git a/compiler/rustc_ty_utils/src/instance.rs b/compiler/rustc_ty_utils/src/instance.rs index a014098e7ed55..ac4137e9a6680 100644 --- a/compiler/rustc_ty_utils/src/instance.rs +++ b/compiler/rustc_ty_utils/src/instance.rs @@ -386,6 +386,22 @@ fn resolve_associated_item<'tcx>( assert_eq!(name, sym::transmute); let args = tcx.erase_and_anonymize_regions(rcvr_args); Some(ty::Instance::new_raw(trait_item_id, args)) + } else if tcx.is_lang_item(trait_ref.def_id, LangItem::Field) { + if tcx.is_lang_item(trait_item_id, LangItem::FieldOffset) { + let self_ty = trait_ref.self_ty(); + match self_ty.kind() { + ty::Adt(def, _) if def.is_field_representing_type() => {} + _ => bug!("expected field representing type, found {self_ty}"), + } + Some(Instance { + def: ty::InstanceKind::Item( + tcx.lang_items().get(LangItem::FieldOffset).unwrap(), + ), + args: rcvr_args, + }) + } else { + bug!("unexpected associated associated item") + } } else { Instance::try_resolve_item_for_coroutine(tcx, trait_item_id, trait_id, rcvr_args) } diff --git a/compiler/rustc_type_ir/Cargo.toml b/compiler/rustc_type_ir/Cargo.toml index fbe3827128930..10c53c881a362 100644 --- a/compiler/rustc_type_ir/Cargo.toml +++ b/compiler/rustc_type_ir/Cargo.toml @@ -11,6 +11,7 @@ derive-where = "1.2.7" ena = "0.14.4" indexmap = "2.0.0" rustc-hash = "2.0.0" +rustc_abi = { path = "../rustc_abi", default-features = false } rustc_ast_ir = { path = "../rustc_ast_ir", default-features = false } rustc_data_structures = { path = "../rustc_data_structures", optional = true } rustc_error_messages = { path = "../rustc_error_messages", optional = true } diff --git a/compiler/rustc_type_ir/src/inherent.rs b/compiler/rustc_type_ir/src/inherent.rs index c9580d589d217..8256ea7ed312e 100644 --- a/compiler/rustc_type_ir/src/inherent.rs +++ b/compiler/rustc_type_ir/src/inherent.rs @@ -13,7 +13,9 @@ use crate::fold::{TypeFoldable, TypeSuperFoldable}; use crate::relate::Relate; use crate::solve::{AdtDestructorKind, SizedTraitKind}; use crate::visit::{Flags, TypeSuperVisitable, TypeVisitable}; -use crate::{self as ty, ClauseKind, CollectAndApply, Interner, PredicateKind, UpcastFrom}; +use crate::{ + self as ty, ClauseKind, CollectAndApply, FieldInfo, Interner, PredicateKind, UpcastFrom, +}; pub trait Ty>: Copy @@ -577,6 +579,8 @@ pub trait AdtDef: Copy + Debug + Hash + Eq { fn is_struct(self) -> bool; + fn is_packed(self) -> bool; + /// Returns the type of the struct tail. /// /// Expects the `AdtDef` to be a struct. If it is not, then this will panic. @@ -586,6 +590,12 @@ pub trait AdtDef: Copy + Debug + Hash + Eq { fn is_manually_drop(self) -> bool; + fn field_representing_type_info( + self, + interner: I, + args: I::GenericArgs, + ) -> Option>; + // FIXME: perhaps use `all_fields` and expose `FieldDef`. fn all_field_tys(self, interner: I) -> ty::EarlyBinder>; diff --git a/compiler/rustc_type_ir/src/lang_items.rs b/compiler/rustc_type_ir/src/lang_items.rs index 39b575ebab63b..f1c45a4d98b5e 100644 --- a/compiler/rustc_type_ir/src/lang_items.rs +++ b/compiler/rustc_type_ir/src/lang_items.rs @@ -9,6 +9,8 @@ pub enum SolverLangItem { CoroutineReturn, CoroutineYield, DynMetadata, + FieldBase, + FieldType, FutureOutput, Metadata, // tidy-alphabetical-end @@ -36,6 +38,7 @@ pub enum SolverTraitLangItem { Destruct, DiscriminantKind, Drop, + Field, Fn, FnMut, FnOnce, diff --git a/compiler/rustc_type_ir/src/lib.rs b/compiler/rustc_type_ir/src/lib.rs index 3d9fc7f5c998d..62965f947387c 100644 --- a/compiler/rustc_type_ir/src/lib.rs +++ b/compiler/rustc_type_ir/src/lib.rs @@ -13,6 +13,7 @@ extern crate self as rustc_type_ir; use std::fmt; use std::hash::Hash; +use rustc_abi::{FieldIdx, VariantIdx}; #[cfg(feature = "nightly")] use rustc_macros::{Decodable, Encodable, HashStable_NoContext}; @@ -440,3 +441,12 @@ impl fmt::Display for ClosureKind { self.as_str().fmt(f) } } + +pub struct FieldInfo { + pub base: I::Ty, + pub ty: I::Ty, + pub variant: Option, + pub variant_idx: VariantIdx, + pub name: I::Symbol, + pub field_idx: FieldIdx, +} diff --git a/library/core/src/field.rs b/library/core/src/field.rs new file mode 100644 index 0000000000000..e8ef309b9c84f --- /dev/null +++ b/library/core/src/field.rs @@ -0,0 +1,83 @@ +//! Field Reflection + +use crate::marker::PhantomData; + +/// Field Representing Type +#[unstable(feature = "field_representing_type_raw", issue = "none")] +#[lang = "field_representing_type"] +#[expect(missing_debug_implementations)] +#[fundamental] +pub struct FieldRepresentingType { + _phantom: PhantomData, +} + +// SAFETY: `FieldRepresentingType` doesn't contain any `T` +unsafe impl Send + for FieldRepresentingType +{ +} + +// SAFETY: `FieldRepresentingType` doesn't contain any `T` +unsafe impl Sync + for FieldRepresentingType +{ +} + +impl Copy + for FieldRepresentingType +{ +} + +impl Clone + for FieldRepresentingType +{ + fn clone(&self) -> Self { + *self + } +} + +/// Expands to the field representing type of the given field. +/// +/// The container type may be a tuple, `struct`, `union` or `enum`. In the case of an enum, the +/// variant must also be specified. Only a single field is supported. +#[unstable(feature = "field_projections", issue = "145383")] +#[allow_internal_unstable(field_representing_type_raw, builtin_syntax)] +// NOTE: when stabilizing this macro, we can never add new trait impls for `FieldRepresentingType`, +// since it is `#[fundamental]` and thus could break users of this macro, since the compiler expands +// it to `FieldRepresentingType<...>`. Thus stabilizing this requires careful thought about the +// completeness of the trait impls for `FieldRepresentingType`. +pub macro field_of($Container:ty, $($fields:expr)+ $(,)?) { + builtin # field_of($Container, $($fields)+) +} + +/// Type representing a field of a `struct`, `union`, `enum` variant or tuple. +/// +/// # Safety +/// +/// Given a valid value of type `Self::Base`, there exists a valid value of type `Self::Type` at +/// byte offset `OFFSET` +#[lang = "field"] +#[unstable(feature = "field_projections", issue = "145383")] +#[rustc_deny_explicit_impl] +#[rustc_dyn_incompatible_trait] +// NOTE: the compiler provides the impl of `Field` for `FieldRepresentingType` when it can guarantee +// the safety requirements of this trait. It also has to manually add the correct trait bounds on +// associated types (and the `Self` type). Thus any changes to the bounds here must be reflected in +// the old and new trait solver: +// - `fn assemble_candidates_for_field_trait` in +// `compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs`, and +// - `fn consider_builtin_field_candidate` in +// `compiler/rustc_next_trait_solver/src/solve/trait_goals.rs`. +pub unsafe trait Field: Send + Sync + Copy { + /// The type of the base where this field exists in. + #[lang = "field_base"] + type Base; + + /// The type of the field. + #[lang = "field_type"] + type Type; + + /// The offset of the field in bytes. + #[lang = "field_offset"] + const OFFSET: usize = crate::intrinsics::field_offset::(); +} diff --git a/library/core/src/intrinsics/mod.rs b/library/core/src/intrinsics/mod.rs index 95b531994d92a..7e9adc1e8d571 100644 --- a/library/core/src/intrinsics/mod.rs +++ b/library/core/src/intrinsics/mod.rs @@ -2812,6 +2812,20 @@ pub const fn align_of() -> usize; #[lang = "offset_of"] pub const fn offset_of(variant: u32, field: u32) -> usize; +/// The offset of a field queried by its field representing type. +/// +/// Returns the offset of the field represented by `F`. This function essentially does the same as +/// the [`offset_of`] intrinsic, but expects the field to be represented by a generic rather than +/// the variant and field indices. This also is a safe intrinsic and can only be evaluated at +/// compile-time, so it should only appear in constants or inline const blocks. +/// +/// There should be no need to call this intrinsic manually, as its value is used to define +/// [`Field::OFFSET`](crate::field::Field::OFFSET), which is publicly accessible. +#[rustc_intrinsic] +#[unstable(feature = "field_projections", issue = "145383")] +#[rustc_const_unstable(feature = "field_projections", issue = "145383")] +pub const fn field_offset() -> usize; + /// Returns the number of variants of the type `T` cast to a `usize`; /// if `T` has no variants, returns `0`. Uninhabited variants will be counted. /// diff --git a/library/core/src/lib.rs b/library/core/src/lib.rs index ed1b73c6e3d96..06de9a6ce35a5 100644 --- a/library/core/src/lib.rs +++ b/library/core/src/lib.rs @@ -137,6 +137,7 @@ #![feature(extern_types)] #![feature(f16)] #![feature(f128)] +#![feature(field_projections)] #![feature(freeze_impls)] #![feature(fundamental)] #![feature(funnel_shifts)] @@ -274,6 +275,8 @@ pub mod cmp; pub mod convert; pub mod default; pub mod error; +#[unstable(feature = "field_projections", issue = "145383")] +pub mod field; pub mod index; pub mod marker; pub mod ops; diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs index ed1c1a89bd410..346571a3d1372 100644 --- a/library/std/src/lib.rs +++ b/library/std/src/lib.rs @@ -495,6 +495,8 @@ pub use core::cmp; pub use core::convert; #[stable(feature = "rust1", since = "1.0.0")] pub use core::default; +#[unstable(feature = "field_projections", issue = "145383")] +pub use core::field; #[stable(feature = "futures_api", since = "1.36.0")] pub use core::future; #[stable(feature = "core_hint", since = "1.27.0")] diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index 880d2f7f37ccd..1e834fd10e322 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -1809,6 +1809,14 @@ pub(crate) fn clean_ty<'tcx>(ty: &hir::Ty<'tcx>, cx: &mut DocContext<'tcx>) -> T } TyKind::Slice(ty) => Slice(Box::new(clean_ty(ty, cx))), TyKind::Pat(ty, pat) => Type::Pat(Box::new(clean_ty(ty, cx)), format!("{pat:?}").into()), + TyKind::FieldOf(ty, hir::TyFieldPath { variant, field }) => { + let field_str = if let Some(variant) = variant { + format!("{variant}.{field}") + } else { + format!("{field}") + }; + Type::FieldOf(Box::new(clean_ty(ty, cx)), field_str.into()) + } TyKind::Array(ty, const_arg) => { // NOTE(min_const_generics): We can't use `const_eval_poly` for constants // as we currently do not supply the parent generics to anonymous constants diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index 8656378a1e264..e05fa7641db5f 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -1342,6 +1342,7 @@ pub(crate) enum Type { /// The `String` field is a stringified version of the array's length parameter. Array(Box, Box), Pat(Box, Box), + FieldOf(Box, Box), /// A raw pointer type: `*const i32`, `*mut i32` RawPointer(Mutability, Box), /// A reference type: `&i32`, `&'a mut Foo` @@ -1555,6 +1556,7 @@ impl Type { Slice(..) => PrimitiveType::Slice, Array(..) => PrimitiveType::Array, Type::Pat(..) => PrimitiveType::Pat, + Type::FieldOf(..) => PrimitiveType::FieldOf, RawPointer(..) => PrimitiveType::RawPointer, QPath(box QPathData { self_type, .. }) => return self_type.def_id(cache), Generic(_) | SelfTy | Infer | ImplTrait(_) | UnsafeBinder(_) => return None, @@ -1602,6 +1604,7 @@ pub(crate) enum PrimitiveType { Slice, Array, Pat, + FieldOf, Tuple, Unit, RawPointer, @@ -1757,6 +1760,7 @@ impl PrimitiveType { Char => sym::char, Array => sym::array, Pat => sym::pat, + FieldOf => sym::field_of, Slice => sym::slice, Tuple => sym::tuple, Unit => sym::unit, diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs index 720eb8e5e61df..f38883d431438 100644 --- a/src/librustdoc/html/format.rs +++ b/src/librustdoc/html/format.rs @@ -958,6 +958,11 @@ fn fmt_type( fmt::Display::fmt(&print_type(t, cx), f)?; write!(f, " is {pat}") } + clean::Type::FieldOf(t, field) => { + write!(f, "field_of!(")?; + fmt::Display::fmt(&print_type(t, cx), f)?; + write!(f, ", {field})") + } clean::Array(box clean::Generic(name), n) if !f.alternate() => primitive_link( f, PrimitiveType::Array, diff --git a/src/librustdoc/html/render/search_index.rs b/src/librustdoc/html/render/search_index.rs index a3c6525936eea..5f69cc030e266 100644 --- a/src/librustdoc/html/render/search_index.rs +++ b/src/librustdoc/html/render/search_index.rs @@ -2049,6 +2049,7 @@ fn get_index_type_id( } // Not supported yet clean::Type::Pat(..) + | clean::Type::FieldOf(..) | clean::Generic(_) | clean::SelfTy | clean::ImplTrait(_) diff --git a/src/librustdoc/json/conversions.rs b/src/librustdoc/json/conversions.rs index 599d7b10005d9..1848c79322556 100644 --- a/src/librustdoc/json/conversions.rs +++ b/src/librustdoc/json/conversions.rs @@ -579,6 +579,8 @@ impl FromClean for Type { type_: Box::new(t.into_json(renderer)), __pat_unstable_do_not_use: p.to_string(), }, + // FIXME(FRTs): implement + clean::Type::FieldOf(..) => todo!(), ImplTrait(g) => Type::ImplTrait(g.into_json(renderer)), Infer => Type::Infer, RawPointer(mutability, type_) => Type::RawPointer { diff --git a/src/tools/clippy/clippy_lints/src/dereference.rs b/src/tools/clippy/clippy_lints/src/dereference.rs index 954f071b40689..3433ab511b07a 100644 --- a/src/tools/clippy/clippy_lints/src/dereference.rs +++ b/src/tools/clippy/clippy_lints/src/dereference.rs @@ -852,6 +852,7 @@ impl TyCoercionStability { | TyKind::Ptr(_) | TyKind::FnPtr(_) | TyKind::Pat(..) + | TyKind::FieldOf(..) | TyKind::Never | TyKind::Tup(_) | TyKind::Path(_) => Self::Deref, diff --git a/src/tools/clippy/clippy_utils/src/check_proc_macro.rs b/src/tools/clippy/clippy_utils/src/check_proc_macro.rs index def5d968b063f..14389b0d29bb4 100644 --- a/src/tools/clippy/clippy_utils/src/check_proc_macro.rs +++ b/src/tools/clippy/clippy_utils/src/check_proc_macro.rs @@ -531,6 +531,7 @@ fn ast_ty_search_pat(ty: &ast::Ty) -> (Pat, Pat) { // experimental | TyKind::Pat(..) + | TyKind::FieldOf(..) // unused | TyKind::CVarArgs diff --git a/src/tools/clippy/clippy_utils/src/hir_utils.rs b/src/tools/clippy/clippy_utils/src/hir_utils.rs index 3ff40f2fb722b..11d0cee00d0d6 100644 --- a/src/tools/clippy/clippy_utils/src/hir_utils.rs +++ b/src/tools/clippy/clippy_utils/src/hir_utils.rs @@ -14,7 +14,7 @@ use rustc_hir::{ GenericParam, GenericParamKind, GenericParamSource, Generics, HirId, HirIdMap, InlineAsmOperand, ItemId, ItemKind, LetExpr, Lifetime, LifetimeKind, LifetimeParamKind, Node, ParamName, Pat, PatExpr, PatExprKind, PatField, PatKind, Path, PathSegment, PreciseCapturingArgKind, PrimTy, QPath, Stmt, StmtKind, StructTailExpr, TraitBoundModifiers, Ty, - TyKind, TyPat, TyPatKind, UseKind, WherePredicate, WherePredicateKind, + TyFieldPath, TyKind, TyPat, TyPatKind, UseKind, WherePredicate, WherePredicateKind, }; use rustc_lexer::{FrontmatterAllowed, TokenKind, tokenize}; use rustc_lint::LateContext; @@ -1529,6 +1529,13 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { self.hash_ty(ty); self.hash_ty_pat(pat); }, + TyKind::FieldOf(base, TyFieldPath { variant, field }) => { + self.hash_ty(base); + if let Some(variant) = variant { + self.hash_name(variant.name); + } + self.hash_name(field.name); + }, TyKind::Ptr(mut_ty) => { self.hash_ty(mut_ty.ty); mut_ty.mutbl.hash(&mut self.s); diff --git a/src/tools/clippy/clippy_utils/src/sym.rs b/src/tools/clippy/clippy_utils/src/sym.rs index 6965225c12ab9..a2c87fe913d62 100644 --- a/src/tools/clippy/clippy_utils/src/sym.rs +++ b/src/tools/clippy/clippy_utils/src/sym.rs @@ -257,7 +257,6 @@ generate! { f64_legacy_const_nan, f64_legacy_const_neg_infinity, f64_legacy_const_radix, - field, file_options, filter, filter_map, diff --git a/src/tools/rustfmt/src/types.rs b/src/tools/rustfmt/src/types.rs index 2d7bc59c62788..bf0e97cb38b0d 100644 --- a/src/tools/rustfmt/src/types.rs +++ b/src/tools/rustfmt/src/types.rs @@ -1035,6 +1035,14 @@ impl Rewrite for ast::Ty { let pat = pat.rewrite_result(context, shape)?; Ok(format!("{ty} is {pat}")) } + ast::TyKind::FieldOf(ref ty, ref variant, ref field) => { + let ty = ty.rewrite_result(context, shape)?; + if let Some(variant) = variant { + Ok(format!("builtin # field_of({ty}, {variant}.{field})")) + } else { + Ok(format!("builtin # field_of({ty}, {field})")) + } + } ast::TyKind::UnsafeBinder(ref binder) => { let mut result = String::new(); if binder.generic_params.is_empty() { diff --git a/tests/ui/README.md b/tests/ui/README.md index d2b189a0e022c..3ebbe4918a2de 100644 --- a/tests/ui/README.md +++ b/tests/ui/README.md @@ -597,6 +597,12 @@ See: - [`ffi_const` | The Unstable book](https://doc.rust-lang.org/unstable-book/language-features/ffi-const.html) - [`ffi_pure` | The Unstable book](https://doc.rust-lang.org/beta/unstable-book/language-features/ffi-pure.html) +## `tests/ui/field_representing_types`: `#![feature(field_projections)]` + +Tests for field representing types `field_of!(Struct, field)`. + +See: [Tracking Issue for Field Projections #145383](https://github.com/rust-lang/rust/issues/145383) + ## `tests/ui/float` See: [Tracking Issue for `f16` and `f128` float types #116909](https://github.com/rust-lang/rust/issues/116909) diff --git a/tests/ui/feature-gates/feature-gate-field-projections.rs b/tests/ui/feature-gates/feature-gate-field-projections.rs new file mode 100644 index 0000000000000..9cc219460c37b --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-field-projections.rs @@ -0,0 +1,19 @@ +use std::field::{Field, field_of}; //~ ERROR: use of unstable library feature `field_projections` [E0658] +//~^ ERROR: use of unstable library feature `field_projections` [E0658] +use std::ptr; + +fn project_ref( + //~^ ERROR: use of unstable library feature `field_projections` [E0658] + r: &F::Base, //~ ERROR: use of unstable library feature `field_projections` [E0658] +) -> &F::Type +//~^ ERROR: use of unstable library feature `field_projections` [E0658] +where + F::Type: Sized, //~ ERROR: use of unstable library feature `field_projections` [E0658] +{ + unsafe { &*ptr::from_ref(r).byte_add(F::OFFSET).cast() } //~ ERROR: use of unstable library feature `field_projections` [E0658] +} + +fn main() { + struct Foo(()); + let _ = project_ref::(&Foo(())); //~ ERROR: use of unstable library feature `field_projections` [E0658] +} diff --git a/tests/ui/feature-gates/feature-gate-field-projections.stderr b/tests/ui/feature-gates/feature-gate-field-projections.stderr new file mode 100644 index 0000000000000..935e968c5b8ed --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-field-projections.stderr @@ -0,0 +1,83 @@ +error[E0658]: use of unstable library feature `field_projections` + --> $DIR/feature-gate-field-projections.rs:18:27 + | +LL | let _ = project_ref::(&Foo(())); + | ^^^^^^^^ + | + = note: see issue #145383 for more information + = help: add `#![feature(field_projections)]` 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[E0658]: use of unstable library feature `field_projections` + --> $DIR/feature-gate-field-projections.rs:1:18 + | +LL | use std::field::{Field, field_of}; + | ^^^^^ + | + = note: see issue #145383 for more information + = help: add `#![feature(field_projections)]` 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[E0658]: use of unstable library feature `field_projections` + --> $DIR/feature-gate-field-projections.rs:1:25 + | +LL | use std::field::{Field, field_of}; + | ^^^^^^^^ + | + = note: see issue #145383 for more information + = help: add `#![feature(field_projections)]` 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[E0658]: use of unstable library feature `field_projections` + --> $DIR/feature-gate-field-projections.rs:5:19 + | +LL | fn project_ref( + | ^^^^^ + | + = note: see issue #145383 for more information + = help: add `#![feature(field_projections)]` 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[E0658]: use of unstable library feature `field_projections` + --> $DIR/feature-gate-field-projections.rs:11:5 + | +LL | F::Type: Sized, + | ^^^^^^^ + | + = note: see issue #145383 for more information + = help: add `#![feature(field_projections)]` 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[E0658]: use of unstable library feature `field_projections` + --> $DIR/feature-gate-field-projections.rs:7:9 + | +LL | r: &F::Base, + | ^^^^^^^ + | + = note: see issue #145383 for more information + = help: add `#![feature(field_projections)]` 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[E0658]: use of unstable library feature `field_projections` + --> $DIR/feature-gate-field-projections.rs:8:7 + | +LL | ) -> &F::Type + | ^^^^^^^ + | + = note: see issue #145383 for more information + = help: add `#![feature(field_projections)]` 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[E0658]: use of unstable library feature `field_projections` + --> $DIR/feature-gate-field-projections.rs:13:42 + | +LL | unsafe { &*ptr::from_ref(r).byte_add(F::OFFSET).cast() } + | ^^^^^^^^^ + | + = note: see issue #145383 for more information + = help: add `#![feature(field_projections)]` 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: aborting due to 8 previous errors + +For more information about this error, try `rustc --explain E0658`. diff --git a/tests/ui/feature-gates/feature-gate-field-representing-type-raw.rs b/tests/ui/feature-gates/feature-gate-field-representing-type-raw.rs new file mode 100644 index 0000000000000..a315c01c11b83 --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-field-representing-type-raw.rs @@ -0,0 +1,4 @@ +use std::field::FieldRepresentingType; +//~^ ERROR: use of unstable library feature `field_representing_type_raw` [E0658] + +fn main() {} diff --git a/tests/ui/feature-gates/feature-gate-field-representing-type-raw.stderr b/tests/ui/feature-gates/feature-gate-field-representing-type-raw.stderr new file mode 100644 index 0000000000000..fdb5e34d3497a --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-field-representing-type-raw.stderr @@ -0,0 +1,12 @@ +error[E0658]: use of unstable library feature `field_representing_type_raw` + --> $DIR/feature-gate-field-representing-type-raw.rs:1:5 + | +LL | use std::field::FieldRepresentingType; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: add `#![feature(field_representing_type_raw)]` 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: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0658`. diff --git a/tests/ui/field_representing_types/auxiliary/extern-crate.rs b/tests/ui/field_representing_types/auxiliary/extern-crate.rs new file mode 100644 index 0000000000000..8e7e6d2679b44 --- /dev/null +++ b/tests/ui/field_representing_types/auxiliary/extern-crate.rs @@ -0,0 +1,6 @@ +pub struct Point { + pub x: usize, + pub y: usize, +} + +pub trait ForeignTrait {} diff --git a/tests/ui/field_representing_types/incoherent-impl.next.stderr b/tests/ui/field_representing_types/incoherent-impl.next.stderr new file mode 100644 index 0000000000000..47f91e8dd2e9d --- /dev/null +++ b/tests/ui/field_representing_types/incoherent-impl.next.stderr @@ -0,0 +1,27 @@ +error[E0117]: only traits defined in the current crate can be implemented for types defined outside of the crate + --> $DIR/incoherent-impl.rs:22:1 + | +LL | impl ForeignTrait for field_of!(Point, x) {} + | ^^^^^^^^^^^^^^^^^^^^^^------------------- + | | + | `Point` is not defined in the current crate + | + = note: impl doesn't have any local type before any uncovered type parameters + = note: for more information see https://doc.rust-lang.org/reference/items/implementations.html#orphan-rules + = note: define and implement a trait or new type instead + +error[E0117]: only traits defined in the current crate can be implemented for types defined outside of the crate + --> $DIR/incoherent-impl.rs:25:1 + | +LL | impl ForeignTrait for field_of!((usize, usize), 0) {} + | ^^^^^^^^^^^^^^^^^^^^^^---------------------------- + | | + | this is not defined in the current crate because tuples are always foreign + | + = note: impl doesn't have any local type before any uncovered type parameters + = note: for more information see https://doc.rust-lang.org/reference/items/implementations.html#orphan-rules + = note: define and implement a trait or new type instead + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0117`. diff --git a/tests/ui/field_representing_types/incoherent-impl.old.stderr b/tests/ui/field_representing_types/incoherent-impl.old.stderr new file mode 100644 index 0000000000000..47f91e8dd2e9d --- /dev/null +++ b/tests/ui/field_representing_types/incoherent-impl.old.stderr @@ -0,0 +1,27 @@ +error[E0117]: only traits defined in the current crate can be implemented for types defined outside of the crate + --> $DIR/incoherent-impl.rs:22:1 + | +LL | impl ForeignTrait for field_of!(Point, x) {} + | ^^^^^^^^^^^^^^^^^^^^^^------------------- + | | + | `Point` is not defined in the current crate + | + = note: impl doesn't have any local type before any uncovered type parameters + = note: for more information see https://doc.rust-lang.org/reference/items/implementations.html#orphan-rules + = note: define and implement a trait or new type instead + +error[E0117]: only traits defined in the current crate can be implemented for types defined outside of the crate + --> $DIR/incoherent-impl.rs:25:1 + | +LL | impl ForeignTrait for field_of!((usize, usize), 0) {} + | ^^^^^^^^^^^^^^^^^^^^^^---------------------------- + | | + | this is not defined in the current crate because tuples are always foreign + | + = note: impl doesn't have any local type before any uncovered type parameters + = note: for more information see https://doc.rust-lang.org/reference/items/implementations.html#orphan-rules + = note: define and implement a trait or new type instead + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0117`. diff --git a/tests/ui/field_representing_types/incoherent-impl.rs b/tests/ui/field_representing_types/incoherent-impl.rs new file mode 100644 index 0000000000000..22db652359f69 --- /dev/null +++ b/tests/ui/field_representing_types/incoherent-impl.rs @@ -0,0 +1,28 @@ +//@ revisions: old next +//@ [next] compile-flags: -Znext-solver +//@ aux-build:extern-crate.rs +#![expect(incomplete_features)] +#![feature(field_projections)] +extern crate extern_crate; + +use std::field::field_of; + +use extern_crate::{ForeignTrait, Point}; + +pub trait MyTrait {} + +impl MyTrait for field_of!(Point, x) {} +impl MyTrait for field_of!(Player, pos) {} +impl MyTrait for field_of!((usize, usize), 0) {} + +pub struct Player { + pos: Point, +} + +impl ForeignTrait for field_of!(Point, x) {} +//~^ ERROR: only traits defined in the current crate can be implemented for types defined outside of the crate [E0117] +impl ForeignTrait for field_of!(Player, pos) {} +impl ForeignTrait for field_of!((usize, usize), 0) {} +//~^ ERROR: only traits defined in the current crate can be implemented for types defined outside of the crate [E0117] + +fn main() {} diff --git a/tests/ui/field_representing_types/invalid.next.stderr b/tests/ui/field_representing_types/invalid.next.stderr new file mode 100644 index 0000000000000..2a5e884a5e3f9 --- /dev/null +++ b/tests/ui/field_representing_types/invalid.next.stderr @@ -0,0 +1,71 @@ +error: unexpected end of macro invocation + --> $DIR/invalid.rs:23:28 + | +LL | let _: field_of!(Struct); + | ^ missing tokens in macro arguments + | +note: while trying to match `,` + --> $SRC_DIR/core/src/field.rs:LL:COL + +error: unexpected end of macro invocation + --> $DIR/invalid.rs:24:29 + | +LL | let _: field_of!(Struct,); + | ^ missing tokens in macro arguments + | +note: while trying to match meta-variable `$fields:expr` + --> $SRC_DIR/core/src/field.rs:LL:COL + +error: no rules expected `extra` + --> $DIR/invalid.rs:25:37 + | +LL | let _: field_of!(Struct, field, extra); + | ^^^^^ no rules expected this token in macro call + | + = note: while trying to match sequence end + +error: offset_of expects dot-separated field and variant names + --> $DIR/invalid.rs:27:28 + | +LL | let _: field_of!(Enum, Variant..field); + | ^^^^^^^^^^^^^^ + +error: `field_of!` expects dot-separated field and variant names + --> $DIR/invalid.rs:27:12 + | +LL | let _: field_of!(Enum, Variant..field); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | in this macro invocation + | this macro call doesn't expand to a type + | + = note: this error originates in the macro `field_of` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: offset_of expects dot-separated field and variant names + --> $DIR/invalid.rs:29:30 + | +LL | let _: field_of!(Struct, [42]); + | ^^^^ + +error: `field_of!` expects dot-separated field and variant names + --> $DIR/invalid.rs:29:12 + | +LL | let _: field_of!(Struct, [42]); + | ^^^^^^^^^^^^^^^^^^^^^^^ + | | + | in this macro invocation + | this macro call doesn't expand to a type + | + = note: this error originates in the macro `field_of` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: `field_of!` only supports a single field or a variant with a field + --> $DIR/invalid.rs:31:30 + | +LL | let _: field_of!(Struct, field1.field2.field3); + | ------------------^^^^^^-^^^^^^-^^^^^^- + | | + | in this macro invocation + | this macro call doesn't expand to a type + +error: aborting due to 8 previous errors + diff --git a/tests/ui/field_representing_types/invalid.old.stderr b/tests/ui/field_representing_types/invalid.old.stderr new file mode 100644 index 0000000000000..2a5e884a5e3f9 --- /dev/null +++ b/tests/ui/field_representing_types/invalid.old.stderr @@ -0,0 +1,71 @@ +error: unexpected end of macro invocation + --> $DIR/invalid.rs:23:28 + | +LL | let _: field_of!(Struct); + | ^ missing tokens in macro arguments + | +note: while trying to match `,` + --> $SRC_DIR/core/src/field.rs:LL:COL + +error: unexpected end of macro invocation + --> $DIR/invalid.rs:24:29 + | +LL | let _: field_of!(Struct,); + | ^ missing tokens in macro arguments + | +note: while trying to match meta-variable `$fields:expr` + --> $SRC_DIR/core/src/field.rs:LL:COL + +error: no rules expected `extra` + --> $DIR/invalid.rs:25:37 + | +LL | let _: field_of!(Struct, field, extra); + | ^^^^^ no rules expected this token in macro call + | + = note: while trying to match sequence end + +error: offset_of expects dot-separated field and variant names + --> $DIR/invalid.rs:27:28 + | +LL | let _: field_of!(Enum, Variant..field); + | ^^^^^^^^^^^^^^ + +error: `field_of!` expects dot-separated field and variant names + --> $DIR/invalid.rs:27:12 + | +LL | let _: field_of!(Enum, Variant..field); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | in this macro invocation + | this macro call doesn't expand to a type + | + = note: this error originates in the macro `field_of` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: offset_of expects dot-separated field and variant names + --> $DIR/invalid.rs:29:30 + | +LL | let _: field_of!(Struct, [42]); + | ^^^^ + +error: `field_of!` expects dot-separated field and variant names + --> $DIR/invalid.rs:29:12 + | +LL | let _: field_of!(Struct, [42]); + | ^^^^^^^^^^^^^^^^^^^^^^^ + | | + | in this macro invocation + | this macro call doesn't expand to a type + | + = note: this error originates in the macro `field_of` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: `field_of!` only supports a single field or a variant with a field + --> $DIR/invalid.rs:31:30 + | +LL | let _: field_of!(Struct, field1.field2.field3); + | ------------------^^^^^^-^^^^^^-^^^^^^- + | | + | in this macro invocation + | this macro call doesn't expand to a type + +error: aborting due to 8 previous errors + diff --git a/tests/ui/field_representing_types/invalid.rs b/tests/ui/field_representing_types/invalid.rs new file mode 100644 index 0000000000000..d1fc217db7ddd --- /dev/null +++ b/tests/ui/field_representing_types/invalid.rs @@ -0,0 +1,32 @@ +//@ revisions: old next +//@ [next] compile-flags: -Znext-solver +#![expect(incomplete_features)] +#![feature(field_projections)] + +use std::field::field_of; + +pub struct Struct { + field: i32, +} + +pub union Union { + field: i32, +} + +pub enum Enum { + Variant { field: i32 }, +} + +pub trait Trait {} + +fn main() { + let _: field_of!(Struct); //~ ERROR: unexpected end of macro invocation + let _: field_of!(Struct,); //~ ERROR: unexpected end of macro invocation + let _: field_of!(Struct, field, extra); //~ ERROR: no rules expected `extra` + // FIXME(FRTs): adjust error message to mention `field_of!` & prevent double errors + let _: field_of!(Enum, Variant..field); //~ ERROR: offset_of expects dot-separated field and variant names + //~^ ERROR: `field_of!` expects dot-separated field and variant names + let _: field_of!(Struct, [42]); //~ ERROR: offset_of expects dot-separated field and variant names + //~^ ERROR: `field_of!` expects dot-separated field and variant names + let _: field_of!(Struct, field1.field2.field3); //~ ERROR: `field_of!` only supports a single field or a variant with a field +} diff --git a/tests/ui/field_representing_types/non-struct.next.stderr b/tests/ui/field_representing_types/non-struct.next.stderr new file mode 100644 index 0000000000000..7a6c33c3b6f54 --- /dev/null +++ b/tests/ui/field_representing_types/non-struct.next.stderr @@ -0,0 +1,27 @@ +error[E0277]: the trait bound `field_of!(MyUnion, field): std::field::Field` is not satisfied + --> $DIR/non-struct.rs:22:20 + | +LL | assert_field::(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ the nightly-only, unstable trait `std::field::Field` is not implemented for `field_of!(MyUnion, field)` + | +note: required by a bound in `assert_field` + --> $DIR/non-struct.rs:18:20 + | +LL | fn assert_field() {} + | ^^^^^ required by this bound in `assert_field` + +error[E0277]: the trait bound `field_of!(MyEnum, A.a): std::field::Field` is not satisfied + --> $DIR/non-struct.rs:25:20 + | +LL | assert_field::(); + | ^^^^^^^^^^^^^^^^^^^^^^ the nightly-only, unstable trait `std::field::Field` is not implemented for `field_of!(MyEnum, A.a)` + | +note: required by a bound in `assert_field` + --> $DIR/non-struct.rs:18:20 + | +LL | fn assert_field() {} + | ^^^^^ required by this bound in `assert_field` + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/field_representing_types/non-struct.old.stderr b/tests/ui/field_representing_types/non-struct.old.stderr new file mode 100644 index 0000000000000..7a6c33c3b6f54 --- /dev/null +++ b/tests/ui/field_representing_types/non-struct.old.stderr @@ -0,0 +1,27 @@ +error[E0277]: the trait bound `field_of!(MyUnion, field): std::field::Field` is not satisfied + --> $DIR/non-struct.rs:22:20 + | +LL | assert_field::(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ the nightly-only, unstable trait `std::field::Field` is not implemented for `field_of!(MyUnion, field)` + | +note: required by a bound in `assert_field` + --> $DIR/non-struct.rs:18:20 + | +LL | fn assert_field() {} + | ^^^^^ required by this bound in `assert_field` + +error[E0277]: the trait bound `field_of!(MyEnum, A.a): std::field::Field` is not satisfied + --> $DIR/non-struct.rs:25:20 + | +LL | assert_field::(); + | ^^^^^^^^^^^^^^^^^^^^^^ the nightly-only, unstable trait `std::field::Field` is not implemented for `field_of!(MyEnum, A.a)` + | +note: required by a bound in `assert_field` + --> $DIR/non-struct.rs:18:20 + | +LL | fn assert_field() {} + | ^^^^^ required by this bound in `assert_field` + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/field_representing_types/non-struct.rs b/tests/ui/field_representing_types/non-struct.rs new file mode 100644 index 0000000000000..50fd4b0e6b0e7 --- /dev/null +++ b/tests/ui/field_representing_types/non-struct.rs @@ -0,0 +1,27 @@ +//@ revisions: old next +//@ [next] compile-flags: -Znext-solver +#![expect(incomplete_features)] +#![feature(field_projections)] + +use std::field::{Field, field_of}; + +pub union MyUnion { + field: u32, + other: i32, +} + +pub enum MyEnum { + A { a: i32, b: i64 }, + B { x: i64, y: i32 }, +} + +fn assert_field() {} + +fn main() { + // FIXME(FRTs): improve this error message, point to the `union`. + assert_field::(); + //~^ ERROR: the trait bound `field_of!(MyUnion, field): std::field::Field` is not satisfied [E0277] + // FIXME(FRTs): improve this error message, point to the `enum`. + assert_field::(); + //~^ ERROR: the trait bound `field_of!(MyEnum, A.a): std::field::Field` is not satisfied [E0277] +} diff --git a/tests/ui/field_representing_types/nonexistent.next.stderr b/tests/ui/field_representing_types/nonexistent.next.stderr new file mode 100644 index 0000000000000..76026ab4a496f --- /dev/null +++ b/tests/ui/field_representing_types/nonexistent.next.stderr @@ -0,0 +1,124 @@ +error[E0573]: expected type, found function `main` + --> $DIR/nonexistent.rs:43:22 + | +LL | let _: field_of!(main, field); + | ^^^^ not a type + +error[E0609]: no field `other` on type `Struct` + --> $DIR/nonexistent.rs:23:30 + | +LL | let _: field_of!(Struct, other); + | ^^^^^ + +error[E0609]: no field `0` on type `Struct` + --> $DIR/nonexistent.rs:24:30 + | +LL | let _: field_of!(Struct, 0); + | ^ + +error[E0609]: no field `other` on type `Union` + --> $DIR/nonexistent.rs:25:29 + | +LL | let _: field_of!(Union, other); + | ^^^^^ + +error[E0609]: no field `0` on type `Union` + --> $DIR/nonexistent.rs:26:29 + | +LL | let _: field_of!(Union, 0); + | ^ + +error[E0609]: no field `other` on type `Enum` + --> $DIR/nonexistent.rs:28:36 + | +LL | let _: field_of!(Enum, Variant.other); + | ^^^^^ + +error[E0609]: no field `0` on type `Enum` + --> $DIR/nonexistent.rs:29:36 + | +LL | let _: field_of!(Enum, Variant.0); + | ^ + +error[E0599]: no variant named `OtherVariant` found for enum `Enum` + --> $DIR/nonexistent.rs:31:28 + | +LL | let _: field_of!(Enum, OtherVariant.field); + | ^^^^^^^^^^^^ + +error[E0599]: no variant named `OtherVariant` found for enum `Enum` + --> $DIR/nonexistent.rs:32:28 + | +LL | let _: field_of!(Enum, OtherVariant.0); + | ^^^^^^^^^^^^ + +error[E0609]: no field `2` on type `((), ())` + --> $DIR/nonexistent.rs:33:32 + | +LL | let _: field_of!(((), ()), 2); + | ^ + +error[E0609]: no field `field` on type `((), ())` + --> $DIR/nonexistent.rs:34:32 + | +LL | let _: field_of!(((), ()), field); + | ^^^^^ + +error: type `i32` doesn't have fields + --> $DIR/nonexistent.rs:36:22 + | +LL | let _: field_of!(i32, field); + | ^^^ + +error: type `[Struct]` doesn't have fields + --> $DIR/nonexistent.rs:37:22 + | +LL | let _: field_of!([Struct], field); + | ^^^^^^^^ + +error: type `[Struct; 42]` is not yet supported in `field_of!` + --> $DIR/nonexistent.rs:38:22 + | +LL | let _: field_of!([Struct; 42], field); + | ^^^^^^^^^^^^ + +error: type `&'static Struct` doesn't have fields + --> $DIR/nonexistent.rs:39:22 + | +LL | let _: field_of!(&'static Struct, field); + | ^^^^^^^^^^^^^^^ + +error: type `*const Struct` doesn't have fields + --> $DIR/nonexistent.rs:40:22 + | +LL | let _: field_of!(*const Struct, field); + | ^^^^^^^^^^^^^ + +error: type `fn() -> Struct` doesn't have fields + --> $DIR/nonexistent.rs:41:22 + | +LL | let _: field_of!(fn() -> Struct, field); + | ^^^^^^^^^^^^^^ + +error: type `dyn Trait` doesn't have fields + --> $DIR/nonexistent.rs:42:22 + | +LL | let _: field_of!(dyn Trait, field); + | ^^^^^^^^^ + +error: cannot use `_` in this position + --> $DIR/nonexistent.rs:44:22 + | +LL | let _: field_of!(_, field); + | ^ + +error: type `T` doesn't have fields + --> $DIR/nonexistent.rs:48:22 + | +LL | let _: field_of!(T, field); + | ^ + +error: aborting due to 20 previous errors + +Some errors have detailed explanations: E0573, E0599, E0609. +For more information about an error, try `rustc --explain E0573`. diff --git a/tests/ui/field_representing_types/nonexistent.old.stderr b/tests/ui/field_representing_types/nonexistent.old.stderr new file mode 100644 index 0000000000000..76026ab4a496f --- /dev/null +++ b/tests/ui/field_representing_types/nonexistent.old.stderr @@ -0,0 +1,124 @@ +error[E0573]: expected type, found function `main` + --> $DIR/nonexistent.rs:43:22 + | +LL | let _: field_of!(main, field); + | ^^^^ not a type + +error[E0609]: no field `other` on type `Struct` + --> $DIR/nonexistent.rs:23:30 + | +LL | let _: field_of!(Struct, other); + | ^^^^^ + +error[E0609]: no field `0` on type `Struct` + --> $DIR/nonexistent.rs:24:30 + | +LL | let _: field_of!(Struct, 0); + | ^ + +error[E0609]: no field `other` on type `Union` + --> $DIR/nonexistent.rs:25:29 + | +LL | let _: field_of!(Union, other); + | ^^^^^ + +error[E0609]: no field `0` on type `Union` + --> $DIR/nonexistent.rs:26:29 + | +LL | let _: field_of!(Union, 0); + | ^ + +error[E0609]: no field `other` on type `Enum` + --> $DIR/nonexistent.rs:28:36 + | +LL | let _: field_of!(Enum, Variant.other); + | ^^^^^ + +error[E0609]: no field `0` on type `Enum` + --> $DIR/nonexistent.rs:29:36 + | +LL | let _: field_of!(Enum, Variant.0); + | ^ + +error[E0599]: no variant named `OtherVariant` found for enum `Enum` + --> $DIR/nonexistent.rs:31:28 + | +LL | let _: field_of!(Enum, OtherVariant.field); + | ^^^^^^^^^^^^ + +error[E0599]: no variant named `OtherVariant` found for enum `Enum` + --> $DIR/nonexistent.rs:32:28 + | +LL | let _: field_of!(Enum, OtherVariant.0); + | ^^^^^^^^^^^^ + +error[E0609]: no field `2` on type `((), ())` + --> $DIR/nonexistent.rs:33:32 + | +LL | let _: field_of!(((), ()), 2); + | ^ + +error[E0609]: no field `field` on type `((), ())` + --> $DIR/nonexistent.rs:34:32 + | +LL | let _: field_of!(((), ()), field); + | ^^^^^ + +error: type `i32` doesn't have fields + --> $DIR/nonexistent.rs:36:22 + | +LL | let _: field_of!(i32, field); + | ^^^ + +error: type `[Struct]` doesn't have fields + --> $DIR/nonexistent.rs:37:22 + | +LL | let _: field_of!([Struct], field); + | ^^^^^^^^ + +error: type `[Struct; 42]` is not yet supported in `field_of!` + --> $DIR/nonexistent.rs:38:22 + | +LL | let _: field_of!([Struct; 42], field); + | ^^^^^^^^^^^^ + +error: type `&'static Struct` doesn't have fields + --> $DIR/nonexistent.rs:39:22 + | +LL | let _: field_of!(&'static Struct, field); + | ^^^^^^^^^^^^^^^ + +error: type `*const Struct` doesn't have fields + --> $DIR/nonexistent.rs:40:22 + | +LL | let _: field_of!(*const Struct, field); + | ^^^^^^^^^^^^^ + +error: type `fn() -> Struct` doesn't have fields + --> $DIR/nonexistent.rs:41:22 + | +LL | let _: field_of!(fn() -> Struct, field); + | ^^^^^^^^^^^^^^ + +error: type `dyn Trait` doesn't have fields + --> $DIR/nonexistent.rs:42:22 + | +LL | let _: field_of!(dyn Trait, field); + | ^^^^^^^^^ + +error: cannot use `_` in this position + --> $DIR/nonexistent.rs:44:22 + | +LL | let _: field_of!(_, field); + | ^ + +error: type `T` doesn't have fields + --> $DIR/nonexistent.rs:48:22 + | +LL | let _: field_of!(T, field); + | ^ + +error: aborting due to 20 previous errors + +Some errors have detailed explanations: E0573, E0599, E0609. +For more information about an error, try `rustc --explain E0573`. diff --git a/tests/ui/field_representing_types/nonexistent.rs b/tests/ui/field_representing_types/nonexistent.rs new file mode 100644 index 0000000000000..74083666af76d --- /dev/null +++ b/tests/ui/field_representing_types/nonexistent.rs @@ -0,0 +1,49 @@ +//@ revisions: old next +//@ [next] compile-flags: -Znext-solver +#![expect(incomplete_features)] +#![feature(field_projections)] + +use std::field::field_of; + +pub struct Struct { + field: i32, +} + +pub union Union { + field: i32, +} + +pub enum Enum { + Variant { field: i32 }, +} + +pub trait Trait {} + +fn main() { + let _: field_of!(Struct, other); //~ ERROR: no field `other` on type `Struct` [E0609] + let _: field_of!(Struct, 0); //~ ERROR: no field `0` on type `Struct` [E0609] + let _: field_of!(Union, other); //~ ERROR: no field `other` on type `Union` [E0609] + let _: field_of!(Union, 0); //~ ERROR: no field `0` on type `Union` [E0609] + // FIXME(FRTs): make the error mention the variant too. + let _: field_of!(Enum, Variant.other); //~ ERROR: no field `other` on type `Enum` [E0609] + let _: field_of!(Enum, Variant.0); //~ ERROR: no field `0` on type `Enum` [E0609] + // FIXME(FRTs): select correct error code + let _: field_of!(Enum, OtherVariant.field); //~ ERROR: no variant named `OtherVariant` found for enum `Enum` + let _: field_of!(Enum, OtherVariant.0); //~ ERROR: no variant named `OtherVariant` found for enum `Enum` + let _: field_of!(((), ()), 2); //~ ERROR: no field `2` on type `((), ())` [E0609] + let _: field_of!(((), ()), field); //~ ERROR: no field `field` on type `((), ())` [E0609] + + let _: field_of!(i32, field); //~ ERROR: type `i32` doesn't have fields + let _: field_of!([Struct], field); //~ ERROR: type `[Struct]` doesn't have fields + let _: field_of!([Struct; 42], field); //~ ERROR: type `[Struct; 42]` is not yet supported in `field_of!` + let _: field_of!(&'static Struct, field); //~ ERROR: type `&'static Struct` doesn't have fields + let _: field_of!(*const Struct, field); //~ ERROR: type `*const Struct` doesn't have fields + let _: field_of!(fn() -> Struct, field); //~ ERROR: type `fn() -> Struct` doesn't have fields + let _: field_of!(dyn Trait, field); //~ ERROR: type `dyn Trait` doesn't have fields + let _: field_of!(main, field); //~ ERROR: expected type, found function `main` + let _: field_of!(_, field); //~ ERROR: cannot use `_` in this position +} + +fn generic() { + let _: field_of!(T, field); //~ ERROR: type `T` doesn't have fields +} diff --git a/tests/ui/field_representing_types/not-field-if-packed.next.stderr b/tests/ui/field_representing_types/not-field-if-packed.next.stderr new file mode 100644 index 0000000000000..a4ff5875a87a1 --- /dev/null +++ b/tests/ui/field_representing_types/not-field-if-packed.next.stderr @@ -0,0 +1,15 @@ +error[E0277]: the trait bound `field_of!(MyStruct, 0): std::field::Field` is not satisfied + --> $DIR/not-field-if-packed.rs:15:20 + | +LL | assert_field::(); + | ^^^^^^^^^^^^^^^^^^^^^^ the nightly-only, unstable trait `std::field::Field` is not implemented for `field_of!(MyStruct, 0)` + | +note: required by a bound in `assert_field` + --> $DIR/not-field-if-packed.rs:11:20 + | +LL | fn assert_field() {} + | ^^^^^ required by this bound in `assert_field` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/field_representing_types/not-field-if-packed.old.stderr b/tests/ui/field_representing_types/not-field-if-packed.old.stderr new file mode 100644 index 0000000000000..a4ff5875a87a1 --- /dev/null +++ b/tests/ui/field_representing_types/not-field-if-packed.old.stderr @@ -0,0 +1,15 @@ +error[E0277]: the trait bound `field_of!(MyStruct, 0): std::field::Field` is not satisfied + --> $DIR/not-field-if-packed.rs:15:20 + | +LL | assert_field::(); + | ^^^^^^^^^^^^^^^^^^^^^^ the nightly-only, unstable trait `std::field::Field` is not implemented for `field_of!(MyStruct, 0)` + | +note: required by a bound in `assert_field` + --> $DIR/not-field-if-packed.rs:11:20 + | +LL | fn assert_field() {} + | ^^^^^ required by this bound in `assert_field` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/field_representing_types/not-field-if-packed.rs b/tests/ui/field_representing_types/not-field-if-packed.rs new file mode 100644 index 0000000000000..7391ef312ba2c --- /dev/null +++ b/tests/ui/field_representing_types/not-field-if-packed.rs @@ -0,0 +1,17 @@ +//@ revisions: old next +//@ [next] compile-flags: -Znext-solver +#![expect(incomplete_features)] +#![feature(field_projections)] + +use std::field::{Field, field_of}; + +#[repr(packed)] +pub struct MyStruct(usize); + +fn assert_field() {} + +fn main() { + // FIXME(FRTs): improve this error message, point to the `repr(packed)` span. + assert_field::(); + //~^ ERROR: the trait bound `field_of!(MyStruct, 0): std::field::Field` is not satisfied [E0277] +} diff --git a/tests/ui/field_representing_types/offset.rs b/tests/ui/field_representing_types/offset.rs new file mode 100644 index 0000000000000..8c769de884d29 --- /dev/null +++ b/tests/ui/field_representing_types/offset.rs @@ -0,0 +1,52 @@ +//@ revisions: old next +//@ [next] compile-flags: -Znext-solver +//@ run-pass +#![expect(incomplete_features)] +#![feature(field_projections)] + +use std::field::{Field, field_of}; +use std::mem::offset_of; +use std::ptr; + +#[repr(C)] +pub struct Struct { + a: i32, + b: i64, +} + +// FIXME(FRTs): need to mark these fields as used by the `field_of!` macro. +#[expect(dead_code)] +pub union Union { + a: i32, + b: i64, +} + +#[repr(C, i8)] +pub enum Enum { + A { a: i32, b: i64 }, + B { x: i64, y: i32 }, +} + +fn project_ref<'a, T, F: Field>(r: &'a T) -> &'a F::Type { + unsafe { &*ptr::from_ref(r).byte_add(F::OFFSET).cast() } +} + +fn main() { + assert_eq!(::OFFSET, offset_of!(Struct, a)); + assert_eq!(::OFFSET, offset_of!(Struct, b)); + + let _: field_of!(Union, a); + let _: field_of!(Union, b); + + let _: field_of!(Enum, A.a); + let _: field_of!(Enum, A.b); + let _: field_of!(Enum, B.x); + let _: field_of!(Enum, B.y); + + let s = Struct { a: 42, b: 24 }; + let r = &s; + let a = project_ref::(r); + let b = project_ref::(r); + assert_eq!(*a, 42); + assert_eq!(*b, 24); +} diff --git a/tests/ui/field_representing_types/privacy.next.stderr b/tests/ui/field_representing_types/privacy.next.stderr new file mode 100644 index 0000000000000..237a2cbc55ff7 --- /dev/null +++ b/tests/ui/field_representing_types/privacy.next.stderr @@ -0,0 +1,15 @@ +error[E0616]: field `a` of struct `foo::Struct` is private + --> $DIR/privacy.rs:28:30 + | +LL | let _: field_of!(Struct, a); + | ^ private field + +error[E0616]: field `a` of union `foo::Union` is private + --> $DIR/privacy.rs:30:29 + | +LL | let _: field_of!(Union, a); + | ^ private field + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0616`. diff --git a/tests/ui/field_representing_types/privacy.old.stderr b/tests/ui/field_representing_types/privacy.old.stderr new file mode 100644 index 0000000000000..237a2cbc55ff7 --- /dev/null +++ b/tests/ui/field_representing_types/privacy.old.stderr @@ -0,0 +1,15 @@ +error[E0616]: field `a` of struct `foo::Struct` is private + --> $DIR/privacy.rs:28:30 + | +LL | let _: field_of!(Struct, a); + | ^ private field + +error[E0616]: field `a` of union `foo::Union` is private + --> $DIR/privacy.rs:30:29 + | +LL | let _: field_of!(Union, a); + | ^ private field + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0616`. diff --git a/tests/ui/field_representing_types/privacy.rs b/tests/ui/field_representing_types/privacy.rs new file mode 100644 index 0000000000000..7805ff6721a6e --- /dev/null +++ b/tests/ui/field_representing_types/privacy.rs @@ -0,0 +1,34 @@ +//@ revisions: old next +//@ [next] compile-flags: -Znext-solver +#![expect(incomplete_features)] +#![feature(field_projections)] + +use std::field::field_of; + +mod foo { + pub struct Struct { + a: i32, + pub b: i32, + } + + pub union Union { + a: i32, + pub b: u32, + } + + pub enum Enum { + A { field: i32 }, + B(i32), + } +} + +use foo::{Enum, Struct, Union}; + +fn main() { + let _: field_of!(Struct, a); //~ ERROR: field `a` of struct `foo::Struct` is private [E0616] + let _: field_of!(Struct, b); + let _: field_of!(Union, a); //~ ERROR: field `a` of union `foo::Union` is private [E0616] + let _: field_of!(Union, b); + let _: field_of!(Enum, A.field); + let _: field_of!(Enum, B.0); +} diff --git a/tests/ui/field_representing_types/projections.next.stderr b/tests/ui/field_representing_types/projections.next.stderr new file mode 100644 index 0000000000000..bf3b41cc23a54 --- /dev/null +++ b/tests/ui/field_representing_types/projections.next.stderr @@ -0,0 +1,32 @@ +error: could not resolve fields of `<::Assoc as Special>::Assoc` + --> $DIR/projections.rs:39:18 + | +LL | _: field_of!(<::Assoc as Special>::Assoc, field), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: could not resolve fields of `<::Assoc as Special>::Assoc` + --> $DIR/projections.rs:45:19 + | +LL | _x: field_of!(<::Assoc as Special>::Assoc, field), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: could not resolve fields of `::Assoc` + --> $DIR/projections.rs:24:22 + | +LL | let _: field_of!(::Assoc, field); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error: could not resolve fields of `<::Assoc as Special>::Assoc` + --> $DIR/projections.rs:51:22 + | +LL | let _: field_of!(<::Assoc as Special>::Assoc, field); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: could not resolve fields of `<::Assoc as Special>::Assoc` + --> $DIR/projections.rs:56:22 + | +LL | let _: field_of!(<::Assoc as Special>::Assoc, other); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 5 previous errors + diff --git a/tests/ui/field_representing_types/projections.old.stderr b/tests/ui/field_representing_types/projections.old.stderr new file mode 100644 index 0000000000000..bf3b41cc23a54 --- /dev/null +++ b/tests/ui/field_representing_types/projections.old.stderr @@ -0,0 +1,32 @@ +error: could not resolve fields of `<::Assoc as Special>::Assoc` + --> $DIR/projections.rs:39:18 + | +LL | _: field_of!(<::Assoc as Special>::Assoc, field), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: could not resolve fields of `<::Assoc as Special>::Assoc` + --> $DIR/projections.rs:45:19 + | +LL | _x: field_of!(<::Assoc as Special>::Assoc, field), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: could not resolve fields of `::Assoc` + --> $DIR/projections.rs:24:22 + | +LL | let _: field_of!(::Assoc, field); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error: could not resolve fields of `<::Assoc as Special>::Assoc` + --> $DIR/projections.rs:51:22 + | +LL | let _: field_of!(<::Assoc as Special>::Assoc, field); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: could not resolve fields of `<::Assoc as Special>::Assoc` + --> $DIR/projections.rs:56:22 + | +LL | let _: field_of!(<::Assoc as Special>::Assoc, other); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 5 previous errors + diff --git a/tests/ui/field_representing_types/projections.rs b/tests/ui/field_representing_types/projections.rs new file mode 100644 index 0000000000000..625f22e25e991 --- /dev/null +++ b/tests/ui/field_representing_types/projections.rs @@ -0,0 +1,58 @@ +//@ revisions: old next +//@ [next] compile-flags: -Znext-solver +#![feature(field_projections, freeze)] +#![expect(incomplete_features, dead_code)] + +use std::field::field_of; + +struct Struct { + field: u32, +} + +type Alias = Struct; + +trait Trait { + type Assoc; +} + +impl Trait for Struct { + type Assoc = Struct; +} + +fn main() { + let _: field_of!(Alias, field); + let _: field_of!(::Assoc, field); + //~^ ERROR: could not resolve fields of `::Assoc` +} + +trait Special { + type Assoc; +} + +trait Constraint: Trait {} + +impl Special for Struct { + type Assoc = Self; +} + +fn with_constraint1( + _: field_of!(<::Assoc as Special>::Assoc, field), + //~^ ERROR: could not resolve fields of `<::Assoc as Special>::Assoc` +) { +} + +fn with_constraint2( + _x: field_of!(<::Assoc as Special>::Assoc, field), + //~^ ERROR: could not resolve fields of `<::Assoc as Special>::Assoc` +) { +} + +fn with_constraint3() { + let _: field_of!(<::Assoc as Special>::Assoc, field); + //~^ ERROR: could not resolve fields of `<::Assoc as Special>::Assoc` +} + +fn with_constraint_invalid_field() { + let _: field_of!(<::Assoc as Special>::Assoc, other); + //~^ ERROR: could not resolve fields of `<::Assoc as Special>::Assoc` +} diff --git a/tests/ui/field_representing_types/sized.next.stderr b/tests/ui/field_representing_types/sized.next.stderr new file mode 100644 index 0000000000000..9d79d05c356b2 --- /dev/null +++ b/tests/ui/field_representing_types/sized.next.stderr @@ -0,0 +1,51 @@ +error[E0277]: the trait bound `field_of!(Generic, count): std::field::Field` is not satisfied + --> $DIR/sized.rs:20:19 + | +LL | impls_field::, count)>(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the nightly-only, unstable trait `std::field::Field` is not implemented for `field_of!(Generic, count)` + | +note: required by a bound in `impls_field` + --> $DIR/sized.rs:38:19 + | +LL | fn impls_field() {} + | ^^^^^ required by this bound in `impls_field` + +error[E0277]: the trait bound `field_of!(Generic, last): std::field::Field` is not satisfied + --> $DIR/sized.rs:22:19 + | +LL | impls_field::, last)>(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ the nightly-only, unstable trait `std::field::Field` is not implemented for `field_of!(Generic, last)` + | +note: required by a bound in `impls_field` + --> $DIR/sized.rs:38:19 + | +LL | fn impls_field() {} + | ^^^^^ required by this bound in `impls_field` + +error[E0277]: the trait bound `field_of!(MyDST, count): std::field::Field` is not satisfied + --> $DIR/sized.rs:32:19 + | +LL | impls_field::(); + | ^^^^^^^^^^^^^^^^^^^^^^^ the nightly-only, unstable trait `std::field::Field` is not implemented for `field_of!(MyDST, count)` + | +note: required by a bound in `impls_field` + --> $DIR/sized.rs:38:19 + | +LL | fn impls_field() {} + | ^^^^^ required by this bound in `impls_field` + +error[E0277]: the trait bound `field_of!(MyDST, last): std::field::Field` is not satisfied + --> $DIR/sized.rs:34:19 + | +LL | impls_field::(); + | ^^^^^^^^^^^^^^^^^^^^^^ the nightly-only, unstable trait `std::field::Field` is not implemented for `field_of!(MyDST, last)` + | +note: required by a bound in `impls_field` + --> $DIR/sized.rs:38:19 + | +LL | fn impls_field() {} + | ^^^^^ required by this bound in `impls_field` + +error: aborting due to 4 previous errors + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/field_representing_types/sized.old.stderr b/tests/ui/field_representing_types/sized.old.stderr new file mode 100644 index 0000000000000..9d79d05c356b2 --- /dev/null +++ b/tests/ui/field_representing_types/sized.old.stderr @@ -0,0 +1,51 @@ +error[E0277]: the trait bound `field_of!(Generic, count): std::field::Field` is not satisfied + --> $DIR/sized.rs:20:19 + | +LL | impls_field::, count)>(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the nightly-only, unstable trait `std::field::Field` is not implemented for `field_of!(Generic, count)` + | +note: required by a bound in `impls_field` + --> $DIR/sized.rs:38:19 + | +LL | fn impls_field() {} + | ^^^^^ required by this bound in `impls_field` + +error[E0277]: the trait bound `field_of!(Generic, last): std::field::Field` is not satisfied + --> $DIR/sized.rs:22:19 + | +LL | impls_field::, last)>(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ the nightly-only, unstable trait `std::field::Field` is not implemented for `field_of!(Generic, last)` + | +note: required by a bound in `impls_field` + --> $DIR/sized.rs:38:19 + | +LL | fn impls_field() {} + | ^^^^^ required by this bound in `impls_field` + +error[E0277]: the trait bound `field_of!(MyDST, count): std::field::Field` is not satisfied + --> $DIR/sized.rs:32:19 + | +LL | impls_field::(); + | ^^^^^^^^^^^^^^^^^^^^^^^ the nightly-only, unstable trait `std::field::Field` is not implemented for `field_of!(MyDST, count)` + | +note: required by a bound in `impls_field` + --> $DIR/sized.rs:38:19 + | +LL | fn impls_field() {} + | ^^^^^ required by this bound in `impls_field` + +error[E0277]: the trait bound `field_of!(MyDST, last): std::field::Field` is not satisfied + --> $DIR/sized.rs:34:19 + | +LL | impls_field::(); + | ^^^^^^^^^^^^^^^^^^^^^^ the nightly-only, unstable trait `std::field::Field` is not implemented for `field_of!(MyDST, last)` + | +note: required by a bound in `impls_field` + --> $DIR/sized.rs:38:19 + | +LL | fn impls_field() {} + | ^^^^^ required by this bound in `impls_field` + +error: aborting due to 4 previous errors + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/field_representing_types/sized.rs b/tests/ui/field_representing_types/sized.rs new file mode 100644 index 0000000000000..8516ef2e2955f --- /dev/null +++ b/tests/ui/field_representing_types/sized.rs @@ -0,0 +1,38 @@ +//@ revisions: old next +//@ [next] compile-flags: -Znext-solver +#![expect(incomplete_features)] +#![feature(field_projections)] + +use std::field::{Field, field_of}; +use std::fmt::Debug; + +pub struct MyDST { + count: usize, + last: dyn Debug, +} + +pub struct Generic { + count: usize, + last: T, +} + +fn generic() { + impls_field::, count)>(); + //~^ ERROR: the trait bound `field_of!(Generic, count): std::field::Field` is not satisfied [E0277] + impls_field::, last)>(); + //~^ ERROR: the trait bound `field_of!(Generic, last): std::field::Field` is not satisfied [E0277] +} + +fn ok() { + impls_field::, count)>(); + impls_field::, last)>(); +} + +fn main() { + impls_field::(); + //~^ ERROR: the trait bound `field_of!(MyDST, count): std::field::Field` is not satisfied [E0277] + impls_field::(); + //~^ ERROR: the trait bound `field_of!(MyDST, last): std::field::Field` is not satisfied [E0277] +} + +fn impls_field() {} diff --git a/tests/ui/field_representing_types/traits.rs b/tests/ui/field_representing_types/traits.rs new file mode 100644 index 0000000000000..6b5bb15f9ee9a --- /dev/null +++ b/tests/ui/field_representing_types/traits.rs @@ -0,0 +1,29 @@ +//@ revisions: old next +//@ [next] compile-flags: -Znext-solver +//@ run-pass +#![feature(field_projections, freeze)] +#![expect(incomplete_features, dead_code)] +use std::field::field_of; +use std::marker::{Freeze, Unpin}; + +struct Struct { + field: u32, +} + +union Union { + field: u32, +} + +enum Enum { + Variant1 { field: u32 }, + Variant2(u32), +} + +fn assert_traits() {} + +fn main() { + assert_traits::(); + assert_traits::(); + assert_traits::(); + assert_traits::(); +} diff --git a/tests/ui/field_representing_types/weird-impls.next.stderr b/tests/ui/field_representing_types/weird-impls.next.stderr new file mode 100644 index 0000000000000..41cc0bc697689 --- /dev/null +++ b/tests/ui/field_representing_types/weird-impls.next.stderr @@ -0,0 +1,30 @@ +error[E0120]: the `Drop` trait may only be implemented for local structs, enums, and unions + --> $DIR/weird-impls.rs:10:15 + | +LL | impl Drop for field_of!(MyStruct, 0) { + | ^^^^^^^^^^^^^^^^^^^^^^ must be a struct, enum, or union in the current crate + | + = note: this error originates in the macro `field_of` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0321]: cross-crate traits with a default impl, like `Send`, can only be implemented for a struct/enum type defined in the current crate + --> $DIR/weird-impls.rs:15:1 + | +LL | unsafe impl Send for field_of!(MyStruct, 0) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ can't implement cross-crate trait for type in another crate + +error[E0322]: explicit impls for the `Field` trait are not permitted + --> $DIR/weird-impls.rs:21:1 + | +LL | unsafe impl Field for field_of!(MyStruct2, 0) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ impl of `Field` not allowed + +error[E0322]: explicit impls for the `Field` trait are not permitted + --> $DIR/weird-impls.rs:30:1 + | +LL | unsafe impl Field for MyField { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ impl of `Field` not allowed + +error: aborting due to 4 previous errors + +Some errors have detailed explanations: E0120, E0321, E0322. +For more information about an error, try `rustc --explain E0120`. diff --git a/tests/ui/field_representing_types/weird-impls.old.stderr b/tests/ui/field_representing_types/weird-impls.old.stderr new file mode 100644 index 0000000000000..41cc0bc697689 --- /dev/null +++ b/tests/ui/field_representing_types/weird-impls.old.stderr @@ -0,0 +1,30 @@ +error[E0120]: the `Drop` trait may only be implemented for local structs, enums, and unions + --> $DIR/weird-impls.rs:10:15 + | +LL | impl Drop for field_of!(MyStruct, 0) { + | ^^^^^^^^^^^^^^^^^^^^^^ must be a struct, enum, or union in the current crate + | + = note: this error originates in the macro `field_of` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0321]: cross-crate traits with a default impl, like `Send`, can only be implemented for a struct/enum type defined in the current crate + --> $DIR/weird-impls.rs:15:1 + | +LL | unsafe impl Send for field_of!(MyStruct, 0) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ can't implement cross-crate trait for type in another crate + +error[E0322]: explicit impls for the `Field` trait are not permitted + --> $DIR/weird-impls.rs:21:1 + | +LL | unsafe impl Field for field_of!(MyStruct2, 0) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ impl of `Field` not allowed + +error[E0322]: explicit impls for the `Field` trait are not permitted + --> $DIR/weird-impls.rs:30:1 + | +LL | unsafe impl Field for MyField { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ impl of `Field` not allowed + +error: aborting due to 4 previous errors + +Some errors have detailed explanations: E0120, E0321, E0322. +For more information about an error, try `rustc --explain E0120`. diff --git a/tests/ui/field_representing_types/weird-impls.rs b/tests/ui/field_representing_types/weird-impls.rs new file mode 100644 index 0000000000000..d61c0c7a6a056 --- /dev/null +++ b/tests/ui/field_representing_types/weird-impls.rs @@ -0,0 +1,37 @@ +//@ revisions: old next +//@ [next] compile-flags: -Znext-solver +#![expect(incomplete_features)] +#![feature(field_projections)] + +use std::field::{Field, field_of}; + +pub struct MyStruct(()); + +impl Drop for field_of!(MyStruct, 0) { + //~^ ERROR: the `Drop` trait may only be implemented for local structs, enums, and unions [E0120] + fn drop(&mut self) {} +} + +unsafe impl Send for field_of!(MyStruct, 0) {} +//~^ ERROR: cross-crate traits with a default impl, like `Send`, can only be implemented for a struct/enum type defined in the current crate [E0321] + +#[repr(packed)] +pub struct MyStruct2(usize); + +unsafe impl Field for field_of!(MyStruct2, 0) { + //~^ ERROR: explicit impls for the `Field` trait are not permitted [E0322] + type Base = MyStruct2; + type Type = usize; + const OFFSET: usize = 0; +} + +pub struct MyField; + +unsafe impl Field for MyField { + //~^ ERROR: explicit impls for the `Field` trait are not permitted [E0322] + type Base = (); + type Type = (); + const OFFSET: usize = 0; +} + +fn main() {} From 350a964d7ca480dcfcae58ea397f8886ce004e9d Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Fri, 27 Feb 2026 15:22:47 +0000 Subject: [PATCH 33/39] Fix subdiagnostics that use non-local variables --- compiler/rustc_hir_analysis/src/errors.rs | 3 ++ .../src/hir_ty_lowering/errors.rs | 22 ++++++--- compiler/rustc_lint/src/builtin.rs | 5 +- .../src/deref_into_dyn_supertrait.rs | 5 +- compiler/rustc_lint/src/early/diagnostics.rs | 12 ++--- .../rustc_lint/src/interior_mutable_consts.rs | 3 +- compiler/rustc_lint/src/lints.rs | 49 +++++++++++++------ compiler/rustc_lint/src/types/literal.rs | 29 ++++++----- compiler/rustc_lint/src/unused.rs | 1 + .../rustc_metadata/src/dependency_format.rs | 2 +- compiler/rustc_metadata/src/errors.rs | 4 +- .../rustc_mir_build/src/check_unsafety.rs | 2 +- compiler/rustc_mir_build/src/errors.rs | 2 +- compiler/rustc_mir_transform/src/errors.rs | 1 + compiler/rustc_mir_transform/src/inline.rs | 6 ++- compiler/rustc_parse/src/errors.rs | 4 +- compiler/rustc_parse/src/parser/expr.rs | 3 +- compiler/rustc_parse/src/parser/pat.rs | 1 + compiler/rustc_query_impl/src/error.rs | 4 +- compiler/rustc_query_impl/src/job.rs | 9 +++- compiler/rustc_resolve/src/diagnostics.rs | 13 +++-- compiler/rustc_resolve/src/errors.rs | 4 ++ .../traits/fulfillment_errors.rs | 2 + compiler/rustc_trait_selection/src/errors.rs | 2 + 24 files changed, 123 insertions(+), 65 deletions(-) diff --git a/compiler/rustc_hir_analysis/src/errors.rs b/compiler/rustc_hir_analysis/src/errors.rs index c91b67e763811..79b6f9ea84f28 100644 --- a/compiler/rustc_hir_analysis/src/errors.rs +++ b/compiler/rustc_hir_analysis/src/errors.rs @@ -88,6 +88,8 @@ pub(crate) enum AssocItemNotFoundLabel<'a> { NotFound { #[primary_span] span: Span, + assoc_ident: Ident, + assoc_kind: &'static str, }, #[label( "there is {$identically_named -> @@ -149,6 +151,7 @@ pub(crate) enum AssocItemNotFoundSugg<'a> { trait_ref: String, suggested_name: Symbol, identically_named: bool, + assoc_kind: &'static str, #[applicability] applicability: Applicability, }, diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs index 52e5cce140ad8..6bcc2e40e5248 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs @@ -141,7 +141,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { ); } - let assoc_kind_str = assoc_tag_str(assoc_tag); + let assoc_kind = assoc_tag_str(assoc_tag); let qself_str = qself.to_string(tcx); // The fallback span is needed because `assoc_name` might be an `Fn()`'s `Output` without a @@ -151,7 +151,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { let mut err = errors::AssocItemNotFound { span: if is_dummy { span } else { assoc_ident.span }, assoc_ident, - assoc_kind: assoc_kind_str, + assoc_kind, qself: &qself_str, label: None, sugg: None, @@ -161,7 +161,8 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { }; if is_dummy { - err.label = Some(errors::AssocItemNotFoundLabel::NotFound { span }); + err.label = + Some(errors::AssocItemNotFoundLabel::NotFound { span, assoc_ident, assoc_kind }); return self.dcx().emit_err(err); } @@ -181,7 +182,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { { err.sugg = Some(errors::AssocItemNotFoundSugg::Similar { span: assoc_ident.span, - assoc_kind: assoc_kind_str, + assoc_kind, suggested_name, }); return self.dcx().emit_err(err); @@ -224,7 +225,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { let trait_name = tcx.def_path_str(best_trait); err.label = Some(errors::AssocItemNotFoundLabel::FoundInOtherTrait { span: assoc_ident.span, - assoc_kind: assoc_kind_str, + assoc_kind, trait_name: &trait_name, suggested_name, identically_named: suggested_name == assoc_ident.name, @@ -256,7 +257,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { err.sugg = Some(errors::AssocItemNotFoundSugg::SimilarInOtherTrait { span: assoc_ident.span, trait_name: &trait_name, - assoc_kind: assoc_kind_str, + assoc_kind, suggested_name, }); return self.dcx().emit_err(err); @@ -286,6 +287,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { trait_ref, identically_named, suggested_name, + assoc_kind, applicability, }); } else { @@ -322,11 +324,15 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { err.sugg = Some(errors::AssocItemNotFoundSugg::Other { span: assoc_ident.span, qself: &qself_str, - assoc_kind: assoc_kind_str, + assoc_kind, suggested_name: *candidate_name, }); } else { - err.label = Some(errors::AssocItemNotFoundLabel::NotFound { span: assoc_ident.span }); + err.label = Some(errors::AssocItemNotFoundLabel::NotFound { + span: assoc_ident.span, + assoc_ident, + assoc_kind, + }); } self.dcx().emit_err(err) diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs index e7b821a400ea4..85b881cddc2b8 100644 --- a/compiler/rustc_lint/src/builtin.rs +++ b/compiler/rustc_lint/src/builtin.rs @@ -2320,8 +2320,9 @@ impl EarlyLintPass for IncompleteInternalFeatures { if features.incomplete(name) { let note = rustc_feature::find_feature_issue(name, GateIssue::Language) .map(|n| BuiltinFeatureIssueNote { n }); - let help = - HAS_MIN_FEATURES.contains(&name).then_some(BuiltinIncompleteFeaturesHelp); + let help = HAS_MIN_FEATURES + .contains(&name) + .then_some(BuiltinIncompleteFeaturesHelp { name }); cx.emit_span_lint( INCOMPLETE_FEATURES, diff --git a/compiler/rustc_lint/src/deref_into_dyn_supertrait.rs b/compiler/rustc_lint/src/deref_into_dyn_supertrait.rs index 703f757abd50c..3e1a418c8c962 100644 --- a/compiler/rustc_lint/src/deref_into_dyn_supertrait.rs +++ b/compiler/rustc_lint/src/deref_into_dyn_supertrait.rs @@ -87,7 +87,10 @@ impl<'tcx> LateLintPass<'tcx> for DerefIntoDynSupertrait { ty::AssocTag::Type, item.owner_id.to_def_id(), ) - .map(|label| SupertraitAsDerefTargetLabel { label: tcx.def_span(label.def_id) }); + .map(|label| SupertraitAsDerefTargetLabel { + label: tcx.def_span(label.def_id), + self_ty, + }); let span = tcx.def_span(item.owner_id.def_id); cx.emit_span_lint( DEREF_INTO_DYN_SUPERTRAIT, diff --git a/compiler/rustc_lint/src/early/diagnostics.rs b/compiler/rustc_lint/src/early/diagnostics.rs index 5aee3f382ff3c..31a6351c6e70b 100644 --- a/compiler/rustc_lint/src/early/diagnostics.rs +++ b/compiler/rustc_lint/src/early/diagnostics.rs @@ -105,13 +105,11 @@ pub fn decorate_builtin_lint( BuiltinLintDiag::RedundantImport(spans, ident) => { let subs = spans .into_iter() - .map(|(span, is_imported)| { - (match (span.is_dummy(), is_imported) { - (false, true) => lints::RedundantImportSub::ImportedHere, - (false, false) => lints::RedundantImportSub::DefinedHere, - (true, true) => lints::RedundantImportSub::ImportedPrelude, - (true, false) => lints::RedundantImportSub::DefinedPrelude, - })(span) + .map(|(span, is_imported)| match (span.is_dummy(), is_imported) { + (false, true) => lints::RedundantImportSub::ImportedHere { span, ident }, + (false, false) => lints::RedundantImportSub::DefinedHere { span, ident }, + (true, true) => lints::RedundantImportSub::ImportedPrelude { span, ident }, + (true, false) => lints::RedundantImportSub::DefinedPrelude { span, ident }, }) .collect(); lints::RedundantImport { subs, ident }.decorate_lint(diag); diff --git a/compiler/rustc_lint/src/interior_mutable_consts.rs b/compiler/rustc_lint/src/interior_mutable_consts.rs index 807a121abd9e6..7d796acb260c4 100644 --- a/compiler/rustc_lint/src/interior_mutable_consts.rs +++ b/compiler/rustc_lint/src/interior_mutable_consts.rs @@ -105,9 +105,10 @@ impl<'tcx> LateLintPass<'tcx> for InteriorMutableConsts { Some(ConstItemInteriorMutationsSuggestionStatic::Spanful { const_: const_item.vis_span.between(ident.span), before: if !vis_span.is_empty() { " " } else { "" }, + const_name, }) } else { - Some(ConstItemInteriorMutationsSuggestionStatic::Spanless) + Some(ConstItemInteriorMutationsSuggestionStatic::Spanless { const_name }) } } else { None diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs index 0461d19d544c6..bd7ce30e2ccad 100644 --- a/compiler/rustc_lint/src/lints.rs +++ b/compiler/rustc_lint/src/lints.rs @@ -532,7 +532,9 @@ pub(crate) struct BuiltinInternalFeatures { #[derive(Subdiagnostic)] #[help("consider using `min_{$name}` instead, which is more stable and complete")] -pub(crate) struct BuiltinIncompleteFeaturesHelp; +pub(crate) struct BuiltinIncompleteFeaturesHelp { + pub name: Symbol, +} #[derive(Subdiagnostic)] #[note("see issue #{$n} for more information")] @@ -663,14 +665,15 @@ pub(crate) struct SupertraitAsDerefTarget<'a> { )] pub label: Span, #[subdiagnostic] - pub label2: Option, + pub label2: Option>, } #[derive(Subdiagnostic)] #[label("target type is a supertrait of `{$self_ty}`")] -pub(crate) struct SupertraitAsDerefTargetLabel { +pub(crate) struct SupertraitAsDerefTargetLabel<'a> { #[primary_span] pub label: Span, + pub self_ty: Ty<'a>, } // enum_intrinsics_non_enums.rs @@ -958,9 +961,10 @@ pub(crate) enum ConstItemInteriorMutationsSuggestionStatic { #[primary_span] const_: Span, before: &'static str, + const_name: Ident, }, #[help("for a shared instance of `{$const_name}`, consider making it a `static` item instead")] - Spanless, + Spanless { const_name: Ident }, } // reference_casting.rs @@ -1972,11 +1976,8 @@ pub(crate) enum UseInclusiveRange<'a> { #[diag("literal out of range for `{$ty}`")] pub(crate) struct OverflowingBinHex<'a> { pub ty: &'a str, - pub lit: String, - pub dec: u128, - pub actually: String, #[subdiagnostic] - pub sign: OverflowingBinHexSign, + pub sign: OverflowingBinHexSign<'a>, #[subdiagnostic] pub sub: Option>, #[subdiagnostic] @@ -1984,14 +1985,14 @@ pub(crate) struct OverflowingBinHex<'a> { } #[derive(Subdiagnostic)] -pub(crate) enum OverflowingBinHexSign { +pub(crate) enum OverflowingBinHexSign<'a> { #[note( "the literal `{$lit}` (decimal `{$dec}`) does not fit into the type `{$ty}` and will become `{$actually}{$ty}`" )] - Positive, + Positive { lit: String, ty: &'a str, actually: String, dec: u128 }, #[note("the literal `{$lit}` (decimal `{$dec}`) does not fit into the type `{$ty}`")] #[note("and the value `-{$lit}` will become `{$actually}{$ty}`")] - Negative, + Negative { lit: String, ty: &'a str, actually: String, dec: u128 }, } #[derive(Subdiagnostic)] @@ -2562,6 +2563,7 @@ pub(crate) struct UnusedDelimSuggestion { #[suggestion_part(code = "{end_replace}")] pub end_span: Span, pub end_replace: &'static str, + pub delim: &'static str, } #[derive(Diagnostic)] @@ -3131,20 +3133,35 @@ pub(crate) enum UnusedImportsSugg { pub(crate) struct RedundantImport { #[subdiagnostic] pub subs: Vec, - pub ident: Ident, } #[derive(Subdiagnostic)] pub(crate) enum RedundantImportSub { #[label("the item `{$ident}` is already imported here")] - ImportedHere(#[primary_span] Span), + ImportedHere { + #[primary_span] + span: Span, + ident: Ident, + }, #[label("the item `{$ident}` is already defined here")] - DefinedHere(#[primary_span] Span), + DefinedHere { + #[primary_span] + span: Span, + ident: Ident, + }, #[label("the item `{$ident}` is already imported by the extern prelude")] - ImportedPrelude(#[primary_span] Span), + ImportedPrelude { + #[primary_span] + span: Span, + ident: Ident, + }, #[label("the item `{$ident}` is already defined by the extern prelude")] - DefinedPrelude(#[primary_span] Span), + DefinedPrelude { + #[primary_span] + span: Span, + ident: Ident, + }, } #[derive(LintDiagnostic)] diff --git a/compiler/rustc_lint/src/types/literal.rs b/compiler/rustc_lint/src/types/literal.rs index b6c67549c47e2..71e011049e83a 100644 --- a/compiler/rustc_lint/src/types/literal.rs +++ b/compiler/rustc_lint/src/types/literal.rs @@ -157,8 +157,21 @@ fn report_bin_hex_error( (t.name_str(), actually.to_string()) } }; - let sign = - if negative { OverflowingBinHexSign::Negative } else { OverflowingBinHexSign::Positive }; + let sign = if negative { + OverflowingBinHexSign::Negative { + lit: repr_str.clone(), + dec: val, + actually: actually.clone(), + ty: t, + } + } else { + OverflowingBinHexSign::Positive { + lit: repr_str.clone(), + dec: val, + actually: actually.clone(), + ty: t, + } + }; let sub = get_type_suggestion(cx.typeck_results().node_type(hir_id), val, negative).map( |suggestion_ty| { if let Some(pos) = repr_str.chars().position(|c| c == 'i' || c == 'u') { @@ -194,7 +207,7 @@ fn report_bin_hex_error( Some(OverflowingBinHexSignBitSub { span, lit_no_suffix, - negative_val: actually.clone(), + negative_val: actually, int_ty: int_ty.name_str(), uint_ty: Integer::fit_unsigned(val).uint_ty_str(), }) @@ -204,15 +217,7 @@ fn report_bin_hex_error( cx.emit_span_lint( OVERFLOWING_LITERALS, span, - OverflowingBinHex { - ty: t, - lit: repr_str.clone(), - dec: val, - actually, - sign, - sub, - sign_bit_sub, - }, + OverflowingBinHex { ty: t, sign, sub, sign_bit_sub }, ) } diff --git a/compiler/rustc_lint/src/unused.rs b/compiler/rustc_lint/src/unused.rs index 1fd0ee754eb46..03a566efc8a54 100644 --- a/compiler/rustc_lint/src/unused.rs +++ b/compiler/rustc_lint/src/unused.rs @@ -350,6 +350,7 @@ trait UnusedDelimLint { start_replace: lo_replace, end_span: hi, end_replace: hi_replace, + delim: Self::DELIM_STR, } }); cx.emit_span_lint( diff --git a/compiler/rustc_metadata/src/dependency_format.rs b/compiler/rustc_metadata/src/dependency_format.rs index 30721784ad65e..1547648100d58 100644 --- a/compiler/rustc_metadata/src/dependency_format.rs +++ b/compiler/rustc_metadata/src/dependency_format.rs @@ -315,7 +315,7 @@ fn add_library( crate_name: tcx.crate_name(cnum), non_static_deps: unavailable_as_static .drain(..) - .map(|cnum| NonStaticCrateDep { crate_name_: tcx.crate_name(cnum) }) + .map(|cnum| NonStaticCrateDep { sub_crate_name: tcx.crate_name(cnum) }) .collect(), rustc_driver_help: linking_to_rustc_driver, }); diff --git a/compiler/rustc_metadata/src/errors.rs b/compiler/rustc_metadata/src/errors.rs index 7320ad98d113a..d4c61441bcea7 100644 --- a/compiler/rustc_metadata/src/errors.rs +++ b/compiler/rustc_metadata/src/errors.rs @@ -48,10 +48,10 @@ pub struct CrateDepMultiple { } #[derive(Subdiagnostic)] -#[note("`{$crate_name}` was unavailable as a static crate, preventing fully static linking")] +#[note("`{$sub_crate_name}` was unavailable as a static crate, preventing fully static linking")] pub struct NonStaticCrateDep { /// It's different from `crate_name` in main Diagnostic. - pub crate_name_: Symbol, + pub sub_crate_name: Symbol, } #[derive(Diagnostic)] diff --git a/compiler/rustc_mir_build/src/check_unsafety.rs b/compiler/rustc_mir_build/src/check_unsafety.rs index 24c85b7ec3f5d..f79adb2eb5983 100644 --- a/compiler/rustc_mir_build/src/check_unsafety.rs +++ b/compiler/rustc_mir_build/src/check_unsafety.rs @@ -115,12 +115,12 @@ impl<'tcx> UnsafetyVisitor<'_, 'tcx> { CallToDeprecatedSafeFnRequiresUnsafe { span, function: with_no_trimmed_paths!(self.tcx.def_path_str(id)), - guarantee, sub: CallToDeprecatedSafeFnRequiresUnsafeSub { start_of_line_suggestion: suggestion, start_of_line: sm.span_extend_to_line(span).shrink_to_lo(), left: span.shrink_to_lo(), right: span.shrink_to_hi(), + guarantee, }, }, ); diff --git a/compiler/rustc_mir_build/src/errors.rs b/compiler/rustc_mir_build/src/errors.rs index fead221b9a0ca..ccd0fdfcd1e7a 100644 --- a/compiler/rustc_mir_build/src/errors.rs +++ b/compiler/rustc_mir_build/src/errors.rs @@ -15,7 +15,6 @@ pub(crate) struct CallToDeprecatedSafeFnRequiresUnsafe { #[label("call to unsafe function")] pub(crate) span: Span, pub(crate) function: String, - pub(crate) guarantee: String, #[subdiagnostic] pub(crate) sub: CallToDeprecatedSafeFnRequiresUnsafeSub, } @@ -33,6 +32,7 @@ pub(crate) struct CallToDeprecatedSafeFnRequiresUnsafeSub { pub(crate) left: Span, #[suggestion_part(code = " }}")] pub(crate) right: Span, + pub(crate) guarantee: String, } #[derive(Diagnostic)] diff --git a/compiler/rustc_mir_transform/src/errors.rs b/compiler/rustc_mir_transform/src/errors.rs index 820becd7031a4..6f0f9cc23a55d 100644 --- a/compiler/rustc_mir_transform/src/errors.rs +++ b/compiler/rustc_mir_transform/src/errors.rs @@ -389,4 +389,5 @@ pub(crate) struct ForceInlineFailure { #[note("`{$callee}` is required to be inlined to: {$sym}")] pub(crate) struct ForceInlineJustification { pub sym: Symbol, + pub callee: String, } diff --git a/compiler/rustc_mir_transform/src/inline.rs b/compiler/rustc_mir_transform/src/inline.rs index 4e2d9130f2a4f..97441bf29bbdf 100644 --- a/compiler/rustc_mir_transform/src/inline.rs +++ b/compiler/rustc_mir_transform/src/inline.rs @@ -251,15 +251,17 @@ impl<'tcx> Inliner<'tcx> for ForceInliner<'tcx> { }; let call_span = callsite.source_info.span; + let callee = tcx.def_path_str(callsite.callee.def_id()); tcx.dcx().emit_err(crate::errors::ForceInlineFailure { call_span, attr_span, caller_span: tcx.def_span(self.def_id), caller: tcx.def_path_str(self.def_id), callee_span: tcx.def_span(callsite.callee.def_id()), - callee: tcx.def_path_str(callsite.callee.def_id()), + callee: callee.clone(), reason, - justification: justification.map(|sym| crate::errors::ForceInlineJustification { sym }), + justification: justification + .map(|sym| crate::errors::ForceInlineJustification { sym, callee }), }); } } diff --git a/compiler/rustc_parse/src/errors.rs b/compiler/rustc_parse/src/errors.rs index cefd0e35f8a5c..c7e2b67d88d2f 100644 --- a/compiler/rustc_parse/src/errors.rs +++ b/compiler/rustc_parse/src/errors.rs @@ -1016,7 +1016,6 @@ pub(crate) struct LeadingPlusNotSupported { pub(crate) struct ParenthesesWithStructFields { #[primary_span] pub span: Span, - pub r#type: Path, #[subdiagnostic] pub braces_for_struct: BracesForStructLiteral, #[subdiagnostic] @@ -1029,6 +1028,7 @@ pub(crate) struct ParenthesesWithStructFields { applicability = "maybe-incorrect" )] pub(crate) struct BracesForStructLiteral { + pub r#type: Path, #[suggestion_part(code = " {{ ")] pub first: Span, #[suggestion_part(code = " }}")] @@ -1041,6 +1041,7 @@ pub(crate) struct BracesForStructLiteral { applicability = "maybe-incorrect" )] pub(crate) struct NoFieldsForFnCall { + pub r#type: Path, #[suggestion_part(code = "")] pub fields: Vec, } @@ -3176,6 +3177,7 @@ pub(crate) struct UnexpectedVertVertInPattern { pub(crate) struct TrailingVertSuggestion { #[primary_span] pub span: Span, + pub token: Token, } #[derive(Diagnostic)] diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index 44e8f13dd2364..52df6e1b06a4d 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -1309,12 +1309,13 @@ impl<'a> Parser<'a> { self.dcx() .create_err(errors::ParenthesesWithStructFields { span, - r#type: path, braces_for_struct: errors::BracesForStructLiteral { first: open_paren, second: close_paren, + r#type: path.clone(), }, no_fields_for_fn: errors::NoFieldsForFnCall { + r#type: path, fields: fields .into_iter() .map(|field| field.span.until(field.expr.span)) diff --git a/compiler/rustc_parse/src/parser/pat.rs b/compiler/rustc_parse/src/parser/pat.rs index bc73c3a2007a0..528b69abbf1aa 100644 --- a/compiler/rustc_parse/src/parser/pat.rs +++ b/compiler/rustc_parse/src/parser/pat.rs @@ -364,6 +364,7 @@ impl<'a> Parser<'a> { start: lo, suggestion: TrailingVertSuggestion { span: self.prev_token.span.shrink_to_hi().with_hi(self.token.span.hi()), + token: self.token, }, token: self.token, note_double_vert: self.token.kind == token::OrOr, diff --git a/compiler/rustc_query_impl/src/error.rs b/compiler/rustc_query_impl/src/error.rs index 6d3eb29509826..44d53f87aae29 100644 --- a/compiler/rustc_query_impl/src/error.rs +++ b/compiler/rustc_query_impl/src/error.rs @@ -35,9 +35,9 @@ pub(crate) struct CycleStack { #[derive(Subdiagnostic)] pub(crate) enum StackCount { #[note("...which immediately requires {$stack_bottom} again")] - Single, + Single { stack_bottom: String }, #[note("...which again requires {$stack_bottom}, completing the cycle")] - Multiple, + Multiple { stack_bottom: String }, } #[derive(Subdiagnostic)] diff --git a/compiler/rustc_query_impl/src/job.rs b/compiler/rustc_query_impl/src/job.rs index 2d9824a783ea5..54b011a17ffc7 100644 --- a/compiler/rustc_query_impl/src/job.rs +++ b/compiler/rustc_query_impl/src/job.rs @@ -438,7 +438,12 @@ pub(crate) fn report_cycle<'a>( let mut cycle_stack = Vec::new(); use crate::error::StackCount; - let stack_count = if stack.len() == 1 { StackCount::Single } else { StackCount::Multiple }; + let stack_bottom = stack[0].frame.info.description.to_owned(); + let stack_count = if stack.len() == 1 { + StackCount::Single { stack_bottom: stack_bottom.clone() } + } else { + StackCount::Multiple { stack_bottom: stack_bottom.clone() } + }; for i in 1..stack.len() { let frame = &stack[i].frame; @@ -467,7 +472,7 @@ pub(crate) fn report_cycle<'a>( let cycle_diag = crate::error::Cycle { span, cycle_stack, - stack_bottom: stack[0].frame.info.description.to_owned(), + stack_bottom, alias, cycle_usage, stack_count, diff --git a/compiler/rustc_resolve/src/diagnostics.rs b/compiler/rustc_resolve/src/diagnostics.rs index f1beae0eaf78d..716e986b4b4c5 100644 --- a/compiler/rustc_resolve/src/diagnostics.rs +++ b/compiler/rustc_resolve/src/diagnostics.rs @@ -249,20 +249,23 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { }; let label = match new_binding.is_import_user_facing() { - true => errors::NameDefinedMultipleTimeLabel::Reimported { span }, - false => errors::NameDefinedMultipleTimeLabel::Redefined { span }, + true => errors::NameDefinedMultipleTimeLabel::Reimported { span, name }, + false => errors::NameDefinedMultipleTimeLabel::Redefined { span, name }, }; let old_binding_label = (!old_binding.span.is_dummy() && old_binding.span != span).then(|| { let span = self.tcx.sess.source_map().guess_head_span(old_binding.span); match old_binding.is_import_user_facing() { - true => { - errors::NameDefinedMultipleTimeOldBindingLabel::Import { span, old_kind } - } + true => errors::NameDefinedMultipleTimeOldBindingLabel::Import { + span, + old_kind, + name, + }, false => errors::NameDefinedMultipleTimeOldBindingLabel::Definition { span, old_kind, + name, }, } }); diff --git a/compiler/rustc_resolve/src/errors.rs b/compiler/rustc_resolve/src/errors.rs index c107f15bdad51..ee07f5b8f43d4 100644 --- a/compiler/rustc_resolve/src/errors.rs +++ b/compiler/rustc_resolve/src/errors.rs @@ -1126,11 +1126,13 @@ pub(crate) enum NameDefinedMultipleTimeLabel { Reimported { #[primary_span] span: Span, + name: Symbol, }, #[label("`{$name}` redefined here")] Redefined { #[primary_span] span: Span, + name: Symbol, }, } @@ -1141,12 +1143,14 @@ pub(crate) enum NameDefinedMultipleTimeOldBindingLabel { #[primary_span] span: Span, old_kind: &'static str, + name: Symbol, }, #[label("previous definition of the {$old_kind} `{$name}` here")] Definition { #[primary_span] span: Span, old_kind: &'static str, + name: Symbol, }, } 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 f4a9b6635c91b..4f51435290e49 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 @@ -3288,12 +3288,14 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { err.fn_once_label = Some(ClosureFnOnceLabel { span: *span, place: ty::place_to_string_for_capture(self.tcx, place), + trait_prefix, }) } (ty::ClosureKind::FnMut, Some((span, place))) => { err.fn_mut_label = Some(ClosureFnMutLabel { span: *span, place: ty::place_to_string_for_capture(self.tcx, place), + trait_prefix, }) } _ => {} diff --git a/compiler/rustc_trait_selection/src/errors.rs b/compiler/rustc_trait_selection/src/errors.rs index 0f4ff1790bdf2..d30cd807647f3 100644 --- a/compiler/rustc_trait_selection/src/errors.rs +++ b/compiler/rustc_trait_selection/src/errors.rs @@ -157,6 +157,7 @@ pub struct ClosureFnOnceLabel { #[primary_span] pub span: Span, pub place: String, + pub trait_prefix: &'static str, } #[derive(Subdiagnostic)] @@ -165,6 +166,7 @@ pub struct ClosureFnMutLabel { #[primary_span] pub span: Span, pub place: String, + pub trait_prefix: &'static str, } #[derive(Diagnostic)] From f6e527ed28621751552ddf55822807277887a4d5 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Fri, 27 Feb 2026 19:22:20 +0100 Subject: [PATCH 34/39] Improve code by ensuring that if new doc attributes are added, we will be forced to take them into account when inlining --- src/librustdoc/clean/mod.rs | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index d109dd31bb855..a01473c632f4b 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -2701,7 +2701,29 @@ fn add_without_unwanted_attributes<'hir>( } hir::Attribute::Parsed(AttributeKind::Doc(box d)) => { // Remove attributes from `normal` that should not be inherited by `use` re-export. - let DocAttribute { hidden, inline, cfg, aliases, .. } = d; + let DocAttribute { + aliases, + hidden, + inline, + cfg, + auto_cfg: _, + auto_cfg_change: _, + fake_variadic: _, + keyword: _, + attribute: _, + masked: _, + notable_trait: _, + search_unbox: _, + html_favicon_url: _, + html_logo_url: _, + html_playground_url: _, + html_root_url: _, + html_no_source: _, + issue_tracker_base_url: _, + rust_logo: _, + test_attrs: _, + no_crate_inject: _, + } = d; let mut attr = DocAttribute::default(); if is_inline { attr.cfg = cfg.clone(); From ab9e1da0a1a4f3bc5d54c6d0521428d80ce1c4d4 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Fri, 27 Feb 2026 19:22:38 +0100 Subject: [PATCH 35/39] Add regression test for `search_unbox` inlined reexport --- .../auxiliary/reexport-search_unbox.rs | 20 +++++++++++++++++++ tests/rustdoc-js/reexport-search_unbox.js | 12 +++++++++++ tests/rustdoc-js/reexport-search_unbox.rs | 12 +++++++++++ 3 files changed, 44 insertions(+) create mode 100644 tests/rustdoc-js/auxiliary/reexport-search_unbox.rs create mode 100644 tests/rustdoc-js/reexport-search_unbox.js create mode 100644 tests/rustdoc-js/reexport-search_unbox.rs diff --git a/tests/rustdoc-js/auxiliary/reexport-search_unbox.rs b/tests/rustdoc-js/auxiliary/reexport-search_unbox.rs new file mode 100644 index 0000000000000..ee9b6acfedd5c --- /dev/null +++ b/tests/rustdoc-js/auxiliary/reexport-search_unbox.rs @@ -0,0 +1,20 @@ +#![feature(rustdoc_internals)] + +#[doc(search_unbox)] +pub struct Inside(T); + +#[doc(search_unbox)] +pub struct Out { + a: A, + b: B, +} + +#[doc(search_unbox)] +pub struct Out1 { + a: [A; N], +} + +#[doc(search_unbox)] +pub struct Out2 { + a: [A; N], +} diff --git a/tests/rustdoc-js/reexport-search_unbox.js b/tests/rustdoc-js/reexport-search_unbox.js new file mode 100644 index 0000000000000..87c8f63354a61 --- /dev/null +++ b/tests/rustdoc-js/reexport-search_unbox.js @@ -0,0 +1,12 @@ +// exact-check + +// This test ensures that `search_unbox` works even on inlined reexports. + +const EXPECTED = [ + { + 'query': 'Inside -> Out1', + 'others': [ + { 'path': 'foo', 'name': 'alpha' }, + ], + }, +] diff --git a/tests/rustdoc-js/reexport-search_unbox.rs b/tests/rustdoc-js/reexport-search_unbox.rs new file mode 100644 index 0000000000000..2103ac9c9a052 --- /dev/null +++ b/tests/rustdoc-js/reexport-search_unbox.rs @@ -0,0 +1,12 @@ +//@ aux-crate:priv:reexport_search_unbox=reexport-search_unbox.rs +//@ compile-flags: -Zunstable-options --extern equivalent + +#![crate_name = "foo"] + +extern crate reexport_search_unbox; + +pub use reexport_search_unbox::{Inside, Out, Out1, Out2}; + +pub fn alpha(_: Inside) -> Out, Out2> { + loop {} +} From ca3867b89f4ef58c155d99c83538f20d8681faf5 Mon Sep 17 00:00:00 2001 From: Aleksandr Antonov <109909821+AbstractiveNord@users.noreply.github.com> Date: Fri, 27 Feb 2026 21:06:21 +0000 Subject: [PATCH 36/39] Remove redundant clone --- compiler/rustc_parse/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_parse/src/lib.rs b/compiler/rustc_parse/src/lib.rs index 1502e5ef5496f..d239be6074c91 100644 --- a/compiler/rustc_parse/src/lib.rs +++ b/compiler/rustc_parse/src/lib.rs @@ -118,14 +118,14 @@ pub fn new_parser_from_file<'a>( let msg = format!("couldn't read `{}`: {}", path.display(), e); let mut err = psess.dcx().struct_fatal(msg); if let Ok(contents) = std::fs::read(path) - && let Err(utf8err) = String::from_utf8(contents.clone()) + && let Err(utf8err) = std::str::from_utf8(&contents) { utf8_error( sm, &path.display().to_string(), sp, &mut err, - utf8err.utf8_error(), + utf8err, &contents, ); } From c97f2c3e5990d59607017c92c0d57e6f3c7cc32c Mon Sep 17 00:00:00 2001 From: Aleksandr Antonov <109909821+AbstractiveNord@users.noreply.github.com> Date: Fri, 27 Feb 2026 21:24:49 +0000 Subject: [PATCH 37/39] Apply tidy suggest --- compiler/rustc_parse/src/lib.rs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/compiler/rustc_parse/src/lib.rs b/compiler/rustc_parse/src/lib.rs index d239be6074c91..fa9ec01233b6e 100644 --- a/compiler/rustc_parse/src/lib.rs +++ b/compiler/rustc_parse/src/lib.rs @@ -120,14 +120,7 @@ pub fn new_parser_from_file<'a>( if let Ok(contents) = std::fs::read(path) && let Err(utf8err) = std::str::from_utf8(&contents) { - utf8_error( - sm, - &path.display().to_string(), - sp, - &mut err, - utf8err, - &contents, - ); + utf8_error(sm, &path.display().to_string(), sp, &mut err, utf8err, &contents); } if let Some(sp) = sp { err.span(sp); From 97748ad557dd02842cdcffdd9d99a9a6e7d660f2 Mon Sep 17 00:00:00 2001 From: reddevilmidzy Date: Mon, 17 Nov 2025 23:16:56 +0900 Subject: [PATCH 38/39] Improve cross-crate trait impl param mismatch suggestions --- .../src/check/compare_impl_item.rs | 102 +++++++++++++++++- tests/ui/fn/issue-39259.stderr | 4 + tests/ui/impl-trait/trait_type.stderr | 4 + .../auxiliary/trait-impl-params.rs | 19 ++++ ...it-impl-param-mismatched-cross-crate.fixed | 64 +++++++++++ ...trait-impl-param-mismatched-cross-crate.rs | 64 +++++++++++ ...t-impl-param-mismatched-cross-crate.stderr | 102 ++++++++++++++++++ 7 files changed, 358 insertions(+), 1 deletion(-) create mode 100644 tests/ui/suggestions/auxiliary/trait-impl-params.rs create mode 100644 tests/ui/suggestions/trait-impl-param-mismatched-cross-crate.fixed create mode 100644 tests/ui/suggestions/trait-impl-param-mismatched-cross-crate.rs create mode 100644 tests/ui/suggestions/trait-impl-param-mismatched-cross-crate.stderr diff --git a/compiler/rustc_hir_analysis/src/check/compare_impl_item.rs b/compiler/rustc_hir_analysis/src/check/compare_impl_item.rs index 43a4f0574f5b0..28671cc36ee9a 100644 --- a/compiler/rustc_hir_analysis/src/check/compare_impl_item.rs +++ b/compiler/rustc_hir_analysis/src/check/compare_impl_item.rs @@ -1,5 +1,6 @@ use core::ops::ControlFlow; use std::borrow::Cow; +use std::cmp::Ordering; use std::iter; use hir::def_id::{DefId, DefIdMap, LocalDefId}; @@ -18,7 +19,7 @@ use rustc_middle::ty::{ Upcast, }; use rustc_middle::{bug, span_bug}; -use rustc_span::{DUMMY_SP, Span}; +use rustc_span::{BytePos, DUMMY_SP, Span}; use rustc_trait_selection::error_reporting::InferCtxtErrorExt; use rustc_trait_selection::infer::InferCtxtExt; use rustc_trait_selection::regions::InferCtxtRegionExt; @@ -1795,6 +1796,105 @@ fn compare_number_of_method_arguments<'tcx>( ), ); + // Only emit verbose suggestions when the trait span isn’t local (e.g., cross-crate). + if !trait_m.def_id.is_local() { + let trait_sig = tcx.fn_sig(trait_m.def_id); + let trait_arg_idents = tcx.fn_arg_idents(trait_m.def_id); + let sm = tcx.sess.source_map(); + // Find the span of the space between the parentheses in a method. + // fn foo(...) {} + // ^^^ + let impl_inputs_span = if let (Some(first), Some(last)) = + (impl_m_sig.decl.inputs.first(), impl_m_sig.decl.inputs.last()) + { + // We have inputs; construct the span from those. + // fn foo( a: i32, b: u32 ) {} + // ^^^^^^^^^^^^^^^^ + let arg_idents = tcx.fn_arg_idents(impl_m.def_id); + let first_lo = arg_idents + .get(0) + .and_then(|id| id.map(|id| id.span.lo())) + .unwrap_or(first.span.lo()); + Some(impl_m_sig.span.with_lo(first_lo).with_hi(last.span.hi())) + } else { + // We have no inputs; construct the span to the left of the last parenthesis + // fn foo( ) {} + // ^ + // FIXME: Keep spans for function parentheses around to make this more robust. + sm.span_to_snippet(impl_m_sig.span).ok().and_then(|s| { + let right_paren = s.as_bytes().iter().rposition(|&b| b == b')')?; + let pos = impl_m_sig.span.lo() + BytePos(right_paren as u32); + Some(impl_m_sig.span.with_lo(pos).with_hi(pos)) + }) + }; + let suggestion = match trait_number_args.cmp(&impl_number_args) { + Ordering::Greater => { + // Span is right before the end parenthesis: + // fn foo(a: i32 ) {} + // ^ + let trait_inputs = trait_sig.skip_binder().inputs().skip_binder(); + let missing = trait_inputs + .iter() + .enumerate() + .skip(impl_number_args) + .map(|(idx, ty)| { + let name = trait_arg_idents + .get(idx) + .and_then(|ident| *ident) + .map(|ident| ident.to_string()) + .unwrap_or_else(|| "_".to_string()); + format!("{name}: {ty}") + }) + .collect::>(); + + if missing.is_empty() { + None + } else { + impl_inputs_span.map(|s| { + let span = s.shrink_to_hi(); + let prefix = if impl_number_args == 0 { "" } else { ", " }; + let replacement = format!("{prefix}{}", missing.join(", ")); + ( + span, + format!( + "add the missing parameter{} from the trait", + pluralize!(trait_number_args - impl_number_args) + ), + replacement, + ) + }) + } + } + Ordering::Less => impl_inputs_span.and_then(|full| { + // Span of the arguments that there are too many of: + // fn foo(a: i32, b: u32) {} + // ^^^^^^^^ + let lo = if trait_number_args == 0 { + full.lo() + } else { + impl_m_sig + .decl + .inputs + .get(trait_number_args - 1) + .map(|arg| arg.span.hi())? + }; + let span = full.with_lo(lo); + Some(( + span, + format!( + "remove the extra parameter{} to match the trait", + pluralize!(impl_number_args - trait_number_args) + ), + String::new(), + )) + }), + Ordering::Equal => unreachable!(), + }; + if let Some((span, msg, replacement)) = suggestion { + err.span_suggestion_verbose(span, msg, replacement, Applicability::MaybeIncorrect); + } + } + return Err(err.emit_unless_delay(delay)); } diff --git a/tests/ui/fn/issue-39259.stderr b/tests/ui/fn/issue-39259.stderr index fc5c828250353..b932731b5275e 100644 --- a/tests/ui/fn/issue-39259.stderr +++ b/tests/ui/fn/issue-39259.stderr @@ -17,6 +17,10 @@ LL | fn call(&self) -> u32 { | ^^^^^ expected 2 parameters, found 1 | = note: `call` from trait: `extern "rust-call" fn(&Self, Args) -> >::Output` +help: add the missing parameter from the trait + | +LL | fn call(&self, args: Args) -> u32 { + | ++++++++++++ error[E0277]: expected a `FnMut(u32)` closure, found `S` --> $DIR/issue-39259.rs:6:25 diff --git a/tests/ui/impl-trait/trait_type.stderr b/tests/ui/impl-trait/trait_type.stderr index ce5b2f1cca626..69cab0708e687 100644 --- a/tests/ui/impl-trait/trait_type.stderr +++ b/tests/ui/impl-trait/trait_type.stderr @@ -19,6 +19,10 @@ LL | fn fmt(&self) -> () { } | ^^^^^ expected 2 parameters, found 1 | = note: `fmt` from trait: `fn(&Self, &mut Formatter<'_>) -> Result<(), std::fmt::Error>` +help: add the missing parameter from the trait + | +LL | fn fmt(&self, f: &mut Formatter<'_>) -> () { } + | +++++++++++++++++++++++ error[E0186]: method `fmt` has a `&self` declaration in the trait, but not in the impl --> $DIR/trait_type.rs:17:4 diff --git a/tests/ui/suggestions/auxiliary/trait-impl-params.rs b/tests/ui/suggestions/auxiliary/trait-impl-params.rs new file mode 100644 index 0000000000000..e7884e2b76333 --- /dev/null +++ b/tests/ui/suggestions/auxiliary/trait-impl-params.rs @@ -0,0 +1,19 @@ +//@ edition:2018 +pub trait Foo { + fn foo(a: i32, b: u32); +} + +pub trait Bar { + fn no_params(); + fn has_self(&self); + fn has_two_self(self: &Self, other: &Self); +} + +pub trait Baz { + fn generic_no_params>(); + fn generic_one_param>(a: U); +} + +pub trait NonIdentArguments { + fn pattern_types_in_arguments(_: i32, (_a, _b): (i32, i32)) {} +} diff --git a/tests/ui/suggestions/trait-impl-param-mismatched-cross-crate.fixed b/tests/ui/suggestions/trait-impl-param-mismatched-cross-crate.fixed new file mode 100644 index 0000000000000..a31763dde1293 --- /dev/null +++ b/tests/ui/suggestions/trait-impl-param-mismatched-cross-crate.fixed @@ -0,0 +1,64 @@ +//@ run-rustfix +//@ aux-build:trait-impl-params.rs +//@ edition:2018 +//! Tests cross-crate trait impl param mismatch suggestions (keep aliases when matching, +//! append missing; replace otherwise). +//! regression test for https://github.com/rust-lang/rust/issues/106999 + +#![allow(unused)] +extern crate trait_impl_params; + +use trait_impl_params::{Bar, Baz, Foo, NonIdentArguments}; + +struct MyStruct1; +impl Foo for MyStruct1 { + fn foo(a: i32, b: u32) {} + //~^ ERROR: method `foo` has 0 parameters but the declaration in trait `foo` has 2 + //~| HELP: add the missing parameters from the trait +} + +struct MyStruct2; +type AliasOfI32 = i32; +impl Foo for MyStruct2 { + fn foo(a: AliasOfI32, b: u32) {} + //~^ ERROR: method `foo` has 1 parameter but the declaration in trait `foo` has 2 + //~| HELP: add the missing parameter from the trait +} + +struct MyStruct3; +type AliasOfU32 = u32; +impl Foo for MyStruct3 { + fn foo(a: AliasOfI32, b: AliasOfU32) {} // Ok +} + +struct MyStruct4; +impl Bar for MyStruct4 { + fn no_params() {} + //~^ ERROR: method `no_params` has 1 parameter but the declaration in trait `no_params` has 0 + //~| HELP: remove the extra parameter to match the trait + fn has_self(&self) {} + //~^ ERROR: method `has_self` has 2 parameters but the declaration in trait `has_self` has 1 + //~| HELP: remove the extra parameter to match the trait + fn has_two_self(self: &Self, other: &Self) {} + //~^ ERROR: method `has_two_self` has 1 parameter but the declaration in trait `has_two_self` has 2 + //~| HELP: add the missing parameter from the trait +} + +struct MyStruct5; +impl NonIdentArguments for MyStruct5 { + fn pattern_types_in_arguments(_: i32, _: (i32, i32)) {} + //~^ ERROR: method `pattern_types_in_arguments` has 0 parameters but the declaration in trait + //~| HELP: add the missing parameters from the trait +} + +struct MyStruct6; +impl Baz<()> for MyStruct6 { + fn generic_no_params>() {} + //~^ ERROR: method `generic_no_params` has 1 parameter but the declaration in trait + //~| HELP: remove the extra parameter to match the trait + fn generic_one_param>(a: U) {} + //~^ ERROR: method `generic_one_param` has 0 parameters but the declaration in trait + //~| HELP: add the missing parameter from the trait +} + +fn main() {} diff --git a/tests/ui/suggestions/trait-impl-param-mismatched-cross-crate.rs b/tests/ui/suggestions/trait-impl-param-mismatched-cross-crate.rs new file mode 100644 index 0000000000000..d61dcb648c015 --- /dev/null +++ b/tests/ui/suggestions/trait-impl-param-mismatched-cross-crate.rs @@ -0,0 +1,64 @@ +//@ run-rustfix +//@ aux-build:trait-impl-params.rs +//@ edition:2018 +//! Tests cross-crate trait impl param mismatch suggestions (keep aliases when matching, +//! append missing; replace otherwise). +//! regression test for https://github.com/rust-lang/rust/issues/106999 + +#![allow(unused)] +extern crate trait_impl_params; + +use trait_impl_params::{Bar, Baz, Foo, NonIdentArguments}; + +struct MyStruct1; +impl Foo for MyStruct1 { + fn foo() {} + //~^ ERROR: method `foo` has 0 parameters but the declaration in trait `foo` has 2 + //~| HELP: add the missing parameters from the trait +} + +struct MyStruct2; +type AliasOfI32 = i32; +impl Foo for MyStruct2 { + fn foo(a: AliasOfI32) {} + //~^ ERROR: method `foo` has 1 parameter but the declaration in trait `foo` has 2 + //~| HELP: add the missing parameter from the trait +} + +struct MyStruct3; +type AliasOfU32 = u32; +impl Foo for MyStruct3 { + fn foo(a: AliasOfI32, b: AliasOfU32) {} // Ok +} + +struct MyStruct4; +impl Bar for MyStruct4 { + fn no_params(x: i32) {} + //~^ ERROR: method `no_params` has 1 parameter but the declaration in trait `no_params` has 0 + //~| HELP: remove the extra parameter to match the trait + fn has_self(&self, y: i32) {} + //~^ ERROR: method `has_self` has 2 parameters but the declaration in trait `has_self` has 1 + //~| HELP: remove the extra parameter to match the trait + fn has_two_self(self: &Self) {} + //~^ ERROR: method `has_two_self` has 1 parameter but the declaration in trait `has_two_self` has 2 + //~| HELP: add the missing parameter from the trait +} + +struct MyStruct5; +impl NonIdentArguments for MyStruct5 { + fn pattern_types_in_arguments() {} + //~^ ERROR: method `pattern_types_in_arguments` has 0 parameters but the declaration in trait + //~| HELP: add the missing parameters from the trait +} + +struct MyStruct6; +impl Baz<()> for MyStruct6 { + fn generic_no_params>(a: U) {} + //~^ ERROR: method `generic_no_params` has 1 parameter but the declaration in trait + //~| HELP: remove the extra parameter to match the trait + fn generic_one_param>() {} + //~^ ERROR: method `generic_one_param` has 0 parameters but the declaration in trait + //~| HELP: add the missing parameter from the trait +} + +fn main() {} diff --git a/tests/ui/suggestions/trait-impl-param-mismatched-cross-crate.stderr b/tests/ui/suggestions/trait-impl-param-mismatched-cross-crate.stderr new file mode 100644 index 0000000000000..8dd045a6de585 --- /dev/null +++ b/tests/ui/suggestions/trait-impl-param-mismatched-cross-crate.stderr @@ -0,0 +1,102 @@ +error[E0050]: method `foo` has 0 parameters but the declaration in trait `foo` has 2 + --> $DIR/trait-impl-param-mismatched-cross-crate.rs:15:5 + | +LL | fn foo() {} + | ^^^^^^^^ expected 2 parameters, found 0 + | + = note: `foo` from trait: `fn(i32, u32)` +help: add the missing parameters from the trait + | +LL | fn foo(a: i32, b: u32) {} + | ++++++++++++++ + +error[E0050]: method `foo` has 1 parameter but the declaration in trait `foo` has 2 + --> $DIR/trait-impl-param-mismatched-cross-crate.rs:23:15 + | +LL | fn foo(a: AliasOfI32) {} + | ^^^^^^^^^^ expected 2 parameters, found 1 + | + = note: `foo` from trait: `fn(i32, u32)` +help: add the missing parameter from the trait + | +LL | fn foo(a: AliasOfI32, b: u32) {} + | ++++++++ + +error[E0050]: method `no_params` has 1 parameter but the declaration in trait `no_params` has 0 + --> $DIR/trait-impl-param-mismatched-cross-crate.rs:36:21 + | +LL | fn no_params(x: i32) {} + | ^^^ expected 0 parameters, found 1 + | + = note: `no_params` from trait: `fn()` +help: remove the extra parameter to match the trait + | +LL - fn no_params(x: i32) {} +LL + fn no_params() {} + | + +error[E0050]: method `has_self` has 2 parameters but the declaration in trait `has_self` has 1 + --> $DIR/trait-impl-param-mismatched-cross-crate.rs:39:17 + | +LL | fn has_self(&self, y: i32) {} + | ^^^^^^^^^^^^^ expected 1 parameter, found 2 + | + = note: `has_self` from trait: `fn(&Self)` +help: remove the extra parameter to match the trait + | +LL - fn has_self(&self, y: i32) {} +LL + fn has_self(&self) {} + | + +error[E0050]: method `has_two_self` has 1 parameter but the declaration in trait `has_two_self` has 2 + --> $DIR/trait-impl-param-mismatched-cross-crate.rs:42:27 + | +LL | fn has_two_self(self: &Self) {} + | ^^^^^ expected 2 parameters, found 1 + | + = note: `has_two_self` from trait: `fn(&Self, &Self)` +help: add the missing parameter from the trait + | +LL | fn has_two_self(self: &Self, other: &Self) {} + | ++++++++++++++ + +error[E0050]: method `pattern_types_in_arguments` has 0 parameters but the declaration in trait `pattern_types_in_arguments` has 2 + --> $DIR/trait-impl-param-mismatched-cross-crate.rs:49:5 + | +LL | fn pattern_types_in_arguments() {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected 2 parameters, found 0 + | + = note: `pattern_types_in_arguments` from trait: `fn(i32, (i32, i32))` +help: add the missing parameters from the trait + | +LL | fn pattern_types_in_arguments(_: i32, _: (i32, i32)) {} + | +++++++++++++++++++++ + +error[E0050]: method `generic_no_params` has 1 parameter but the declaration in trait `generic_no_params` has 0 + --> $DIR/trait-impl-param-mismatched-cross-crate.rs:56:41 + | +LL | fn generic_no_params>(a: U) {} + | ^ expected 0 parameters, found 1 + | + = note: `generic_no_params` from trait: `fn()` +help: remove the extra parameter to match the trait + | +LL - fn generic_no_params>(a: U) {} +LL + fn generic_no_params>() {} + | + +error[E0050]: method `generic_one_param` has 0 parameters but the declaration in trait `generic_one_param` has 1 + --> $DIR/trait-impl-param-mismatched-cross-crate.rs:59:5 + | +LL | fn generic_one_param>() {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected 1 parameter, found 0 + | + = note: `generic_one_param` from trait: `fn(U)` +help: add the missing parameter from the trait + | +LL | fn generic_one_param>(a: U) {} + | ++++ + +error: aborting due to 8 previous errors + +For more information about this error, try `rustc --explain E0050`. From c9b2784731332e4e9a715d5eb5dfbde282f26fd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Rakic?= Date: Sat, 28 Feb 2026 10:48:28 +0100 Subject: [PATCH 39/39] mark two polonius tests as known-bug As tracked in the soundness issue 153215. --- .../rpit-hide-lifetime-for-swap.edition2015.stderr | 2 +- .../rpit-hide-lifetime-for-swap.edition2024.stderr | 2 +- .../alias-liveness/rpit-hide-lifetime-for-swap.rs | 12 +++++++++--- .../impl-trait/hidden-lifetimes.edition2015.stderr | 4 ++-- .../impl-trait/hidden-lifetimes.edition2024.stderr | 4 ++-- tests/ui/impl-trait/hidden-lifetimes.rs | 11 ++++++++--- 6 files changed, 23 insertions(+), 12 deletions(-) diff --git a/tests/ui/impl-trait/alias-liveness/rpit-hide-lifetime-for-swap.edition2015.stderr b/tests/ui/impl-trait/alias-liveness/rpit-hide-lifetime-for-swap.edition2015.stderr index 769a878a45c77..48efb0ca558c0 100644 --- a/tests/ui/impl-trait/alias-liveness/rpit-hide-lifetime-for-swap.edition2015.stderr +++ b/tests/ui/impl-trait/alias-liveness/rpit-hide-lifetime-for-swap.edition2015.stderr @@ -1,5 +1,5 @@ error[E0700]: hidden type for `impl Swap + 'a` captures lifetime that does not appear in bounds - --> $DIR/rpit-hide-lifetime-for-swap.rs:20:5 + --> $DIR/rpit-hide-lifetime-for-swap.rs:26:5 | LL | fn hide<'a, 'b: 'a, T: 'static>(x: Rc>) -> impl Swap + 'a { | -- -------------- opaque type defined here diff --git a/tests/ui/impl-trait/alias-liveness/rpit-hide-lifetime-for-swap.edition2024.stderr b/tests/ui/impl-trait/alias-liveness/rpit-hide-lifetime-for-swap.edition2024.stderr index 6109184250bbe..e4f5475cdb0a6 100644 --- a/tests/ui/impl-trait/alias-liveness/rpit-hide-lifetime-for-swap.edition2024.stderr +++ b/tests/ui/impl-trait/alias-liveness/rpit-hide-lifetime-for-swap.edition2024.stderr @@ -1,5 +1,5 @@ error[E0597]: `x` does not live long enough - --> $DIR/rpit-hide-lifetime-for-swap.rs:27:38 + --> $DIR/rpit-hide-lifetime-for-swap.rs:33:38 | LL | let x = [1, 2, 3]; | - binding `x` declared here diff --git a/tests/ui/impl-trait/alias-liveness/rpit-hide-lifetime-for-swap.rs b/tests/ui/impl-trait/alias-liveness/rpit-hide-lifetime-for-swap.rs index c4eaec478b840..de5335be73dbe 100644 --- a/tests/ui/impl-trait/alias-liveness/rpit-hide-lifetime-for-swap.rs +++ b/tests/ui/impl-trait/alias-liveness/rpit-hide-lifetime-for-swap.rs @@ -1,6 +1,12 @@ -//@revisions: edition2015 edition2024 -//@[edition2015] edition:2015 -//@[edition2024] edition:2024 +//@ revisions: edition2015 edition2024 polonius_alpha +//@ ignore-compare-mode-polonius (explicit revisions) +//@ [edition2015] edition: 2015 +//@ [edition2024] edition: 2024 +//@ [polonius_alpha] known-bug: #153215 +//@ [polonius_alpha] check-pass +//@ [polonius_alpha] edition: 2024 +//@ [polonius_alpha] compile-flags: -Zpolonius=next + // This test should never pass! use std::cell::RefCell; diff --git a/tests/ui/impl-trait/hidden-lifetimes.edition2015.stderr b/tests/ui/impl-trait/hidden-lifetimes.edition2015.stderr index b63115f76588f..bc545b40c6a0e 100644 --- a/tests/ui/impl-trait/hidden-lifetimes.edition2015.stderr +++ b/tests/ui/impl-trait/hidden-lifetimes.edition2015.stderr @@ -1,5 +1,5 @@ error[E0700]: hidden type for `impl Swap + 'a` captures lifetime that does not appear in bounds - --> $DIR/hidden-lifetimes.rs:33:5 + --> $DIR/hidden-lifetimes.rs:38:5 | LL | fn hide_ref<'a, 'b, T: 'static>(x: &'a mut &'b T) -> impl Swap + 'a { | -- -------------- opaque type defined here @@ -14,7 +14,7 @@ LL | fn hide_ref<'a, 'b, T: 'static>(x: &'a mut &'b T) -> impl Swap + 'a + use<' | ++++++++++++++++ error[E0700]: hidden type for `impl Swap + 'a` captures lifetime that does not appear in bounds - --> $DIR/hidden-lifetimes.rs:50:5 + --> $DIR/hidden-lifetimes.rs:55:5 | LL | fn hide_rc_refcell<'a, 'b: 'a, T: 'static>(x: Rc>) -> impl Swap + 'a { | -- -------------- opaque type defined here diff --git a/tests/ui/impl-trait/hidden-lifetimes.edition2024.stderr b/tests/ui/impl-trait/hidden-lifetimes.edition2024.stderr index d585bb50b13f7..5165e627022c1 100644 --- a/tests/ui/impl-trait/hidden-lifetimes.edition2024.stderr +++ b/tests/ui/impl-trait/hidden-lifetimes.edition2024.stderr @@ -1,5 +1,5 @@ error[E0515]: cannot return value referencing local variable `x` - --> $DIR/hidden-lifetimes.rs:41:5 + --> $DIR/hidden-lifetimes.rs:46:5 | LL | hide_ref(&mut res).swap(hide_ref(&mut &x)); | -- `x` is borrowed here @@ -7,7 +7,7 @@ LL | res | ^^^ returns a value referencing data owned by the current function error[E0597]: `x` does not live long enough - --> $DIR/hidden-lifetimes.rs:57:38 + --> $DIR/hidden-lifetimes.rs:62:38 | LL | let x = [1, 2, 3]; | - binding `x` declared here diff --git a/tests/ui/impl-trait/hidden-lifetimes.rs b/tests/ui/impl-trait/hidden-lifetimes.rs index b50c43bd3fa00..d113953c5bbc7 100644 --- a/tests/ui/impl-trait/hidden-lifetimes.rs +++ b/tests/ui/impl-trait/hidden-lifetimes.rs @@ -1,6 +1,11 @@ -//@revisions: edition2015 edition2024 -//@[edition2015] edition:2015 -//@[edition2024] edition:2024 +//@ revisions: edition2015 edition2024 polonius_alpha +//@ ignore-compare-mode-polonius (explicit revisions) +//@ [edition2015] edition: 2015 +//@ [edition2024] edition: 2024 +//@ [polonius_alpha] known-bug: #153215 +//@ [polonius_alpha] check-pass +//@ [polonius_alpha] edition: 2024 +//@ [polonius_alpha] compile-flags: -Zpolonius=next // Test to show what happens if we were not careful and allowed invariant // lifetimes to escape though an impl trait.