From 6fe8b8d4a0c359b467918f62c774241dd4a63157 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Sun, 9 Feb 2025 19:09:43 +0000 Subject: [PATCH 01/13] Remove lifetime_capture_rules_2024 feature --- compiler/rustc_feature/src/removed.rs | 2 ++ compiler/rustc_feature/src/unstable.rs | 2 -- .../src/collect/resolve_bound_vars.rs | 33 +++++++------------ .../rustc_lint/src/impl_trait_overcaptures.rs | 6 ++-- tests/ui/impl-trait/implicit-capture-late.rs | 6 ++-- .../impl-trait/implicit-capture-late.stderr | 6 ++-- .../precise-capturing/higher-ranked.rs | 3 +- .../impl-trait/precise-capturing/outlives.rs | 3 +- tests/ui/impl-trait/variance.e2024.stderr | 8 ++--- tests/ui/impl-trait/variance.new.stderr | 26 --------------- tests/ui/impl-trait/variance.old.stderr | 8 ++--- tests/ui/impl-trait/variance.rs | 10 ++---- 12 files changed, 35 insertions(+), 78 deletions(-) delete mode 100644 tests/ui/impl-trait/variance.new.stderr diff --git a/compiler/rustc_feature/src/removed.rs b/compiler/rustc_feature/src/removed.rs index 2fb0c8e43448a..60e7788f2c05d 100644 --- a/compiler/rustc_feature/src/removed.rs +++ b/compiler/rustc_feature/src/removed.rs @@ -135,6 +135,8 @@ declare_features! ( Some("removed as it caused some confusion and discussion was inactive for years")), /// Lazily evaluate constants. This allows constants to depend on type parameters. (removed, lazy_normalization_consts, "1.46.0", Some(72219), Some("superseded by `generic_const_exprs`")), + /// Changes `impl Trait` to capture all lifetimes in scope. + (removed, lifetime_capture_rules_2024, "1.76.0", None, Some("unnecessary -- use edition 2024 instead")), /// Allows using the `#[link_args]` attribute. (removed, link_args, "1.53.0", Some(29596), Some("removed in favor of using `-C link-arg=ARG` on command line, \ diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index 3a2e810dc6af7..b0d1ba917054a 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -214,8 +214,6 @@ declare_features! ( (internal, intrinsics, "1.0.0", None), /// Allows using `#[lang = ".."]` attribute for linking items to special compiler logic. (internal, lang_items, "1.0.0", None), - /// Changes `impl Trait` to capture all lifetimes in scope. - (unstable, lifetime_capture_rules_2024, "1.76.0", None), /// Allows `#[link(..., cfg(..))]`; perma-unstable per #37406 (internal, link_cfg, "1.14.0", None), /// Allows using `?Trait` trait bounds in more contexts. diff --git a/compiler/rustc_hir_analysis/src/collect/resolve_bound_vars.rs b/compiler/rustc_hir_analysis/src/collect/resolve_bound_vars.rs index 76006354717e1..739a50db3fc8d 100644 --- a/compiler/rustc_hir_analysis/src/collect/resolve_bound_vars.rs +++ b/compiler/rustc_hir_analysis/src/collect/resolve_bound_vars.rs @@ -305,21 +305,15 @@ fn generic_param_def_as_bound_arg(param: &ty::GenericParamDef) -> ty::BoundVaria } /// Whether this opaque always captures lifetimes in scope. -/// Right now, this is all RPITIT and TAITs, and when `lifetime_capture_rules_2024` -/// is enabled. We don't check the span of the edition, since this is done -/// on a per-opaque basis to account for nested opaques. -fn opaque_captures_all_in_scope_lifetimes<'tcx>( - tcx: TyCtxt<'tcx>, - opaque: &'tcx hir::OpaqueTy<'tcx>, -) -> bool { +/// Right now, this is all RPITIT and TAITs, and when the opaque +/// is coming from a span corresponding to edition 2024. +fn opaque_captures_all_in_scope_lifetimes<'tcx>(opaque: &'tcx hir::OpaqueTy<'tcx>) -> bool { match opaque.origin { // if the opaque has the `use<...>` syntax, the user is telling us that they only want // to account for those lifetimes, so do not try to be clever. _ if opaque.bounds.iter().any(|bound| matches!(bound, hir::GenericBound::Use(..))) => false, hir::OpaqueTyOrigin::AsyncFn { .. } | hir::OpaqueTyOrigin::TyAlias { .. } => true, - _ if tcx.features().lifetime_capture_rules_2024() || opaque.span.at_least_rust_2024() => { - true - } + _ if opaque.span.at_least_rust_2024() => true, hir::OpaqueTyOrigin::FnReturn { in_trait_or_impl, .. } => in_trait_or_impl.is_some(), } } @@ -519,8 +513,7 @@ impl<'a, 'tcx> Visitor<'tcx> for BoundVarContext<'a, 'tcx> { fn visit_opaque_ty(&mut self, opaque: &'tcx rustc_hir::OpaqueTy<'tcx>) { let captures = RefCell::new(FxIndexMap::default()); - let capture_all_in_scope_lifetimes = - opaque_captures_all_in_scope_lifetimes(self.tcx, opaque); + let capture_all_in_scope_lifetimes = opaque_captures_all_in_scope_lifetimes(opaque); if capture_all_in_scope_lifetimes { let lifetime_ident = |def_id: LocalDefId| { let name = self.tcx.item_name(def_id.to_def_id()); @@ -2273,7 +2266,7 @@ fn is_late_bound_map( } let mut appears_in_output = - AllCollector { tcx, has_fully_capturing_opaque: false, regions: Default::default() }; + AllCollector { has_fully_capturing_opaque: false, regions: Default::default() }; intravisit::walk_fn_ret_ty(&mut appears_in_output, &sig.decl.output); if appears_in_output.has_fully_capturing_opaque { appears_in_output.regions.extend(generics.params.iter().map(|param| param.def_id)); @@ -2286,7 +2279,7 @@ fn is_late_bound_map( // Subtle point: because we disallow nested bindings, we can just // ignore binders here and scrape up all names we see. let mut appears_in_where_clause = - AllCollector { tcx, has_fully_capturing_opaque: true, regions: Default::default() }; + AllCollector { has_fully_capturing_opaque: true, regions: Default::default() }; appears_in_where_clause.visit_generics(generics); debug!(?appears_in_where_clause.regions); @@ -2452,23 +2445,21 @@ fn is_late_bound_map( } } - struct AllCollector<'tcx> { - tcx: TyCtxt<'tcx>, + struct AllCollector { has_fully_capturing_opaque: bool, regions: FxHashSet, } - impl<'v> Visitor<'v> for AllCollector<'v> { - fn visit_lifetime(&mut self, lifetime_ref: &'v hir::Lifetime) { + impl<'tcx> Visitor<'tcx> for AllCollector { + fn visit_lifetime(&mut self, lifetime_ref: &'tcx hir::Lifetime) { if let hir::LifetimeName::Param(def_id) = lifetime_ref.res { self.regions.insert(def_id); } } - fn visit_opaque_ty(&mut self, opaque: &'v hir::OpaqueTy<'v>) { + fn visit_opaque_ty(&mut self, opaque: &'tcx hir::OpaqueTy<'tcx>) { if !self.has_fully_capturing_opaque { - self.has_fully_capturing_opaque = - opaque_captures_all_in_scope_lifetimes(self.tcx, opaque); + self.has_fully_capturing_opaque = opaque_captures_all_in_scope_lifetimes(opaque); } intravisit::walk_opaque_ty(self, opaque); } diff --git a/compiler/rustc_lint/src/impl_trait_overcaptures.rs b/compiler/rustc_lint/src/impl_trait_overcaptures.rs index d251b4b7459e2..a83f50938349e 100644 --- a/compiler/rustc_lint/src/impl_trait_overcaptures.rs +++ b/compiler/rustc_lint/src/impl_trait_overcaptures.rs @@ -86,8 +86,7 @@ declare_lint! { /// /// ### Example /// - /// ```rust,compile_fail - /// # #![feature(lifetime_capture_rules_2024)] + /// ```rust,edition2024,compile_fail /// # #![deny(impl_trait_redundant_captures)] /// fn test<'a>(x: &'a i32) -> impl Sized + use<'a> { x } /// ``` @@ -268,8 +267,7 @@ where && parent == self.parent_def_id { let opaque_span = self.tcx.def_span(opaque_def_id); - let new_capture_rules = opaque_span.at_least_rust_2024() - || self.tcx.features().lifetime_capture_rules_2024(); + let new_capture_rules = opaque_span.at_least_rust_2024(); if !new_capture_rules && !opaque.bounds.iter().any(|bound| matches!(bound, hir::GenericBound::Use(..))) { diff --git a/tests/ui/impl-trait/implicit-capture-late.rs b/tests/ui/impl-trait/implicit-capture-late.rs index 986620b101bdd..13cbcd66f8d87 100644 --- a/tests/ui/impl-trait/implicit-capture-late.rs +++ b/tests/ui/impl-trait/implicit-capture-late.rs @@ -1,13 +1,13 @@ -//@ known-bug: #117647 +//@ edition: 2024 -#![feature(lifetime_capture_rules_2024)] #![feature(rustc_attrs)] #![allow(internal_features)] #![rustc_variance_of_opaques] use std::ops::Deref; -fn foo(x: Vec) -> Box Deref> { +fn foo(x: Vec) -> Box Deref> { //~ ['a: o] + //~^ ERROR cannot capture higher-ranked lifetime Box::new(x) } diff --git a/tests/ui/impl-trait/implicit-capture-late.stderr b/tests/ui/impl-trait/implicit-capture-late.stderr index 3adf78322d2e5..085d3eaedd48c 100644 --- a/tests/ui/impl-trait/implicit-capture-late.stderr +++ b/tests/ui/impl-trait/implicit-capture-late.stderr @@ -1,17 +1,17 @@ error[E0657]: `impl Trait` cannot capture higher-ranked lifetime from `dyn` type - --> $DIR/implicit-capture-late.rs:10:55 + --> $DIR/implicit-capture-late.rs:9:55 | LL | fn foo(x: Vec) -> Box Deref> { | ^^^^^^^^^^^ `impl Trait` implicitly captures all lifetimes in scope | note: lifetime declared here - --> $DIR/implicit-capture-late.rs:10:36 + --> $DIR/implicit-capture-late.rs:9:36 | LL | fn foo(x: Vec) -> Box Deref> { | ^^ error: ['a: o] - --> $DIR/implicit-capture-late.rs:10:55 + --> $DIR/implicit-capture-late.rs:9:55 | LL | fn foo(x: Vec) -> Box Deref> { | ^^^^^^^^^^^ diff --git a/tests/ui/impl-trait/precise-capturing/higher-ranked.rs b/tests/ui/impl-trait/precise-capturing/higher-ranked.rs index 3dc8523e9635d..925f03589e894 100644 --- a/tests/ui/impl-trait/precise-capturing/higher-ranked.rs +++ b/tests/ui/impl-trait/precise-capturing/higher-ranked.rs @@ -1,9 +1,8 @@ //@ check-pass +//@ edition: 2024 // Show how precise captures allow us to skip capturing a higher-ranked lifetime -#![feature(lifetime_capture_rules_2024)] - trait Trait<'a> { type Item; } diff --git a/tests/ui/impl-trait/precise-capturing/outlives.rs b/tests/ui/impl-trait/precise-capturing/outlives.rs index f86a61be1e078..45e46848eea7c 100644 --- a/tests/ui/impl-trait/precise-capturing/outlives.rs +++ b/tests/ui/impl-trait/precise-capturing/outlives.rs @@ -1,9 +1,8 @@ //@ check-pass +//@ edition: 2024 // Show that precise captures allow us to skip a lifetime param for outlives -#![feature(lifetime_capture_rules_2024)] - fn hello<'a: 'a, 'b: 'b>() -> impl Sized + use<'a> { } fn outlives<'a, T: 'a>(_: T) {} diff --git a/tests/ui/impl-trait/variance.e2024.stderr b/tests/ui/impl-trait/variance.e2024.stderr index 361a165da66f3..adbe8b91e866f 100644 --- a/tests/ui/impl-trait/variance.e2024.stderr +++ b/tests/ui/impl-trait/variance.e2024.stderr @@ -1,23 +1,23 @@ error: ['a: *, 'a: o] - --> $DIR/variance.rs:13:36 + --> $DIR/variance.rs:11:36 | LL | fn not_captured_early<'a: 'a>() -> impl Sized {} | ^^^^^^^^^^ error: ['a: *, 'a: o] - --> $DIR/variance.rs:18:32 + --> $DIR/variance.rs:15:32 | LL | fn captured_early<'a: 'a>() -> impl Sized + Captures<'a> {} | ^^^^^^^^^^^^^^^^^^^^^^^^^ error: ['a: o] - --> $DIR/variance.rs:20:40 + --> $DIR/variance.rs:17:40 | LL | fn not_captured_late<'a>(_: &'a ()) -> impl Sized {} | ^^^^^^^^^^ error: ['a: o] - --> $DIR/variance.rs:25:36 + --> $DIR/variance.rs:21:36 | LL | fn captured_late<'a>(_: &'a ()) -> impl Sized + Captures<'a> {} | ^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/impl-trait/variance.new.stderr b/tests/ui/impl-trait/variance.new.stderr deleted file mode 100644 index 361a165da66f3..0000000000000 --- a/tests/ui/impl-trait/variance.new.stderr +++ /dev/null @@ -1,26 +0,0 @@ -error: ['a: *, 'a: o] - --> $DIR/variance.rs:13:36 - | -LL | fn not_captured_early<'a: 'a>() -> impl Sized {} - | ^^^^^^^^^^ - -error: ['a: *, 'a: o] - --> $DIR/variance.rs:18:32 - | -LL | fn captured_early<'a: 'a>() -> impl Sized + Captures<'a> {} - | ^^^^^^^^^^^^^^^^^^^^^^^^^ - -error: ['a: o] - --> $DIR/variance.rs:20:40 - | -LL | fn not_captured_late<'a>(_: &'a ()) -> impl Sized {} - | ^^^^^^^^^^ - -error: ['a: o] - --> $DIR/variance.rs:25:36 - | -LL | fn captured_late<'a>(_: &'a ()) -> impl Sized + Captures<'a> {} - | ^^^^^^^^^^^^^^^^^^^^^^^^^ - -error: aborting due to 4 previous errors - diff --git a/tests/ui/impl-trait/variance.old.stderr b/tests/ui/impl-trait/variance.old.stderr index 578d6fd14cd20..57ffa7cde31c4 100644 --- a/tests/ui/impl-trait/variance.old.stderr +++ b/tests/ui/impl-trait/variance.old.stderr @@ -1,23 +1,23 @@ error: ['a: *] - --> $DIR/variance.rs:13:36 + --> $DIR/variance.rs:11:36 | LL | fn not_captured_early<'a: 'a>() -> impl Sized {} | ^^^^^^^^^^ error: ['a: *, 'a: o] - --> $DIR/variance.rs:18:32 + --> $DIR/variance.rs:15:32 | LL | fn captured_early<'a: 'a>() -> impl Sized + Captures<'a> {} | ^^^^^^^^^^^^^^^^^^^^^^^^^ error: [] - --> $DIR/variance.rs:20:40 + --> $DIR/variance.rs:17:40 | LL | fn not_captured_late<'a>(_: &'a ()) -> impl Sized {} | ^^^^^^^^^^ error: ['a: o] - --> $DIR/variance.rs:25:36 + --> $DIR/variance.rs:21:36 | LL | fn captured_late<'a>(_: &'a ()) -> impl Sized + Captures<'a> {} | ^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/impl-trait/variance.rs b/tests/ui/impl-trait/variance.rs index 1e359f033ff52..bde3a886a4d38 100644 --- a/tests/ui/impl-trait/variance.rs +++ b/tests/ui/impl-trait/variance.rs @@ -1,8 +1,6 @@ -//@ revisions: old new e2024 +//@ revisions: old e2024 //@[e2024] edition: 2024 -#![cfg_attr(new, feature(lifetime_capture_rules_2024))] - #![feature(rustc_attrs)] #![allow(internal_features)] #![rustc_variance_of_opaques] @@ -12,15 +10,13 @@ impl Captures<'_> for T {} fn not_captured_early<'a: 'a>() -> impl Sized {} //[old]~^ ['a: *] -//[new]~^^ ['a: *, 'a: o] -//[e2024]~^^^ ['a: *, 'a: o] +//[e2024]~^^ ['a: *, 'a: o] fn captured_early<'a: 'a>() -> impl Sized + Captures<'a> {} //~ ['a: *, 'a: o] fn not_captured_late<'a>(_: &'a ()) -> impl Sized {} //[old]~^ [] -//[new]~^^ ['a: o] -//[e2024]~^^^ ['a: o] +//[e2024]~^^ ['a: o] fn captured_late<'a>(_: &'a ()) -> impl Sized + Captures<'a> {} //~ ['a: o] From 4e4cb10b846c71f2993d2e4babbc026ff68bab3b Mon Sep 17 00:00:00 2001 From: Peter Todd Date: Tue, 18 Feb 2025 04:56:03 +0000 Subject: [PATCH 02/13] Add #[track_caller] to Duration Div impl Previously the location of the divide-by-zero error condition would be attributed to the code in the rust standard library, eg: thread 'main' panicked at /home/user/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/time.rs:1172:31: divide by zero error when dividing duration by scalar With #[track_caller] the error is correctly attributed to the callee. --- library/core/src/time.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/core/src/time.rs b/library/core/src/time.rs index 22bd46c567eaa..8b211b442eab6 100644 --- a/library/core/src/time.rs +++ b/library/core/src/time.rs @@ -1168,6 +1168,7 @@ impl Div for Duration { type Output = Duration; #[inline] + #[track_caller] fn div(self, rhs: u32) -> Duration { self.checked_div(rhs).expect("divide by zero error when dividing duration by scalar") } @@ -1176,6 +1177,7 @@ impl Div for Duration { #[stable(feature = "time_augmented_assignment", since = "1.9.0")] impl DivAssign for Duration { #[inline] + #[track_caller] fn div_assign(&mut self, rhs: u32) { *self = *self / rhs; } From e565eeed78d0e59e475dce34b0833bbf8f84871b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Tue, 18 Feb 2025 20:29:10 +0000 Subject: [PATCH 03/13] Tweak E0277 when predicate comes indirectly from `?` When a `?` operation requires an `Into` conversion with additional bounds (like having a concrete error but wanting to convert to a trait object), we handle it speficically and provide the same kind of information we give other `?` related errors. ``` error[E0277]: `?` couldn't convert the error: `E: std::error::Error` is not satisfied --> $DIR/bad-question-mark-on-trait-object.rs:5:13 | LL | fn foo() -> Result<(), Box> { | -------------------------------------- required `E: std::error::Error` because of this LL | Ok(bar()?) | ^ the trait `std::error::Error` is not implemented for `E` | = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait = note: required for `Box` to implement `From` ``` Avoid talking about `FromResidual` when other more relevant information is being given, particularly from `rust_on_unimplemented`. --- .../traits/fulfillment_errors.rs | 68 ++++++++++++++----- tests/ui/async-await/issue-84841.stderr | 2 - .../async-await/try-on-option-in-async.stderr | 6 -- ...urn-from-residual-sugg-issue-125997.stderr | 6 -- tests/ui/try-trait/bad-interconversion.stderr | 12 ---- .../bad-question-mark-on-trait-object.rs | 26 +++++++ .../bad-question-mark-on-trait-object.stderr | 26 +++++++ tests/ui/try-trait/option-to-result.stderr | 6 -- .../try-on-option-diagnostics.stderr | 8 --- tests/ui/try-trait/try-on-option.stderr | 5 -- .../ui/try-trait/try-operator-on-main.stderr | 3 - 11 files changed, 103 insertions(+), 65 deletions(-) create mode 100644 tests/ui/try-trait/bad-question-mark-on-trait-object.rs create mode 100644 tests/ui/try-trait/bad-question-mark-on-trait-object.stderr diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs index a8ee4d61e65ed..133d8a88de37c 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs @@ -192,19 +192,38 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { let have_alt_message = message.is_some() || label.is_some(); let is_try_conversion = self.is_try_conversion(span, main_trait_predicate.def_id()); + let is_question_mark = matches!( + root_obligation.cause.code().peel_derives(), + ObligationCauseCode::QuestionMark, + ) && !( + self.tcx.is_diagnostic_item(sym::FromResidual, main_trait_predicate.def_id()) + || self.tcx.is_lang_item(main_trait_predicate.def_id(), LangItem::Try) + ); let is_unsize = self.tcx.is_lang_item(leaf_trait_predicate.def_id(), LangItem::Unsize); + let question_mark_message = "the question mark operation (`?`) implicitly \ + performs a conversion on the error value \ + using the `From` trait"; let (message, notes, append_const_msg) = if is_try_conversion { + // We have a `-> Result<_, E1>` and `gives_E2()?`. ( Some(format!( "`?` couldn't convert the error to `{}`", main_trait_predicate.skip_binder().self_ty(), )), - vec![ - "the question mark operation (`?`) implicitly performs a \ - conversion on the error value using the `From` trait" - .to_owned(), - ], + vec![question_mark_message.to_owned()], + Some(AppendConstMessage::Default), + ) + } else if is_question_mark { + // Similar to the case above, but in this case the conversion is for a + // trait object: `-> Result<_, Box` and `gives_E()?` when + // `E: Error` isn't met. + ( + Some(format!( + "`?` couldn't convert the error: `{main_trait_predicate}` is \ + not satisfied", + )), + vec![question_mark_message.to_owned()], Some(AppendConstMessage::Default), ) } else { @@ -220,8 +239,10 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { &mut long_ty_file, ); - let (err_msg, safe_transmute_explanation) = if self.tcx.is_lang_item(main_trait_predicate.def_id(), LangItem::TransmuteTrait) - { + let (err_msg, safe_transmute_explanation) = if self.tcx.is_lang_item( + main_trait_predicate.def_id(), + LangItem::TransmuteTrait, + ) { // Recompute the safe transmute reason and use that for the error reporting match self.get_safe_transmute_error_and_reason( obligation.clone(), @@ -249,18 +270,22 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { *err.long_ty_path() = long_ty_file; let mut suggested = false; - if is_try_conversion { + if is_try_conversion || is_question_mark { suggested = self.try_conversion_context(&obligation, main_trait_predicate, &mut err); } - if is_try_conversion && let Some(ret_span) = self.return_type_span(&obligation) { - err.span_label( - ret_span, - format!( - "expected `{}` because of this", - main_trait_predicate.skip_binder().self_ty() - ), - ); + if let Some(ret_span) = self.return_type_span(&obligation) { + if is_try_conversion { + err.span_label( + ret_span, + format!( + "expected `{}` because of this", + main_trait_predicate.skip_binder().self_ty() + ), + ); + } else if is_question_mark { + err.span_label(ret_span, format!("required `{main_trait_predicate}` because of this")); + } } if tcx.is_lang_item(leaf_trait_predicate.def_id(), LangItem::Tuple) { @@ -302,10 +327,14 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { // If it has a custom `#[rustc_on_unimplemented]` // error message, let's display it as the label! err.span_label(span, s); - if !matches!(leaf_trait_predicate.skip_binder().self_ty().kind(), ty::Param(_)) { + if !matches!(leaf_trait_predicate.skip_binder().self_ty().kind(), ty::Param(_)) // When the self type is a type param We don't need to "the trait // `std::marker::Sized` is not implemented for `T`" as we will point // at the type param with a label to suggest constraining it. + && !self.tcx.is_diagnostic_item(sym::FromResidual, leaf_trait_predicate.def_id()) + // Don't say "the trait `FromResidual>` is + // not implemented for `Result`". + { err.help(explanation); } } else if let Some(custom_explanation) = safe_transmute_explanation { @@ -2035,6 +2064,11 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { return false; } if let &[cand] = &candidates[..] { + if self.tcx.is_diagnostic_item(sym::FromResidual, cand.def_id) + && !self.tcx.features().enabled(sym::try_trait_v2) + { + return false; + } let (desc, mention_castable) = match (cand.self_ty().kind(), trait_pred.self_ty().skip_binder().kind()) { (ty::FnPtr(..), ty::FnDef(..)) => { diff --git a/tests/ui/async-await/issue-84841.stderr b/tests/ui/async-await/issue-84841.stderr index 69c1c882d60ac..0d008477310a7 100644 --- a/tests/ui/async-await/issue-84841.stderr +++ b/tests/ui/async-await/issue-84841.stderr @@ -17,8 +17,6 @@ LL | | test()?; ... | LL | | } | |_- this function should return `Result` or `Option` to accept `?` - | - = help: the trait `FromResidual<_>` is not implemented for `()` error: aborting due to 2 previous errors diff --git a/tests/ui/async-await/try-on-option-in-async.stderr b/tests/ui/async-await/try-on-option-in-async.stderr index 9e0bb42a697cc..332f4d4ec0c27 100644 --- a/tests/ui/async-await/try-on-option-in-async.stderr +++ b/tests/ui/async-await/try-on-option-in-async.stderr @@ -6,8 +6,6 @@ LL | async { LL | let x: Option = None; LL | x?; | ^ cannot use the `?` operator in an async block that returns `{integer}` - | - = help: the trait `FromResidual>` is not implemented for `{integer}` error[E0277]: the `?` operator can only be used in an async closure that returns `Result` or `Option` (or another type that implements `FromResidual`) --> $DIR/try-on-option-in-async.rs:16:10 @@ -20,8 +18,6 @@ LL | | x?; LL | | 22_u32 LL | | }; | |_____- this function should return `Result` or `Option` to accept `?` - | - = help: the trait `FromResidual>` is not implemented for `u32` error[E0277]: the `?` operator can only be used in an async function that returns `Result` or `Option` (or another type that implements `FromResidual`) --> $DIR/try-on-option-in-async.rs:25:6 @@ -34,8 +30,6 @@ LL | | x?; LL | | 22 LL | | } | |_- this function should return `Result` or `Option` to accept `?` - | - = help: the trait `FromResidual>` is not implemented for `u32` error: aborting due to 3 previous errors diff --git a/tests/ui/return/return-from-residual-sugg-issue-125997.stderr b/tests/ui/return/return-from-residual-sugg-issue-125997.stderr index e22f33fd242a6..877995dfe1266 100644 --- a/tests/ui/return/return-from-residual-sugg-issue-125997.stderr +++ b/tests/ui/return/return-from-residual-sugg-issue-125997.stderr @@ -6,7 +6,6 @@ LL | fn test1() { LL | let mut _file = File::create("foo.txt")?; | ^ cannot use the `?` operator in a function that returns `()` | - = help: the trait `FromResidual>` is not implemented for `()` help: consider adding return type | LL ~ fn test1() -> Result<(), Box> { @@ -23,7 +22,6 @@ LL | fn test2() { LL | let mut _file = File::create("foo.txt")?; | ^ cannot use the `?` operator in a function that returns `()` | - = help: the trait `FromResidual>` is not implemented for `()` help: consider adding return type | LL ~ fn test2() -> Result<(), Box> { @@ -41,7 +39,6 @@ LL | fn test4(&self) { LL | let mut _file = File::create("foo.txt")?; | ^ cannot use the `?` operator in a method that returns `()` | - = help: the trait `FromResidual>` is not implemented for `()` help: consider adding return type | LL ~ fn test4(&self) -> Result<(), Box> { @@ -59,7 +56,6 @@ LL | fn test5(&self) { LL | let mut _file = File::create("foo.txt")?; | ^ cannot use the `?` operator in a method that returns `()` | - = help: the trait `FromResidual>` is not implemented for `()` help: consider adding return type | LL ~ fn test5(&self) -> Result<(), Box> { @@ -78,7 +74,6 @@ LL | fn main() { LL | let mut _file = File::create("foo.txt")?; | ^ cannot use the `?` operator in a function that returns `()` | - = help: the trait `FromResidual>` is not implemented for `()` help: consider adding return type | LL ~ fn main() -> Result<(), Box> { @@ -99,7 +94,6 @@ LL | let mut _file = File::create("foo.txt")?; LL | mac!(); | ------ in this macro invocation | - = help: the trait `FromResidual>` is not implemented for `()` = note: this error originates in the macro `mac` (in Nightly builds, run with -Z macro-backtrace for more info) help: consider adding return type | diff --git a/tests/ui/try-trait/bad-interconversion.stderr b/tests/ui/try-trait/bad-interconversion.stderr index 45422be946e82..6df05747f32c4 100644 --- a/tests/ui/try-trait/bad-interconversion.stderr +++ b/tests/ui/try-trait/bad-interconversion.stderr @@ -20,9 +20,6 @@ LL | fn option_to_result() -> Result { | -------------------------------------------- this function returns a `Result` LL | Some(3)?; | ^ use `.ok_or(...)?` to provide an error compatible with `Result` - | - = help: the trait `FromResidual>` is not implemented for `Result` - = help: the trait `FromResidual>` is implemented for `Result` error[E0277]: the `?` operator can only be used on `Result`s in a function that returns `Result` --> $DIR/bad-interconversion.rs:15:31 @@ -31,9 +28,6 @@ LL | fn control_flow_to_result() -> Result { | -------------------------------------------------- this function returns a `Result` LL | Ok(ControlFlow::Break(123)?) | ^ this `?` produces `ControlFlow<{integer}, Infallible>`, which is incompatible with `Result` - | - = help: the trait `FromResidual>` is not implemented for `Result` - = help: the trait `FromResidual>` is implemented for `Result` error[E0277]: the `?` operator can only be used on `Option`s, not `Result`s, in a function that returns `Option` --> $DIR/bad-interconversion.rs:20:22 @@ -42,9 +36,6 @@ LL | fn result_to_option() -> Option { | ------------------------------------ this function returns an `Option` LL | Some(Err("hello")?) | ^ use `.ok()?` if you want to discard the `Result` error information - | - = help: the trait `FromResidual>` is not implemented for `Option` - = help: the trait `FromResidual>` is implemented for `Option` error[E0277]: the `?` operator can only be used on `Option`s in a function that returns `Option` --> $DIR/bad-interconversion.rs:25:33 @@ -53,9 +44,6 @@ LL | fn control_flow_to_option() -> Option { | ------------------------------------------ this function returns an `Option` LL | Some(ControlFlow::Break(123)?) | ^ this `?` produces `ControlFlow<{integer}, Infallible>`, which is incompatible with `Option` - | - = help: the trait `FromResidual>` is not implemented for `Option` - = help: the trait `FromResidual>` is implemented for `Option` error[E0277]: the `?` operator can only be used on `ControlFlow`s in a function that returns `ControlFlow` --> $DIR/bad-interconversion.rs:30:39 diff --git a/tests/ui/try-trait/bad-question-mark-on-trait-object.rs b/tests/ui/try-trait/bad-question-mark-on-trait-object.rs new file mode 100644 index 0000000000000..f319610cb3aec --- /dev/null +++ b/tests/ui/try-trait/bad-question-mark-on-trait-object.rs @@ -0,0 +1,26 @@ +struct E; +struct X; + +fn foo() -> Result<(), Box> { //~ NOTE required `E: std::error::Error` because of this + Ok(bar()?) + //~^ ERROR `?` couldn't convert the error: `E: std::error::Error` is not satisfied + //~| NOTE the trait `std::error::Error` is not implemented for `E` + //~| NOTE the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + //~| NOTE required for `Box` to implement `From` + //~| NOTE in this expansion + //~| NOTE in this expansion + //~| NOTE in this expansion +} +fn bat() -> Result<(), X> { //~ NOTE expected `X` because of this + Ok(bar()?) + //~^ ERROR `?` couldn't convert the error to `X` + //~| NOTE the trait `From` is not implemented for `X` + //~| NOTE this can't be annotated with `?` because it has type `Result<_, E>` + //~| NOTE the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + //~| NOTE in this expansion + //~| NOTE in this expansion +} +fn bar() -> Result<(), E> { + Err(E) +} +fn main() {} diff --git a/tests/ui/try-trait/bad-question-mark-on-trait-object.stderr b/tests/ui/try-trait/bad-question-mark-on-trait-object.stderr new file mode 100644 index 0000000000000..4e7cd1e288963 --- /dev/null +++ b/tests/ui/try-trait/bad-question-mark-on-trait-object.stderr @@ -0,0 +1,26 @@ +error[E0277]: `?` couldn't convert the error: `E: std::error::Error` is not satisfied + --> $DIR/bad-question-mark-on-trait-object.rs:5:13 + | +LL | fn foo() -> Result<(), Box> { + | -------------------------------------- required `E: std::error::Error` because of this +LL | Ok(bar()?) + | ^ the trait `std::error::Error` is not implemented for `E` + | + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + = note: required for `Box` to implement `From` + +error[E0277]: `?` couldn't convert the error to `X` + --> $DIR/bad-question-mark-on-trait-object.rs:15:13 + | +LL | fn bat() -> Result<(), X> { + | ------------- expected `X` because of this +LL | Ok(bar()?) + | -----^ the trait `From` is not implemented for `X` + | | + | this can't be annotated with `?` because it has type `Result<_, E>` + | + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/try-trait/option-to-result.stderr b/tests/ui/try-trait/option-to-result.stderr index 1a5a925f92fce..8a4c4707942ce 100644 --- a/tests/ui/try-trait/option-to-result.stderr +++ b/tests/ui/try-trait/option-to-result.stderr @@ -6,9 +6,6 @@ LL | fn test_result() -> Result<(),()> { LL | let a:Option<()> = Some(()); LL | a?; | ^ use `.ok_or(...)?` to provide an error compatible with `Result<(), ()>` - | - = help: the trait `FromResidual>` is not implemented for `Result<(), ()>` - = help: the trait `FromResidual>` is implemented for `Result` error[E0277]: the `?` operator can only be used on `Option`s, not `Result`s, in a function that returns `Option` --> $DIR/option-to-result.rs:11:6 @@ -18,9 +15,6 @@ LL | fn test_option() -> Option{ LL | let a:Result = Ok(5); LL | a?; | ^ use `.ok()?` if you want to discard the `Result` error information - | - = help: the trait `FromResidual>` is not implemented for `Option` - = help: the trait `FromResidual>` is implemented for `Option` error: aborting due to 2 previous errors diff --git a/tests/ui/try-trait/try-on-option-diagnostics.stderr b/tests/ui/try-trait/try-on-option-diagnostics.stderr index 9ee540c79fdda..08675e242a354 100644 --- a/tests/ui/try-trait/try-on-option-diagnostics.stderr +++ b/tests/ui/try-trait/try-on-option-diagnostics.stderr @@ -6,8 +6,6 @@ LL | fn a_function() -> u32 { LL | let x: Option = None; LL | x?; | ^ cannot use the `?` operator in a function that returns `u32` - | - = help: the trait `FromResidual>` is not implemented for `u32` error[E0277]: the `?` operator can only be used in a closure that returns `Result` or `Option` (or another type that implements `FromResidual`) --> $DIR/try-on-option-diagnostics.rs:14:10 @@ -17,8 +15,6 @@ LL | let a_closure = || { LL | let x: Option = None; LL | x?; | ^ cannot use the `?` operator in a closure that returns `{integer}` - | - = help: the trait `FromResidual>` is not implemented for `{integer}` error[E0277]: the `?` operator can only be used in a method that returns `Result` or `Option` (or another type that implements `FromResidual`) --> $DIR/try-on-option-diagnostics.rs:26:14 @@ -28,8 +24,6 @@ LL | fn a_method() { LL | let x: Option = None; LL | x?; | ^ cannot use the `?` operator in a method that returns `()` - | - = help: the trait `FromResidual>` is not implemented for `()` error[E0277]: the `?` operator can only be used in a trait method that returns `Result` or `Option` (or another type that implements `FromResidual`) --> $DIR/try-on-option-diagnostics.rs:39:14 @@ -39,8 +33,6 @@ LL | fn a_trait_method() { LL | let x: Option = None; LL | x?; | ^ cannot use the `?` operator in a trait method that returns `()` - | - = help: the trait `FromResidual>` is not implemented for `()` error: aborting due to 4 previous errors diff --git a/tests/ui/try-trait/try-on-option.stderr b/tests/ui/try-trait/try-on-option.stderr index 15d0b28ddc1d7..aeb519086d8c1 100644 --- a/tests/ui/try-trait/try-on-option.stderr +++ b/tests/ui/try-trait/try-on-option.stderr @@ -6,9 +6,6 @@ LL | fn foo() -> Result { LL | let x: Option = None; LL | x?; | ^ use `.ok_or(...)?` to provide an error compatible with `Result` - | - = help: the trait `FromResidual>` is not implemented for `Result` - = help: the trait `FromResidual>` is implemented for `Result` error[E0277]: the `?` operator can only be used in a function that returns `Result` or `Option` (or another type that implements `FromResidual`) --> $DIR/try-on-option.rs:11:6 @@ -18,8 +15,6 @@ LL | fn bar() -> u32 { LL | let x: Option = None; LL | x?; | ^ cannot use the `?` operator in a function that returns `u32` - | - = help: the trait `FromResidual>` is not implemented for `u32` error: aborting due to 2 previous errors diff --git a/tests/ui/try-trait/try-operator-on-main.stderr b/tests/ui/try-trait/try-operator-on-main.stderr index 311e8076ed48e..9c2526442ab5b 100644 --- a/tests/ui/try-trait/try-operator-on-main.stderr +++ b/tests/ui/try-trait/try-operator-on-main.stderr @@ -7,7 +7,6 @@ LL | // error for a `Try` type on a non-`Try` fn LL | std::fs::File::open("foo")?; | ^ cannot use the `?` operator in a function that returns `()` | - = help: the trait `FromResidual>` is not implemented for `()` help: consider adding return type | LL ~ fn main() -> Result<(), Box> { @@ -33,8 +32,6 @@ LL | fn main() { ... LL | ()?; | ^ cannot use the `?` operator in a function that returns `()` - | - = help: the trait `FromResidual<_>` is not implemented for `()` error[E0277]: the trait bound `(): Try` is not satisfied --> $DIR/try-operator-on-main.rs:14:25 From 8ef535e03d8f34c7ad7aab14eea6a73bf4445b4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Thu, 20 Feb 2025 19:07:39 +0000 Subject: [PATCH 04/13] Point out the type of more expressions on bad `?` --- .../src/error_reporting/traits/fulfillment_errors.rs | 7 +------ tests/ui/try-trait/bad-question-mark-on-trait-object.rs | 1 + .../ui/try-trait/bad-question-mark-on-trait-object.stderr | 6 ++++-- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs index 133d8a88de37c..4004fdd073ce0 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs @@ -961,14 +961,9 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { let Some(typeck) = &self.typeck_results else { return false; }; - let Some((ObligationCauseCode::QuestionMark, Some(y))) = - obligation.cause.code().parent_with_predicate() - else { + let ObligationCauseCode::QuestionMark = obligation.cause.code().peel_derives() else { return false; }; - if !self.tcx.is_diagnostic_item(sym::FromResidual, y.def_id()) { - return false; - } let self_ty = trait_pred.skip_binder().self_ty(); let found_ty = trait_pred.skip_binder().trait_ref.args.get(1).and_then(|a| a.as_type()); diff --git a/tests/ui/try-trait/bad-question-mark-on-trait-object.rs b/tests/ui/try-trait/bad-question-mark-on-trait-object.rs index f319610cb3aec..9efac78b3d787 100644 --- a/tests/ui/try-trait/bad-question-mark-on-trait-object.rs +++ b/tests/ui/try-trait/bad-question-mark-on-trait-object.rs @@ -7,6 +7,7 @@ fn foo() -> Result<(), Box> { //~ NOTE required `E: std:: //~| NOTE the trait `std::error::Error` is not implemented for `E` //~| NOTE the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait //~| NOTE required for `Box` to implement `From` + //~| NOTE this has type `Result<_, E>` //~| NOTE in this expansion //~| NOTE in this expansion //~| NOTE in this expansion diff --git a/tests/ui/try-trait/bad-question-mark-on-trait-object.stderr b/tests/ui/try-trait/bad-question-mark-on-trait-object.stderr index 4e7cd1e288963..adebc16915d75 100644 --- a/tests/ui/try-trait/bad-question-mark-on-trait-object.stderr +++ b/tests/ui/try-trait/bad-question-mark-on-trait-object.stderr @@ -4,13 +4,15 @@ error[E0277]: `?` couldn't convert the error: `E: std::error::Error` is not sati LL | fn foo() -> Result<(), Box> { | -------------------------------------- required `E: std::error::Error` because of this LL | Ok(bar()?) - | ^ the trait `std::error::Error` is not implemented for `E` + | -----^ the trait `std::error::Error` is not implemented for `E` + | | + | this has type `Result<_, E>` | = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait = note: required for `Box` to implement `From` error[E0277]: `?` couldn't convert the error to `X` - --> $DIR/bad-question-mark-on-trait-object.rs:15:13 + --> $DIR/bad-question-mark-on-trait-object.rs:16:13 | LL | fn bat() -> Result<(), X> { | ------------- expected `X` because of this From ef3c339f9ce35aaecf71114a6ec0f618f73dd520 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=AE=B8=E6=9D=B0=E5=8F=8B=20Jieyou=20Xu=20=28Joe=29?= <39484203+jieyouxu@users.noreply.github.com> Date: Mon, 3 Feb 2025 17:22:13 +0800 Subject: [PATCH 05/13] compiletest: introduce and use `--src-root` and `--src-test-suite-root` Instead of only having `--src-base` and `src_base` which *actually* refers to the directory containing the test suite and not the sources root. More importantly, kill off `find_rust_src_root` when we can simply pass that info from bootstrap. --- src/tools/compiletest/src/common.rs | 6 +- src/tools/compiletest/src/header.rs | 15 +--- src/tools/compiletest/src/header/tests.rs | 3 +- src/tools/compiletest/src/lib.rs | 69 ++++++++++++------- src/tools/compiletest/src/runtest.rs | 6 +- .../compiletest/src/runtest/debuginfo.rs | 29 +++----- src/tools/compiletest/src/runtest/js_doc.rs | 3 +- src/tools/compiletest/src/runtest/run_make.rs | 35 ++-------- src/tools/compiletest/src/runtest/rustdoc.rs | 5 +- 9 files changed, 74 insertions(+), 97 deletions(-) diff --git a/src/tools/compiletest/src/common.rs b/src/tools/compiletest/src/common.rs index 1614c35cb1ce4..98ab7adf5a726 100644 --- a/src/tools/compiletest/src/common.rs +++ b/src/tools/compiletest/src/common.rs @@ -215,8 +215,10 @@ pub struct Config { /// `None` then these tests will be ignored. pub run_clang_based_tests_with: Option, - /// The directory containing the tests to run - pub src_base: PathBuf, + /// The directory containing the sources. + pub src_root: PathBuf, + /// The directory containing the test suite sources. Must be a subdirectory of `src_root`. + pub src_test_suite_root: PathBuf, /// The directory where programs should be built pub build_base: PathBuf, diff --git a/src/tools/compiletest/src/header.rs b/src/tools/compiletest/src/header.rs index 452a2e9a9d518..3bdf37a1f294a 100644 --- a/src/tools/compiletest/src/header.rs +++ b/src/tools/compiletest/src/header.rs @@ -1026,19 +1026,6 @@ impl Config { } } - pub fn find_rust_src_root(&self) -> Option { - let mut path = self.src_base.clone(); - let path_postfix = Path::new("src/etc/lldb_batchmode.py"); - - while path.pop() { - if path.join(&path_postfix).is_file() { - return Some(path); - } - } - - None - } - fn parse_edition(&self, line: &str) -> Option { self.parse_name_value_directive(line, "edition") } @@ -1098,7 +1085,7 @@ fn expand_variables(mut value: String, config: &Config) -> String { } if value.contains(SRC_BASE) { - value = value.replace(SRC_BASE, &config.src_base.to_string_lossy()); + value = value.replace(SRC_BASE, &config.src_test_suite_root.to_str().unwrap()); } if value.contains(BUILD_BASE) { diff --git a/src/tools/compiletest/src/header/tests.rs b/src/tools/compiletest/src/header/tests.rs index c65f10a52bd5b..c9536b7a19b79 100644 --- a/src/tools/compiletest/src/header/tests.rs +++ b/src/tools/compiletest/src/header/tests.rs @@ -153,7 +153,8 @@ impl ConfigBuilder { "--run-lib-path=", "--python=", "--jsondocck-path=", - "--src-base=", + "--src-root=", + "--src-test-suite-root=", "--build-base=", "--sysroot-base=", "--cc=c", diff --git a/src/tools/compiletest/src/lib.rs b/src/tools/compiletest/src/lib.rs index d0a83cab9cd99..979821701385e 100644 --- a/src/tools/compiletest/src/lib.rs +++ b/src/tools/compiletest/src/lib.rs @@ -61,7 +61,8 @@ pub fn parse_config(args: Vec) -> Config { .optopt("", "jsondoclint-path", "path to jsondoclint to use for doc tests", "PATH") .optopt("", "run-clang-based-tests-with", "path to Clang executable", "PATH") .optopt("", "llvm-filecheck", "path to LLVM's FileCheck binary", "DIR") - .reqopt("", "src-base", "directory to scan for test files", "PATH") + .reqopt("", "src-root", "directory containing sources", "PATH") + .reqopt("", "src-test-suite-root", "directory containing test suite sources", "PATH") .reqopt("", "build-base", "directory to deposit test outputs", "PATH") .reqopt("", "sysroot-base", "directory containing the compiler sysroot", "PATH") .reqopt("", "stage", "stage number under test", "N") @@ -243,7 +244,6 @@ pub fn parse_config(args: Vec) -> Config { || header::extract_llvm_version_from_binary(&matches.opt_str("llvm-filecheck")?), ); - let src_base = opt_path(matches, "src-base"); let run_ignored = matches.opt_present("ignored"); let with_rustc_debug_assertions = matches.opt_present("with-rustc-debug-assertions"); let with_std_debug_assertions = matches.opt_present("with-std-debug-assertions"); @@ -300,6 +300,15 @@ pub fn parse_config(args: Vec) -> Config { None => panic!("`--stage` is required"), }; + let src_root = opt_path(matches, "src-root"); + let src_test_suite_root = opt_path(matches, "src-test-suite-root"); + assert!( + src_test_suite_root.starts_with(&src_root), + "`src-root` must be a parent of `src-test-suite-root`: `src-root`=`{}`, `src-test-suite-root` = `{}`", + src_root.display(), + src_test_suite_root.display() + ); + Config { bless: matches.opt_present("bless"), compile_lib_path: make_absolute(opt_path(matches, "compile-lib-path")), @@ -314,7 +323,10 @@ pub fn parse_config(args: Vec) -> Config { run_clang_based_tests_with: matches.opt_str("run-clang-based-tests-with"), llvm_filecheck: matches.opt_str("llvm-filecheck").map(PathBuf::from), llvm_bin_dir: matches.opt_str("llvm-bin-dir").map(PathBuf::from), - src_base, + + src_root, + src_test_suite_root, + build_base: opt_path(matches, "build-base"), sysroot_base: opt_path(matches, "sysroot-base"), @@ -422,7 +434,10 @@ pub fn log_config(config: &Config) { logv(c, format!("rustc_path: {:?}", config.rustc_path.display())); logv(c, format!("cargo_path: {:?}", config.cargo_path)); logv(c, format!("rustdoc_path: {:?}", config.rustdoc_path)); - logv(c, format!("src_base: {:?}", config.src_base.display())); + + logv(c, format!("src_root: {}", config.src_root.display())); + logv(c, format!("src_test_suite_root: {}", config.src_test_suite_root.display())); + logv(c, format!("build_base: {:?}", config.build_base.display())); logv(c, format!("stage: {}", config.stage)); logv(c, format!("stage_id: {}", config.stage_id)); @@ -620,20 +635,29 @@ struct TestCollector { /// regardless of whether any filters/tests were specified on the command-line, /// because filtering is handled later by libtest. pub fn collect_and_make_tests(config: Arc) -> Vec { - debug!("making tests from {:?}", config.src_base.display()); + debug!("making tests from {}", config.src_test_suite_root.display()); let common_inputs_stamp = common_inputs_stamp(&config); - let modified_tests = modified_tests(&config, &config.src_base).unwrap_or_else(|err| { - panic!("modified_tests got error from dir: {}, error: {}", config.src_base.display(), err) - }); + let modified_tests = + modified_tests(&config, &config.src_test_suite_root).unwrap_or_else(|err| { + panic!( + "modified_tests got error from dir: {}, error: {}", + config.src_test_suite_root.display(), + err + ) + }); let cache = HeadersCache::load(&config); let cx = TestCollectorCx { config, cache, common_inputs_stamp, modified_tests }; let mut collector = TestCollector { tests: vec![], found_path_stems: HashSet::new(), poisoned: false }; - collect_tests_from_dir(&cx, &mut collector, &cx.config.src_base, Path::new("")).unwrap_or_else( - |reason| panic!("Could not read tests from {}: {reason}", cx.config.src_base.display()), - ); + collect_tests_from_dir(&cx, &mut collector, &cx.config.src_test_suite_root, Path::new("")) + .unwrap_or_else(|reason| { + panic!( + "Could not read tests from {}: {reason}", + cx.config.src_test_suite_root.display() + ) + }); let TestCollector { tests, found_path_stems, poisoned } = collector; @@ -655,7 +679,7 @@ pub fn collect_and_make_tests(config: Arc) -> Vec { /// common to some subset of tests, and are hopefully unlikely to be modified /// while working on other tests.) fn common_inputs_stamp(config: &Config) -> Stamp { - let rust_src_dir = config.find_rust_src_root().expect("Could not find Rust source root"); + let src_root = &config.src_root; let mut stamp = Stamp::from_path(&config.rustc_path); @@ -670,17 +694,17 @@ fn common_inputs_stamp(config: &Config) -> Stamp { "src/etc/lldb_providers.py", ]; for file in &pretty_printer_files { - let path = rust_src_dir.join(file); + let path = src_root.join(file); stamp.add_path(&path); } - stamp.add_dir(&rust_src_dir.join("src/etc/natvis")); + stamp.add_dir(&src_root.join("src/etc/natvis")); stamp.add_dir(&config.run_lib_path); if let Some(ref rustdoc_path) = config.rustdoc_path { stamp.add_path(&rustdoc_path); - stamp.add_path(&rust_src_dir.join("src/etc/htmldocck.py")); + stamp.add_path(&src_root.join("src/etc/htmldocck.py")); } // Re-run coverage tests if the `coverage-dump` tool was modified, @@ -689,10 +713,10 @@ fn common_inputs_stamp(config: &Config) -> Stamp { stamp.add_path(coverage_dump_path) } - stamp.add_dir(&rust_src_dir.join("src/tools/run-make-support")); + stamp.add_dir(&src_root.join("src/tools/run-make-support")); // Compiletest itself. - stamp.add_dir(&rust_src_dir.join("src/tools/compiletest/")); + stamp.add_dir(&src_root.join("src/tools/compiletest")); stamp } @@ -933,10 +957,7 @@ fn files_related_to_test( } // `minicore.rs` test auxiliary: we need to make sure tests get rerun if this changes. - // - // FIXME(jieyouxu): untangle these paths, we should provide both a path to root `tests/` or - // `tests/auxiliary/` and the test suite in question. `src_base` is also a terrible name. - related.push(config.src_base.parent().unwrap().join("auxiliary").join("minicore.rs")); + related.push(config.src_root.join("tests").join("auxiliary").join("minicore.rs")); related } @@ -1026,10 +1047,8 @@ fn make_test_name( testpaths: &TestPaths, revision: Option<&str>, ) -> test::TestName { - // Print the name of the file, relative to the repository root. - // `src_base` looks like `/path/to/rust/tests/ui` - let root_directory = config.src_base.parent().unwrap().parent().unwrap(); - let path = testpaths.file.strip_prefix(root_directory).unwrap(); + // Print the name of the file, relative to the sources root. + let path = testpaths.file.strip_prefix(&config.src_root).unwrap(); let debugger = match config.debugger { Some(d) => format!("-{}", d), None => String::new(), diff --git a/src/tools/compiletest/src/runtest.rs b/src/tools/compiletest/src/runtest.rs index 0e2da2b02ca0f..536e19bc4933e 100644 --- a/src/tools/compiletest/src/runtest.rs +++ b/src/tools/compiletest/src/runtest.rs @@ -1365,7 +1365,7 @@ impl<'test> TestCx<'test> { // // Note: avoid adding a subdirectory of an already filtered directory here, otherwise the // same slice of text will be double counted and the truncation might not happen. - add_path(&self.config.src_base); + add_path(&self.config.src_test_suite_root); add_path(&self.config.build_base); read2_abbreviated(child, &filter_paths_from_len).expect("failed to read output") @@ -1471,7 +1471,7 @@ impl<'test> TestCx<'test> { // Similarly, vendored sources shouldn't be shown when running from a dist tarball. rustc.arg("-Z").arg(format!( "ignore-directory-in-diagnostics-source-blocks={}", - self.config.find_rust_src_root().unwrap().join("vendor").display(), + self.config.src_root.join("vendor").to_str().unwrap(), )); // Optionally prevent default --sysroot if specified in test compile-flags. @@ -1632,7 +1632,7 @@ impl<'test> TestCx<'test> { if self.props.remap_src_base { rustc.arg(format!( "--remap-path-prefix={}={}", - self.config.src_base.display(), + self.config.src_test_suite_root.to_str().unwrap(), FAKE_SRC_BASE, )); } diff --git a/src/tools/compiletest/src/runtest/debuginfo.rs b/src/tools/compiletest/src/runtest/debuginfo.rs index b236b06756903..170b8a8099687 100644 --- a/src/tools/compiletest/src/runtest/debuginfo.rs +++ b/src/tools/compiletest/src/runtest/debuginfo.rs @@ -257,11 +257,8 @@ impl TestCx<'_> { println!("Adb process is already finished."); } } else { - let rust_src_root = - self.config.find_rust_src_root().expect("Could not find Rust source root"); - let rust_pp_module_rel_path = Path::new("./src/etc"); - let rust_pp_module_abs_path = - rust_src_root.join(rust_pp_module_rel_path).to_str().unwrap().to_owned(); + let rust_pp_module_abs_path = self.config.src_root.join("src").join("etc"); + let rust_pp_module_abs_path = rust_pp_module_abs_path.to_str().unwrap(); // write debugger script let mut script_str = String::with_capacity(2048); script_str.push_str(&format!("set charset {}\n", Self::charset())); @@ -338,7 +335,7 @@ impl TestCx<'_> { let pythonpath = if let Ok(pp) = std::env::var("PYTHONPATH") { format!("{pp}:{rust_pp_module_abs_path}") } else { - rust_pp_module_abs_path + rust_pp_module_abs_path.to_string() }; gdb.args(debugger_opts).env("PYTHONPATH", pythonpath); @@ -407,11 +404,8 @@ impl TestCx<'_> { // Make LLDB emit its version, so we have it documented in the test output script_str.push_str("version\n"); - // Switch LLDB into "Rust mode" - let rust_src_root = - self.config.find_rust_src_root().expect("Could not find Rust source root"); - let rust_pp_module_rel_path = Path::new("./src/etc"); - let rust_pp_module_abs_path = rust_src_root.join(rust_pp_module_rel_path); + // Switch LLDB into "Rust mode". + let rust_pp_module_abs_path = self.config.src_root.join("src/etc"); script_str.push_str(&format!( "command script import {}/lldb_lookup.py\n", @@ -445,7 +439,7 @@ impl TestCx<'_> { let debugger_script = self.make_out_name("debugger.script"); // Let LLDB execute the script via lldb_batchmode.py - let debugger_run_result = self.run_lldb(&exe_file, &debugger_script, &rust_src_root); + let debugger_run_result = self.run_lldb(&exe_file, &debugger_script); if !debugger_run_result.status.success() { self.fatal_proc_rec("Error while running LLDB", &debugger_run_result); @@ -456,18 +450,13 @@ impl TestCx<'_> { } } - fn run_lldb( - &self, - test_executable: &Path, - debugger_script: &Path, - rust_src_root: &Path, - ) -> ProcRes { + fn run_lldb(&self, test_executable: &Path, debugger_script: &Path) -> ProcRes { // Prepare the lldb_batchmode which executes the debugger script - let lldb_script_path = rust_src_root.join("src/etc/lldb_batchmode.py"); + let lldb_script_path = self.config.src_root.join("src/etc/lldb_batchmode.py"); let pythonpath = if let Ok(pp) = std::env::var("PYTHONPATH") { format!("{pp}:{}", self.config.lldb_python_dir.as_ref().unwrap()) } else { - self.config.lldb_python_dir.as_ref().unwrap().to_string() + self.config.lldb_python_dir.clone().unwrap() }; self.run_command_to_procres( Command::new(&self.config.python) diff --git a/src/tools/compiletest/src/runtest/js_doc.rs b/src/tools/compiletest/src/runtest/js_doc.rs index a83bcd70c8718..d630affbec104 100644 --- a/src/tools/compiletest/src/runtest/js_doc.rs +++ b/src/tools/compiletest/src/runtest/js_doc.rs @@ -9,12 +9,11 @@ impl TestCx<'_> { self.document(&out_dir, &self.testpaths); - let root = self.config.find_rust_src_root().unwrap(); let file_stem = self.testpaths.file.file_stem().and_then(|f| f.to_str()).expect("no file stem"); let res = self.run_command_to_procres( Command::new(&nodejs) - .arg(root.join("src/tools/rustdoc-js/tester.js")) + .arg(self.config.src_root.join("src/tools/rustdoc-js/tester.js")) .arg("--doc-folder") .arg(out_dir) .arg("--crate-name") diff --git a/src/tools/compiletest/src/runtest/run_make.rs b/src/tools/compiletest/src/runtest/run_make.rs index 16c46fc13390f..74e6af36ea1dd 100644 --- a/src/tools/compiletest/src/runtest/run_make.rs +++ b/src/tools/compiletest/src/runtest/run_make.rs @@ -21,8 +21,6 @@ impl TestCx<'_> { fn run_rmake_legacy_test(&self) { let cwd = env::current_dir().unwrap(); - let src_root = self.config.src_base.parent().unwrap().parent().unwrap(); - let src_root = cwd.join(&src_root); // FIXME(Zalathar): This should probably be `output_base_dir` to avoid // an unnecessary extra subdirectory, but since legacy Makefile tests @@ -51,7 +49,7 @@ impl TestCx<'_> { .stderr(Stdio::piped()) .env("TARGET", &self.config.target) .env("PYTHON", &self.config.python) - .env("S", src_root) + .env("S", &self.config.src_root) .env("RUST_BUILD_STAGE", &self.config.stage_id) .env("RUSTC", cwd.join(&self.config.rustc_path)) .env("TMPDIR", &tmpdir) @@ -181,28 +179,10 @@ impl TestCx<'_> { // library. // 2. We need to run the recipe binary. - // So we assume the rust-lang/rust project setup looks like the following (our `.` is the - // top-level directory, irrelevant entries to our purposes omitted): - // - // ``` - // . // <- `source_root` - // ├── build/ // <- `build_root` - // ├── compiler/ - // ├── library/ - // ├── src/ - // │ └── tools/ - // │ └── run_make_support/ - // └── tests - // └── run-make/ - // ``` - - // `source_root` is the top-level directory containing the rust-lang/rust checkout. - let source_root = - self.config.find_rust_src_root().expect("could not determine rust source root"); // `self.config.build_base` is actually the build base folder + "test" + test suite name, it // looks like `build//test/run-make`. But we want `build//`. Note // that the `build` directory does not need to be called `build`, nor does it need to be - // under `source_root`, so we must compute it based off of `self.config.build_base`. + // under `src_root`, so we must compute it based off of `self.config.build_base`. let build_root = self.config.build_base.parent().and_then(Path::parent).unwrap().to_path_buf(); @@ -389,10 +369,9 @@ impl TestCx<'_> { .env("TARGET", &self.config.target) // Some tests unfortunately still need Python, so provide path to a Python interpreter. .env("PYTHON", &self.config.python) - // Provide path to checkout root. This is the top-level directory containing - // rust-lang/rust checkout. - .env("SOURCE_ROOT", &source_root) - // Path to the build directory. This is usually the same as `source_root.join("build").join("host")`. + // Provide path to sources root. + .env("SOURCE_ROOT", &self.config.src_root) + // Path to the host build directory. .env("BUILD_ROOT", &build_root) // Provide path to stage-corresponding rustc. .env("RUSTC", &self.config.rustc_path) @@ -408,11 +387,11 @@ impl TestCx<'_> { .env("LLVM_COMPONENTS", &self.config.llvm_components); if let Some(ref cargo) = self.config.cargo_path { - cmd.env("CARGO", source_root.join(cargo)); + cmd.env("CARGO", cargo); } if let Some(ref rustdoc) = self.config.rustdoc_path { - cmd.env("RUSTDOC", source_root.join(rustdoc)); + cmd.env("RUSTDOC", rustdoc); } if let Some(ref node) = self.config.nodejs { diff --git a/src/tools/compiletest/src/runtest/rustdoc.rs b/src/tools/compiletest/src/runtest/rustdoc.rs index 3f33862d2cfe2..2583ae96a6788 100644 --- a/src/tools/compiletest/src/runtest/rustdoc.rs +++ b/src/tools/compiletest/src/runtest/rustdoc.rs @@ -17,9 +17,10 @@ impl TestCx<'_> { if self.props.check_test_line_numbers_match { self.check_rustdoc_test_option(proc_res); } else { - let root = self.config.find_rust_src_root().unwrap(); let mut cmd = Command::new(&self.config.python); - cmd.arg(root.join("src/etc/htmldocck.py")).arg(&out_dir).arg(&self.testpaths.file); + cmd.arg(self.config.src_root.join("src/etc/htmldocck.py")) + .arg(&out_dir) + .arg(&self.testpaths.file); if self.config.bless { cmd.arg("--bless"); } From 6d0c27e9763cdf6c1cad46d7a71ff83468ad6371 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=AE=B8=E6=9D=B0=E5=8F=8B=20Jieyou=20Xu=20=28Joe=29?= <39484203+jieyouxu@users.noreply.github.com> Date: Mon, 3 Feb 2025 16:28:32 +0800 Subject: [PATCH 06/13] bootstrap: pass `--src-root` and `--src-test-suite-root` instead of `--src-base` --- src/bootstrap/src/core/build_steps/test.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/bootstrap/src/core/build_steps/test.rs b/src/bootstrap/src/core/build_steps/test.rs index b3f4a7bad99c2..5fc3e8469bb75 100644 --- a/src/bootstrap/src/core/build_steps/test.rs +++ b/src/bootstrap/src/core/build_steps/test.rs @@ -1762,7 +1762,9 @@ NOTE: if you're sure you want to do this, please open an issue as to why. In the cmd.arg("--coverage-dump-path").arg(coverage_dump); } - cmd.arg("--src-base").arg(builder.src.join("tests").join(suite)); + cmd.arg("--src-root").arg(&builder.src); + cmd.arg("--src-test-suite-root").arg(builder.src.join("tests").join(suite)); + cmd.arg("--build-base").arg(testdir(builder, compiler.host).join(suite)); // When top stage is 0, that means that we're testing an externally provided compiler. From 0713bbcdfa91c02d35c13482ef506906b4035990 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Wed, 19 Feb 2025 05:23:33 +0000 Subject: [PATCH 07/13] Ignore fake borrows for packed field check --- compiler/rustc_middle/src/mir/visit.rs | 8 +++--- .../lint/unaligned_references_fake_borrow.rs | 27 +++++++++++++++++++ 2 files changed, 31 insertions(+), 4 deletions(-) create mode 100644 tests/ui/lint/unaligned_references_fake_borrow.rs diff --git a/compiler/rustc_middle/src/mir/visit.rs b/compiler/rustc_middle/src/mir/visit.rs index 98e8f269c57b7..af09a6d570bab 100644 --- a/compiler/rustc_middle/src/mir/visit.rs +++ b/compiler/rustc_middle/src/mir/visit.rs @@ -1364,13 +1364,13 @@ impl PlaceContext { matches!(self, PlaceContext::MutatingUse(MutatingUseContext::Drop)) } - /// Returns `true` if this place context represents a borrow. + /// Returns `true` if this place context represents a borrow, excluding fake borrows + /// (which are an artifact of borrowck and not actually borrows in runtime MIR). pub fn is_borrow(self) -> bool { matches!( self, - PlaceContext::NonMutatingUse( - NonMutatingUseContext::SharedBorrow | NonMutatingUseContext::FakeBorrow - ) | PlaceContext::MutatingUse(MutatingUseContext::Borrow) + PlaceContext::NonMutatingUse(NonMutatingUseContext::SharedBorrow) + | PlaceContext::MutatingUse(MutatingUseContext::Borrow) ) } diff --git a/tests/ui/lint/unaligned_references_fake_borrow.rs b/tests/ui/lint/unaligned_references_fake_borrow.rs new file mode 100644 index 0000000000000..b0ef8b471caad --- /dev/null +++ b/tests/ui/lint/unaligned_references_fake_borrow.rs @@ -0,0 +1,27 @@ +//@ check-pass + +// Regression test for . + +// Ensure that we don't emit unaligned packed field reference errors for the fake +// borrows that we generate during match lowering. These fake borrows are there to +// ensure in *borrow-checking* that we don't modify the value being matched, but +// they are removed after the MIR is processed by `CleanupPostBorrowck`. + +#[repr(packed)] +pub struct Packed(i32); + +fn f(x: Packed) { + match &x { + Packed(4) => {}, + _ if true => {}, + _ => {} + } + + match x { + Packed(4) => {}, + _ if true => {}, + _ => {} + } +} + +fn main() {} From 241a602d2714d98e65727bce01c7f1e17021b645 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Wed, 5 Feb 2025 17:27:24 +0000 Subject: [PATCH 08/13] Make sure we don't overrun the stack in canonicalizer --- .../src/canonicalizer.rs | 4 +- tests/ui/associated-consts/issue-93775.rs | 67 +++++++++++++++++-- 2 files changed, 65 insertions(+), 6 deletions(-) diff --git a/compiler/rustc_next_trait_solver/src/canonicalizer.rs b/compiler/rustc_next_trait_solver/src/canonicalizer.rs index 7eeed721d5a0f..9cae7f27947b4 100644 --- a/compiler/rustc_next_trait_solver/src/canonicalizer.rs +++ b/compiler/rustc_next_trait_solver/src/canonicalizer.rs @@ -1,6 +1,6 @@ use std::cmp::Ordering; -use rustc_type_ir::data_structures::HashMap; +use rustc_type_ir::data_structures::{HashMap, ensure_sufficient_stack}; use rustc_type_ir::fold::{TypeFoldable, TypeFolder, TypeSuperFoldable}; use rustc_type_ir::inherent::*; use rustc_type_ir::solve::{Goal, QueryInput}; @@ -389,7 +389,7 @@ impl<'a, D: SolverDelegate, I: Interner> Canonicalizer<'a, D, I> { | ty::Alias(_, _) | ty::Bound(_, _) | ty::Error(_) => { - return t.super_fold_with(self); + return ensure_sufficient_stack(|| t.super_fold_with(self)); } }; diff --git a/tests/ui/associated-consts/issue-93775.rs b/tests/ui/associated-consts/issue-93775.rs index 88e88b559870f..a9b8d10a83fbf 100644 --- a/tests/ui/associated-consts/issue-93775.rs +++ b/tests/ui/associated-consts/issue-93775.rs @@ -2,11 +2,13 @@ // Similar to stress testing, the test case requires a larger call stack, // so we ignore rustc's debug assertions. -//@ build-pass -// ignore-tidy-linelength - // Regression for #93775, needs build-pass to test it. +//@ build-pass +//@ revisions: current next +//@ ignore-compare-mode-next-solver (explicit revisions) +//@[next] compile-flags: -Znext-solver + #![recursion_limit = "1001"] use std::marker::PhantomData; @@ -14,7 +16,64 @@ use std::marker::PhantomData; struct Z; struct S(PhantomData); -type Nested = S>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>; +type Nested = S>>>>>>>>>>>>> +>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>; trait AsNum { const NUM: u32; From 31febc684ba8847494b1d1e223e8fc2cc87f3dcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Thu, 20 Feb 2025 19:47:31 +0000 Subject: [PATCH 09/13] Point at type that doesn't implement needed trait ``` error[E0277]: `?` couldn't convert the error: `E: std::error::Error` is not satisfied --> $DIR/bad-question-mark-on-trait-object.rs:7:13 | LL | fn foo() -> Result<(), Box> { | -------------------------------------- required `E: std::error::Error` because of this LL | Ok(bar()?) | -----^ the trait `std::error::Error` is not implemented for `E` | | | this has type `Result<_, E>` | note: `E` needs to implement `std::error::Error` --> $DIR/bad-question-mark-on-trait-object.rs:1:1 | LL | struct E; | ^^^^^^^^ = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait = note: required for `Box` to implement `From` error[E0277]: `?` couldn't convert the error to `X` --> $DIR/bad-question-mark-on-trait-object.rs:18:13 | LL | fn bat() -> Result<(), X> { | ------------- expected `X` because of this LL | Ok(bar()?) | -----^ the trait `From` is not implemented for `X` | | | this can't be annotated with `?` because it has type `Result<_, E>` | note: `X` needs to implement `From` --> $DIR/bad-question-mark-on-trait-object.rs:4:1 | LL | struct X; | ^^^^^^^^ note: alternatively, `E` needs to implement `Into` --> $DIR/bad-question-mark-on-trait-object.rs:1:1 | LL | struct E; | ^^^^^^^^ = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait ``` --- .../traits/fulfillment_errors.rs | 51 +++++++++++++++++++ .../bad-question-mark-on-trait-object.rs | 4 +- .../bad-question-mark-on-trait-object.stderr | 19 ++++++- 3 files changed, 71 insertions(+), 3 deletions(-) diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs index 4004fdd073ce0..c92dfa24f85ed 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs @@ -966,6 +966,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { }; let self_ty = trait_pred.skip_binder().self_ty(); let found_ty = trait_pred.skip_binder().trait_ref.args.get(1).and_then(|a| a.as_type()); + self.note_missing_impl_for_question_mark(err, self_ty, found_ty, trait_pred); let mut prev_ty = self.resolve_vars_if_possible( typeck.expr_ty_adjusted_opt(expr).unwrap_or(Ty::new_misc_error(self.tcx)), @@ -1130,6 +1131,56 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { suggested } + fn note_missing_impl_for_question_mark( + &self, + err: &mut Diag<'_>, + self_ty: Ty<'_>, + found_ty: Option>, + trait_pred: ty::PolyTraitPredicate<'tcx>, + ) { + match (self_ty.kind(), found_ty) { + (ty::Adt(def, _), Some(ty)) + if let ty::Adt(found, _) = ty.kind() + && def.did().is_local() + && found.did().is_local() => + { + err.span_note( + self.tcx.def_span(def.did()), + format!("`{self_ty}` needs to implement `From<{ty}>`"), + ); + err.span_note( + self.tcx.def_span(found.did()), + format!("alternatively, `{ty}` needs to implement `Into<{self_ty}>`"), + ); + } + (ty::Adt(def, _), None) if def.did().is_local() => { + err.span_note( + self.tcx.def_span(def.did()), + format!( + "`{self_ty}` needs to implement `{}`", + trait_pred.skip_binder().trait_ref.print_only_trait_path(), + ), + ); + } + (ty::Adt(def, _), Some(ty)) if def.did().is_local() => { + err.span_note( + self.tcx.def_span(def.did()), + format!("`{self_ty}` needs to implement `From<{ty}>`"), + ); + } + (_, Some(ty)) + if let ty::Adt(def, _) = ty.kind() + && def.did().is_local() => + { + err.span_note( + self.tcx.def_span(def.did()), + format!("`{ty}` needs to implement `Into<{self_ty}>`"), + ); + } + _ => {} + } + } + fn report_const_param_not_wf( &self, ty: Ty<'tcx>, diff --git a/tests/ui/try-trait/bad-question-mark-on-trait-object.rs b/tests/ui/try-trait/bad-question-mark-on-trait-object.rs index 9efac78b3d787..2a0d14b175030 100644 --- a/tests/ui/try-trait/bad-question-mark-on-trait-object.rs +++ b/tests/ui/try-trait/bad-question-mark-on-trait-object.rs @@ -1,5 +1,7 @@ struct E; -struct X; +//~^ NOTE `E` needs to implement `std::error::Error` +//~| NOTE alternatively, `E` needs to implement `Into` +struct X; //~ NOTE `X` needs to implement `From` fn foo() -> Result<(), Box> { //~ NOTE required `E: std::error::Error` because of this Ok(bar()?) diff --git a/tests/ui/try-trait/bad-question-mark-on-trait-object.stderr b/tests/ui/try-trait/bad-question-mark-on-trait-object.stderr index adebc16915d75..dd380850c9ec3 100644 --- a/tests/ui/try-trait/bad-question-mark-on-trait-object.stderr +++ b/tests/ui/try-trait/bad-question-mark-on-trait-object.stderr @@ -1,5 +1,5 @@ error[E0277]: `?` couldn't convert the error: `E: std::error::Error` is not satisfied - --> $DIR/bad-question-mark-on-trait-object.rs:5:13 + --> $DIR/bad-question-mark-on-trait-object.rs:7:13 | LL | fn foo() -> Result<(), Box> { | -------------------------------------- required `E: std::error::Error` because of this @@ -8,11 +8,16 @@ LL | Ok(bar()?) | | | this has type `Result<_, E>` | +note: `E` needs to implement `std::error::Error` + --> $DIR/bad-question-mark-on-trait-object.rs:1:1 + | +LL | struct E; + | ^^^^^^^^ = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait = note: required for `Box` to implement `From` error[E0277]: `?` couldn't convert the error to `X` - --> $DIR/bad-question-mark-on-trait-object.rs:16:13 + --> $DIR/bad-question-mark-on-trait-object.rs:18:13 | LL | fn bat() -> Result<(), X> { | ------------- expected `X` because of this @@ -21,6 +26,16 @@ LL | Ok(bar()?) | | | this can't be annotated with `?` because it has type `Result<_, E>` | +note: `X` needs to implement `From` + --> $DIR/bad-question-mark-on-trait-object.rs:4:1 + | +LL | struct X; + | ^^^^^^^^ +note: alternatively, `E` needs to implement `Into` + --> $DIR/bad-question-mark-on-trait-object.rs:1:1 + | +LL | struct E; + | ^^^^^^^^ = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait error: aborting due to 2 previous errors From a825e37fe4dba0e8b33ed05611ba130d396a5509 Mon Sep 17 00:00:00 2001 From: Lukas Markeffsky <@> Date: Fri, 21 Feb 2025 18:00:56 +0100 Subject: [PATCH 10/13] layout_of: put back not-so-unreachable case --- compiler/rustc_ty_utils/src/layout.rs | 15 ++++++++++- .../ui/layout/gce-rigid-const-in-array-len.rs | 27 +++++++++++++++++++ .../gce-rigid-const-in-array-len.stderr | 17 ++++++++++++ .../layout/unconstrained-param-ice-137308.rs | 18 +++++++++++++ .../unconstrained-param-ice-137308.stderr | 15 +++++++++++ 5 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 tests/ui/layout/gce-rigid-const-in-array-len.rs create mode 100644 tests/ui/layout/gce-rigid-const-in-array-len.stderr create mode 100644 tests/ui/layout/unconstrained-param-ice-137308.rs create mode 100644 tests/ui/layout/unconstrained-param-ice-137308.stderr diff --git a/compiler/rustc_ty_utils/src/layout.rs b/compiler/rustc_ty_utils/src/layout.rs index b6a14d147cad4..c8b3c8a796f3a 100644 --- a/compiler/rustc_ty_utils/src/layout.rs +++ b/compiler/rustc_ty_utils/src/layout.rs @@ -147,12 +147,25 @@ fn extract_const_value<'tcx>( ) -> Result, &'tcx LayoutError<'tcx>> { match ct.kind() { ty::ConstKind::Value(cv) => Ok(cv), - ty::ConstKind::Param(_) | ty::ConstKind::Expr(_) | ty::ConstKind::Unevaluated(_) => { + ty::ConstKind::Param(_) | ty::ConstKind::Expr(_) => { if !ct.has_param() { bug!("failed to normalize const, but it is not generic: {ct:?}"); } Err(error(cx, LayoutError::TooGeneric(ty))) } + ty::ConstKind::Unevaluated(_) => { + let err = if ct.has_param() { + LayoutError::TooGeneric(ty) + } else { + // This case is reachable with unsatisfiable predicates and GCE (which will + // cause anon consts to inherit the unsatisfiable predicates). For example + // if we have an unsatisfiable `u8: Trait` bound, then it's not a compile + // error to mention `[u8; ::CONST]`, but we can't compute its + // layout. + LayoutError::Unknown(ty) + }; + Err(error(cx, err)) + } ty::ConstKind::Infer(_) | ty::ConstKind::Bound(..) | ty::ConstKind::Placeholder(_) diff --git a/tests/ui/layout/gce-rigid-const-in-array-len.rs b/tests/ui/layout/gce-rigid-const-in-array-len.rs new file mode 100644 index 0000000000000..8e57907a2c5ec --- /dev/null +++ b/tests/ui/layout/gce-rigid-const-in-array-len.rs @@ -0,0 +1,27 @@ +//! With `feature(generic_const_exprs)`, anon consts (e.g. length in array types) will +//! inherit their parent's predicates. When combined with `feature(trivial_bounds)`, it +//! is possible to have an unevaluated constant that is rigid, but not generic. +//! +//! This is what happens below: `u8: A` does not hold in the global environment, but +//! with trivial bounds + GCE it it possible that `::B` can appear in an array +//! length without causing a compile error. This constant is *rigid* (i.e. it cannot be +//! normalized further), but it is *not generic* (i.e. it does not depend on any generic +//! parameters). +//! +//! This test ensures that we do not ICE in layout computation when encountering such a +//! constant. + +#![feature(rustc_attrs)] +#![feature(generic_const_exprs)] //~ WARNING: the feature `generic_const_exprs` is incomplete +#![feature(trivial_bounds)] + +#![crate_type = "lib"] + +trait A { + const B: usize; +} + +#[rustc_layout(debug)] +struct S([u8; ::B]) //~ ERROR: the type `[u8; ::B]` has an unknown layout +where + u8: A; diff --git a/tests/ui/layout/gce-rigid-const-in-array-len.stderr b/tests/ui/layout/gce-rigid-const-in-array-len.stderr new file mode 100644 index 0000000000000..6149debdfe88d --- /dev/null +++ b/tests/ui/layout/gce-rigid-const-in-array-len.stderr @@ -0,0 +1,17 @@ +warning: the feature `generic_const_exprs` is incomplete and may not be safe to use and/or cause compiler crashes + --> $DIR/gce-rigid-const-in-array-len.rs:15:12 + | +LL | #![feature(generic_const_exprs)] + | ^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #76560 for more information + = note: `#[warn(incomplete_features)]` on by default + +error: the type `[u8; ::B]` has an unknown layout + --> $DIR/gce-rigid-const-in-array-len.rs:25:1 + | +LL | struct S([u8; ::B]) + | ^^^^^^^^ + +error: aborting due to 1 previous error; 1 warning emitted + diff --git a/tests/ui/layout/unconstrained-param-ice-137308.rs b/tests/ui/layout/unconstrained-param-ice-137308.rs new file mode 100644 index 0000000000000..d460fc64f1f94 --- /dev/null +++ b/tests/ui/layout/unconstrained-param-ice-137308.rs @@ -0,0 +1,18 @@ +//! Regression test for . +//! +//! This used to ICE in layout computation, because `::B` fails to normalize +//! due to the unconstrained param on the impl. + +#![feature(rustc_attrs)] +#![crate_type = "lib"] + +trait A { + const B: usize; +} + +impl A for u8 { //~ ERROR: the type parameter `C` is not constrained + const B: usize = 42; +} + +#[rustc_layout(debug)] +struct S([u8; ::B]); //~ ERROR: the type `[u8; ::B]` has an unknown layout diff --git a/tests/ui/layout/unconstrained-param-ice-137308.stderr b/tests/ui/layout/unconstrained-param-ice-137308.stderr new file mode 100644 index 0000000000000..03ee941d37e13 --- /dev/null +++ b/tests/ui/layout/unconstrained-param-ice-137308.stderr @@ -0,0 +1,15 @@ +error[E0207]: the type parameter `C` is not constrained by the impl trait, self type, or predicates + --> $DIR/unconstrained-param-ice-137308.rs:13:6 + | +LL | impl A for u8 { + | ^ unconstrained type parameter + +error: the type `[u8; ::B]` has an unknown layout + --> $DIR/unconstrained-param-ice-137308.rs:18:1 + | +LL | struct S([u8; ::B]); + | ^^^^^^^^ + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0207`. From 7fea935ec5e2e5ab7f0c86e3b4f89c0da8d646c6 Mon Sep 17 00:00:00 2001 From: Lukas Markeffsky <@> Date: Fri, 21 Feb 2025 18:34:14 +0100 Subject: [PATCH 11/13] don't leave assoc const unnormalized due to unconstrained params --- compiler/rustc_middle/src/traits/mod.rs | 5 ++++- compiler/rustc_traits/src/codegen.rs | 16 +++++++--------- compiler/rustc_ty_utils/src/instance.rs | 1 + .../ui/layout/unconstrained-param-ice-137308.rs | 2 +- .../layout/unconstrained-param-ice-137308.stderr | 2 +- 5 files changed, 14 insertions(+), 12 deletions(-) diff --git a/compiler/rustc_middle/src/traits/mod.rs b/compiler/rustc_middle/src/traits/mod.rs index 53bc9eb7e466c..54cd8cc3efe75 100644 --- a/compiler/rustc_middle/src/traits/mod.rs +++ b/compiler/rustc_middle/src/traits/mod.rs @@ -12,7 +12,7 @@ use std::borrow::Cow; use std::hash::{Hash, Hasher}; use std::sync::Arc; -use rustc_errors::{Applicability, Diag, EmissionGuarantee}; +use rustc_errors::{Applicability, Diag, EmissionGuarantee, ErrorGuaranteed}; use rustc_hir as hir; use rustc_hir::HirId; use rustc_hir::def_id::DefId; @@ -996,4 +996,7 @@ pub enum CodegenObligationError { /// but was included during typeck due to the trivial_bounds feature. Unimplemented, FulfillmentError, + /// The selected impl has unconstrained generic parameters. This will emit an error + /// during impl WF checking. + UnconstrainedParam(ErrorGuaranteed), } diff --git a/compiler/rustc_traits/src/codegen.rs b/compiler/rustc_traits/src/codegen.rs index e5276e6d51585..cc329ca33288c 100644 --- a/compiler/rustc_traits/src/codegen.rs +++ b/compiler/rustc_traits/src/codegen.rs @@ -80,16 +80,14 @@ pub(crate) fn codegen_select_candidate<'tcx>( // but never resolved, causing the return value of a query to contain inference // vars. We do not have a concept for this and will in fact ICE in stable hashing // of the return value. So bail out instead. - match impl_source { - ImplSource::UserDefined(impl_) => { - tcx.dcx().span_delayed_bug( - tcx.def_span(impl_.impl_def_id), - "this impl has unconstrained generic parameters", - ); - } + let guar = match impl_source { + ImplSource::UserDefined(impl_) => tcx.dcx().span_delayed_bug( + tcx.def_span(impl_.impl_def_id), + "this impl has unconstrained generic parameters", + ), _ => unreachable!(), - } - return Err(CodegenObligationError::FulfillmentError); + }; + return Err(CodegenObligationError::UnconstrainedParam(guar)); } Ok(&*tcx.arena.alloc(impl_source)) diff --git a/compiler/rustc_ty_utils/src/instance.rs b/compiler/rustc_ty_utils/src/instance.rs index 8c6e3c86bf5c8..d059d6dcd139c 100644 --- a/compiler/rustc_ty_utils/src/instance.rs +++ b/compiler/rustc_ty_utils/src/instance.rs @@ -112,6 +112,7 @@ fn resolve_associated_item<'tcx>( | CodegenObligationError::Unimplemented | CodegenObligationError::FulfillmentError, ) => return Ok(None), + Err(CodegenObligationError::UnconstrainedParam(guar)) => return Err(guar), }; // Now that we know which impl is being used, we can dispatch to diff --git a/tests/ui/layout/unconstrained-param-ice-137308.rs b/tests/ui/layout/unconstrained-param-ice-137308.rs index d460fc64f1f94..c9b1e0a4b9ec3 100644 --- a/tests/ui/layout/unconstrained-param-ice-137308.rs +++ b/tests/ui/layout/unconstrained-param-ice-137308.rs @@ -15,4 +15,4 @@ impl A for u8 { //~ ERROR: the type parameter `C` is not constrained } #[rustc_layout(debug)] -struct S([u8; ::B]); //~ ERROR: the type `[u8; ::B]` has an unknown layout +struct S([u8; ::B]); //~ ERROR: the type has an unknown layout diff --git a/tests/ui/layout/unconstrained-param-ice-137308.stderr b/tests/ui/layout/unconstrained-param-ice-137308.stderr index 03ee941d37e13..615c131eb9045 100644 --- a/tests/ui/layout/unconstrained-param-ice-137308.stderr +++ b/tests/ui/layout/unconstrained-param-ice-137308.stderr @@ -4,7 +4,7 @@ error[E0207]: the type parameter `C` is not constrained by the impl trait, self LL | impl A for u8 { | ^ unconstrained type parameter -error: the type `[u8; ::B]` has an unknown layout +error: the type has an unknown layout --> $DIR/unconstrained-param-ice-137308.rs:18:1 | LL | struct S([u8; ::B]); From 72bd174c43406068727e17cbf6fc2f4e1a36f6af Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Mon, 3 Feb 2025 02:38:56 +0000 Subject: [PATCH 12/13] Do not deduplicate list of associated types provided by dyn principal --- .../src/hir_ty_lowering/dyn_compatibility.rs | 127 ++++++++++++++---- compiler/rustc_middle/src/ty/relate.rs | 18 +-- compiler/rustc_type_ir/src/elaborate.rs | 15 +-- tests/crashes/125957.rs | 20 --- tests/crashes/132330.rs | 28 ---- .../associated-types-overridden-binding-2.rs | 2 +- ...sociated-types-overridden-binding-2.stderr | 14 +- .../associated-types-overridden-binding.rs | 1 + ...associated-types-overridden-binding.stderr | 13 +- .../closures/deduce-from-object-supertrait.rs | 18 +++ .../multiple-supers-should-work.rs | 21 +++ .../crash-due-to-projections-modulo-norm.rs} | 17 ++- .../incomplete-multiple-super-projection.rs | 32 +++++ ...ncomplete-multiple-super-projection.stderr | 12 ++ .../infer-shadows-implied-projection.rs} | 2 +- tests/ui/traits/object/outlives-super-proj.rs | 24 ++++ tests/ui/traits/object/pretty.stderr | 6 +- tests/ui/traits/object/redundant.rs | 12 ++ 18 files changed, 265 insertions(+), 117 deletions(-) delete mode 100644 tests/crashes/125957.rs delete mode 100644 tests/crashes/132330.rs create mode 100644 tests/ui/closures/deduce-from-object-supertrait.rs create mode 100644 tests/ui/dyn-compatibility/multiple-supers-should-work.rs rename tests/{crashes/126944.rs => ui/traits/object/crash-due-to-projections-modulo-norm.rs} (77%) create mode 100644 tests/ui/traits/object/incomplete-multiple-super-projection.rs create mode 100644 tests/ui/traits/object/incomplete-multiple-super-projection.stderr rename tests/{crashes/79590.rs => ui/traits/object/infer-shadows-implied-projection.rs} (92%) create mode 100644 tests/ui/traits/object/outlives-super-proj.rs create mode 100644 tests/ui/traits/object/redundant.rs diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/dyn_compatibility.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/dyn_compatibility.rs index 830dca0d3cd53..3eb4945ebf80e 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/dyn_compatibility.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/dyn_compatibility.rs @@ -1,4 +1,4 @@ -use rustc_data_structures::fx::{FxHashSet, FxIndexSet}; +use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet}; use rustc_errors::codes::*; use rustc_errors::struct_span_code_err; use rustc_hir as hir; @@ -58,9 +58,9 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { } } - let (trait_bounds, mut projection_bounds) = + let (elaborated_trait_bounds, elaborated_projection_bounds) = traits::expand_trait_aliases(tcx, user_written_bounds.iter().copied()); - let (regular_traits, mut auto_traits): (Vec<_>, Vec<_>) = trait_bounds + let (regular_traits, mut auto_traits): (Vec<_>, Vec<_>) = elaborated_trait_bounds .into_iter() .partition(|(trait_ref, _)| !tcx.trait_is_auto(trait_ref.def_id())); @@ -103,29 +103,81 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { } } + // Map the projection bounds onto a key that makes it easy to remove redundant + // bounds that are constrained by supertraits of the principal def id. + // + // Also make sure we detect conflicting bounds from expanding a trait alias and + // also specifying it manually, like: + // ``` + // type Alias = Trait; + // let _: &dyn Alias = /* ... */; + // ``` + let mut projection_bounds = FxIndexMap::default(); + for (proj, proj_span) in elaborated_projection_bounds { + let key = ( + proj.skip_binder().projection_term.def_id, + tcx.anonymize_bound_vars( + proj.map_bound(|proj| proj.projection_term.trait_ref(tcx)), + ), + ); + if let Some((old_proj, old_proj_span)) = + projection_bounds.insert(key, (proj, proj_span)) + && tcx.anonymize_bound_vars(proj) != tcx.anonymize_bound_vars(old_proj) + { + let item = tcx.item_name(proj.item_def_id()); + self.dcx() + .struct_span_err( + span, + format!( + "conflicting associated type bounds for `{item}` when \ + expanding trait alias" + ), + ) + .with_span_label( + old_proj_span, + format!("`{item}` is specified to be `{}` here", old_proj.term()), + ) + .with_span_label( + proj_span, + format!("`{item}` is specified to be `{}` here", proj.term()), + ) + .emit(); + } + } + let principal_trait = regular_traits.into_iter().next(); - let mut needed_associated_types = FxIndexSet::default(); - if let Some((principal_trait, spans)) = &principal_trait { - let pred: ty::Predicate<'tcx> = (*principal_trait).upcast(tcx); - for ClauseWithSupertraitSpan { pred, supertrait_span } in traits::elaborate( + let mut needed_associated_types = vec![]; + if let Some((principal_trait, ref spans)) = principal_trait { + let principal_trait = principal_trait.map_bound(|trait_pred| { + assert_eq!(trait_pred.polarity, ty::PredicatePolarity::Positive); + trait_pred.trait_ref + }); + + for ClauseWithSupertraitSpan { clause, supertrait_span } in traits::elaborate( tcx, - [ClauseWithSupertraitSpan::new(pred, *spans.last().unwrap())], + [ClauseWithSupertraitSpan::new( + ty::TraitRef::identity(tcx, principal_trait.def_id()).upcast(tcx), + *spans.last().unwrap(), + )], ) .filter_only_self() { - debug!("observing object predicate `{pred:?}`"); + let clause = clause.instantiate_supertrait(tcx, principal_trait); + debug!("observing object predicate `{clause:?}`"); - let bound_predicate = pred.kind(); + let bound_predicate = clause.kind(); match bound_predicate.skip_binder() { - ty::PredicateKind::Clause(ty::ClauseKind::Trait(pred)) => { + ty::ClauseKind::Trait(pred) => { // FIXME(negative_bounds): Handle this correctly... let trait_ref = tcx.anonymize_bound_vars(bound_predicate.rebind(pred.trait_ref)); needed_associated_types.extend( - tcx.associated_items(trait_ref.def_id()) + tcx.associated_items(pred.trait_ref.def_id) .in_definition_order() + // We only care about associated types. .filter(|item| item.kind == ty::AssocKind::Type) + // No RPITITs -- even with `async_fn_in_dyn_trait`, they are implicit. .filter(|item| !item.is_impl_trait_in_trait()) // If the associated type has a `where Self: Sized` bound, // we do not need to constrain the associated type. @@ -133,7 +185,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { .map(|item| (item.def_id, trait_ref)), ); } - ty::PredicateKind::Clause(ty::ClauseKind::Projection(pred)) => { + ty::ClauseKind::Projection(pred) => { let pred = bound_predicate.rebind(pred); // A `Self` within the original bound will be instantiated with a // `trait_object_dummy_self`, so check for that. @@ -161,8 +213,15 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { // `dyn MyTrait`, which is uglier but works. See // the discussion in #56288 for alternatives. if !references_self { - // Include projections defined on supertraits. - projection_bounds.push((pred, supertrait_span)); + let key = ( + pred.skip_binder().projection_term.def_id, + tcx.anonymize_bound_vars( + pred.map_bound(|proj| proj.projection_term.trait_ref(tcx)), + ), + ); + if !projection_bounds.contains_key(&key) { + projection_bounds.insert(key, (pred, supertrait_span)); + } } self.check_elaborated_projection_mentions_input_lifetimes( @@ -182,12 +241,8 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { // types that we expect to be provided by the user, so the following loop // removes all the associated types that have a corresponding `Projection` // clause, either from expanding trait aliases or written by the user. - for &(projection_bound, span) in &projection_bounds { + for &(projection_bound, span) in projection_bounds.values() { let def_id = projection_bound.item_def_id(); - let trait_ref = tcx.anonymize_bound_vars( - projection_bound.map_bound(|p| p.projection_term.trait_ref(tcx)), - ); - needed_associated_types.swap_remove(&(def_id, trait_ref)); if tcx.generics_require_sized_self(def_id) { tcx.emit_node_span_lint( UNUSED_ASSOCIATED_TYPE_BOUNDS, @@ -198,9 +253,22 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { } } + let mut missing_assoc_types = FxIndexSet::default(); + let projection_bounds: Vec<_> = needed_associated_types + .into_iter() + .filter_map(|key| { + if let Some(assoc) = projection_bounds.get(&key) { + Some(*assoc) + } else { + missing_assoc_types.insert(key); + None + } + }) + .collect(); + if let Err(guar) = self.check_for_required_assoc_tys( principal_trait.as_ref().map_or(smallvec![], |(_, spans)| spans.clone()), - needed_associated_types, + missing_assoc_types, potential_assoc_types, hir_bounds, ) { @@ -266,7 +334,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { }) }); - let existential_projections = projection_bounds.iter().map(|(bound, _)| { + let existential_projections = projection_bounds.into_iter().map(|(bound, _)| { bound.map_bound(|mut b| { assert_eq!(b.projection_term.self_ty(), dummy_self); @@ -291,12 +359,16 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { }) }); - let auto_trait_predicates = auto_traits.into_iter().map(|(trait_pred, _)| { - assert_eq!(trait_pred.polarity(), ty::PredicatePolarity::Positive); - assert_eq!(trait_pred.self_ty().skip_binder(), dummy_self); + let mut auto_trait_predicates: Vec<_> = auto_traits + .into_iter() + .map(|(trait_pred, _)| { + assert_eq!(trait_pred.polarity(), ty::PredicatePolarity::Positive); + assert_eq!(trait_pred.self_ty().skip_binder(), dummy_self); - ty::Binder::dummy(ty::ExistentialPredicate::AutoTrait(trait_pred.def_id())) - }); + ty::Binder::dummy(ty::ExistentialPredicate::AutoTrait(trait_pred.def_id())) + }) + .collect(); + auto_trait_predicates.dedup(); // N.b. principal, projections, auto traits // FIXME: This is actually wrong with multiple principals in regards to symbol mangling @@ -306,7 +378,6 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { .chain(auto_trait_predicates) .collect::>(); v.sort_by(|a, b| a.skip_binder().stable_cmp(tcx, &b.skip_binder())); - v.dedup(); let existential_predicates = tcx.mk_poly_existential_predicates(&v); // Use explicitly-specified region bound, unless the bound is missing. diff --git a/compiler/rustc_middle/src/ty/relate.rs b/compiler/rustc_middle/src/ty/relate.rs index afdec7a86d445..1d1aad916bc21 100644 --- a/compiler/rustc_middle/src/ty/relate.rs +++ b/compiler/rustc_middle/src/ty/relate.rs @@ -79,20 +79,11 @@ impl<'tcx> Relate> for &'tcx ty::List RelateResult<'tcx, Self> { let tcx = relation.cx(); - - // FIXME: this is wasteful, but want to do a perf run to see how slow it is. - // We need to perform this deduplication as we sometimes generate duplicate projections - // in `a`. - let mut a_v: Vec<_> = a.into_iter().collect(); - let mut b_v: Vec<_> = b.into_iter().collect(); - a_v.dedup(); - b_v.dedup(); - if a_v.len() != b_v.len() { + if a.len() != b.len() { return Err(TypeError::ExistentialMismatch(ExpectedFound::new(a, b))); } - - let v = iter::zip(a_v, b_v).map(|(ep_a, ep_b)| { - match (ep_a.skip_binder(), ep_b.skip_binder()) { + let v = + iter::zip(a, b).map(|(ep_a, ep_b)| match (ep_a.skip_binder(), ep_b.skip_binder()) { (ty::ExistentialPredicate::Trait(a), ty::ExistentialPredicate::Trait(b)) => { Ok(ep_a.rebind(ty::ExistentialPredicate::Trait( relation.relate(ep_a.rebind(a), ep_b.rebind(b))?.skip_binder(), @@ -109,8 +100,7 @@ impl<'tcx> Relate> for &'tcx ty::List Ok(ep_a.rebind(ty::ExistentialPredicate::AutoTrait(a))), _ => Err(TypeError::ExistentialMismatch(ExpectedFound::new(a, b))), - } - }); + }); tcx.mk_poly_existential_predicates_from_iter(v) } } diff --git a/compiler/rustc_type_ir/src/elaborate.rs b/compiler/rustc_type_ir/src/elaborate.rs index 923b74abdfd2f..b11bcff1d8bbb 100644 --- a/compiler/rustc_type_ir/src/elaborate.rs +++ b/compiler/rustc_type_ir/src/elaborate.rs @@ -44,25 +44,22 @@ pub trait Elaboratable { } pub struct ClauseWithSupertraitSpan { - pub pred: I::Predicate, + pub clause: I::Clause, // Span of the supertrait predicatae that lead to this clause. pub supertrait_span: I::Span, } impl ClauseWithSupertraitSpan { - pub fn new(pred: I::Predicate, span: I::Span) -> Self { - ClauseWithSupertraitSpan { pred, supertrait_span: span } + pub fn new(clause: I::Clause, span: I::Span) -> Self { + ClauseWithSupertraitSpan { clause, supertrait_span: span } } } impl Elaboratable for ClauseWithSupertraitSpan { fn predicate(&self) -> ::Predicate { - self.pred + self.clause.as_predicate() } fn child(&self, clause: ::Clause) -> Self { - ClauseWithSupertraitSpan { - pred: clause.as_predicate(), - supertrait_span: self.supertrait_span, - } + ClauseWithSupertraitSpan { clause, supertrait_span: self.supertrait_span } } fn child_with_derived_cause( @@ -72,7 +69,7 @@ impl Elaboratable for ClauseWithSupertraitSpan { _parent_trait_pred: crate::Binder>, _index: usize, ) -> Self { - ClauseWithSupertraitSpan { pred: clause.as_predicate(), supertrait_span } + ClauseWithSupertraitSpan { clause, supertrait_span } } } diff --git a/tests/crashes/125957.rs b/tests/crashes/125957.rs deleted file mode 100644 index e3abe5262eb90..0000000000000 --- a/tests/crashes/125957.rs +++ /dev/null @@ -1,20 +0,0 @@ -//@ known-bug: rust-lang/rust#125957 -#![feature(generic_const_exprs)] -#![allow(incomplete_features)] -#![feature(associated_const_equality)] - -pub struct Equal(); - -pub enum ParseMode { - Raw, -} -pub trait Parse { - const PARSE_MODE: ParseMode; -} -pub trait RenderRaw: Parse {} - -trait GenericVec { - fn unwrap() -> dyn RenderRaw; -} - -fn main() {} diff --git a/tests/crashes/132330.rs b/tests/crashes/132330.rs deleted file mode 100644 index 3432685749d9d..0000000000000 --- a/tests/crashes/132330.rs +++ /dev/null @@ -1,28 +0,0 @@ -//@ known-bug: #132330 -//@compile-flags: -Znext-solver=globally - -trait Service { - type S; -} - -trait Framing { - type F; -} - -impl Framing for () { - type F = (); -} - -trait HttpService: Service {} - -type BoxService = Box>; - -fn build_server BoxService>(_: F) {} - -fn make_server() -> Box> { - unimplemented!() -} - -fn main() { - build_server(|| make_server()) -} diff --git a/tests/ui/associated-types/associated-types-overridden-binding-2.rs b/tests/ui/associated-types/associated-types-overridden-binding-2.rs index fed60ccf089d0..247724eaaf112 100644 --- a/tests/ui/associated-types/associated-types-overridden-binding-2.rs +++ b/tests/ui/associated-types/associated-types-overridden-binding-2.rs @@ -4,5 +4,5 @@ trait I32Iterator = Iterator; fn main() { let _: &dyn I32Iterator = &vec![42].into_iter(); - //~^ ERROR expected `IntoIter` to be an iterator that yields `i32`, but it yields `u32` + //~^ ERROR conflicting associated type bounds } diff --git a/tests/ui/associated-types/associated-types-overridden-binding-2.stderr b/tests/ui/associated-types/associated-types-overridden-binding-2.stderr index 4dfd275a19058..71a4a2610aac4 100644 --- a/tests/ui/associated-types/associated-types-overridden-binding-2.stderr +++ b/tests/ui/associated-types/associated-types-overridden-binding-2.stderr @@ -1,11 +1,13 @@ -error[E0271]: expected `IntoIter` to be an iterator that yields `i32`, but it yields `u32` - --> $DIR/associated-types-overridden-binding-2.rs:6:43 +error: conflicting associated type bounds for `Item` when expanding trait alias + --> $DIR/associated-types-overridden-binding-2.rs:6:13 | +LL | trait I32Iterator = Iterator; + | ---------- `Item` is specified to be `i32` here +... LL | let _: &dyn I32Iterator = &vec![42].into_iter(); - | ^^^^^^^^^^^^^^^^^^^^^ expected `i32`, found `u32` - | - = note: required for the cast from `&std::vec::IntoIter` to `&dyn Iterator` + | ^^^^^^^^^^^^^^^^----------^ + | | + | `Item` is specified to be `u32` here error: aborting due to 1 previous error -For more information about this error, try `rustc --explain E0271`. diff --git a/tests/ui/associated-types/associated-types-overridden-binding.rs b/tests/ui/associated-types/associated-types-overridden-binding.rs index 9a64a06c31bad..333a3e30c7dcf 100644 --- a/tests/ui/associated-types/associated-types-overridden-binding.rs +++ b/tests/ui/associated-types/associated-types-overridden-binding.rs @@ -8,4 +8,5 @@ trait U32Iterator = I32Iterator; //~ ERROR type annotations needed fn main() { let _: &dyn I32Iterator; + //~^ ERROR conflicting associated type bounds } diff --git a/tests/ui/associated-types/associated-types-overridden-binding.stderr b/tests/ui/associated-types/associated-types-overridden-binding.stderr index dc087e4185fb6..3b20015dfcab3 100644 --- a/tests/ui/associated-types/associated-types-overridden-binding.stderr +++ b/tests/ui/associated-types/associated-types-overridden-binding.stderr @@ -22,6 +22,17 @@ note: required by a bound in `I32Iterator` LL | trait I32Iterator = Iterator; | ^^^^^^^^^^ required by this bound in `I32Iterator` -error: aborting due to 2 previous errors +error: conflicting associated type bounds for `Item` when expanding trait alias + --> $DIR/associated-types-overridden-binding.rs:10:13 + | +LL | trait I32Iterator = Iterator; + | ---------- `Item` is specified to be `i32` here +... +LL | let _: &dyn I32Iterator; + | ^^^^^^^^^^^^^^^^----------^ + | | + | `Item` is specified to be `u32` here + +error: aborting due to 3 previous errors For more information about this error, try `rustc --explain E0284`. diff --git a/tests/ui/closures/deduce-from-object-supertrait.rs b/tests/ui/closures/deduce-from-object-supertrait.rs new file mode 100644 index 0000000000000..aff750dc62ea3 --- /dev/null +++ b/tests/ui/closures/deduce-from-object-supertrait.rs @@ -0,0 +1,18 @@ +//@ check-pass + +// This test checks that we look at consider the super traits of trait objects +// when deducing closure signatures. + +trait Foo: Fn(Bar) {} +impl Foo for T where T: Fn(Bar) {} + +struct Bar; +impl Bar { + fn bar(&self) {} +} + +fn main() { + let x: &dyn Foo = &|x| { + x.bar(); + }; +} diff --git a/tests/ui/dyn-compatibility/multiple-supers-should-work.rs b/tests/ui/dyn-compatibility/multiple-supers-should-work.rs new file mode 100644 index 0000000000000..6f381da9a2200 --- /dev/null +++ b/tests/ui/dyn-compatibility/multiple-supers-should-work.rs @@ -0,0 +1,21 @@ +//@ check-pass + +// We previously incorrectly deduplicated the list of projection bounds +// of trait objects, causing us to incorrectly reject this code, cc #136458. + +trait Sup { + type Assoc; +} + +impl Sup for () { + type Assoc = T; +} + +trait Trait: Sup + Sup {} + +impl Trait for () {} + +fn main() { + let x: &dyn Trait<(), _> = &(); + let y: &dyn Trait<_, ()> = x; +} diff --git a/tests/crashes/126944.rs b/tests/ui/traits/object/crash-due-to-projections-modulo-norm.rs similarity index 77% rename from tests/crashes/126944.rs rename to tests/ui/traits/object/crash-due-to-projections-modulo-norm.rs index c0c5622e26020..b1f7c4a600021 100644 --- a/tests/crashes/126944.rs +++ b/tests/ui/traits/object/crash-due-to-projections-modulo-norm.rs @@ -1,9 +1,12 @@ -//@ known-bug: rust-lang/rust#126944 +//@ check-pass + +// Regression test for #126944. + // Step 1: Create two names for a single type: `Thing` and `AlsoThing` struct Thing; struct Dummy; -pub trait DummyTrait { +trait DummyTrait { type DummyType; } impl DummyTrait for Dummy { @@ -13,7 +16,7 @@ type AlsoThing = ::DummyType; // Step 2: Create names for a single trait object type: `TraitObject` and `AlsoTraitObject` -pub trait SomeTrait { +trait SomeTrait { type Item; } type TraitObject = dyn SomeTrait; @@ -21,12 +24,12 @@ type AlsoTraitObject = dyn SomeTrait; // Step 3: Force the compiler to check whether the two names are the same type -pub trait Supertrait { +trait Supertrait { type Foo; } -pub trait Subtrait: Supertrait {} +trait Subtrait: Supertrait {} -pub trait HasOutput { +trait HasOutput { type Output; } @@ -36,3 +39,5 @@ where { todo!() } + +fn main() {} diff --git a/tests/ui/traits/object/incomplete-multiple-super-projection.rs b/tests/ui/traits/object/incomplete-multiple-super-projection.rs new file mode 100644 index 0000000000000..c7294eca4bdbe --- /dev/null +++ b/tests/ui/traits/object/incomplete-multiple-super-projection.rs @@ -0,0 +1,32 @@ +// Regression test for #133361. + +trait Sup { + type Assoc; +} + +impl Sup for () { + type Assoc = T; +} +impl Dyn for () {} + +trait Dyn: Sup + Sup {} + +trait Trait { + type Assoc; +} +impl Trait for dyn Dyn<(), ()> { + type Assoc = &'static str; +} +impl Trait for dyn Dyn { +//~^ ERROR conflicting implementations of trait `Trait` for type `(dyn Dyn<(), ()> + 'static)` + type Assoc = usize; +} + +fn call(x: usize) -> as Trait>::Assoc { + x +} + +fn main() { + let x: &'static str = call::<(), ()>(0xDEADBEEF); + println!("{x}"); +} diff --git a/tests/ui/traits/object/incomplete-multiple-super-projection.stderr b/tests/ui/traits/object/incomplete-multiple-super-projection.stderr new file mode 100644 index 0000000000000..b4271f70ed055 --- /dev/null +++ b/tests/ui/traits/object/incomplete-multiple-super-projection.stderr @@ -0,0 +1,12 @@ +error[E0119]: conflicting implementations of trait `Trait` for type `(dyn Dyn<(), ()> + 'static)` + --> $DIR/incomplete-multiple-super-projection.rs:20:1 + | +LL | impl Trait for dyn Dyn<(), ()> { + | ------------------------------ first implementation here +... +LL | impl Trait for dyn Dyn { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `(dyn Dyn<(), ()> + 'static)` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0119`. diff --git a/tests/crashes/79590.rs b/tests/ui/traits/object/infer-shadows-implied-projection.rs similarity index 92% rename from tests/crashes/79590.rs rename to tests/ui/traits/object/infer-shadows-implied-projection.rs index b73864cce2349..628912c54faaa 100644 --- a/tests/crashes/79590.rs +++ b/tests/ui/traits/object/infer-shadows-implied-projection.rs @@ -1,4 +1,4 @@ -//@ known-bug: #79590 +//@ check-pass trait Database: Restriction {} diff --git a/tests/ui/traits/object/outlives-super-proj.rs b/tests/ui/traits/object/outlives-super-proj.rs new file mode 100644 index 0000000000000..15b67d9ab68e7 --- /dev/null +++ b/tests/ui/traits/object/outlives-super-proj.rs @@ -0,0 +1,24 @@ +//@ check-pass + +// Make sure that we still deduce outlives bounds from supertrait projections +// and require them for well-formedness. + +trait Trait { + type Assoc; +} + +trait Bar { + type Assoc; +} + +trait Foo<'a, T: 'a>: Bar { + +} + +fn outlives<'a, T: 'a>() {} + +fn implied_outlives<'a, T: Trait>(x: &dyn Foo<'a, T::Assoc>) { + outlives::<'a, T::Assoc>(); +} + +fn main() {} diff --git a/tests/ui/traits/object/pretty.stderr b/tests/ui/traits/object/pretty.stderr index 2f9fdf151f08c..37fe142951d89 100644 --- a/tests/ui/traits/object/pretty.stderr +++ b/tests/ui/traits/object/pretty.stderr @@ -154,12 +154,12 @@ error[E0308]: mismatched types --> $DIR/pretty.rs:41:56 | LL | fn dyn_has_gat(x: &dyn HasGat = ()>) { x } - | - ^ expected `()`, found `&dyn HasGat = ()>` + | - ^ expected `()`, found `&dyn HasGat` | | - | help: try adding a return type: `-> &dyn HasGat = ()>` + | help: try adding a return type: `-> &dyn HasGat` | = note: expected unit type `()` - found reference `&dyn HasGat = ()>` + found reference `&dyn HasGat` error: aborting due to 14 previous errors; 1 warning emitted diff --git a/tests/ui/traits/object/redundant.rs b/tests/ui/traits/object/redundant.rs new file mode 100644 index 0000000000000..be07b15713898 --- /dev/null +++ b/tests/ui/traits/object/redundant.rs @@ -0,0 +1,12 @@ +//@ check-pass + +trait Foo: Bar {} +trait Bar { + type Out; +} + +fn w(x: &dyn Foo) { + let x: &dyn Foo = x; +} + +fn main() {} From a2a0cfe82563146325674b8d437f9f9f6e703703 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Wed, 5 Feb 2025 00:43:32 +0000 Subject: [PATCH 13/13] Assert that we always construct dyn types with the right number of projections --- compiler/rustc_middle/src/ty/relate.rs | 3 +++ compiler/rustc_middle/src/ty/sty.rs | 30 +++++++++++++++++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_middle/src/ty/relate.rs b/compiler/rustc_middle/src/ty/relate.rs index 1d1aad916bc21..839c1c346a47b 100644 --- a/compiler/rustc_middle/src/ty/relate.rs +++ b/compiler/rustc_middle/src/ty/relate.rs @@ -79,6 +79,9 @@ impl<'tcx> Relate> for &'tcx ty::List RelateResult<'tcx, Self> { let tcx = relation.cx(); + // Fast path for when the auto traits do not match, or if the principals + // are from different traits and therefore the projections definitely don't + // match up. if a.len() != b.len() { return Err(TypeError::ExistentialMismatch(ExpectedFound::new(a, b))); } diff --git a/compiler/rustc_middle/src/ty/sty.rs b/compiler/rustc_middle/src/ty/sty.rs index 8a31b2960188e..9eda760870716 100644 --- a/compiler/rustc_middle/src/ty/sty.rs +++ b/compiler/rustc_middle/src/ty/sty.rs @@ -18,7 +18,7 @@ use rustc_macros::{HashStable, TyDecodable, TyEncodable, TypeFoldable, extension use rustc_span::{DUMMY_SP, Span, Symbol, sym}; use rustc_type_ir::TyKind::*; use rustc_type_ir::visit::TypeVisitableExt; -use rustc_type_ir::{self as ir, BoundVar, CollectAndApply, DynKind}; +use rustc_type_ir::{self as ir, BoundVar, CollectAndApply, DynKind, elaborate}; use tracing::instrument; use ty::util::{AsyncDropGlueMorphology, IntTypeExt}; @@ -720,6 +720,34 @@ impl<'tcx> Ty<'tcx> { reg: ty::Region<'tcx>, repr: DynKind, ) -> Ty<'tcx> { + if cfg!(debug_assertions) { + let projection_count = obj.projection_bounds().count(); + let expected_count: usize = obj + .principal_def_id() + .into_iter() + .flat_map(|principal_def_id| { + // NOTE: This should agree with `needed_associated_types` in + // dyn trait lowering, or else we'll have ICEs. + elaborate::supertraits( + tcx, + ty::Binder::dummy(ty::TraitRef::identity(tcx, principal_def_id)), + ) + .map(|principal| { + tcx.associated_items(principal.def_id()) + .in_definition_order() + .filter(|item| item.kind == ty::AssocKind::Type) + .filter(|item| !item.is_impl_trait_in_trait()) + .filter(|item| !tcx.generics_require_sized_self(item.def_id)) + .count() + }) + }) + .sum(); + assert_eq!( + projection_count, expected_count, + "expected {obj:?} to have {expected_count} projections, \ + but it has {projection_count}" + ); + } Ty::new(tcx, Dynamic(obj, reg, repr)) }