From 5b8f281df26dd3e1236df9bedb14930f5460098e Mon Sep 17 00:00:00 2001 From: Jed McKenna Date: Tue, 1 Sep 2026 17:23:20 +1000 Subject: [PATCH 01/14] new branch, fresh git history. --- compiler/rustc_hir_typeck/src/diagnostics.rs | 24 +++++ .../rustc_hir_typeck/src/fn_ctxt/checks.rs | 61 +++++++++--- tests/ui/span/coerce-suggestions.rs | 10 ++ tests/ui/span/coerce-suggestions.stderr | 99 +++++++++++++++++-- 4 files changed, 177 insertions(+), 17 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/diagnostics.rs b/compiler/rustc_hir_typeck/src/diagnostics.rs index 2d4cf3d9b400d..04f0fd19b1482 100644 --- a/compiler/rustc_hir_typeck/src/diagnostics.rs +++ b/compiler/rustc_hir_typeck/src/diagnostics.rs @@ -679,6 +679,30 @@ pub(crate) struct SuggestPtrNullMut { pub span: Span, } +#[derive(Subdiagnostic)] +#[suggestion( + "consider using `&mut` instead", + applicability = "maybe-incorrect", + style = "verbose", + code = "&mut " +)] +pub(crate) struct SuggestRefMut { + #[primary_span] + pub span: Span, +} + +#[derive(Subdiagnostic)] +#[suggestion( + "consider using `&raw mut` instead", + applicability = "maybe-incorrect", + style = "verbose", + code = "&raw mut " +)] +pub(crate) struct SuggestRawMut { + #[primary_span] + pub span: Span, +} + #[derive(Diagnostic)] #[diag( "trivial {$numeric -> diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs index ba44d1966d971..a6792408e34d2 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs @@ -37,7 +37,7 @@ use crate::Expectation::*; use crate::TupleArgumentsFlag::*; use crate::callee::SplatLoweringInfo; use crate::coercion::CoerceMany; -use crate::diagnostics::{ExprParenthesesNeeded, SuggestPtrNullMut}; +use crate::diagnostics::{ExprParenthesesNeeded, SuggestPtrNullMut, SuggestRawMut, SuggestRefMut}; use crate::fn_ctxt::arg_matrix::{ArgMatrix, Compatibility, Error, ExpectedIdx, ProvidedIdx}; use crate::gather_locals::Declaration; use crate::inline_asm::InlineAsmCtxt; @@ -982,23 +982,62 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { err.emit() } - fn suggest_ptr_null_mut( + fn suggest_mut_addr( &self, expected_ty: Ty<'tcx>, provided_ty: Ty<'tcx>, arg: &hir::Expr<'tcx>, err: &mut Diag<'_>, ) { - if let ty::RawPtr(_, hir::Mutability::Mut) = expected_ty.kind() - && let ty::RawPtr(_, hir::Mutability::Not) = provided_ty.kind() - && let hir::ExprKind::Call(callee, _) = arg.kind - && let hir::ExprKind::Path(hir::QPath::Resolved(_, path)) = callee.kind - && let Res::Def(_, def_id) = path.res - && self.tcx.get_diagnostic_item(sym::ptr_null) == Some(def_id) - { + match (expected_ty.kind(), provided_ty.kind(), arg.kind) { // The user provided `ptr::null()`, but the function expects // `ptr::null_mut()`. - err.subdiagnostic(SuggestPtrNullMut { span: arg.span }); + ( + ty::RawPtr(_, hir::Mutability::Mut), + ty::RawPtr(_, hir::Mutability::Not), + hir::ExprKind::Call(callee, _), + ) if let hir::ExprKind::Path(hir::QPath::Resolved(_, path)) = callee.kind + && let Res::Def(_, def_id) = path.res + && self.tcx.get_diagnostic_item(sym::ptr_null) == Some(def_id) => + { + err.subdiagnostic(SuggestPtrNullMut { span: arg.span }); + } + // &mut T expected, &T or *const/mut T found + // suggestion: replace `&` or `&raw const/mut` with `&mut` + ( + ty::Ref(_, expected_ty, hir::Mutability::Mut), + ty::Ref(_, provided_ty, hir::Mutability::Not), + hir::ExprKind::AddrOf(hir::BorrowKind::Ref, hir::Mutability::Not, expr), + ) + | ( + ty::Ref(_, expected_ty, hir::Mutability::Mut), + ty::RawPtr(provided_ty, _), + hir::ExprKind::AddrOf(hir::BorrowKind::Raw, _, expr), + ) if expected_ty == provided_ty => { + let span = arg.span.until(expr.span); + err.subdiagnostic(SuggestRefMut { span }); + } + // *mut T expected, *const T found + // suggestion: replace `&raw const` with `&raw mut` + ( + ty::RawPtr(expected_ty, hir::Mutability::Mut), + ty::RawPtr(provided_ty, hir::Mutability::Not), + hir::ExprKind::AddrOf(hir::BorrowKind::Raw, hir::Mutability::Not, expr), + ) if expected_ty == provided_ty => { + let span = arg.span.until(expr.span); + err.subdiagnostic(SuggestRawMut { span }); + } + // *mut T expected, &T found + // suggestion: replace `&` `&mut` + ( + ty::RawPtr(expected_ty, hir::Mutability::Mut), + ty::Ref(_, provided_ty, hir::Mutability::Not), + hir::ExprKind::AddrOf(hir::BorrowKind::Ref, hir::Mutability::Not, expr), + ) if expected_ty == provided_ty => { + let span = arg.span.until(expr.span); + err.subdiagnostic(SuggestRefMut { span }); + } + _ => {} } } @@ -2491,7 +2530,7 @@ impl<'a, 'tcx> FnCallDiagCtxt<'a, 'tcx> { ); } - self.suggest_ptr_null_mut( + self.suggest_mut_addr( expected_ty, provided_ty, self.provided_args[provided_idx], diff --git a/tests/ui/span/coerce-suggestions.rs b/tests/ui/span/coerce-suggestions.rs index 13331a016fcfe..3395013e9910d 100644 --- a/tests/ui/span/coerce-suggestions.rs +++ b/tests/ui/span/coerce-suggestions.rs @@ -2,6 +2,7 @@ fn test(_x: &mut String) {} fn test2(_x: &mut i32) {} +fn test3(_x: *mut String) {} fn main() { let x: usize = String::new(); @@ -11,9 +12,18 @@ fn main() { let y = String::new(); test(&y); //~^ ERROR E0308 + test(&raw const y); + //~^ ERROR E0308 + test(&raw mut y); + //~^ ERROR E0308 test2(&y); //~^ ERROR E0308 let s = &mut String::new(); s = format!("foo"); //~^ ERROR E0308 + let s = String::new(); + test3(&raw const s); + //~^ ERROR E0308 + test3(&s); + //~^ ERROR E0308 } diff --git a/tests/ui/span/coerce-suggestions.stderr b/tests/ui/span/coerce-suggestions.stderr index d0f76a23edc45..5b9049ab6f214 100644 --- a/tests/ui/span/coerce-suggestions.stderr +++ b/tests/ui/span/coerce-suggestions.stderr @@ -1,5 +1,5 @@ error[E0308]: mismatched types - --> $DIR/coerce-suggestions.rs:7:20 + --> $DIR/coerce-suggestions.rs:8:20 | LL | let x: usize = String::new(); | ----- ^^^^^^^^^^^^^ expected `usize`, found `String` @@ -7,7 +7,7 @@ LL | let x: usize = String::new(); | expected due to this error[E0308]: mismatched types - --> $DIR/coerce-suggestions.rs:9:19 + --> $DIR/coerce-suggestions.rs:10:19 | LL | let x: &str = String::new(); | ---- ^^^^^^^^^^^^^ expected `&str`, found `String` @@ -20,7 +20,7 @@ LL | let x: &str = &String::new(); | + error[E0308]: mismatched types - --> $DIR/coerce-suggestions.rs:12:10 + --> $DIR/coerce-suggestions.rs:13:10 | LL | test(&y); | ---- ^^ types differ in mutability @@ -34,9 +34,55 @@ note: function defined here | LL | fn test(_x: &mut String) {} | ^^^^ --------------- +help: consider using `&mut` instead + | +LL | test(&mut y); + | +++ error[E0308]: mismatched types - --> $DIR/coerce-suggestions.rs:14:11 + --> $DIR/coerce-suggestions.rs:15:10 + | +LL | test(&raw const y); + | ---- ^^^^^^^^^^^^ expected `&mut String`, found `*const String` + | | + | arguments to this function are incorrect + | + = note: expected mutable reference `&mut String` + found raw pointer `*const String` +note: function defined here + --> $DIR/coerce-suggestions.rs:1:4 + | +LL | fn test(_x: &mut String) {} + | ^^^^ --------------- +help: consider using `&mut` instead + | +LL - test(&raw const y); +LL + test(&mut y); + | + +error[E0308]: mismatched types + --> $DIR/coerce-suggestions.rs:17:10 + | +LL | test(&raw mut y); + | ---- ^^^^^^^^^^ expected `&mut String`, found `*mut String` + | | + | arguments to this function are incorrect + | + = note: expected mutable reference `&mut String` + found raw pointer `*mut String` +note: function defined here + --> $DIR/coerce-suggestions.rs:1:4 + | +LL | fn test(_x: &mut String) {} + | ^^^^ --------------- +help: consider using `&mut` instead + | +LL - test(&raw mut y); +LL + test(&mut y); + | + +error[E0308]: mismatched types + --> $DIR/coerce-suggestions.rs:19:11 | LL | test2(&y); | ----- ^^ types differ in mutability @@ -52,11 +98,52 @@ LL | fn test2(_x: &mut i32) {} | ^^^^^ ------------ error[E0308]: mismatched types - --> $DIR/coerce-suggestions.rs:17:9 + --> $DIR/coerce-suggestions.rs:22:9 | LL | s = format!("foo"); | ^^^^^^^^^^^^^^ expected `&mut String`, found `String` -error: aborting due to 5 previous errors +error[E0308]: mismatched types + --> $DIR/coerce-suggestions.rs:25:11 + | +LL | test3(&raw const s); + | ----- ^^^^^^^^^^^^ types differ in mutability + | | + | arguments to this function are incorrect + | + = note: expected raw pointer `*mut String` + found raw pointer `*const String` +note: function defined here + --> $DIR/coerce-suggestions.rs:5:4 + | +LL | fn test3(_x: *mut String) {} + | ^^^^^ --------------- +help: consider using `&raw mut` instead + | +LL - test3(&raw const s); +LL + test3(&raw mut s); + | + +error[E0308]: mismatched types + --> $DIR/coerce-suggestions.rs:27:11 + | +LL | test3(&s); + | ----- ^^ types differ in mutability + | | + | arguments to this function are incorrect + | + = note: expected raw pointer `*mut String` + found reference `&String` +note: function defined here + --> $DIR/coerce-suggestions.rs:5:4 + | +LL | fn test3(_x: *mut String) {} + | ^^^^^ --------------- +help: consider using `&mut` instead + | +LL | test3(&mut s); + | +++ + +error: aborting due to 9 previous errors For more information about this error, try `rustc --explain E0308`. From 533e60decd78a836baef97e06ac1f159c70f7842 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Sun, 30 Aug 2026 04:22:23 +0200 Subject: [PATCH 02/14] Minor cleanup in rustc_hir_analysis::collect --- compiler/rustc_hir_analysis/src/collect.rs | 22 ++++++++----------- .../src/collect/generics_of.rs | 2 +- .../rustc_hir_analysis/src/collect/type_of.rs | 10 ++++----- 3 files changed, 14 insertions(+), 20 deletions(-) diff --git a/compiler/rustc_hir_analysis/src/collect.rs b/compiler/rustc_hir_analysis/src/collect.rs index 1cd1fc356ca7d..fc15c262b0340 100644 --- a/compiler/rustc_hir_analysis/src/collect.rs +++ b/compiler/rustc_hir_analysis/src/collect.rs @@ -1769,28 +1769,24 @@ fn rendered_precise_capturing_args<'tcx>( fn const_param_default<'tcx>( tcx: TyCtxt<'tcx>, - local_def_id: LocalDefId, + param_def_id: LocalDefId, ) -> ty::EarlyBinder<'tcx, Const<'tcx>> { let hir::Node::GenericParam(hir::GenericParam { - kind: hir::GenericParamKind::Const { default: Some(default_ct), .. }, + kind: hir::GenericParamKind::Const { default: Some(ct), .. }, .. - }) = tcx.hir_node_by_def_id(local_def_id) + }) = tcx.hir_node_by_def_id(param_def_id) else { span_bug!( - tcx.def_span(local_def_id), - "`const_param_default` expected a generic parameter with a constant" + tcx.def_span(param_def_id), + "const_param_default: expected a const parameter with a default value" ) }; - let icx = ItemCtxt::new(tcx, local_def_id); + let icx = ItemCtxt::new(tcx, param_def_id); - let def_id = local_def_id.to_def_id(); - let identity_args = ty::GenericArgs::identity_for_item(tcx, tcx.parent(def_id)); - - let ct = icx.lowerer().lower_const_arg( - default_ct, - tcx.type_of(def_id).instantiate(tcx, identity_args).skip_norm_wip(), - ); + let ct = icx + .lowerer() + .lower_const_arg(ct, tcx.type_of(param_def_id).instantiate_identity().skip_norm_wip()); ty::EarlyBinder::bind(tcx, ct) } diff --git a/compiler/rustc_hir_analysis/src/collect/generics_of.rs b/compiler/rustc_hir_analysis/src/collect/generics_of.rs index 4d43a9ca5700e..ea10a93fa71a6 100644 --- a/compiler/rustc_hir_analysis/src/collect/generics_of.rs +++ b/compiler/rustc_hir_analysis/src/collect/generics_of.rs @@ -236,7 +236,7 @@ pub(super) fn generics_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Generics { let opt_self = if let Node::Item(item) = node && let ItemKind::Trait { .. } | ItemKind::TraitAlias(..) = item.kind { - // Something of a hack: We reuse the node ID of the trait for the self type parameter. + // Something of a hack: We reuse the DefId of the trait for the self type parameter. Some(ty::GenericParamDef { index: 0, name: kw::SelfUpper, diff --git a/compiler/rustc_hir_analysis/src/collect/type_of.rs b/compiler/rustc_hir_analysis/src/collect/type_of.rs index 8754430aaa99e..591bcbf77ef26 100644 --- a/compiler/rustc_hir_analysis/src/collect/type_of.rs +++ b/compiler/rustc_hir_analysis/src/collect/type_of.rs @@ -104,7 +104,7 @@ pub(super) fn type_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::EarlyBinder<'_ .unwrap_or_else(|| icx.lower_ty(ty)), TraitItemKind::Type(_, Some(ty)) => icx.lower_ty(ty), TraitItemKind::Type(_, None) => { - span_bug!(item.span, "associated type missing default"); + span_bug!(item.span, "type_of: associated type missing default"); } }, @@ -207,7 +207,7 @@ pub(super) fn type_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::EarlyBinder<'_ | ItemKind::ExternCrate(..) | ItemKind::Use(..) | ItemKind::TestBinderConstraints { .. } => { - span_bug!(item.span, "compute_type_of_item: unexpected item type: {:?}", item.kind); + span_bug!(item.span, "type_of: unexpected item kind: {:?}", item.kind); } }, @@ -263,12 +263,10 @@ pub(super) fn type_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::EarlyBinder<'_ lowered_ty } } - x => bug!("unexpected non-type Node::GenericParam: {:?}", x), + _ => bug!("type_of: unexpected node kind {hir_node:?}"), }, - x => { - bug!("unexpected sort of node in type_of(): {:?}", x); - } + node => bug!("type_of: unexpected node kind: {node:?}"), }; if let Err(e) = icx.check_tainted_by_errors() && !output.references_error() From 29d38e4403562054d630311aa8fc089504099725 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Sun, 30 Aug 2026 03:07:49 +0200 Subject: [PATCH 03/14] Support type-relative assoc item paths in generic param defaults & const param types --- compiler/rustc_hir_analysis/src/collect.rs | 5 ++++- .../src/collect/generics_of.rs | 2 ++ .../rustc_hir_analysis/src/collect/type_of.rs | 20 +++++++++++++----- .../type-relative-in-const-param-ty.rs | 21 +++++++++++++++++++ .../type-relative-in-ty-param-default.rs | 20 ++++++++++++++++++ .../defaults/type-relative-assoc-const.rs | 16 ++++++++++++++ 6 files changed, 78 insertions(+), 6 deletions(-) create mode 100644 tests/ui/associated-types/type-relative-in-const-param-ty.rs create mode 100644 tests/ui/associated-types/type-relative-in-ty-param-default.rs create mode 100644 tests/ui/const-generics/defaults/type-relative-assoc-const.rs diff --git a/compiler/rustc_hir_analysis/src/collect.rs b/compiler/rustc_hir_analysis/src/collect.rs index fc15c262b0340..a4c79aba0147b 100644 --- a/compiler/rustc_hir_analysis/src/collect.rs +++ b/compiler/rustc_hir_analysis/src/collect.rs @@ -1782,7 +1782,10 @@ fn const_param_default<'tcx>( ) }; - let icx = ItemCtxt::new(tcx, param_def_id); + // FIXME: Don't lower in the context of parent. Blocked on the removal of GCE. See comment in + // `type_of.rs` for details (Ctrl+F for `generics_of`). + let item_def_id = tcx.local_parent(param_def_id); + let icx = ItemCtxt::new(tcx, item_def_id); let ct = icx .lowerer() diff --git a/compiler/rustc_hir_analysis/src/collect/generics_of.rs b/compiler/rustc_hir_analysis/src/collect/generics_of.rs index ea10a93fa71a6..74eed3e502e8b 100644 --- a/compiler/rustc_hir_analysis/src/collect/generics_of.rs +++ b/compiler/rustc_hir_analysis/src/collect/generics_of.rs @@ -211,6 +211,8 @@ pub(super) fn generics_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Generics { Node::Item(_) | Node::ForeignItem(_) => None, // Params don't really have generics, but we use it when instantiating their value paths. + // FIXME: Use the HIR parent here similar to `Node::Field`. Blocked on the removal of GCE, + // see comment in `type_of.rs` for details (Ctrl+F for `generics_of`). Node::GenericParam(_) => None, Node::Synthetic => span_bug!( diff --git a/compiler/rustc_hir_analysis/src/collect/type_of.rs b/compiler/rustc_hir_analysis/src/collect/type_of.rs index 591bcbf77ef26..4beefa905a134 100644 --- a/compiler/rustc_hir_analysis/src/collect/type_of.rs +++ b/compiler/rustc_hir_analysis/src/collect/type_of.rs @@ -59,10 +59,6 @@ pub(super) fn type_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::EarlyBinder<'_ None => {} } - let hir_id = tcx.local_def_id_to_hir_id(def_id); - - let icx = ItemCtxt::new(tcx, def_id); - let new_bound_fn_def = |hir: HirId, did| { let args = ty::GenericArgs::identity_for_item(tcx, def_id); Ty::new_fn_def( @@ -80,7 +76,21 @@ pub(super) fn type_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::EarlyBinder<'_ ) }; - let output = match tcx.hir_node(hir_id) { + let hir_id = tcx.local_def_id_to_hir_id(def_id); + let hir_node = tcx.hir_node(hir_id); + let def_id = match hir_node { + // For generic params, we lower in the context of the overarching item to be able to resolve + // type-relative paths in type param defaults & const param types. + // FIXME: Remove this special case again & simply set the `Generics.parent` for generic + // params in `generics_of` similar to struct fields. However, that's blocked on the + // removal of GCE in favor of mGCA. Doing this change now would introduce tons of + // query cycles for GCE. + Node::GenericParam(_) => tcx.local_parent(def_id), + _ => def_id, + }; + let icx = ItemCtxt::new(tcx, def_id); + + let output = match hir_node { Node::TraitItem(item) => match item.kind { TraitItemKind::Fn(_, _) => new_bound_fn_def(item.hir_id(), def_id.to_def_id()), TraitItemKind::Const(ty, rhs) => rhs diff --git a/tests/ui/associated-types/type-relative-in-const-param-ty.rs b/tests/ui/associated-types/type-relative-in-const-param-ty.rs new file mode 100644 index 0000000000000..e10ced8d30fa4 --- /dev/null +++ b/tests/ui/associated-types/type-relative-in-const-param-ty.rs @@ -0,0 +1,21 @@ +// Test that we can resolve type-relative associated type paths inside const parameter types +// where the self type is a simple type parameter. + +//@ check-pass +#![feature(generic_const_parameter_types, adt_const_params, const_param_ty_trait)] + +trait Trait { + type Type; +} + +// Below, `T::Type` resolves to `::Type` since the owner has bound `T: Trait`. + +struct Owner0(T) +where + T: Trait; + +struct Owner1(T) +where + T: Trait; + +fn main() {} diff --git a/tests/ui/associated-types/type-relative-in-ty-param-default.rs b/tests/ui/associated-types/type-relative-in-ty-param-default.rs new file mode 100644 index 0000000000000..33aae581708c8 --- /dev/null +++ b/tests/ui/associated-types/type-relative-in-ty-param-default.rs @@ -0,0 +1,20 @@ +// Test that we can resolve type-relative associated type paths inside type parameter defaults +// where the self type is a simple type parameter. +// +// issue: + +//@ check-pass + +trait Trait { + type Type; +} + +// Below, `T::Type` resolves to `::Type` since the owner has bound `T: Trait`. + +struct Owner0(T, U); + +struct Owner1(T, U) +where + T: Trait; + +fn main() {} diff --git a/tests/ui/const-generics/defaults/type-relative-assoc-const.rs b/tests/ui/const-generics/defaults/type-relative-assoc-const.rs new file mode 100644 index 0000000000000..822825d4e01bf --- /dev/null +++ b/tests/ui/const-generics/defaults/type-relative-assoc-const.rs @@ -0,0 +1,16 @@ +// Test that we can resolve type-relative associated const paths inside const parameter defaults +// where the self type is a simple type parameter. + +//@ check-pass +#![feature(gca_min_const_items, gca_macroless_args)] + +trait Trait { + #[rustc_always_gca] + const CT: usize; +} + +// Below, `T::CT` resolves to `::CT` since the owner has bound `T: Trait`. + +struct Owner(T); + +fn main() {} From 8f2db6e821c9bc87fcca4ffb72bc221ac482ca9b Mon Sep 17 00:00:00 2001 From: Urgau Date: Wed, 7 Oct 2026 20:53:45 +0200 Subject: [PATCH 04/14] Correctly represent doctest source as a `RealFileName` plus line --- compiler/rustc_driver_impl/src/lib.rs | 29 ++++++++++++++------------ compiler/rustc_interface/src/passes.rs | 14 +++++++------ compiler/rustc_session/src/config.rs | 20 +++++++++++++++++- compiler/rustc_span/src/lib.rs | 10 ++++----- src/librustdoc/doctest/markdown.rs | 2 +- 5 files changed, 49 insertions(+), 26 deletions(-) diff --git a/compiler/rustc_driver_impl/src/lib.rs b/compiler/rustc_driver_impl/src/lib.rs index 1ba7ce953b5d4..9b1f43156522e 100644 --- a/compiler/rustc_driver_impl/src/lib.rs +++ b/compiler/rustc_driver_impl/src/lib.rs @@ -391,7 +391,7 @@ fn make_input(early_dcx: &EarlyDiagCtxt, free_matches: &[String]) -> Option { let line = env::var("UNSTABLE_RUSTDOC_TEST_LINE").expect( "when UNSTABLE_RUSTDOC_TEST_PATH is set \ @@ -400,12 +400,13 @@ fn make_input(early_dcx: &EarlyDiagCtxt, free_matches: &[String]) -> Option() .expect("UNSTABLE_RUSTDOC_TEST_LINE needs to be a number"); - FileName::doc_test_source_code(PathBuf::from(path), line) + Some(Input::DocTestStr { file: PathBuf::from(path), line, input }) } - Err(_) => FileName::anon_source_code(&input), - }; - - Some(Input::Str { name, input }) + Err(_) => { + let name = FileName::anon_source_code(&input); + Some(Input::Str { name, input }) + } + } } [ifile] => Some(Input::File(PathBuf::from(ifile))), [ifile1, ifile2, ..] => early_dcx.early_fatal(format!( @@ -619,7 +620,7 @@ fn list_metadata(sess: &Session, metadata_loader: &dyn MetadataLoader) { } safe_println!("{}", String::from_utf8(v).unwrap()); } - Input::Str { .. } => { + Input::Str { .. } | Input::DocTestStr { .. } => { sess.dcx().fatal("cannot list metadata for stdin"); } } @@ -1373,12 +1374,14 @@ fn parse_crate_attrs<'a>(sess: &'a Session) -> PResult<'a, ast::AttrVec> { Input::File(file) => { new_parser_from_file(&sess.psess, file, StripTokens::ShebangAndFrontmatter, None) } - Input::Str { name, input } => new_parser_from_source_str( - &sess.psess, - name.clone(), - input.clone(), - StripTokens::ShebangAndFrontmatter, - ), + instr @ (Input::Str { input, .. } | Input::DocTestStr { input, .. }) => { + new_parser_from_source_str( + &sess.psess, + instr.file_name(sess), + input.clone(), + StripTokens::ShebangAndFrontmatter, + ) + } }); parser.parse_inner_attributes() } diff --git a/compiler/rustc_interface/src/passes.rs b/compiler/rustc_interface/src/passes.rs index 5ea1cb20990b5..cdcc12bcbbd7a 100644 --- a/compiler/rustc_interface/src/passes.rs +++ b/compiler/rustc_interface/src/passes.rs @@ -61,12 +61,14 @@ pub fn parse<'a>(sess: &'a Session) -> ast::Crate { StripTokens::ShebangAndFrontmatter, None, ), - Input::Str { input, name } => new_parser_from_source_str( - &sess.psess, - name.clone(), - input.clone(), - StripTokens::ShebangAndFrontmatter, - ), + instr @ (Input::Str { input, .. } | Input::DocTestStr { input, .. }) => { + new_parser_from_source_str( + &sess.psess, + instr.file_name(sess), + input.clone(), + StripTokens::ShebangAndFrontmatter, + ) + } }); parser.parse_crate_mod() }) diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs index fdfc2aacc79a8..65349f48fcfb9 100644 --- a/compiler/rustc_session/src/config.rs +++ b/compiler/rustc_session/src/config.rs @@ -1064,6 +1064,15 @@ impl Default for NextSolverConfig { pub enum Input { /// Load source code from a file. File(PathBuf), + /// Load source code from a doctest string. + DocTestStr { + /// The file associated with this input. + file: PathBuf, + // Line associated with this input + line: isize, + /// An anonymous string containing the source code. + input: String, + }, /// Load source code from a string. Str { /// A string that is shown in place of a filename. @@ -1094,6 +1103,14 @@ impl Input { .path_mapping() .to_real_filename(session.psess.source_map().working_dir(), ifile.as_path()), ), + Input::DocTestStr { ref file, line, .. } => FileName::DocTest( + session + .psess + .source_map() + .path_mapping() + .to_real_filename(session.psess.source_map().working_dir(), file.as_path()), + line, + ), Input::Str { ref name, .. } => name.clone(), } } @@ -1101,6 +1118,7 @@ impl Input { pub fn opt_path(&self) -> Option<&Path> { match self { Input::File(file) => Some(file), + Input::DocTestStr { file, .. } => Some(file), Input::Str { name, .. } => match name { FileName::Real(real) => real.local_path(), FileName::CfgSpec(_) => None, @@ -1109,7 +1127,7 @@ impl Input { FileName::ProcMacroSourceCode(_) => None, FileName::CliCrateAttr(_) => None, FileName::Custom(_) => None, - FileName::DocTest(path, _) => Some(path), + FileName::DocTest(path, _) => path.local_path(), FileName::InlineAsm(_) => None, }, } diff --git a/compiler/rustc_span/src/lib.rs b/compiler/rustc_span/src/lib.rs index ddec29eca6517..088f4c7216cbd 100644 --- a/compiler/rustc_span/src/lib.rs +++ b/compiler/rustc_span/src/lib.rs @@ -520,7 +520,8 @@ pub enum FileName { CliCrateAttr(Hash64), /// Custom sources for explicit parser calls from plugins and drivers. Custom(String), - DocTest(PathBuf, isize), + /// Custom path for doctests + DocTest(RealFileName, isize), /// Post-substitution inline assembly from LLVM. InlineAsm(Hash64), } @@ -543,7 +544,7 @@ impl fmt::Display for FileNameDisplay<'_> { fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { use FileName::*; match *self.inner { - Real(ref name) => { + Real(ref name) | DocTest(ref name, _) => { write!(fmt, "{}", name.to_string_lossy(self.display_pref)) } CfgSpec(_) => write!(fmt, ""), @@ -552,7 +553,6 @@ impl fmt::Display for FileNameDisplay<'_> { ProcMacroSourceCode(_) => write!(fmt, ""), CliCrateAttr(_) => write!(fmt, ""), Custom(ref s) => write!(fmt, "<{s}>"), - DocTest(ref path, _) => write!(fmt, "{}", path.display()), InlineAsm(_) => write!(fmt, ""), } } @@ -643,7 +643,7 @@ impl FileName { FileName::CliCrateAttr(hasher.finish()) } - pub fn doc_test_source_code(path: PathBuf, line: isize) -> FileName { + pub fn doc_test_source_code(path: RealFileName, line: isize) -> FileName { FileName::DocTest(path, line) } @@ -660,7 +660,7 @@ impl FileName { pub fn into_local_path(self) -> Option { match self { FileName::Real(path) => path.into_local_path(), - FileName::DocTest(path, _) => Some(path), + FileName::DocTest(path, _) => path.into_local_path(), _ => None, } } diff --git a/src/librustdoc/doctest/markdown.rs b/src/librustdoc/doctest/markdown.rs index a994f1d974ae3..05296b791709d 100644 --- a/src/librustdoc/doctest/markdown.rs +++ b/src/librustdoc/doctest/markdown.rs @@ -94,7 +94,7 @@ pub(crate) fn test(input: &Input, options: Options, dcx: DiagCtxtHandle<'_>) -> Input::File(path) => { read_to_string(path).map_err(|err| format!("{}: {err}", path.display()))? } - Input::Str { name: _, input } => input.clone(), + Input::Str { name: _, input } | Input::DocTestStr { input, .. } => input.clone(), }; // Obviously not a real crate name, but close enough for purposes of doctests. From 5941c0b1f97a811f50c36b12994c8da56d88d4f4 Mon Sep 17 00:00:00 2001 From: sjwang05 <63834813+sjwang05@users.noreply.github.com> Date: Thu, 8 Oct 2026 14:24:35 -0700 Subject: [PATCH 05/14] properly ignore the current goal's usages --- compiler/rustc_type_ir/src/search_graph/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_type_ir/src/search_graph/mod.rs b/compiler/rustc_type_ir/src/search_graph/mod.rs index 0a6e351dcdf8f..e427cdcdc8acb 100644 --- a/compiler/rustc_type_ir/src/search_graph/mod.rs +++ b/compiler/rustc_type_ir/src/search_graph/mod.rs @@ -763,7 +763,7 @@ impl, X: Cx> SearchGraph { #[allow(rustc::potential_query_instability)] for (head_index, usages) in usages.into_iter() { if head_index == entry_index { - entry.usages.unwrap().ignore_usages(usages); + entry.usages.as_mut().unwrap().ignore_usages(usages); } else { entry.heads.ignore_usages(head_index, usages); } From f21a12aaa22ad7b3d00cd84c9ba1884ffa798ec5 Mon Sep 17 00:00:00 2001 From: LorrensP-2158466 Date: Mon, 7 Sep 2026 16:07:10 +0200 Subject: [PATCH 06/14] In the `BestObligation` prooftree visitor, we now only skip any candidates who have a failed `Normalization` nested goal. The previous condition on `ImplWhereBound` and co is now removed as it is unessecary. + add test of issue --- .../src/solve/fulfill/derive_errors.rs | 16 ++--- ...agnostic-due-to-best-obligation-visitor.rs | 27 ++++++++ ...stic-due-to-best-obligation-visitor.stderr | 64 +++++++++++++++++++ 3 files changed, 97 insertions(+), 10 deletions(-) create mode 100644 tests/ui/traits/next-solver/confusing-diagnostic-due-to-best-obligation-visitor.rs create mode 100644 tests/ui/traits/next-solver/confusing-diagnostic-due-to-best-obligation-visitor.stderr diff --git a/compiler/rustc_trait_selection/src/solve/fulfill/derive_errors.rs b/compiler/rustc_trait_selection/src/solve/fulfill/derive_errors.rs index 7df3fa41a1df2..992c521261b5c 100644 --- a/compiler/rustc_trait_selection/src/solve/fulfill/derive_errors.rs +++ b/compiler/rustc_trait_selection/src/solve/fulfill/derive_errors.rs @@ -214,20 +214,16 @@ impl<'tcx> BestObligation<'tcx> { // We always handle rigid alias candidates separately as we may not add them for // aliases whose trait bound doesn't hold. candidates.retain(|c| !matches!(c.kind(), inspect::ProbeKind::RigidAlias { .. })); - // If we have >1 candidate, one may still be due to "boring" reasons, like - // an alias-relate that failed to hold when deeply evaluated. We really - // don't care about reasons like this. + // If there are multiple candidates, we can still recurse into a single one if all + // others fail due to boring reasons. Currently that's if we fail to normalize + // while considering the candidate. if candidates.len() > 1 { candidates.retain(|candidate| { goal.infcx().probe(|_| { - candidate.instantiate_nested_goals(self.span()).iter().any( + !candidate.instantiate_nested_goals(self.span()).iter().any( |nested_goal| { - matches!( - nested_goal.source(), - GoalSource::ImplWhereBound - | GoalSource::AliasBoundConstCondition - | GoalSource::AliasWellFormed - ) && nested_goal.result().is_err() + nested_goal.source() == GoalSource::Normalization + && nested_goal.result().is_err() }, ) }) diff --git a/tests/ui/traits/next-solver/confusing-diagnostic-due-to-best-obligation-visitor.rs b/tests/ui/traits/next-solver/confusing-diagnostic-due-to-best-obligation-visitor.rs new file mode 100644 index 0000000000000..b925e6a7745e6 --- /dev/null +++ b/tests/ui/traits/next-solver/confusing-diagnostic-due-to-best-obligation-visitor.rs @@ -0,0 +1,27 @@ +//@compile-flags: -Znext-solver=globally +//@check-fail + +// See issue https://github.com/rust-lang/rust/issues/161882, test makes sure we don't report +// irrelevant and not implemented bounds when we can't proof an obligation. + +struct MyError; + +trait MaybeFallible { + type Error: MaybeError; //~ ERROR: the trait bound `MyError: From<::Error>` is not satisfied +} + +trait MaybeError: Sized +where + MyError: From, +{ + type ComposeWithOther: MaybeError; //~ ERROR: the trait bound `MyError: From<::ComposeWithOther>` is not satisfied +} + +fn compose() -> Result<(), ()> +where + ::ComposeWithOther: From, //~ ERROR: the trait bound `MyError: From<::Error>` is not satisfied +{ + todo!() +} + +fn main() {} diff --git a/tests/ui/traits/next-solver/confusing-diagnostic-due-to-best-obligation-visitor.stderr b/tests/ui/traits/next-solver/confusing-diagnostic-due-to-best-obligation-visitor.stderr new file mode 100644 index 0000000000000..2dd3f4f9a945f --- /dev/null +++ b/tests/ui/traits/next-solver/confusing-diagnostic-due-to-best-obligation-visitor.stderr @@ -0,0 +1,64 @@ +error[E0277]: the trait bound `MyError: From<::Error>` is not satisfied + --> $DIR/confusing-diagnostic-due-to-best-obligation-visitor.rs:22:49 + | +LL | ::ComposeWithOther: From, + | ^^^^^^^ unsatisfied trait bound + | +help: the trait `From<::Error>` is not implemented for `MyError` + --> $DIR/confusing-diagnostic-due-to-best-obligation-visitor.rs:7:1 + | +LL | struct MyError; + | ^^^^^^^^^^^^^^ +note: required by a bound in `MaybeError` + --> $DIR/confusing-diagnostic-due-to-best-obligation-visitor.rs:15:14 + | +LL | trait MaybeError: Sized +LL | where +LL | MyError: From, + | ^^^^^^^^^^ required by this bound in `MaybeError` +help: consider extending the `where` clause, but there might be an alternative better way to express this requirement + | +LL | ::ComposeWithOther: From, MyError: From<::Error> + | ++++++++++++++++++++++++++++++++++++++++++ + +error[E0277]: the trait bound `MyError: From<::Error>` is not satisfied + --> $DIR/confusing-diagnostic-due-to-best-obligation-visitor.rs:10:17 + | +LL | type Error: MaybeError; + | ^^^^^^^^^^ unsatisfied trait bound + | +help: the trait `From<::Error>` is not implemented for `MyError` + --> $DIR/confusing-diagnostic-due-to-best-obligation-visitor.rs:7:1 + | +LL | struct MyError; + | ^^^^^^^^^^^^^^ +note: required by a bound in `MaybeError` + --> $DIR/confusing-diagnostic-due-to-best-obligation-visitor.rs:15:14 + | +LL | trait MaybeError: Sized +LL | where +LL | MyError: From, + | ^^^^^^^^^^ required by this bound in `MaybeError` + +error[E0277]: the trait bound `MyError: From<::ComposeWithOther>` is not satisfied + --> $DIR/confusing-diagnostic-due-to-best-obligation-visitor.rs:17:28 + | +LL | type ComposeWithOther: MaybeError; + | ^^^^^^^^^^ unsatisfied trait bound + | +help: the trait `From<::ComposeWithOther>` is not implemented for `MyError` + --> $DIR/confusing-diagnostic-due-to-best-obligation-visitor.rs:7:1 + | +LL | struct MyError; + | ^^^^^^^^^^^^^^ +note: required by a bound in `MaybeError` + --> $DIR/confusing-diagnostic-due-to-best-obligation-visitor.rs:15:14 + | +LL | trait MaybeError: Sized +LL | where +LL | MyError: From, + | ^^^^^^^^^^ required by this bound in `MaybeError` + +error: aborting due to 3 previous errors + +For more information about this error, try `rustc --explain E0277`. From 9bc17fdcd1ed19ac586d8e25f1a47f0e7d5effcc Mon Sep 17 00:00:00 2001 From: Boxy Uwu Date: Wed, 7 Oct 2026 11:50:23 +0100 Subject: [PATCH 07/14] Don't rewrite alias outlives in smaller universes --- .../rustc_type_ir/src/region_constraint.rs | 85 +++++++++++-------- .../lower_universe_alias_outlives.rs | 26 ++++++ 2 files changed, 75 insertions(+), 36 deletions(-) create mode 100644 tests/ui/assumptions_on_binders/lower_universe_alias_outlives.rs diff --git a/compiler/rustc_type_ir/src/region_constraint.rs b/compiler/rustc_type_ir/src/region_constraint.rs index be05ed2cd4d81..63d0d777e9b3c 100644 --- a/compiler/rustc_type_ir/src/region_constraint.rs +++ b/compiler/rustc_type_ir/src/region_constraint.rs @@ -861,6 +861,7 @@ fn rewrite_type_outlives_constraints_in_universe_for_eager_placeholder_handling< RegionConstraint::new_from_or(Or::build_and(and_constraint, or_constraint)) } +#[instrument(level = "debug", skip(infcx), ret)] fn rewrite_placeholder_ty_outlives_constraints_in_universe_for_eager_placeholder_handling< Infcx: InferCtxtLike, I: Interner, @@ -903,6 +904,7 @@ fn rewrite_placeholder_ty_outlives_constraints_in_universe_for_eager_placeholder Or::new(candidates.into_iter().map(|c| And::new([c]))) } +#[instrument(level = "debug", skip(infcx), ret)] fn rewrite_alias_ty_outlives_constraints_in_universe_for_eager_placeholder_handling< Infcx: InferCtxtLike, I: Interner, @@ -914,6 +916,10 @@ fn rewrite_alias_ty_outlives_constraints_in_universe_for_eager_placeholder_handl ) -> Or { use LeafRegionConstraint::*; + if max_universe(infcx, bound_outlives) != u { + return Or::new_leaf(AliasTyOutlivesViaEnv(bound_outlives, ())); + } + let mut candidates = Vec::new(); // given there can be higher ranked assumptions, e.g. `for<'a> >::Assoc: 'c`, that @@ -931,7 +937,7 @@ fn rewrite_alias_ty_outlives_constraints_in_universe_for_eager_placeholder_handl // handle. // // we don't care about this when rewriting in the root universe as we know the complete set of assumptions - if max_universe(infcx, bound_outlives) == u { + { let mut replacer = PlaceholderReplacer { cx: infcx.cx(), existing_var_count: bound_outlives.bound_vars().len(), @@ -950,6 +956,7 @@ fn rewrite_alias_ty_outlives_constraints_in_universe_for_eager_placeholder_handl I::BoundVarKinds::from_vars(infcx.cx(), bound_vars), ); let candidate = Or::new_leaf(AliasTyOutlivesViaEnv(bound_outlives, ())); + debug!("fully higher ranked candidate: {candidate:?}"); if max_universe(infcx, candidate.clone()) < u { candidates.push(candidate); } else { @@ -968,41 +975,47 @@ fn rewrite_alias_ty_outlives_constraints_in_universe_for_eager_placeholder_handl // given a list of regions which outlive `'u2` // // we don't care about this when rewriting in the root universe as we know the complete set of assumptions - let (escaping_alias, escaping_r) = bound_outlives.skip_binder(); - if max_universe(infcx, escaping_r) == u { - let mut replacer = PlaceholderReplacer { - cx: infcx.cx(), - existing_var_count: bound_outlives.bound_vars().len(), - bound_vars: IndexMap::default(), - universe: u, - current_index: DebruijnIndex::ZERO, - }; - let escaping_alias = escaping_alias.fold_with(&mut replacer); - let bound_vars = bound_outlives.bound_vars().iter().chain( - core::mem::take(&mut replacer.bound_vars) - .into_iter() - .map(|(_, bound_region)| BoundVariableKind::Region(bound_region.kind)), - ); - let bound_alias = Binder::bind_with_vars( - escaping_alias, - I::BoundVarKinds::from_vars(infcx.cx(), bound_vars), - ); - - // while we did skip the binder, bound vars aren't in any universe so - // this can't be an escaping bound var - candidates.push(Or::new( - regions_outliving(escaping_r, assumptions, infcx.cx()) - .filter(|r2| max_universe(infcx, *r2) < u) - .map(|r2| { - let candidate = - AliasTyOutlivesViaEnv(bound_alias.map_bound(|alias| (alias, r2)), ()); - if max_universe(infcx, candidate.clone()) < u { - And::new([candidate]) - } else { - And::new([Ambiguity(())]) - } - }), - )); + { + let (escaping_alias, escaping_r) = bound_outlives.skip_binder(); + let max_u = max_universe(infcx, escaping_r); + debug!(?max_u); + if max_u == u { + let mut replacer = PlaceholderReplacer { + cx: infcx.cx(), + existing_var_count: bound_outlives.bound_vars().len(), + bound_vars: IndexMap::default(), + universe: u, + current_index: DebruijnIndex::ZERO, + }; + let escaping_alias = escaping_alias.fold_with(&mut replacer); + let bound_vars = bound_outlives.bound_vars().iter().chain( + core::mem::take(&mut replacer.bound_vars) + .into_iter() + .map(|(_, bound_region)| BoundVariableKind::Region(bound_region.kind)), + ); + let bound_alias = Binder::bind_with_vars( + escaping_alias, + I::BoundVarKinds::from_vars(infcx.cx(), bound_vars), + ); + + // while we did skip the binder, bound vars aren't in any universe so + // this can't be an escaping bound var + let candidate = Or::new( + regions_outliving(escaping_r, assumptions, infcx.cx()) + .filter(|r2| max_universe(infcx, *r2) < u) + .map(|r2| { + let candidate = + AliasTyOutlivesViaEnv(bound_alias.map_bound(|alias| (alias, r2)), ()); + if max_universe(infcx, candidate.clone()) < u { + And::new([candidate]) + } else { + And::new([Ambiguity(())]) + } + }), + ); + debug!("transitive outlived region candidate: {candidate:?}"); + candidates.push(candidate); + } } // I'm not convinced our handling here is *complete* so for now diff --git a/tests/ui/assumptions_on_binders/lower_universe_alias_outlives.rs b/tests/ui/assumptions_on_binders/lower_universe_alias_outlives.rs new file mode 100644 index 0000000000000..f547295419c4f --- /dev/null +++ b/tests/ui/assumptions_on_binders/lower_universe_alias_outlives.rs @@ -0,0 +1,26 @@ +//@ check-pass + +// test that we don't incorrectly try and eagerly handle alias outlives' from too +// low a universe + +#![feature(test_binder_constraints, generic_const_items)] + +trait AliasHaver { + type Assoc; +} + +core::test_binder_constraints! { + impl + where + T::Assoc: 'static, + { + forall<'b> { + for<> T::Assoc: 'static + } expect { + // previously this would just be `or {}` i.e. false + for<> T::Assoc: 'static + } + } +} + +fn main() {} From 7d43809ccafa351527c5439e6833e2cd05c912e3 Mon Sep 17 00:00:00 2001 From: Boxy Uwu Date: Wed, 7 Oct 2026 13:08:04 +0100 Subject: [PATCH 08/14] Produce less general higher ranked alias constraints --- .../rustc_type_ir/src/region_constraint.rs | 98 +++++++------------ ...lias_outlives_with_repeated_placeholder.rs | 34 +++++++ 2 files changed, 70 insertions(+), 62 deletions(-) create mode 100644 tests/ui/assumptions_on_binders/alias_outlives_with_repeated_placeholder.rs diff --git a/compiler/rustc_type_ir/src/region_constraint.rs b/compiler/rustc_type_ir/src/region_constraint.rs index 63d0d777e9b3c..240e8182cc78f 100644 --- a/compiler/rustc_type_ir/src/region_constraint.rs +++ b/compiler/rustc_type_ir/src/region_constraint.rs @@ -52,6 +52,7 @@ use crate::data_structures::IndexMap; use crate::fold::TypeSuperFoldable; use crate::inherent::*; use crate::relate::{Relate, RelateResult, TypeRelation, VarianceDiagInfo}; +use crate::visit::TypeVisitableExt; use crate::{ AliasTy, Binder, BoundRegion, BoundVar, BoundVariableKind, ClauseKind, DebruijnIndex, InferCtxtLike, Interner, IsRigid, OutlivesClause, Region, RegionKind, TyKind, TypeFoldable, @@ -924,8 +925,7 @@ fn rewrite_alias_ty_outlives_constraints_in_universe_for_eager_placeholder_handl // given there can be higher ranked assumptions, e.g. `for<'a> >::Assoc: 'c`, that // means that it's actually *always* possible for an alias outlive to be satisfied in the root universe - // which means there should *always* be atleast two candidates when destructuring alias outlives. The - // two candidates being component outlives and then a higher ranked alias outlives. + // which means there should *always* be atleast one candidate when destructuring alias outlives. // // we dont care about this for region outlives as `for<'a> 'a: 'b` can't exist as we don't elaborate // higher ranked type outlives assumptions into higher ranked region outlives assumptions. similarly, @@ -935,9 +935,9 @@ fn rewrite_alias_ty_outlives_constraints_in_universe_for_eager_placeholder_handl // // so actually only `for<'a, 'b> Alias<'a>: 'b` and `for<'a> T: 'a` are assumptions we actually need to // handle. - // - // we don't care about this when rewriting in the root universe as we know the complete set of assumptions - { + let higher_ranked_candidate_for_outlived_region = |region| { + let bound_outlives = bound_outlives.map_bound(|(alias, _)| (alias, region)); + let mut replacer = PlaceholderReplacer { cx: infcx.cx(), existing_var_count: bound_outlives.bound_vars().len(), @@ -955,68 +955,42 @@ fn rewrite_alias_ty_outlives_constraints_in_universe_for_eager_placeholder_handl escaping_outlives, I::BoundVarKinds::from_vars(infcx.cx(), bound_vars), ); - let candidate = Or::new_leaf(AliasTyOutlivesViaEnv(bound_outlives, ())); - debug!("fully higher ranked candidate: {candidate:?}"); - if max_universe(infcx, candidate.clone()) < u { - candidates.push(candidate); - } else { - // `PlaceholderReplacer` only folds regions. A non-lifetime binder can leave - // a placeholder type in `u`, so this type-outlives constraint cannot be - // handled by the region-outlives-only eager placeholder machinery. - candidates.push(Or::new_ambig(())); + + // `PlaceholderReplacer` only folds regions. A non-lifetime binder can leave + // a placeholder type in `u`, so this type-outlives constraint cannot be + // handled by the region-outlives-only eager placeholder machinery. + if max_universe(infcx, bound_outlives) == u { + return Or::new_ambig(()); } - } - // Actually look at the assumptions and matching our higher ranked alias outlives goal - // against potentially higher ranked type outlives assumptions. - candidates.push(alias_outlives_candidates_from_assumptions(infcx, bound_outlives, assumptions)); + debug!("higher ranked candidate: {bound_outlives:?}"); + Or::new_leaf(AliasTyOutlivesViaEnv(bound_outlives, ())) + }; - // we can rewrite `Alias_u1: 'u2` into `Or(Alias_u1: 'u1)` - // given a list of regions which outlive `'u2` + // We elaborate something like `Alias<'a_u2>: 'b_u2` into alias outlives involving each + // region which is known to outlive `'b_u2`. So e.g. if `'c_u2: 'b_u2` is known to hold + // would wind up looking at `Alias<'a_u2>: 'c_u2` as well as `Alias<'a_u2>: 'b_u2`. // - // we don't care about this when rewriting in the root universe as we know the complete set of assumptions - { - let (escaping_alias, escaping_r) = bound_outlives.skip_binder(); - let max_u = max_universe(infcx, escaping_r); - debug!(?max_u); - if max_u == u { - let mut replacer = PlaceholderReplacer { - cx: infcx.cx(), - existing_var_count: bound_outlives.bound_vars().len(), - bound_vars: IndexMap::default(), - universe: u, - current_index: DebruijnIndex::ZERO, - }; - let escaping_alias = escaping_alias.fold_with(&mut replacer); - let bound_vars = bound_outlives.bound_vars().iter().chain( - core::mem::take(&mut replacer.bound_vars) - .into_iter() - .map(|(_, bound_region)| BoundVariableKind::Region(bound_region.kind)), - ); - let bound_alias = Binder::bind_with_vars( - escaping_alias, - I::BoundVarKinds::from_vars(infcx.cx(), bound_vars), - ); - - // while we did skip the binder, bound vars aren't in any universe so - // this can't be an escaping bound var - let candidate = Or::new( - regions_outliving(escaping_r, assumptions, infcx.cx()) - .filter(|r2| max_universe(infcx, *r2) < u) - .map(|r2| { - let candidate = - AliasTyOutlivesViaEnv(bound_alias.map_bound(|alias| (alias, r2)), ()); - if max_universe(infcx, candidate.clone()) < u { - And::new([candidate]) - } else { - And::new([Ambiguity(())]) - } - }), - ); - debug!("transitive outlived region candidate: {candidate:?}"); - candidates.push(candidate); + // This is important in two cases. First, if the outliving region is in a lower universe then + // we've successfully lowered the universe of one of the terms in our alias outlives constraint. + // + // Secondly, if it's a region in the current universe *and* it's a region in the components of the + // alias then we get a less general constraint after replacing with bound vars compared to if we + // had just replaced the initial region. See `assumptions_on_binders/alias_outlives_with_repeated_placeholder.rs` + let escaping_r = bound_outlives.skip_binder().1; + if max_universe(infcx, escaping_r) < u || escaping_r.has_escaping_bound_vars() { + // we don't know anything about lower universe regions, and we also don't bother + // looking at the assumptions on the `AliasTyOutlivesViaEnv` binder. + candidates.push(higher_ranked_candidate_for_outlived_region(escaping_r)) + } else { + for r in regions_outliving(escaping_r, assumptions, infcx.cx()) { + candidates.push(higher_ranked_candidate_for_outlived_region(r)); } - } + }; + + // Actually look at the assumptions and matching our higher ranked alias outlives goal + // against potentially higher ranked type outlives assumptions. + candidates.push(alias_outlives_candidates_from_assumptions(infcx, bound_outlives, assumptions)); // I'm not convinced our handling here is *complete* so for now // let's be conservative and not let alias outlives' cause NoSolution diff --git a/tests/ui/assumptions_on_binders/alias_outlives_with_repeated_placeholder.rs b/tests/ui/assumptions_on_binders/alias_outlives_with_repeated_placeholder.rs new file mode 100644 index 0000000000000..b1fa6f7f910a3 --- /dev/null +++ b/tests/ui/assumptions_on_binders/alias_outlives_with_repeated_placeholder.rs @@ -0,0 +1,34 @@ +//@ check-pass +//@ compile-flags: -Zassumptions-on-binders -Znext-solver=globally + +#![feature(test_binder_constraints, non_lifetime_binders)] +#![expect(incomplete_features)] + +trait Trait<'a> { + type Assoc; +} + +core::test_binder_constraints! { + impl + where + for<'a> T: Trait<'a>, + for<'a> >::Assoc: 'a + { + forall<'a, 'b> + where + 'a: 'b, + { + for<> >::Assoc: 'b + } expect { + or { + // this first candidate is the important one! + for<'a> >::Assoc: 'a, + // these won't wind up actually being used to prove the OR + for<'a, 'b> >::Assoc: 'b, + for<'a> >::Assoc: 'static, + } + } + } +} + +fn main() {} From 07d67c60014bb027cda37ec2e8c3e97369ac16bf Mon Sep 17 00:00:00 2001 From: Boxy Uwu Date: Wed, 7 Oct 2026 11:52:38 +0100 Subject: [PATCH 09/14] Better debug impls for abby stuff --- .../rustc_type_ir/src/region_constraint.rs | 32 +++++++++++++++++-- .../test-infra-fails-properly.stderr | 10 ++---- 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/compiler/rustc_type_ir/src/region_constraint.rs b/compiler/rustc_type_ir/src/region_constraint.rs index be05ed2cd4d81..4a9483205568a 100644 --- a/compiler/rustc_type_ir/src/region_constraint.rs +++ b/compiler/rustc_type_ir/src/region_constraint.rs @@ -59,7 +59,7 @@ use crate::{ set_aliases_to_non_rigid, }; -#[derive_where(Clone, Debug; I: Interner)] +#[derive_where(Clone; I: Interner)] pub struct Assumptions { pub type_outlives: Vec>>, /// Known `'a: 'b` assumptions, stored as an edge from the outliving region to the @@ -70,6 +70,15 @@ pub struct Assumptions { pub inverse_region_outlives: TransitiveRelation>, } +impl std::fmt::Debug for Assumptions { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + f.debug_struct("Assumptions") + .field("type_outlives", &self.type_outlives) + .field("region_graph", &"elided") + .finish() + } +} + impl Assumptions { pub fn empty() -> Self { Self { @@ -156,10 +165,10 @@ impl Assumptions { } } -#[derive_where(Clone, Hash, PartialEq, Eq, Debug; I: Interner, S)] +#[derive_where(Clone, Hash, PartialEq, Eq; I: Interner, S)] #[derive(TypeVisitable_Generic, GenericTypeVisitable, TypeFoldable_Generic)] #[cfg_attr(feature = "nightly", derive(StableHash_NoContext))] -pub enum LeafRegionConstraint { +pub enum LeafRegionConstraint { Ambiguity(S), RegionOutlives(Region, Region, S), /// Requirement that a (potentially higher ranked) alias outlives some (potentially higher ranked) @@ -184,6 +193,23 @@ pub enum LeafRegionConstraint { PlaceholderTyOutlives(I::Ty, Region, S), } +impl std::fmt::Debug for LeafRegionConstraint { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + match self { + Self::Ambiguity(_span) => write!(f, "Ambiguity"), + Self::RegionOutlives(r1, r2, _span) => { + f.debug_tuple("RegionOutlives").field(r1).field(r2).finish() + } + Self::AliasTyOutlivesViaEnv(bound_outlives, _span) => { + f.debug_tuple("AliasTyOutlivesViaEnv").field(bound_outlives).finish() + } + Self::PlaceholderTyOutlives(ty, r, _span) => { + f.debug_tuple("PlaceholderTyOutlives").field(ty).field(r).finish() + } + } + } +} + impl LeafRegionConstraint { pub fn with_span( self, diff --git a/tests/ui/assumptions_on_binders/test-infra-fails-properly.stderr b/tests/ui/assumptions_on_binders/test-infra-fails-properly.stderr index 04eb93f7fe03c..5736d8ef5dbaa 100644 --- a/tests/ui/assumptions_on_binders/test-infra-fails-properly.stderr +++ b/tests/ui/assumptions_on_binders/test-infra-fails-properly.stderr @@ -72,12 +72,10 @@ LL | forall<'a> where 'b: 'a { = note: expected: RegionOutlives( 'b/#0, 'c/#1, - $DIR/test-infra-fails-properly.rs:64:17: 64:23 (#0), ) = note: actual: RegionOutlives( 'c/#1, 'static, - $DIR/test-infra-fails-properly.rs:59:9: 59:15 (#0), ) error: bound type test binder constraint must be alias (it's a AliasTyOutlivesViaEnv) @@ -110,9 +108,7 @@ LL | ambiguity | = note: expected: And( [ - Ambiguity( - $DIR/test-infra-fails-properly.rs:91:13: 91:22 (#0), - ), + Ambiguity, ], ) = note: actual: And( @@ -135,9 +131,7 @@ LL | forall<'a> where 'a: 'static { ) = note: actual: And( [ - Ambiguity( - $DIR/test-infra-fails-properly.rs:99:9: 99:15 (#0), - ), + Ambiguity, ], ) From f4b09769f924387b74b73612a8becc7d8fba5135 Mon Sep 17 00:00:00 2001 From: James Barford-Evans Date: Fri, 9 Oct 2026 14:22:34 +0100 Subject: [PATCH 10/14] Fix for const generic parameter checking --- .../src/check/compare_impl_item.rs | 135 ++++++++++++++---- 1 file changed, 104 insertions(+), 31 deletions(-) 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 204b6f913cb8b..e8a421afacabd 100644 --- a/compiler/rustc_hir_analysis/src/check/compare_impl_item.rs +++ b/compiler/rustc_hir_analysis/src/check/compare_impl_item.rs @@ -86,6 +86,7 @@ fn check_method_is_structurally_compatible<'tcx>( compare_self_type(tcx, impl_m, trait_m, impl_trait_ref, delay)?; compare_number_of_generics(tcx, impl_m, trait_m, delay)?; compare_generic_param_kinds(tcx, impl_m, trait_m, delay)?; + compare_const_generic_param_types(tcx, impl_m, trait_m, impl_trait_ref, delay)?; compare_number_of_method_arguments(tcx, impl_m, trait_m, delay)?; compare_synthetic_generics(tcx, impl_m, trait_m, delay)?; check_region_bounds_on_impl_item(tcx, impl_m, trait_m, delay)?; @@ -2034,31 +2035,8 @@ fn compare_synthetic_generics<'tcx>( if let Some(reported) = error_found { Err(reported) } else { Ok(()) } } -/// Checks that all parameters in the generics of a given assoc item in a trait impl have -/// the same kind as the respective generic parameter in the trait def. -/// -/// For example all 4 errors in the following code are emitted here: -/// ```rust,ignore (pseudo-Rust) -/// trait Foo { -/// fn foo(); -/// type Bar; -/// fn baz(); -/// type Blah; -/// } -/// -/// impl Foo for () { -/// fn foo() {} -/// //~^ error -/// type Bar = (); -/// //~^ error -/// fn baz() {} -/// //~^ error -/// type Blah = u32; -/// //~^ error -/// } -/// ``` -/// -/// This function does not handle lifetime parameters +/// Checks that corresponding generic parameters in a trait item and its impl +/// are both type parameters or both const parameters. fn compare_generic_param_kinds<'tcx>( tcx: TyCtxt<'tcx>, impl_item: ty::AssocItem, @@ -2081,11 +2059,6 @@ fn compare_generic_param_kinds<'tcx>( { use GenericParamDefKind::*; if match (¶m_impl.kind, ¶m_trait.kind) { - (Const { .. }, Const { .. }) - if tcx.type_of(param_impl.def_id) != tcx.type_of(param_trait.def_id) => - { - true - } (Const { .. }, Type { .. }) | (Type { .. }, Const { .. }) => true, // this is exhaustive so that anyone adding new generic param kinds knows // to make sure this error is reported for them. @@ -2104,7 +2077,7 @@ fn compare_generic_param_kinds<'tcx>( "{} `{}` has an incompatible generic parameter for trait `{}`", impl_item.descr(), trait_item.name(), - &tcx.def_path_str(tcx.parent(trait_item.def_id)) + tcx.def_path_str(tcx.parent(trait_item.def_id)) ); let make_param_message = |prefix: &str, param: &ty::GenericParamDef| match param.kind { @@ -2138,6 +2111,98 @@ fn compare_generic_param_kinds<'tcx>( Ok(()) } +/// Checks that corresponding const generic parameters in a trait item and its +/// impl have compatible types. +fn compare_const_generic_param_types<'tcx>( + tcx: TyCtxt<'tcx>, + impl_item: ty::AssocItem, + trait_item: ty::AssocItem, + impl_trait_ref: ty::TraitRef<'tcx>, + delay: bool, +) -> Result<(), ErrorGuaranteed> { + assert_eq!(impl_item.tag(), trait_item.tag()); + use GenericParamDefKind::*; + + let const_params_of = |def_id| { + tcx.generics_of(def_id).own_params.iter().filter(|param| matches!(param.kind, Const { .. })) + }; + + // Map the trait item's generic parameters into the impl item's generic context. + let trait_to_impl_args = ty::GenericArgs::identity_for_item(tcx, impl_item.def_id).rebase_onto( + tcx, + impl_item.container_id(tcx), + impl_trait_ref.args, + ); + let infcx = tcx.infer_ctxt().build(TypingMode::non_body_analysis()); + let ocx = ObligationCtxt::new_with_diagnostics(&infcx); + let param_env = tcx.param_env(impl_item.def_id); + + let param_iter = + iter::zip(const_params_of(impl_item.def_id), const_params_of(trait_item.def_id)); + + for (param_impl, param_trait) in param_iter { + let param_impl_ty_span = tcx.ty_span(param_impl.def_id.expect_local()); + + let cause = ObligationCause::new( + param_impl_ty_span, + impl_item.def_id.expect_local(), + ObligationCauseCode::CompareImplItem { + impl_item_def_id: impl_item.def_id.expect_local(), + trait_item_def_id: trait_item.def_id, + kind: impl_item.kind, + }, + ); + + let impl_ty = + ocx.normalize(&cause, param_env, tcx.type_of(param_impl.def_id).instantiate_identity()); + + let trait_ty = ocx.normalize( + &cause, + param_env, + tcx.type_of(param_trait.def_id).instantiate(tcx, trait_to_impl_args), + ); + + match ocx.eq(&cause, param_env, trait_ty, impl_ty) { + // Despite returning `Ok` all may not be well. As such, outside of this + // loop we invoke `ocx.evaluate_obligations_error_on_ambiguity()` to check + // for any other errors that may have occurred. + Ok(_) => {} + Err(terr) => { + let param_trait_ty_span = + param_trait.def_id.as_local().map(|def_id| tcx.ty_span(def_id)); + let mut diag = struct_span_code_err!( + tcx.dcx(), + param_impl_ty_span, + E0053, + "{} `{}` has an incompatible type for const generic parameter", + trait_item.descr(), + trait_item.name(), + ); + infcx.err_ctxt().note_type_err( + &mut diag, + &cause, + param_trait_ty_span.map(|span| (span, Cow::from("type in trait"), false)), + Some(param_env.and(infer::ValuePairs::Terms(ExpectedFound { + expected: trait_ty.into(), + found: impl_ty.into(), + }))), + terr, + false, + None, + ); + let reported = diag.emit_err_unless_delay(delay); + return Err(reported); + } + } + } + + if let TraitErrors::HasErrors(errors) = ocx.evaluate_obligations_error_on_ambiguity() { + Err(infcx.err_ctxt().report_fulfillment_errors(errors)) + } else { + Ok(()) + } +} + fn compare_impl_const<'tcx>( tcx: TyCtxt<'tcx>, impl_const_item: ty::AssocItem, @@ -2147,6 +2212,13 @@ fn compare_impl_const<'tcx>( compare_const_directness(tcx, impl_const_item, trait_const_item)?; compare_number_of_generics(tcx, impl_const_item, trait_const_item, false)?; compare_generic_param_kinds(tcx, impl_const_item, trait_const_item, false)?; + compare_const_generic_param_types( + tcx, + impl_const_item, + trait_const_item, + impl_trait_ref, + false, + )?; check_region_bounds_on_impl_item(tcx, impl_const_item, trait_const_item, false)?; compare_const_clause_entailment(tcx, impl_const_item, trait_const_item, impl_trait_ref) } @@ -2325,6 +2397,7 @@ fn compare_impl_ty<'tcx>( ) -> Result<(), ErrorGuaranteed> { compare_number_of_generics(tcx, impl_ty, trait_ty, false)?; compare_generic_param_kinds(tcx, impl_ty, trait_ty, false)?; + compare_const_generic_param_types(tcx, impl_ty, trait_ty, impl_trait_ref, false)?; check_region_bounds_on_impl_item(tcx, impl_ty, trait_ty, false)?; compare_type_clause_entailment(tcx, impl_ty, trait_ty, impl_trait_ref)?; check_type_bounds(tcx, trait_ty, impl_ty, impl_trait_ref) From 901cebde1c202a491690f4d6258e6873d9691488 Mon Sep 17 00:00:00 2001 From: James Barford-Evans Date: Fri, 9 Oct 2026 14:22:59 +0100 Subject: [PATCH 11/14] Update tests and create new tests for const generic parameter checking --- tests/ui/associated-types/issue-38821.stderr | 60 +++++++++---------- .../async-await/in-trait/generics-mismatch.rs | 2 +- .../ui/const-generics/auxiliary/non_local.rs | 3 + .../ban-self-when-feature-not-enabled.rs | 1 - .../ban-self-when-feature-not-enabled.stderr | 14 +---- .../mismatched_ty_const_in_trait_impl.rs | 10 ++-- .../mismatched_ty_const_in_trait_impl.stderr | 17 +++--- tests/ui/const-generics/issues/issue-86820.rs | 2 +- .../const-generics/issues/issue-86820.stderr | 17 +++--- tests/ui/const-generics/malformed-parse.rs | 15 +++++ .../ui/const-generics/malformed-parse.stderr | 24 ++++++++ .../ui/const-generics/method-instantiation.rs | 16 +++++ .../method-instatiation-from-const.rs | 18 ++++++ tests/ui/const-generics/non-local-method.rs | 15 +++++ .../ui/const-generics/non-local-method.stderr | 9 +++ .../const_params_have_right_type.rs | 2 +- .../const_params_have_right_type.stderr | 17 +++--- .../generic-const-items/compare-impl-item.rs | 2 +- .../compare-impl-item.stderr | 19 +++--- 19 files changed, 176 insertions(+), 87 deletions(-) create mode 100644 tests/ui/const-generics/auxiliary/non_local.rs create mode 100644 tests/ui/const-generics/malformed-parse.rs create mode 100644 tests/ui/const-generics/malformed-parse.stderr create mode 100644 tests/ui/const-generics/method-instantiation.rs create mode 100644 tests/ui/const-generics/method-instatiation-from-const.rs create mode 100644 tests/ui/const-generics/non-local-method.rs create mode 100644 tests/ui/const-generics/non-local-method.stderr diff --git a/tests/ui/associated-types/issue-38821.stderr b/tests/ui/associated-types/issue-38821.stderr index ad59184366ae1..19f813156da5b 100644 --- a/tests/ui/associated-types/issue-38821.stderr +++ b/tests/ui/associated-types/issue-38821.stderr @@ -81,6 +81,21 @@ LL | impl IntoNullable for T { | unsatisfied trait bound introduced here = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` +error[E0277]: the trait bound `::SqlType: NotNull` is not satisfied + --> $DIR/issue-38821.rs:23:10 + | +LL | #[derive(Debug, Copy, Clone)] + | ^^^^^ the trait `NotNull` is not implemented for `::SqlType` + | +note: required for `::SqlType` to implement `IntoNullable` + --> $DIR/issue-38821.rs:9:18 + | +LL | impl IntoNullable for T { + | ------- ^^^^^^^^^^^^ ^ + | | + | unsatisfied trait bound introduced here + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + error[E0277]: the trait bound `::SqlType: NotNull` is not satisfied --> $DIR/issue-38821.rs:35:10 | @@ -210,6 +225,21 @@ LL | impl IntoNullable for T { | unsatisfied trait bound introduced here = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` +error[E0277]: the trait bound `::SqlType: NotNull` is not satisfied + --> $DIR/issue-38821.rs:23:23 + | +LL | #[derive(Debug, Copy, Clone)] + | ^^^^^ the trait `NotNull` is not implemented for `::SqlType` + | +note: required for `::SqlType` to implement `IntoNullable` + --> $DIR/issue-38821.rs:9:18 + | +LL | impl IntoNullable for T { + | ------- ^^^^^^^^^^^^ ^ + | | + | unsatisfied trait bound introduced here + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + error[E0277]: the trait bound `::SqlType: NotNull` is not satisfied --> $DIR/issue-38821.rs:35:10 | @@ -257,36 +287,6 @@ LL | impl IntoNullable for T { | unsatisfied trait bound introduced here = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` -error[E0277]: the trait bound `::SqlType: NotNull` is not satisfied - --> $DIR/issue-38821.rs:23:10 - | -LL | #[derive(Debug, Copy, Clone)] - | ^^^^^ the trait `NotNull` is not implemented for `::SqlType` - | -note: required for `::SqlType` to implement `IntoNullable` - --> $DIR/issue-38821.rs:9:18 - | -LL | impl IntoNullable for T { - | ------- ^^^^^^^^^^^^ ^ - | | - | unsatisfied trait bound introduced here - = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` - -error[E0277]: the trait bound `::SqlType: NotNull` is not satisfied - --> $DIR/issue-38821.rs:23:23 - | -LL | #[derive(Debug, Copy, Clone)] - | ^^^^^ the trait `NotNull` is not implemented for `::SqlType` - | -note: required for `::SqlType` to implement `IntoNullable` - --> $DIR/issue-38821.rs:9:18 - | -LL | impl IntoNullable for T { - | ------- ^^^^^^^^^^^^ ^ - | | - | unsatisfied trait bound introduced here - = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` - error[E0277]: the trait bound `::SqlType: NotNull` is not satisfied --> $DIR/issue-38821.rs:23:23 | diff --git a/tests/ui/async-await/in-trait/generics-mismatch.rs b/tests/ui/async-await/in-trait/generics-mismatch.rs index a5c81a9299810..f928d975f1a30 100644 --- a/tests/ui/async-await/in-trait/generics-mismatch.rs +++ b/tests/ui/async-await/in-trait/generics-mismatch.rs @@ -6,7 +6,7 @@ trait Foo { impl Foo for () { async fn foo() {} - //~^ ERROR: associated function `foo` has an incompatible generic parameter for trait `Foo` [E0053] + //~^ ERROR: associated function `foo` has an incompatible generic parameter for trait `Foo` } fn main() {} diff --git a/tests/ui/const-generics/auxiliary/non_local.rs b/tests/ui/const-generics/auxiliary/non_local.rs new file mode 100644 index 0000000000000..38a030d610be6 --- /dev/null +++ b/tests/ui/const-generics/auxiliary/non_local.rs @@ -0,0 +1,3 @@ +pub trait NonLocal { + fn method(); +} diff --git a/tests/ui/const-generics/ban-self-when-feature-not-enabled.rs b/tests/ui/const-generics/ban-self-when-feature-not-enabled.rs index 49241033b7251..cac6009f987a8 100644 --- a/tests/ui/const-generics/ban-self-when-feature-not-enabled.rs +++ b/tests/ui/const-generics/ban-self-when-feature-not-enabled.rs @@ -7,7 +7,6 @@ trait MyTrait { impl MyTrait for i32 { fn foo() {} //~^ ERROR cannot use `Self` in const parameter type - //~| ERROR associated function `foo` has an incompatible generic parameter for trait `MyTrait` } fn main(){} diff --git a/tests/ui/const-generics/ban-self-when-feature-not-enabled.stderr b/tests/ui/const-generics/ban-self-when-feature-not-enabled.stderr index 4a74de9d5665f..ed44be85780ad 100644 --- a/tests/ui/const-generics/ban-self-when-feature-not-enabled.stderr +++ b/tests/ui/const-generics/ban-self-when-feature-not-enabled.stderr @@ -6,17 +6,5 @@ LL | fn foo() {} | = help: add `#![feature(min_adt_const_params)]` to the crate attributes to enable `Self` as a const parameter type -error[E0053]: associated function `foo` has an incompatible generic parameter for trait `MyTrait` - --> $DIR/ban-self-when-feature-not-enabled.rs:8:12 - | -LL | trait MyTrait { -LL | fn foo(); - | ------------ expected const parameter of type `i32` -... -LL | impl MyTrait for i32 { -LL | fn foo() {} - | ^^^^^^^^^^^^^ found const parameter of type `{type error}` - -error: aborting due to 2 previous errors +error: aborting due to 1 previous error -For more information about this error, try `rustc --explain E0053`. diff --git a/tests/ui/const-generics/defaults/mismatched_ty_const_in_trait_impl.rs b/tests/ui/const-generics/defaults/mismatched_ty_const_in_trait_impl.rs index 9eb33acbb24d1..1726bbf34d248 100644 --- a/tests/ui/const-generics/defaults/mismatched_ty_const_in_trait_impl.rs +++ b/tests/ui/const-generics/defaults/mismatched_ty_const_in_trait_impl.rs @@ -3,7 +3,7 @@ trait Trait { } impl Trait for () { fn foo() {} - //~^ error: associated function `foo` has an incompatible generic parameter for trait + //~^ error: associated function `foo` has an incompatible generic parameter for trait `Trait` } trait Other { @@ -11,7 +11,7 @@ trait Other { } impl Other for () { fn bar() {} - //~^ error: associated function `bar` has an incompatible generic parameter for trait + //~^ error: associated function `bar` has an incompatible generic parameter for trait `Other` } trait Uwu { @@ -19,7 +19,7 @@ trait Uwu { } impl Uwu for () { fn baz() {} - //~^ error: associated function `baz` has an incompatible generic parameter for trait + //~^ error: associated function `baz` has an incompatible type for const generic parameter } trait Aaaaaa { @@ -27,7 +27,7 @@ trait Aaaaaa { } impl Aaaaaa for () { fn bbbb() {} - //~^ error: associated function `bbbb` has an incompatible generic parameter for trait + //~^ error: associated function `bbbb` has an incompatible generic parameter for trait `Aaaaaa` } trait Names { @@ -35,7 +35,7 @@ trait Names { } impl Names for () { fn abcd() {} - //~^ error: associated function `abcd` has an incompatible generic parameter for trait + //~^ error: associated function `abcd` has an incompatible generic parameter for trait `Names` } fn main() {} diff --git a/tests/ui/const-generics/defaults/mismatched_ty_const_in_trait_impl.stderr b/tests/ui/const-generics/defaults/mismatched_ty_const_in_trait_impl.stderr index cb37701f9ad37..e279d586c9f5d 100644 --- a/tests/ui/const-generics/defaults/mismatched_ty_const_in_trait_impl.stderr +++ b/tests/ui/const-generics/defaults/mismatched_ty_const_in_trait_impl.stderr @@ -20,16 +20,17 @@ LL | impl Other for () { LL | fn bar() {} | ^ found type parameter -error[E0053]: associated function `baz` has an incompatible generic parameter for trait `Uwu` - --> $DIR/mismatched_ty_const_in_trait_impl.rs:21:12 +error[E0053]: associated function `baz` has an incompatible type for const generic parameter + --> $DIR/mismatched_ty_const_in_trait_impl.rs:21:21 | -LL | trait Uwu { -LL | fn baz() {} - | ------------ expected const parameter of type `u32` -LL | } -LL | impl Uwu for () { LL | fn baz() {} - | ^^^^^^^^^^^^ found const parameter of type `i32` + | ^^^ expected `u32`, found `i32` + | +note: type in trait + --> $DIR/mismatched_ty_const_in_trait_impl.rs:18:21 + | +LL | fn baz() {} + | ^^^ error[E0053]: associated function `bbbb` has an incompatible generic parameter for trait `Aaaaaa` --> $DIR/mismatched_ty_const_in_trait_impl.rs:29:13 diff --git a/tests/ui/const-generics/issues/issue-86820.rs b/tests/ui/const-generics/issues/issue-86820.rs index ae4bd943fd415..c2c0c1a8d0390 100644 --- a/tests/ui/const-generics/issues/issue-86820.rs +++ b/tests/ui/const-generics/issues/issue-86820.rs @@ -14,7 +14,7 @@ trait Bits { impl Bits for u8 { fn bit(self) -> bool { - //~^ ERROR: method `bit` has an incompatible generic parameter for trait `Bits` [E0053] + //~^ ERROR: method `bit` has an incompatible type for const generic parameter [E0053] let i = 1 << I; let mask = u8::from(i); mask & self == mask diff --git a/tests/ui/const-generics/issues/issue-86820.stderr b/tests/ui/const-generics/issues/issue-86820.stderr index fda12fbda3af4..9f0c896771926 100644 --- a/tests/ui/const-generics/issues/issue-86820.stderr +++ b/tests/ui/const-generics/issues/issue-86820.stderr @@ -1,13 +1,14 @@ -error[E0053]: method `bit` has an incompatible generic parameter for trait `Bits` - --> $DIR/issue-86820.rs:16:12 +error[E0053]: method `bit` has an incompatible type for const generic parameter + --> $DIR/issue-86820.rs:16:21 | -LL | trait Bits { -LL | fn bit(self) -> bool; - | ----------- expected const parameter of type `u8` -... -LL | impl Bits for u8 { LL | fn bit(self) -> bool { - | ^^^^^^^^^^^^^^ found const parameter of type `usize` + | ^^^^^ expected `u8`, found `usize` + | +note: type in trait + --> $DIR/issue-86820.rs:12:21 + | +LL | fn bit(self) -> bool; + | ^^ error: aborting due to 1 previous error diff --git a/tests/ui/const-generics/malformed-parse.rs b/tests/ui/const-generics/malformed-parse.rs new file mode 100644 index 0000000000000..9f5adff63a31f --- /dev/null +++ b/tests/ui/const-generics/malformed-parse.rs @@ -0,0 +1,15 @@ +//@ compile-flags: --crate-type lib + +trait Foo { + fn hello(); +} + +struct Bar; + +impl Foo for Bar { + //~^ ERROR: not all trait items implemented, missing: `hello` + fn hello() { + //~^ ERROR: expected type, found `>` + println!("woof woof") + } +} diff --git a/tests/ui/const-generics/malformed-parse.stderr b/tests/ui/const-generics/malformed-parse.stderr new file mode 100644 index 0000000000000..f30ee07d0c594 --- /dev/null +++ b/tests/ui/const-generics/malformed-parse.stderr @@ -0,0 +1,24 @@ +error: expected type, found `>` + --> $DIR/malformed-parse.rs:11:23 + | +LL | impl Foo for Bar { + | - while parsing this item list starting here +LL | +LL | fn hello() { + | ^ expected type +... +LL | } + | - the item list ends here + +error[E0046]: not all trait items implemented, missing: `hello` + --> $DIR/malformed-parse.rs:9:1 + | +LL | fn hello(); + | ------------------------- `hello` from trait +... +LL | impl Foo for Bar { + | ^^^^^^^^^^^^^^^^ missing `hello` in implementation + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0046`. diff --git a/tests/ui/const-generics/method-instantiation.rs b/tests/ui/const-generics/method-instantiation.rs new file mode 100644 index 0000000000000..c04786c7e75d4 --- /dev/null +++ b/tests/ui/const-generics/method-instantiation.rs @@ -0,0 +1,16 @@ +//@ compile-flags: --crate-type lib +//@ check-pass + +#![feature(min_adt_const_params)] +#![feature(generic_const_parameter_types)] +#![allow(incomplete_features)] + +pub trait Trait { + fn method(); +} + +pub struct Foo; + +impl Trait for Foo { + fn method() {} +} diff --git a/tests/ui/const-generics/method-instatiation-from-const.rs b/tests/ui/const-generics/method-instatiation-from-const.rs new file mode 100644 index 0000000000000..b78523d4e6904 --- /dev/null +++ b/tests/ui/const-generics/method-instatiation-from-const.rs @@ -0,0 +1,18 @@ +//@ compile-flags: --crate-type lib +//@ check-pass + +#![feature(generic_const_parameter_types, min_adt_const_params)] +#![allow(incomplete_features)] + +const FOO: usize = 5; +const BAR: usize = 5; + +pub trait Trait { + fn method(); +} + +pub struct Foo; + +impl Trait for Foo { + fn method() {} +} diff --git a/tests/ui/const-generics/non-local-method.rs b/tests/ui/const-generics/non-local-method.rs new file mode 100644 index 0000000000000..290d7fc903eff --- /dev/null +++ b/tests/ui/const-generics/non-local-method.rs @@ -0,0 +1,15 @@ +//@ compile-flags: --crate-type lib +//@ aux-build:non_local.rs + +extern crate non_local; + +use non_local::NonLocal; + +struct Local; + +impl NonLocal for Local { + fn method() { + //~^ ERROR: associated function `method` has an incompatible type for const generic parameter [E0053] + print!("hello\n"); + } +} diff --git a/tests/ui/const-generics/non-local-method.stderr b/tests/ui/const-generics/non-local-method.stderr new file mode 100644 index 0000000000000..0619570689da3 --- /dev/null +++ b/tests/ui/const-generics/non-local-method.stderr @@ -0,0 +1,9 @@ +error[E0053]: associated function `method` has an incompatible type for const generic parameter + --> $DIR/non-local-method.rs:11:24 + | +LL | fn method() { + | ^^^ expected `i32`, found `u16` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0053`. diff --git a/tests/ui/generic-associated-types/const_params_have_right_type.rs b/tests/ui/generic-associated-types/const_params_have_right_type.rs index d2cb12697e423..d3552c3773b1f 100644 --- a/tests/ui/generic-associated-types/const_params_have_right_type.rs +++ b/tests/ui/generic-associated-types/const_params_have_right_type.rs @@ -4,7 +4,7 @@ trait Trait { impl Trait for () { type Foo = u32; - //~^ error: type `Foo` has an incompatible generic parameter for trait + //~^ error: associated type `Foo` has an incompatible type for const generic parameter [E0053] } fn main() {} diff --git a/tests/ui/generic-associated-types/const_params_have_right_type.stderr b/tests/ui/generic-associated-types/const_params_have_right_type.stderr index f477ac3027726..ef81514d8ce90 100644 --- a/tests/ui/generic-associated-types/const_params_have_right_type.stderr +++ b/tests/ui/generic-associated-types/const_params_have_right_type.stderr @@ -1,13 +1,14 @@ -error[E0053]: associated type `Foo` has an incompatible generic parameter for trait `Trait` - --> $DIR/const_params_have_right_type.rs:6:14 +error[E0053]: associated type `Foo` has an incompatible type for const generic parameter + --> $DIR/const_params_have_right_type.rs:6:23 | -LL | trait Trait { -LL | type Foo; - | ----------- expected const parameter of type `u8` -... -LL | impl Trait for () { LL | type Foo = u32; - | ^^^^^^^^^^^^ found const parameter of type `u64` + | ^^^ expected `u8`, found `u64` + | +note: type in trait + --> $DIR/const_params_have_right_type.rs:2:23 + | +LL | type Foo; + | ^^ error: aborting due to 1 previous error diff --git a/tests/ui/generic-const-items/compare-impl-item.rs b/tests/ui/generic-const-items/compare-impl-item.rs index e8a23b5c44bb2..8bf89eb7a0aa4 100644 --- a/tests/ui/generic-const-items/compare-impl-item.rs +++ b/tests/ui/generic-const-items/compare-impl-item.rs @@ -20,7 +20,7 @@ impl

