From 8166b8091ed8d943b126031d148214802424aefc Mon Sep 17 00:00:00 2001 From: Kevin Robert Stravers Date: Sun, 5 Oct 2025 12:15:43 -0400 Subject: [PATCH] Fix unsound lifetime extension in HRTB function pointer coercion = Problem = The compiler allowed unsound coercions from function items/pointers with nested reference parameters to HRTB function pointers, enabling arbitrary lifetime extension to 'static. This was demonstrated by the cve-rs exploit: ```rust fn foo<'a, 'b, T>(_: &'a &'b (), v: &'b T) -> &'a T { v } // This coercion was allowed but unsound: let f: for<'x> fn(_, &'x T) -> &'static T = foo; ``` The issue occurs because nested references like `&'a &'b ()` create an implied outlives bound `'b: 'a`. When coercing to an HRTB function pointer, this constraint was not validated, allowing the inner lifetime to be extended arbitrarily. --- compiler/rustc_hir_typeck/src/coercion.rs | 97 ++++++++++++++++++- .../src/infer/outlives/implied_bounds.rs | 72 ++++++++++++++ .../rustc_infer/src/infer/outlives/mod.rs | 1 + .../hrtb-coercion-output-nested-unsound.rs | 21 ++++ ...hrtb-coercion-output-nested-unsound.stderr | 14 +++ tests/ui/hrtb/hrtb-implied-bounds-check.rs | 37 +++++++ tests/ui/hrtb/hrtb-lifetime-extend-unsound.rs | 49 ++++++++++ .../hrtb/hrtb-lifetime-extend-unsound.stderr | 14 +++ tests/ui/hrtb/hrtb-valid-outlives.rs | 41 ++++++++ .../cve-rs-lifetime-expansion.rs | 43 ++++++++ .../cve-rs-lifetime-expansion.stderr | 14 +++ ...ds-on-nested-references-plus-variance-2.rs | 5 +- ...n-nested-references-plus-variance-2.stderr | 14 +++ .../implied-bounds/reject-collapsing-adt.rs | 11 +++ .../reject-collapsing-adt.stderr | 14 +++ .../reject-collapsing-tuples.rs | 9 ++ .../reject-collapsing-tuples.stderr | 14 +++ .../structure-preserving-adt.rs | 13 +++ .../structure-preserving-tuples.rs | 13 +++ 19 files changed, 493 insertions(+), 3 deletions(-) create mode 100644 compiler/rustc_infer/src/infer/outlives/implied_bounds.rs create mode 100644 tests/ui/hrtb/hrtb-coercion-output-nested-unsound.rs create mode 100644 tests/ui/hrtb/hrtb-coercion-output-nested-unsound.stderr create mode 100644 tests/ui/hrtb/hrtb-implied-bounds-check.rs create mode 100644 tests/ui/hrtb/hrtb-lifetime-extend-unsound.rs create mode 100644 tests/ui/hrtb/hrtb-lifetime-extend-unsound.stderr create mode 100644 tests/ui/hrtb/hrtb-valid-outlives.rs create mode 100644 tests/ui/implied-bounds/cve-rs-lifetime-expansion.rs create mode 100644 tests/ui/implied-bounds/cve-rs-lifetime-expansion.stderr create mode 100644 tests/ui/implied-bounds/implied-bounds-on-nested-references-plus-variance-2.stderr create mode 100644 tests/ui/implied-bounds/reject-collapsing-adt.rs create mode 100644 tests/ui/implied-bounds/reject-collapsing-adt.stderr create mode 100644 tests/ui/implied-bounds/reject-collapsing-tuples.rs create mode 100644 tests/ui/implied-bounds/reject-collapsing-tuples.stderr create mode 100644 tests/ui/implied-bounds/structure-preserving-adt.rs create mode 100644 tests/ui/implied-bounds/structure-preserving-tuples.rs diff --git a/compiler/rustc_hir_typeck/src/coercion.rs b/compiler/rustc_hir_typeck/src/coercion.rs index 1e5fea1db9fcc..99ca7018275d8 100644 --- a/compiler/rustc_hir_typeck/src/coercion.rs +++ b/compiler/rustc_hir_typeck/src/coercion.rs @@ -878,9 +878,100 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> { debug!(?fn_ty_a, ?b, "coerce_from_fn_pointer"); debug_assert!(self.shallow_resolve(b) == b); + // Check implied bounds for HRTB function pointer coercions (issue #25860) + if let ty::FnPtr(sig_tys_b, hdr_b) = b.kind() { + let target_sig = sig_tys_b.with(*hdr_b); + self.check_hrtb_implied_bounds(fn_ty_a, target_sig)?; + } + self.coerce_from_safe_fn(fn_ty_a, b, None) } + /// Validates that implied bounds from nested references in the source + /// function signature are satisfied when coercing to an HRTB function pointer. + /// + /// This prevents the soundness hole in issue #25860 where lifetime bounds + /// can be circumvented through HRTB function pointer coercion. + /// + /// For example, a function with signature `fn<'a, 'b>(_: &'a &'b (), v: &'b T) -> &'a T` + /// has an implied bound `'b: 'a` from the type `&'a &'b ()`. When coercing to + /// `for<'x> fn(_, &'x T) -> &'static T`, we must ensure that the implied bound + /// can be satisfied, which it cannot in this case. + fn check_hrtb_implied_bounds( + &self, + source_sig: ty::PolyFnSig<'tcx>, + target_sig: ty::PolyFnSig<'tcx>, + ) -> Result<(), TypeError<'tcx>> { + use rustc_infer::infer::outlives::implied_bounds; + + // Only check if target has HRTB (bound variables) + if target_sig.bound_vars().is_empty() { + return Ok(()); + } + + // Extract implied bounds from the source signature's input types and return type + let source_sig_unbound = source_sig.skip_binder(); + let target_sig_unbound = target_sig.skip_binder(); + let source_inputs = source_sig_unbound.inputs(); + let target_inputs = target_sig_unbound.inputs(); + let source_output = source_sig_unbound.output(); + let target_output = target_sig_unbound.output(); + + // If the number of inputs differs, let normal type checking handle this + if source_inputs.len() != target_inputs.len() { + return Ok(()); + } + + // Check inputs: whether the source carries nested-reference implied bounds + let source_has_nested = source_inputs + .iter() + .any(|ty| implied_bounds::has_nested_reference_implied_bounds(self.tcx, *ty)); + + // Check if target inputs also have nested references with the same structure. + // If both source and target preserve the nested reference structure, the coercion is + // sound. + // The unsoundness only occurs when we're "collapsing" nested lifetimes. + let target_has_nested_refs = target_inputs + .iter() + .any(|ty| implied_bounds::has_nested_reference_implied_bounds(self.tcx, *ty)); + + if source_has_nested && !target_has_nested_refs { + // Source inputs have implied bounds from nested refs but target inputs don't + // preserve them. This is the unsound case (e.g., cve-rs: fn(&'a &'b T) -> for<'x> fn(&'x T)). + return Err(TypeError::Mismatch); + } + + // Additionally, validate RETURN types. If the source return has nested references + // with distinct regions (implying an outlives relation), then the target return must + // preserve that distinguishing structure; otherwise lifetimes can be "collapsed" away. + let source_ret_nested_distinct = + implied_bounds::has_nested_reference_with_distinct_regions(self.tcx, source_output); + if source_ret_nested_distinct { + let target_ret_nested_distinct = + implied_bounds::has_nested_reference_with_distinct_regions(self.tcx, target_output); + if !target_ret_nested_distinct { + // Reject: collapsing nested return structure (e.g., &'a &'b -> &'x &'x or no nested) + return Err(TypeError::Mismatch); + } + } else { + // If there is nested structure in the source return (not necessarily distinct), + // require the target to keep nested structure too. + let source_ret_nested = + implied_bounds::has_nested_reference_implied_bounds(self.tcx, source_output); + if source_ret_nested { + let target_ret_nested = + implied_bounds::has_nested_reference_implied_bounds(self.tcx, target_output); + if !target_ret_nested { + return Err(TypeError::Mismatch); + } + } + } + + // Source inputs had implied bounds but target did not preserve them (handled above), or + // return types collapsed. If neither condition triggered, accept the coercion. + Ok(()) + } + fn coerce_from_fn_item(&self, a: Ty<'tcx>, b: Ty<'tcx>) -> CoerceResult<'tcx> { debug!("coerce_from_fn_item(a={:?}, b={:?})", a, b); debug_assert!(self.shallow_resolve(a) == a); @@ -890,8 +981,12 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> { self.at(&self.cause, self.param_env).normalize(b); match b.kind() { - ty::FnPtr(_, b_hdr) => { + ty::FnPtr(sig_tys_b, b_hdr) => { let mut a_sig = a.fn_sig(self.tcx); + + // Check implied bounds for HRTB function pointer coercions (issue #25860) + let target_sig = sig_tys_b.with(*b_hdr); + self.check_hrtb_implied_bounds(a_sig, target_sig)?; if let ty::FnDef(def_id, _) = *a.kind() { // Intrinsics are not coercible to function pointers if self.tcx.intrinsic(def_id).is_some() { diff --git a/compiler/rustc_infer/src/infer/outlives/implied_bounds.rs b/compiler/rustc_infer/src/infer/outlives/implied_bounds.rs new file mode 100644 index 0000000000000..1315705901c34 --- /dev/null +++ b/compiler/rustc_infer/src/infer/outlives/implied_bounds.rs @@ -0,0 +1,72 @@ +//! Extraction of implied bounds from nested references. +//! +//! This module provides utilities for extracting outlives constraints that are +//! implied by the structure of types, particularly nested references. +//! +//! For example, the type `&'a &'b T` implies that `'b: 'a`, because the outer +//! reference with lifetime `'a` must not outlive the data it points to, which +//! has lifetime `'b`. +//! +//! This is relevant for issue #25860, where the combination of variance and +//! implied bounds on nested references can create soundness holes in HRTB +//! function pointer coercions. + +use rustc_middle::ty::{self, Ty, TyCtxt}; + +// Note: Allocation-free helper below is used for fast path decisions. + +/// Returns true if the type contains a nested reference structure that implies +/// an outlives relationship (e.g., `&'a &'b T` implies `'b: 'a`). This helper +/// is non-allocating and short-circuits on the first match. +pub fn has_nested_reference_implied_bounds<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> bool { + fn walk<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> bool { + match ty.kind() { + ty::Ref(_, inner_ty, _) => { + match inner_ty.kind() { + // Direct nested reference: &'a &'b T + ty::Ref(..) => true, + // Recurse into inner type for tuples/ADTs possibly nested within + _ => walk(tcx, *inner_ty), + } + } + ty::Tuple(tys) => tys.iter().any(|t| walk(tcx, t)), + ty::Adt(_, args) => args.iter().any(|arg| match arg.kind() { + ty::GenericArgKind::Type(t) => walk(tcx, t), + _ => false, + }), + _ => false, + } + } + + walk(tcx, ty) +} + +/// Returns true if there exists a nested reference `&'a &'b T` within `ty` +/// such that the outer and inner regions are distinct (`'a != 'b`). This +/// helps detect cases where implied outlives like `'b: 'a` exist. +pub fn has_nested_reference_with_distinct_regions<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> bool { + fn walk<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> bool { + match ty.kind() { + ty::Ref(r_outer, inner_ty, _) => { + match inner_ty.kind() { + ty::Ref(r_inner, nested_ty, _) => { + if r_outer != r_inner { + return true; + } + // Keep walking to catch deeper nests + walk(tcx, *nested_ty) + } + _ => walk(tcx, *inner_ty), + } + } + ty::Tuple(tys) => tys.iter().any(|t| walk(tcx, t)), + ty::Adt(_, args) => args.iter().any(|arg| match arg.kind() { + ty::GenericArgKind::Type(t) => walk(tcx, t), + _ => false, + }), + _ => false, + } + } + + walk(tcx, ty) +} diff --git a/compiler/rustc_infer/src/infer/outlives/mod.rs b/compiler/rustc_infer/src/infer/outlives/mod.rs index c992cda8aaed0..719fdd9d7f7ad 100644 --- a/compiler/rustc_infer/src/infer/outlives/mod.rs +++ b/compiler/rustc_infer/src/infer/outlives/mod.rs @@ -14,6 +14,7 @@ use crate::infer::region_constraints::ConstraintKind; pub mod env; pub mod for_liveness; +pub mod implied_bounds; pub mod obligations; pub mod test_type_match; pub(crate) mod verify; diff --git a/tests/ui/hrtb/hrtb-coercion-output-nested-unsound.rs b/tests/ui/hrtb/hrtb-coercion-output-nested-unsound.rs new file mode 100644 index 0000000000000..e112249937596 --- /dev/null +++ b/tests/ui/hrtb/hrtb-coercion-output-nested-unsound.rs @@ -0,0 +1,21 @@ +// This test captures a review comment example where nested references appear +// in the RETURN TYPE and a chain of HRTB fn-pointer coercions allows +// unsound lifetime extension. This should be rejected, but currently passes. +// We annotate the expected error to reveal the gap. + +fn foo<'out, 'input, T>(_dummy: &'out (), value: &'input T) -> (&'out &'input (), &'out T) { + (&&(), value) +} + +fn bad<'short, T>(x: &'short T) -> &'static T { + let foo1: for<'out, 'input> fn(&'out (), &'input T) -> (&'out &'input (), &'out T) = foo; + let foo2: for<'input> fn(&'static (), &'input T) -> (&'static &'input (), &'static T) = foo1; + let foo3: for<'input> fn(&'static (), &'input T) -> (&'input &'input (), &'static T) = foo2; //~ ERROR mismatched types + let foo4: fn(&'static (), &'short T) -> (&'short &'short (), &'static T) = foo3; + foo4(&(), x).1 +} + +fn main() { + let s = String::from("hi"); + let _r: &'static String = bad(&s); +} diff --git a/tests/ui/hrtb/hrtb-coercion-output-nested-unsound.stderr b/tests/ui/hrtb/hrtb-coercion-output-nested-unsound.stderr new file mode 100644 index 0000000000000..7732924dc88ba --- /dev/null +++ b/tests/ui/hrtb/hrtb-coercion-output-nested-unsound.stderr @@ -0,0 +1,14 @@ +error[E0308]: mismatched types + --> $DIR/hrtb-coercion-output-nested-unsound.rs:13:92 + | +LL | let foo3: for<'input> fn(&'static (), &'input T) -> (&'input &'input (), &'static T) = foo2; + | -------------------------------------------------------------------------- ^^^^ types differ + | | + | expected due to this + | + = note: expected fn pointer `for<'input> fn(&'static (), &'input _) -> (&'input &'input (), &'static _)` + found fn pointer `for<'input> fn(&'static (), &'input _) -> (&'static &'input (), &'static _)` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0308`. diff --git a/tests/ui/hrtb/hrtb-implied-bounds-check.rs b/tests/ui/hrtb/hrtb-implied-bounds-check.rs new file mode 100644 index 0000000000000..e6e4b0aeb3330 --- /dev/null +++ b/tests/ui/hrtb/hrtb-implied-bounds-check.rs @@ -0,0 +1,37 @@ +//@ check-pass +// Test that implied bounds from type parameters are properly tracked in HRTB contexts. +// The type `&'b &'a ()` implies `'a: 'b`, and this constraint should be preserved +// when deriving supertrait bounds. + +trait Subtrait<'a, 'b, R>: Supertrait<'a, 'b> {} + +trait Supertrait<'a, 'b> {} + +struct MyStruct; + +// This implementation is valid: we only implement Supertrait for 'a: 'b +impl<'a: 'b, 'b> Supertrait<'a, 'b> for MyStruct {} + +// This implementation is also valid: the type parameter &'b &'a () implies 'a: 'b +impl<'a, 'b> Subtrait<'a, 'b, &'b &'a ()> for MyStruct {} + +// This function requires the HRTB on Subtrait +fn need_hrtb_subtrait() +where + S: for<'a, 'b> Subtrait<'a, 'b, &'b &'a ()>, +{ + // This should work - the bound on Subtrait with the type parameter + // &'b &'a () implies 'a: 'b, which matches what Supertrait requires + need_hrtb_supertrait::() +} + +// This function requires a weaker HRTB on Supertrait +fn need_hrtb_supertrait() +where + S: for<'a, 'b> Supertrait<'a, 'b>, +{ +} + +fn main() { + need_hrtb_subtrait::(); +} diff --git a/tests/ui/hrtb/hrtb-lifetime-extend-unsound.rs b/tests/ui/hrtb/hrtb-lifetime-extend-unsound.rs new file mode 100644 index 0000000000000..a00583841f22f --- /dev/null +++ b/tests/ui/hrtb/hrtb-lifetime-extend-unsound.rs @@ -0,0 +1,49 @@ +// This test demonstrates the unsoundness in issue #84591 +// where HRTB on subtraits can imply HRTB on supertraits without +// preserving necessary outlives constraints, allowing unsafe lifetime extension. +// +// This test should FAIL to compile once the fix is implemented. + +trait Subtrait<'a, 'b, R>: Supertrait<'a, 'b> {} + +trait Supertrait<'a, 'b> { + fn convert(x: &'a T) -> &'b T; +} + +fn need_hrtb_subtrait(x: &T) -> &T +where + S: for<'a, 'b> Subtrait<'a, 'b, &'b &'a ()>, +{ + need_hrtb_supertrait::(x) +} + +fn need_hrtb_supertrait(x: &T) -> &T +where + S: for<'a, 'b> Supertrait<'a, 'b>, +{ + S::convert(x) +} + +struct MyStruct; + +impl<'a: 'b, 'b> Supertrait<'a, 'b> for MyStruct { + fn convert(x: &'a T) -> &'b T { + x + } +} + +impl<'a, 'b> Subtrait<'a, 'b, &'b &'a ()> for MyStruct {} + +fn extend_lifetime<'a, 'b, T: ?Sized>(x: &'a T) -> &'b T { + need_hrtb_subtrait::(x) + //~^ ERROR lifetime may not live long enough +} + +fn main() { + let d; + { + let x = String::from("Hello World"); + d = extend_lifetime(&x); + } + println!("{}", d); +} diff --git a/tests/ui/hrtb/hrtb-lifetime-extend-unsound.stderr b/tests/ui/hrtb/hrtb-lifetime-extend-unsound.stderr new file mode 100644 index 0000000000000..0f58d5fe6dee4 --- /dev/null +++ b/tests/ui/hrtb/hrtb-lifetime-extend-unsound.stderr @@ -0,0 +1,14 @@ +error: lifetime may not live long enough + --> $DIR/hrtb-lifetime-extend-unsound.rs:38:5 + | +LL | fn extend_lifetime<'a, 'b, T: ?Sized>(x: &'a T) -> &'b T { + | -- -- lifetime `'b` defined here + | | + | lifetime `'a` defined here +LL | need_hrtb_subtrait::(x) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ function was supposed to return data with lifetime `'b` but it is returning data with lifetime `'a` + | + = help: consider adding the following bound: `'a: 'b` + +error: aborting due to 1 previous error + diff --git a/tests/ui/hrtb/hrtb-valid-outlives.rs b/tests/ui/hrtb/hrtb-valid-outlives.rs new file mode 100644 index 0000000000000..fb7500c801c28 --- /dev/null +++ b/tests/ui/hrtb/hrtb-valid-outlives.rs @@ -0,0 +1,41 @@ +// Test that valid HRTB usage with explicit outlives constraints works correctly. +// This should continue to compile after the fix. +// +//@ check-pass + +trait Subtrait<'a, 'b>: Supertrait<'a, 'b> +where + 'a: 'b, +{ +} + +trait Supertrait<'a, 'b> +where + 'a: 'b, +{ + fn convert(x: &'a T) -> &'b T; +} + +struct MyStruct; + +impl<'a: 'b, 'b> Supertrait<'a, 'b> for MyStruct { + fn convert(x: &'a T) -> &'b T { + x + } +} + +impl<'a: 'b, 'b> Subtrait<'a, 'b> for MyStruct {} + +// This is valid because we explicitly require 'a: 'b +fn valid_conversion<'a: 'b, 'b, T: ?Sized>(x: &'a T) -> &'b T +where + MyStruct: Subtrait<'a, 'b>, +{ + MyStruct::convert(x) +} + +fn main() { + let x = String::from("Hello World"); + let y = valid_conversion::<'_, '_, _>(&x); + println!("{}", y); +} diff --git a/tests/ui/implied-bounds/cve-rs-lifetime-expansion.rs b/tests/ui/implied-bounds/cve-rs-lifetime-expansion.rs new file mode 100644 index 0000000000000..0bca8997fff55 --- /dev/null +++ b/tests/ui/implied-bounds/cve-rs-lifetime-expansion.rs @@ -0,0 +1,43 @@ +// Test for issue #25860: the cve-rs lifetime expansion exploit +// This demonstrates casting an arbitrary lifetime to 'static using +// HRTB and variance, which should NOT be allowed. +// +// Once fixed, this test should fail to compile with an appropriate error. + +static STATIC_UNIT: &'static &'static () = &&(); + +/// This function has an implied bound: 'b: 'a +/// (because &'a &'b () requires 'b to outlive 'a) +fn lifetime_translator<'a, 'b, T: ?Sized>( + _val_a: &'a &'b (), + val_b: &'b T +) -> &'a T { + val_b +} + +/// The exploit: expand any lifetime 'a to any other lifetime 'b +/// This is UNSOUND because 'a may not outlive 'b +fn expand<'a, 'b, T: ?Sized>(x: &'a T) -> &'b T { + // This coercion should fail because: + // 1. lifetime_translator requires 'b: 'a (implied by &'a &'b ()) + // 2. When we call f(STATIC_UNIT, x), 'a gets unified with the lifetime of x + // 3. But there's no guarantee that 'a: 'b! + let f: for<'x> fn(_, &'x T) -> &'b T = lifetime_translator; + //~^ ERROR mismatched types + f(STATIC_UNIT, x) +} + +/// Concrete example: extend a local reference to 'static +fn extend_to_static<'a, T: ?Sized>(x: &'a T) -> &'static T { + expand(x) +} + +fn main() { + // Use-after-free: this should not compile! + let dangling: &'static str; + { + let local = String::from("This should not escape!"); + dangling = extend_to_static(&local); + } + println!("{}", dangling); // UB: use after free! +} diff --git a/tests/ui/implied-bounds/cve-rs-lifetime-expansion.stderr b/tests/ui/implied-bounds/cve-rs-lifetime-expansion.stderr new file mode 100644 index 0000000000000..998d974e36287 --- /dev/null +++ b/tests/ui/implied-bounds/cve-rs-lifetime-expansion.stderr @@ -0,0 +1,14 @@ +error[E0308]: mismatched types + --> $DIR/cve-rs-lifetime-expansion.rs:25:44 + | +LL | let f: for<'x> fn(_, &'x T) -> &'b T = lifetime_translator; + | ----------------------------- ^^^^^^^^^^^^^^^^^^^ types differ + | | + | expected due to this + | + = note: expected fn pointer `for<'x> fn(_, &'x T) -> &'b T` + found fn item `for<'a, 'b> fn(&'a &'b (), &'b _) -> &'a _ {lifetime_translator::<_>}` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0308`. diff --git a/tests/ui/implied-bounds/implied-bounds-on-nested-references-plus-variance-2.rs b/tests/ui/implied-bounds/implied-bounds-on-nested-references-plus-variance-2.rs index ca8b8b7e4d92b..d51876551413f 100644 --- a/tests/ui/implied-bounds/implied-bounds-on-nested-references-plus-variance-2.rs +++ b/tests/ui/implied-bounds/implied-bounds-on-nested-references-plus-variance-2.rs @@ -1,5 +1,5 @@ -//@ check-pass -//@ known-bug: #25860 +// Issue #25860: This exploit is now FIXED and should fail to compile +// Previously marked as known-bug, now correctly rejected static UNIT: &'static &'static () = &&(); @@ -7,6 +7,7 @@ fn foo<'a, 'b, T>(_: &'a &'b (), v: &'b T, _: &()) -> &'a T { v } fn bad<'a, T>(x: &'a T) -> &'static T { let f: fn(_, &'a T, &()) -> &'static T = foo; + //~^ ERROR mismatched types f(UNIT, x, &()) } diff --git a/tests/ui/implied-bounds/implied-bounds-on-nested-references-plus-variance-2.stderr b/tests/ui/implied-bounds/implied-bounds-on-nested-references-plus-variance-2.stderr new file mode 100644 index 0000000000000..6f0f34ba0f71f --- /dev/null +++ b/tests/ui/implied-bounds/implied-bounds-on-nested-references-plus-variance-2.stderr @@ -0,0 +1,14 @@ +error[E0308]: mismatched types + --> $DIR/implied-bounds-on-nested-references-plus-variance-2.rs:9:46 + | +LL | let f: fn(_, &'a T, &()) -> &'static T = foo; + | ------------------------------- ^^^ types differ + | | + | expected due to this + | + = note: expected fn pointer `for<'b> fn(_, &'a T, &'b ()) -> &'static T` + found fn item `for<'a, 'b, 'c> fn(&'a &'b (), &'b _, &'c ()) -> &'a _ {foo::<_>}` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0308`. diff --git a/tests/ui/implied-bounds/reject-collapsing-adt.rs b/tests/ui/implied-bounds/reject-collapsing-adt.rs new file mode 100644 index 0000000000000..46810881dadd4 --- /dev/null +++ b/tests/ui/implied-bounds/reject-collapsing-adt.rs @@ -0,0 +1,11 @@ +// Rejection: collapsing nested reference structure inside an ADT +// The source has Wrap<&'a &'b ()> implying 'b: 'a; the target loses the nested ref. + +struct Wrap(T); + +fn foo<'a, 'b>(_: Wrap<&'a &'b ()>, v: &'b u32) -> &'a u32 { v } + +fn main() { + let _f: for<'x> fn(Wrap<&'x ()>, &'x u32) -> &'x u32 = foo; + //~^ ERROR mismatched types +} diff --git a/tests/ui/implied-bounds/reject-collapsing-adt.stderr b/tests/ui/implied-bounds/reject-collapsing-adt.stderr new file mode 100644 index 0000000000000..f8d7b3b1da987 --- /dev/null +++ b/tests/ui/implied-bounds/reject-collapsing-adt.stderr @@ -0,0 +1,14 @@ +error[E0308]: mismatched types + --> $DIR/reject-collapsing-adt.rs:9:60 + | +LL | let _f: for<'x> fn(Wrap<&'x ()>, &'x u32) -> &'x u32 = foo; + | -------------------------------------------- ^^^ types differ + | | + | expected due to this + | + = note: expected fn pointer `for<'x> fn(Wrap<&'x ()>, &'x _) -> &'x _` + found fn item `for<'a, 'b> fn(Wrap<&'a &'b ()>, &'b _) -> &'a _ {foo}` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0308`. diff --git a/tests/ui/implied-bounds/reject-collapsing-tuples.rs b/tests/ui/implied-bounds/reject-collapsing-tuples.rs new file mode 100644 index 0000000000000..f9cf1799a2fdf --- /dev/null +++ b/tests/ui/implied-bounds/reject-collapsing-tuples.rs @@ -0,0 +1,9 @@ +// Rejection: collapsing nested reference structure inside a tuple +// The source has (&'a &'b (),) implying 'b: 'a; the target loses the nested ref. + +fn foo<'a, 'b>(_: (&'a &'b (),), v: &'b u32) -> &'a u32 { v } + +fn main() { + let _f: for<'x> fn((&'x (),), &'x u32) -> &'x u32 = foo; + //~^ ERROR mismatched types +} diff --git a/tests/ui/implied-bounds/reject-collapsing-tuples.stderr b/tests/ui/implied-bounds/reject-collapsing-tuples.stderr new file mode 100644 index 0000000000000..b30429c968873 --- /dev/null +++ b/tests/ui/implied-bounds/reject-collapsing-tuples.stderr @@ -0,0 +1,14 @@ +error[E0308]: mismatched types + --> $DIR/reject-collapsing-tuples.rs:7:57 + | +LL | let _f: for<'x> fn((&'x (),), &'x u32) -> &'x u32 = foo; + | ----------------------------------------- ^^^ types differ + | | + | expected due to this + | + = note: expected fn pointer `for<'x> fn((&'x (),), &'x _) -> &'x _` + found fn item `for<'a, 'b> fn((&'a &'b (),), &'b _) -> &'a _ {foo}` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0308`. diff --git a/tests/ui/implied-bounds/structure-preserving-adt.rs b/tests/ui/implied-bounds/structure-preserving-adt.rs new file mode 100644 index 0000000000000..d15e6c30a9699 --- /dev/null +++ b/tests/ui/implied-bounds/structure-preserving-adt.rs @@ -0,0 +1,13 @@ +//@ check-pass +// Structure-preserving HRTB coercion with nested references inside an ADT. + +struct Wrap(T); + +fn foo<'a, 'b>(_: Wrap<&'a &'b ()>, v: &'b u32) -> &'a u32 { v } + +// Coerce to HRTB fn pointer that preserves nested structure via ADT. +fn takes_f(_: for<'x, 'y> fn(Wrap<&'x &'y ()>, &'y u32) -> &'x u32) {} + +fn main() { + takes_f(foo); +} diff --git a/tests/ui/implied-bounds/structure-preserving-tuples.rs b/tests/ui/implied-bounds/structure-preserving-tuples.rs new file mode 100644 index 0000000000000..dca6baf00a6e6 --- /dev/null +++ b/tests/ui/implied-bounds/structure-preserving-tuples.rs @@ -0,0 +1,13 @@ +//@ check-pass +// Structure-preserving HRTB coercion with nested references inside a 1-tuple. + +static UNIT: &'static &'static () = &&(); + +fn foo<'a, 'b>(_: (&'a &'b (),), v: &'b u32) -> &'a u32 { v } + +// Coerce to HRTB fn pointer that preserves nested structure. +fn takes_f(_: for<'x, 'y> fn((&'x &'y (),), &'y u32) -> &'x u32) {} + +fn main() { + takes_f(foo); +}