From b77739ad815abef544be28928d7631001e6bfcdc Mon Sep 17 00:00:00 2001 From: Shoyu Vanilla Date: Sat, 14 Mar 2026 22:47:42 +0900 Subject: [PATCH] Do not shallow resolve to root var while fudging --- compiler/rustc_hir_typeck/src/expr.rs | 2 +- .../rustc_hir_typeck/src/fn_ctxt/checks.rs | 7 +---- compiler/rustc_infer/src/infer/mod.rs | 30 +++++++++++++++++++ compiler/rustc_infer/src/infer/resolve.rs | 18 +++++++++-- .../rustc_infer/src/infer/snapshot/fudge.rs | 2 +- .../input-ty-closure-param-fn-trait-bounds.rs | 17 +++++++++++ .../input-ty-higher-ranked-fn-trait.rs | 21 +++++++++++++ 7 files changed, 86 insertions(+), 11 deletions(-) create mode 100644 tests/ui/coercion/fudge-inference/input-ty-closure-param-fn-trait-bounds.rs create mode 100644 tests/ui/coercion/fudge-inference/input-ty-higher-ranked-fn-trait.rs diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index 17c7c4b76b2d..6b77169994a0 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -1879,7 +1879,7 @@ fn check_expr_struct_fields( if !ocx.try_evaluate_obligations().is_empty() { return Err(TypeError::Mismatch); } - Ok(self.resolve_vars_if_possible(adt_ty)) + Ok(adt_ty) }) .ok() }); diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs index 9faa75e18480..166c1b79576c 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs @@ -276,12 +276,7 @@ pub(in super::super) fn check_argument_types( // Record all the argument types, with the args // produced from the above subtyping unification. - Ok(Some( - formal_input_tys - .iter() - .map(|&ty| self.resolve_vars_if_possible(ty)) - .collect(), - )) + Ok(Some(formal_input_tys.to_vec())) }) .ok() }) diff --git a/compiler/rustc_infer/src/infer/mod.rs b/compiler/rustc_infer/src/infer/mod.rs index b57306536260..10e7b1e72f44 100644 --- a/compiler/rustc_infer/src/infer/mod.rs +++ b/compiler/rustc_infer/src/infer/mod.rs @@ -1250,6 +1250,36 @@ pub fn resolve_vars_if_possible(&self, value: T) -> T value.fold_with(&mut r) } + /// Normally, we shallow-resolve unresolved type variables to their root + /// variables. This is mainly done for performance reasons, and in most + /// cases resolving to the root variable (instead of the variable itself) + /// does not affect type inference. + /// + /// However, there is an exceptional case: *fudging*. Fudging is intended + /// to guide inference rather than impose hard requirements. But our current + /// handling here is somewhat janky. + /// + /// In particular, inference variables that are considered equal within the + /// fudging scope may not remain equal outside of it. This makes it observable + /// which inference variable we resolve to. For backwards compatibility, we + /// avoid resolving to the root variable by using this function inside the + /// fudge instead of [`InferCtxt::resolve_vars_if_possible`]. + /// + /// See #153869 for more details. + pub fn resolve_vars_if_possible_for_fudging(&self, value: T) -> T + where + T: TypeFoldable>, + { + if let Err(guar) = value.error_reported() { + self.set_tainted_by_errors(guar); + } + if !value.has_non_region_infer() { + return value; + } + let mut r = resolve::OpportunisticVarResolver::new_for_fudging(self); + value.fold_with(&mut r) + } + pub fn resolve_numeric_literals_with_default(&self, value: T) -> T where T: TypeFoldable>, diff --git a/compiler/rustc_infer/src/infer/resolve.rs b/compiler/rustc_infer/src/infer/resolve.rs index 13df23a39b96..917d7b6653e0 100644 --- a/compiler/rustc_infer/src/infer/resolve.rs +++ b/compiler/rustc_infer/src/infer/resolve.rs @@ -17,6 +17,9 @@ /// points for correctness. pub struct OpportunisticVarResolver<'a, 'tcx> { infcx: &'a InferCtxt<'tcx>, + /// If true, we don't resolve ty/const vars to their roots. + /// See comments on [`InferCtxt::resolve_vars_if_possible_for_fudging`] + for_fudging: bool, /// We're able to use a cache here as the folder does /// not have any mutable state. cache: DelayedMap, Ty<'tcx>>, @@ -25,7 +28,12 @@ pub struct OpportunisticVarResolver<'a, 'tcx> { impl<'a, 'tcx> OpportunisticVarResolver<'a, 'tcx> { #[inline] pub fn new(infcx: &'a InferCtxt<'tcx>) -> Self { - OpportunisticVarResolver { infcx, cache: Default::default() } + OpportunisticVarResolver { infcx, for_fudging: false, cache: Default::default() } + } + + #[inline] + pub fn new_for_fudging(infcx: &'a InferCtxt<'tcx>) -> Self { + OpportunisticVarResolver { infcx, for_fudging: true, cache: Default::default() } } } @@ -43,6 +51,7 @@ fn fold_ty(&mut self, t: Ty<'tcx>) -> Ty<'tcx> { } else { let shallow = self.infcx.shallow_resolve(t); let res = shallow.super_fold_with(self); + let res = if self.for_fudging && res.is_ty_var() { t } else { res }; assert!(self.cache.insert(t, res)); res } @@ -52,8 +61,11 @@ fn fold_const(&mut self, ct: Const<'tcx>) -> Const<'tcx> { if !ct.has_non_region_infer() { ct // micro-optimize -- if there is nothing in this const that this fold affects... } else { - let ct = self.infcx.shallow_resolve_const(ct); - ct.super_fold_with(self) + let res = self.infcx.shallow_resolve_const(ct); + if self.for_fudging && res.is_ct_infer() { + return ct; + }; + res.super_fold_with(self) } } diff --git a/compiler/rustc_infer/src/infer/snapshot/fudge.rs b/compiler/rustc_infer/src/infer/snapshot/fudge.rs index 6709c822dc7b..56cae2c1e396 100644 --- a/compiler/rustc_infer/src/infer/snapshot/fudge.rs +++ b/compiler/rustc_infer/src/infer/snapshot/fudge.rs @@ -101,7 +101,7 @@ pub fn fudge_inference_if_ok(&self, f: F) -> Result // going to be popped, so we will have to // eliminate any references to them. let snapshot_vars = SnapshotVarData::new(self, variable_lengths); - Ok((snapshot_vars, self.resolve_vars_if_possible(value))) + Ok((snapshot_vars, self.resolve_vars_if_possible_for_fudging(value))) })?; // At this point, we need to replace any of the now-popped diff --git a/tests/ui/coercion/fudge-inference/input-ty-closure-param-fn-trait-bounds.rs b/tests/ui/coercion/fudge-inference/input-ty-closure-param-fn-trait-bounds.rs new file mode 100644 index 000000000000..91dc8e394563 --- /dev/null +++ b/tests/ui/coercion/fudge-inference/input-ty-closure-param-fn-trait-bounds.rs @@ -0,0 +1,17 @@ +//@ check-pass + +// Regression test for + +struct Inv(*mut (T, U)); + +fn pass_through(_: F) -> Inv { + todo!() +} + +fn map(_: Inv) {} + +fn traverse() { + map(pass_through(|| ())) +} + +fn main() {} diff --git a/tests/ui/coercion/fudge-inference/input-ty-higher-ranked-fn-trait.rs b/tests/ui/coercion/fudge-inference/input-ty-higher-ranked-fn-trait.rs new file mode 100644 index 000000000000..241a177e4c15 --- /dev/null +++ b/tests/ui/coercion/fudge-inference/input-ty-higher-ranked-fn-trait.rs @@ -0,0 +1,21 @@ +//@ check-pass + +// Regression test for + +#[expect(dead_code)] +// Must be invariant +pub struct Server(*mut T); +impl Server { + fn new(_: T) -> Self + where + // Must be higher-ranked + T: Fn(&mut i32), + { + todo!() + } +} + +fn main() { + // Must have a type annotation + let _: Server<_> = Server::new(|_| ()); +}