Trait

for () { const C<'a>: &'a str = ""; //~^ ERROR constant `C` has 0 type parameters but its trait declaration has 1 type parameter const D: u16 = N; - //~^ ERROR constant `D` has an incompatible generic parameter for trait `Trait` + //~^ ERROR associated constant `D` has an incompatible type for const generic parameter const E: &'static () = &(); //~^ ERROR lifetime parameters or bounds on associated constant `E` do not match the trait declaration diff --git a/tests/ui/generic-const-items/compare-impl-item.stderr b/tests/ui/generic-const-items/compare-impl-item.stderr index e61a1fdc0cf5a..6577fa8a375fc 100644 --- a/tests/ui/generic-const-items/compare-impl-item.stderr +++ b/tests/ui/generic-const-items/compare-impl-item.stderr @@ -27,18 +27,17 @@ LL | const C: T; LL | const C<'a>: &'a str = ""; | ^^ found 0 type parameters -error[E0053]: associated constant `D` has an incompatible generic parameter for trait `Trait` - --> $DIR/compare-impl-item.rs:22:13 +error[E0053]: associated constant `D` has an incompatible type for const generic parameter + --> $DIR/compare-impl-item.rs:22:22 | -LL | trait Trait

{ -... -LL | const D: usize; - | -------------- expected const parameter of type `usize` -... -LL | impl

Trait

for () { -... LL | const D: u16 = N; - | ^^^^^^^^^^^^ found const parameter of type `u16` + | ^^^ expected `usize`, found `u16` + | +note: type in trait + --> $DIR/compare-impl-item.rs:8:22 + | +LL | const D: usize; + | ^^^^^ error[E0195]: lifetime parameters or bounds on associated constant `E` do not match the trait declaration --> $DIR/compare-impl-item.rs:24:12 From e3941d67fb71ffc2ed82abb48be4627b4045b722 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Thu, 8 Oct 2026 09:35:31 +0200 Subject: [PATCH 12/14] const-eval: ICE when we hit a non-const fn --- .../src/const_eval/machine.rs | 13 +++- .../consts/const-eval/do_not_const_check.rs | 12 +++ .../const-eval/do_not_const_check.stderr | 41 +++++++--- .../consts/qualif-indirect-mutation-fail.rs | 42 +++++----- .../qualif-indirect-mutation-fail.stderr | 77 ++++++------------- 5 files changed, 102 insertions(+), 83 deletions(-) diff --git a/compiler/rustc_const_eval/src/const_eval/machine.rs b/compiler/rustc_const_eval/src/const_eval/machine.rs index 1b99f54719fb4..2a03a840eb1aa 100644 --- a/compiler/rustc_const_eval/src/const_eval/machine.rs +++ b/compiler/rustc_const_eval/src/const_eval/machine.rs @@ -449,9 +449,16 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> { // all. That said, we have to allow calling functions inside a `const trait`. These // *are* const-checked! if !ecx.tcx.is_const_fn(def) || find_attr!(ecx.tcx, def, RustcDoNotConstCheck) { - // We certainly do *not* want to actually call the fn - // though, so be sure we return here. - throw_unsup_format!("calling non-const function `{}`", instance) + // We certainly do *not* want to actually call this fn. const-checking should have + // prevented this, but this can be reached with unleashed-miri, so make testing + // simpler by avoiding ICEs there. (Technically this can still ICE if miri-unleash + // was set in a dependency rather than the current crate. That's okay, it's an + // internal unstable feature.) + if ecx.tcx.sess.opts.unstable_opts.unleash_the_miri_inside_of_you { + throw_unsup_format!("calling non-const function `{}`", instance) + } else { + span_bug!(ecx.cur_span(), "calling non-const function `{}`", instance) + } } } diff --git a/tests/ui/consts/const-eval/do_not_const_check.rs b/tests/ui/consts/const-eval/do_not_const_check.rs index ced2557bffd19..7ed6ca1491f26 100644 --- a/tests/ui/consts/const-eval/do_not_const_check.rs +++ b/tests/ui/consts/const-eval/do_not_const_check.rs @@ -1,5 +1,17 @@ //! Ensure that we refuse to run a do_not_const_check function, even if the body *would* const-check //! at the moment. + +//@ rustc-env:RUSTC_ICE=0 +//@ failure-status: 101 +//@ normalize-stderr: "note: compiler flags.*\n\n" -> "" +//@ normalize-stderr: "note: rustc.*running on.*" -> "note: rustc {version} running on {platform}" +//@ normalize-stderr: "thread 'rustc'.*panicked.*:\n.*\n" -> "" +//@ normalize-stderr: " +\d{1,}: .*\n" -> "" +//@ normalize-stderr: " + at .*\n" -> "" +//@ normalize-stderr: " +.*omitted.*frames?.*\n" -> "" +//@ normalize-stderr: ".*note: Some details are omitted.*\n" -> "" +//@ normalize-stderr: "(internal compiler error: ).*(compiler/[^:]+):\d+:\d+: " -> "$1$2:LL:CC: " + #![feature(rustc_attrs, intrinsics)] #[rustc_do_not_const_check] diff --git a/tests/ui/consts/const-eval/do_not_const_check.stderr b/tests/ui/consts/const-eval/do_not_const_check.stderr index 7b91d75298e06..7db40e7ab0114 100644 --- a/tests/ui/consts/const-eval/do_not_const_check.stderr +++ b/tests/ui/consts/const-eval/do_not_const_check.stderr @@ -1,17 +1,40 @@ -error[E0080]: calling non-const function `mostly_harmless` - --> $DIR/do_not_const_check.rs:9:5 +error: internal compiler error: compiler/rustc_const_eval/src/const_eval/machine.rs:LL:CC: calling non-const function `mostly_harmless` + --> $DIR/do_not_const_check.rs:21:5 | -LL | const _: () = { LL | mostly_harmless(); - | ^^^^^^^^^^^^^^^^^ evaluation of `_` failed here + | ^^^^^^^^^^^^^^^^^ -error[E0080]: calling non-const function `integer_min::` - --> $DIR/do_not_const_check.rs:22:5 + +stack backtrace: + +note: using internal features is not supported and expected to cause internal compiler errors when used incorrectly + +note: rustc {version} running on {platform} + +query stack during panic: +#0 [eval_to_allocation_raw] const-evaluating + checking `_` +#1 [eval_to_const_value_raw] simplifying constant for the type system `_` +#2 [eval_to_const_value_raw] simplifying constant for the type system `_` +#3 [analysis] running analysis passes on crate `do_not_const_check` +end of query stack +error: internal compiler error: compiler/rustc_const_eval/src/const_eval/machine.rs:LL:CC: calling non-const function `integer_min::` + --> $DIR/do_not_const_check.rs:34:5 | -LL | const _: () = { LL | integer_min(0, 1); - | ^^^^^^^^^^^^^^^^^ evaluation of `_` failed here + | ^^^^^^^^^^^^^^^^^ + + +stack backtrace: + +note: using internal features is not supported and expected to cause internal compiler errors when used incorrectly + +note: rustc {version} running on {platform} +query stack during panic: +#0 [eval_to_allocation_raw] const-evaluating + checking `_` +#1 [eval_to_const_value_raw] simplifying constant for the type system `_` +#2 [eval_to_const_value_raw] simplifying constant for the type system `_` +#3 [analysis] running analysis passes on crate `do_not_const_check` +end of query stack error: aborting due to 2 previous errors -For more information about this error, try `rustc --explain E0080`. diff --git a/tests/ui/consts/qualif-indirect-mutation-fail.rs b/tests/ui/consts/qualif-indirect-mutation-fail.rs index 00c9d4900f5f6..26d251788c3ab 100644 --- a/tests/ui/consts/qualif-indirect-mutation-fail.rs +++ b/tests/ui/consts/qualif-indirect-mutation-fail.rs @@ -13,26 +13,30 @@ pub const fn f() { let _ = &mut a.1; } -// Mutable borrow of a type with drop impl. -pub const A1: () = { - let mut x = None; //~ ERROR destructor of - let mut y = Some(NotConstDestruct); - let a = &mut x; - let b = &mut y; - std::mem::swap(a, b); - std::mem::forget(y); -}; //~ ERROR calling non-const function `::drop` +// FIXME: we make these associated consts to work around +// . +impl NotConstDestruct { + // Mutable borrow of a type with drop impl. + pub const A1: () = { + let mut x = None; //~ ERROR destructor of + let mut y = Some(NotConstDestruct); + let a = &mut x; + let b = &mut y; + std::mem::swap(a, b); + std::mem::forget(y); + }; -// Mutable borrow of a type with drop impl. -pub const A2: () = { - let mut x = None; - let mut y = Some(NotConstDestruct); - let a = &mut x; - let b = &mut y; - std::mem::swap(a, b); - std::mem::forget(y); - let _z = x; //~ ERROR destructor of -}; //~ ERROR calling non-const function `::drop` + // Mutable borrow of a type with drop impl. + pub const A2: () = { + let mut x = None; + let mut y = Some(NotConstDestruct); + let a = &mut x; + let b = &mut y; + std::mem::swap(a, b); + std::mem::forget(y); + let _z = x; //~ ERROR destructor of + }; +} // Shared borrow of a type that might be !Freeze and Drop. pub const fn g1() { diff --git a/tests/ui/consts/qualif-indirect-mutation-fail.stderr b/tests/ui/consts/qualif-indirect-mutation-fail.stderr index aef0bed72d892..2301490065d45 100644 --- a/tests/ui/consts/qualif-indirect-mutation-fail.stderr +++ b/tests/ui/consts/qualif-indirect-mutation-fail.stderr @@ -1,46 +1,3 @@ -error[E0493]: destructor of `Option` cannot be evaluated at compile-time - --> $DIR/qualif-indirect-mutation-fail.rs:18:9 - | -LL | let mut x = None; - | ^^^^^ the destructor for this type cannot be evaluated in constants -... -LL | }; - | - value is dropped here - -error[E0080]: calling non-const function `::drop` - --> $DIR/qualif-indirect-mutation-fail.rs:24:1 - | -LL | pub const A1: () = { -... -LL | }; - | ^ evaluation of `A1` failed inside this call - | -note: inside `std::ptr::drop_glue::> - shim(Some(Option))` - --> $SRC_DIR/core/src/ptr/mod.rs:LL:COL -note: inside `std::ptr::drop_glue:: - shim(Some(NotConstDestruct))` - --> $SRC_DIR/core/src/ptr/mod.rs:LL:COL - -error[E0493]: destructor of `Option` cannot be evaluated at compile-time - --> $DIR/qualif-indirect-mutation-fail.rs:34:9 - | -LL | let _z = x; - | ^^ the destructor for this type cannot be evaluated in constants -LL | }; - | - value is dropped here - -error[E0080]: calling non-const function `::drop` - --> $DIR/qualif-indirect-mutation-fail.rs:35:1 - | -LL | pub const A2: () = { -... -LL | }; - | ^ evaluation of `A2` failed inside this call - | -note: inside `std::ptr::drop_glue::> - shim(Some(Option))` - --> $SRC_DIR/core/src/ptr/mod.rs:LL:COL -note: inside `std::ptr::drop_glue:: - shim(Some(NotConstDestruct))` - --> $SRC_DIR/core/src/ptr/mod.rs:LL:COL - error[E0493]: destructor of `(u32, Option)` cannot be evaluated at compile-time --> $DIR/qualif-indirect-mutation-fail.rs:12:9 | @@ -50,8 +7,25 @@ LL | let _ = &mut a.1; LL | } | - value is dropped here +error[E0493]: destructor of `Option` cannot be evaluated at compile-time + --> $DIR/qualif-indirect-mutation-fail.rs:21:13 + | +LL | let mut x = None; + | ^^^^^ the destructor for this type cannot be evaluated in constants +... +LL | }; + | - value is dropped here + +error[E0493]: destructor of `Option` cannot be evaluated at compile-time + --> $DIR/qualif-indirect-mutation-fail.rs:37:13 + | +LL | let _z = x; + | ^^ the destructor for this type cannot be evaluated in constants +LL | }; + | - value is dropped here + error[E0493]: destructor of `Option` cannot be evaluated at compile-time - --> $DIR/qualif-indirect-mutation-fail.rs:39:9 + --> $DIR/qualif-indirect-mutation-fail.rs:43:9 | LL | let x: Option = None; | ^ the destructor for this type cannot be evaluated in constant functions @@ -60,7 +34,7 @@ LL | } | - value is dropped here error[E0493]: destructor of `Option` cannot be evaluated at compile-time - --> $DIR/qualif-indirect-mutation-fail.rs:47:9 + --> $DIR/qualif-indirect-mutation-fail.rs:51:9 | LL | let _y = x; | ^^ the destructor for this type cannot be evaluated in constant functions @@ -68,7 +42,7 @@ LL | } | - value is dropped here error[E0493]: destructor of `Option` cannot be evaluated at compile-time - --> $DIR/qualif-indirect-mutation-fail.rs:55:9 + --> $DIR/qualif-indirect-mutation-fail.rs:59:9 | LL | let mut y: Option = None; | ^^^^^ the destructor for this type cannot be evaluated in constant functions @@ -77,7 +51,7 @@ LL | } | - value is dropped here error[E0493]: destructor of `Option` cannot be evaluated at compile-time - --> $DIR/qualif-indirect-mutation-fail.rs:52:9 + --> $DIR/qualif-indirect-mutation-fail.rs:56:9 | LL | let mut x: Option = None; | ^^^^^ the destructor for this type cannot be evaluated in constant functions @@ -86,7 +60,7 @@ LL | } | - value is dropped here error[E0493]: destructor of `Option` cannot be evaluated at compile-time - --> $DIR/qualif-indirect-mutation-fail.rs:65:9 + --> $DIR/qualif-indirect-mutation-fail.rs:69:9 | LL | let y: Option = None; | ^ the destructor for this type cannot be evaluated in constant functions @@ -95,7 +69,7 @@ LL | } | - value is dropped here error[E0493]: destructor of `Option` cannot be evaluated at compile-time - --> $DIR/qualif-indirect-mutation-fail.rs:62:9 + --> $DIR/qualif-indirect-mutation-fail.rs:66:9 | LL | let x: Option = None; | ^ the destructor for this type cannot be evaluated in constant functions @@ -103,7 +77,6 @@ LL | let x: Option = None; LL | } | - value is dropped here -error: aborting due to 11 previous errors +error: aborting due to 9 previous errors -Some errors have detailed explanations: E0080, E0493. -For more information about an error, try `rustc --explain E0080`. +For more information about this error, try `rustc --explain E0493`. From 02dbf08320a53cd5d65a3d3c596d0666911b4e62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Fri, 9 Oct 2026 17:03:02 +0200 Subject: [PATCH 13/14] Allow testing cg-gcc on any target --- src/bootstrap/src/core/build_steps/test.rs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/test.rs b/src/bootstrap/src/core/build_steps/test.rs index ceaa28fc4bdb8..c532b670dbef1 100644 --- a/src/bootstrap/src/core/build_steps/test.rs +++ b/src/bootstrap/src/core/build_steps/test.rs @@ -4587,14 +4587,6 @@ impl CommandLineStep for CodegenGCC { return; } - let triple = run.target.triple; - let target_supported = - if triple.contains("linux") { triple.contains("x86_64") } else { false }; - if !target_supported { - builder.info("target not supported by rustc_codegen_gcc. skipping"); - return; - } - if builder.remote_tested(run.target) { builder.info("remote testing is not supported by rustc_codegen_gcc. skipping"); return; @@ -4976,7 +4968,7 @@ impl CommandLineStep for StdSemverCheck { res.stdout() ); if builder.fail_fast { - eprintln!("{error}",); + eprintln!("{error}"); helpers::exit_process(1); } else { builder.config.exec_ctx().add_to_delay_failure(error); From 85b2304ba2d1ef3f30e1239b9080b684c16daeb3 Mon Sep 17 00:00:00 2001 From: Urgau Date: Wed, 7 Oct 2026 19:11:22 +0200 Subject: [PATCH 14/14] Pass `--remap-path-{prefix,scope}` to the `rustc` doctest invocation --- src/librustdoc/config.rs | 6 +++ src/librustdoc/doctest.rs | 24 +++++++----- src/librustdoc/doctest/runner.rs | 3 +- ...path-prefix-doctest.with-diag-scope.stdout | 8 ++-- ...-path-prefix-doctest.with-doc-scope.stdout | 8 ++-- .../remap-path-prefix-file.all_scope.stdout | 6 +++ ...ap-path-prefix-file.debuginfo_scope.stdout | 6 +++ ...-path-prefix-file.diagnostics_scope.stdout | 6 +++ ...ath-prefix-file.documentation_scope.stdout | 6 +++ .../remap-path-prefix-file.macro_scope.stdout | 6 +++ .../remap-path-prefix-file.no_scopes.stdout | 6 +++ ...remap-path-prefix-file.object_scope.stdout | 6 +++ tests/rustdoc-ui/remap-path-prefix-file.rs | 37 +++++++++++++++++++ 13 files changed, 110 insertions(+), 18 deletions(-) create mode 100644 tests/rustdoc-ui/remap-path-prefix-file.all_scope.stdout create mode 100644 tests/rustdoc-ui/remap-path-prefix-file.debuginfo_scope.stdout create mode 100644 tests/rustdoc-ui/remap-path-prefix-file.diagnostics_scope.stdout create mode 100644 tests/rustdoc-ui/remap-path-prefix-file.documentation_scope.stdout create mode 100644 tests/rustdoc-ui/remap-path-prefix-file.macro_scope.stdout create mode 100644 tests/rustdoc-ui/remap-path-prefix-file.no_scopes.stdout create mode 100644 tests/rustdoc-ui/remap-path-prefix-file.object_scope.stdout create mode 100644 tests/rustdoc-ui/remap-path-prefix-file.rs diff --git a/src/librustdoc/config.rs b/src/librustdoc/config.rs index 170352dabf656..9134b63dc5032 100644 --- a/src/librustdoc/config.rs +++ b/src/librustdoc/config.rs @@ -129,8 +129,10 @@ pub(crate) struct Options { pub(crate) no_run: bool, /// What sources are being mapped. pub(crate) remap_path_prefix: Vec<(PathBuf, PathBuf)>, + pub(crate) remap_path_prefix_strs: Vec, /// Which scope(s) to use with `--remap-path-prefix` pub(crate) remap_path_scope: RemapPathScopeComponents, + pub(crate) remap_path_scope_str: Option, /// The path to a rustc-like binary to build tests with. If not set, we /// default to loading from `$sysroot/bin/rustc`. @@ -859,6 +861,8 @@ impl Options { let unstable_opts_strs = matches.opt_strs("Z"); let lib_strs = matches.opt_strs("L"); let extern_strs = matches.opt_strs("extern"); + let remap_path_prefix_strs = matches.opt_strs("remap-path-prefix"); + let remap_path_scope_str = matches.opt_str("remap-path-scope"); let test_runtool = matches.opt_str("test-runtool"); let test_runtool_args = matches.opt_strs("test-runtool-arg"); let document_private = matches.opt_present("document-private-items"); @@ -934,7 +938,9 @@ impl Options { no_run, test_builder_wrappers, remap_path_prefix, + remap_path_prefix_strs, remap_path_scope, + remap_path_scope_str, no_capture, crate_name, output_format, diff --git a/src/librustdoc/doctest.rs b/src/librustdoc/doctest.rs index c8d1cfd599909..35b2af5370fff 100644 --- a/src/librustdoc/doctest.rs +++ b/src/librustdoc/doctest.rs @@ -103,6 +103,13 @@ pub(crate) fn generate_args_file(file_path: &Path, options: &RustdocOptions) -> content.push(format!("--check-cfg={check_cfg}")); } + for prefix in &options.remap_path_prefix_strs { + content.push(format!("--remap-path-prefix={prefix}")); + } + if let Some(scope) = &options.remap_path_scope_str { + content.push(format!("--remap-path-scope={scope}")); + } + for lib_str in &options.lib_strs { content.push(format!("-L{lib_str}")); } @@ -986,7 +993,7 @@ impl ScrapedDocTest { } let name = format!( "{} - {item_path}(line {line})", - filename.display(RemapPathScopeComponents::DOCUMENTATION) + filename.display(RemapPathScopeComponents::DIAGNOSTICS) ); Self { filename, line, langstr, text, name, span, code_mappings, global_crate_attrs } @@ -999,13 +1006,12 @@ impl ScrapedDocTest { self.langstr.no_run || opts.no_run } - fn path(&self) -> PathBuf { - match &self.filename { - FileName::Real(name) => { - name.path(RemapPathScopeComponents::DOCUMENTATION).to_path_buf() - } - _ => PathBuf::from(r"doctest.rs"), - } + fn path(&self, scope: RemapPathScopeComponents) -> String { + self.filename.display(scope).to_string() + } + + fn local_path(&self) -> PathBuf { + self.filename.clone().into_local_path().unwrap_or_else(|| PathBuf::from(r"doctest.rs")) } } @@ -1138,7 +1144,7 @@ fn generate_test_desc_and_fn( ) -> test::TestDescAndFn { let target_str = rustdoc_options.target.to_string(); let rustdoc_test_options = - IndividualTestOptions::new(&rustdoc_options, &test.test_id, scraped_test.path()); + IndividualTestOptions::new(&rustdoc_options, &test.test_id, scraped_test.local_path()); debug!("creating test {}: {}", scraped_test.name, scraped_test.text); test::TestDescAndFn { diff --git a/src/librustdoc/doctest/runner.rs b/src/librustdoc/doctest/runner.rs index 43ed48aad95d4..3e4b4209f4145 100644 --- a/src/librustdoc/doctest/runner.rs +++ b/src/librustdoc/doctest/runner.rs @@ -2,6 +2,7 @@ use std::fmt::Write; use std::time::Duration; use rustc_data_structures::fx::FxIndexSet; +use rustc_span::RemapPathScopeComponents; use rustc_span::edition::Edition; use crate::doctest::{ @@ -264,7 +265,7 @@ test::StaticTestFn( )); }}", test_name = scraped_test.name, - file = scraped_test.path(), + file = scraped_test.path(RemapPathScopeComponents::MACRO), line = scraped_test.line, no_run = scraped_test.langstr.no_run, should_panic = !scraped_test.langstr.no_run && scraped_test.langstr.should_panic, diff --git a/tests/rustdoc-ui/remap-path-prefix-doctest.with-diag-scope.stdout b/tests/rustdoc-ui/remap-path-prefix-doctest.with-diag-scope.stdout index 22f4fe70c6175..248f7734652fa 100644 --- a/tests/rustdoc-ui/remap-path-prefix-doctest.with-diag-scope.stdout +++ b/tests/rustdoc-ui/remap-path-prefix-doctest.with-diag-scope.stdout @@ -1,12 +1,12 @@ running 1 test -test $DIR/remap-path-prefix-doctest.rs - SomeStruct (line 19) ... FAILED +test remapped_path/remap-path-prefix-doctest.rs - SomeStruct (line 19) ... FAILED failures: ----- $DIR/remap-path-prefix-doctest.rs - SomeStruct (line 19) stdout ---- +---- remapped_path/remap-path-prefix-doctest.rs - SomeStruct (line 19) stdout ---- error: this file contains an unclosed delimiter - --> $DIR/remap-path-prefix-doctest.rs:20:12 + --> remapped_path/remap-path-prefix-doctest.rs:20:12 | LL | fn invalid( | -^ @@ -18,7 +18,7 @@ error: aborting due to 1 previous error Couldn't compile the test. failures: - $DIR/remap-path-prefix-doctest.rs - SomeStruct (line 19) + remapped_path/remap-path-prefix-doctest.rs - SomeStruct (line 19) test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME diff --git a/tests/rustdoc-ui/remap-path-prefix-doctest.with-doc-scope.stdout b/tests/rustdoc-ui/remap-path-prefix-doctest.with-doc-scope.stdout index 248f7734652fa..22f4fe70c6175 100644 --- a/tests/rustdoc-ui/remap-path-prefix-doctest.with-doc-scope.stdout +++ b/tests/rustdoc-ui/remap-path-prefix-doctest.with-doc-scope.stdout @@ -1,12 +1,12 @@ running 1 test -test remapped_path/remap-path-prefix-doctest.rs - SomeStruct (line 19) ... FAILED +test $DIR/remap-path-prefix-doctest.rs - SomeStruct (line 19) ... FAILED failures: ----- remapped_path/remap-path-prefix-doctest.rs - SomeStruct (line 19) stdout ---- +---- $DIR/remap-path-prefix-doctest.rs - SomeStruct (line 19) stdout ---- error: this file contains an unclosed delimiter - --> remapped_path/remap-path-prefix-doctest.rs:20:12 + --> $DIR/remap-path-prefix-doctest.rs:20:12 | LL | fn invalid( | -^ @@ -18,7 +18,7 @@ error: aborting due to 1 previous error Couldn't compile the test. failures: - remapped_path/remap-path-prefix-doctest.rs - SomeStruct (line 19) + $DIR/remap-path-prefix-doctest.rs - SomeStruct (line 19) test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME diff --git a/tests/rustdoc-ui/remap-path-prefix-file.all_scope.stdout b/tests/rustdoc-ui/remap-path-prefix-file.all_scope.stdout new file mode 100644 index 0000000000000..910e5a362cabb --- /dev/null +++ b/tests/rustdoc-ui/remap-path-prefix-file.all_scope.stdout @@ -0,0 +1,6 @@ + +running 1 test +test /REMAPPED/remap-path-prefix-file.rs - f (line 24) ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME + diff --git a/tests/rustdoc-ui/remap-path-prefix-file.debuginfo_scope.stdout b/tests/rustdoc-ui/remap-path-prefix-file.debuginfo_scope.stdout new file mode 100644 index 0000000000000..72c61da715f74 --- /dev/null +++ b/tests/rustdoc-ui/remap-path-prefix-file.debuginfo_scope.stdout @@ -0,0 +1,6 @@ + +running 1 test +test $DIR/remap-path-prefix-file.rs - f (line 24) ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME + diff --git a/tests/rustdoc-ui/remap-path-prefix-file.diagnostics_scope.stdout b/tests/rustdoc-ui/remap-path-prefix-file.diagnostics_scope.stdout new file mode 100644 index 0000000000000..910e5a362cabb --- /dev/null +++ b/tests/rustdoc-ui/remap-path-prefix-file.diagnostics_scope.stdout @@ -0,0 +1,6 @@ + +running 1 test +test /REMAPPED/remap-path-prefix-file.rs - f (line 24) ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME + diff --git a/tests/rustdoc-ui/remap-path-prefix-file.documentation_scope.stdout b/tests/rustdoc-ui/remap-path-prefix-file.documentation_scope.stdout new file mode 100644 index 0000000000000..72c61da715f74 --- /dev/null +++ b/tests/rustdoc-ui/remap-path-prefix-file.documentation_scope.stdout @@ -0,0 +1,6 @@ + +running 1 test +test $DIR/remap-path-prefix-file.rs - f (line 24) ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME + diff --git a/tests/rustdoc-ui/remap-path-prefix-file.macro_scope.stdout b/tests/rustdoc-ui/remap-path-prefix-file.macro_scope.stdout new file mode 100644 index 0000000000000..72c61da715f74 --- /dev/null +++ b/tests/rustdoc-ui/remap-path-prefix-file.macro_scope.stdout @@ -0,0 +1,6 @@ + +running 1 test +test $DIR/remap-path-prefix-file.rs - f (line 24) ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME + diff --git a/tests/rustdoc-ui/remap-path-prefix-file.no_scopes.stdout b/tests/rustdoc-ui/remap-path-prefix-file.no_scopes.stdout new file mode 100644 index 0000000000000..910e5a362cabb --- /dev/null +++ b/tests/rustdoc-ui/remap-path-prefix-file.no_scopes.stdout @@ -0,0 +1,6 @@ + +running 1 test +test /REMAPPED/remap-path-prefix-file.rs - f (line 24) ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME + diff --git a/tests/rustdoc-ui/remap-path-prefix-file.object_scope.stdout b/tests/rustdoc-ui/remap-path-prefix-file.object_scope.stdout new file mode 100644 index 0000000000000..72c61da715f74 --- /dev/null +++ b/tests/rustdoc-ui/remap-path-prefix-file.object_scope.stdout @@ -0,0 +1,6 @@ + +running 1 test +test $DIR/remap-path-prefix-file.rs - f (line 24) ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME + diff --git a/tests/rustdoc-ui/remap-path-prefix-file.rs b/tests/rustdoc-ui/remap-path-prefix-file.rs new file mode 100644 index 0000000000000..8ec5c88b0b7d6 --- /dev/null +++ b/tests/rustdoc-ui/remap-path-prefix-file.rs @@ -0,0 +1,37 @@ +// Checks that macros uses inside doctests are correctly gated under the +// `macro` and al --remap-path-scope scopes. + +//@ only-linux +//@ compile-flags: --test --test-args=--test-threads=1 +//@ compile-flags: --remap-path-prefix={{src-base}}=/REMAPPED +//@ normalize-stdout: "finished in \d+\.\d+s" -> "finished in $$TIME" +//@ rustc-env: RUST_BACKTRACE=0 +//@ check-pass + +//@ revisions: macro_scope diagnostics_scope documentation_scope debuginfo_scope +//@ revisions: object_scope all_scope no_scopes + +//@[macro_scope] compile-flags: --remap-path-scope=macro -Z unstable-options +//@[diagnostics_scope] compile-flags: --remap-path-scope=diagnostics -Z unstable-options +//@[documentation_scope] compile-flags: --remap-path-scope=documentation -Z unstable-options +//@[debuginfo_scope] compile-flags: --remap-path-scope=debuginfo -Z unstable-options +//@[object_scope] compile-flags: --remap-path-scope=object -Z unstable-options +//@[all_scope] compile-flags: --remap-path-scope=all -Z unstable-options +// `no_scopes` passes no --remap-path-scope, so defaults to `all` + +/// Only `macro`, `object` and `all` should remap what `file!()` expands to. +/// +/// ``` +/// #[cfg(any(macro_scope, object_scope, all_scope, no_scopes))] +/// const EXPECT_REMAPPED: bool = true; +/// #[cfg(not(any(macro_scope, object_scope, all_scope, no_scopes)))] +/// const EXPECT_REMAPPED: bool = false; +/// +/// let file = file!(); +/// assert_eq!( +/// file.starts_with("/REMAPPED"), +/// EXPECT_REMAPPED, +/// "unexpected file!() = {file}", +/// ); +/// ``` +pub fn f() {}