Skip to content

Remove unsizing coercions for tuples #137728

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Mar 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions compiler/rustc_feature/src/removed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,8 @@ declare_features! (
/// Allows unnamed fields of struct and union type
(removed, unnamed_fields, "1.83.0", Some(49804), Some("feature needs redesign")),
(removed, unsafe_no_drop_flag, "1.0.0", None, None),
(removed, unsized_tuple_coercion, "CURRENT_RUSTC_VERSION", Some(42877),
Some("The feature restricts possible layouts for tuples, and this restriction is not worth it.")),
/// Allows `union` fields that don't implement `Copy` as long as they don't have any drop glue.
(removed, untagged_unions, "1.13.0", Some(55149),
Some("unions with `Copy` and `ManuallyDrop` fields are stable; there is no intent to stabilize more")),
Expand Down
2 changes: 0 additions & 2 deletions compiler/rustc_feature/src/unstable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -655,8 +655,6 @@ declare_features! (
(internal, unsized_fn_params, "1.49.0", Some(48055)),
/// Allows unsized rvalues at arguments and parameters.
(incomplete, unsized_locals, "1.30.0", Some(48055)),
/// Allows unsized tuple coercion.
(unstable, unsized_tuple_coercion, "1.20.0", Some(42877)),
/// Allows using the `#[used(linker)]` (or `#[used(compiler)]`) attribute.
(unstable, used_with_arg, "1.60.0", Some(93798)),
/// Allows use of x86 `AMX` target-feature attributes and intrinsics
Expand Down
29 changes: 2 additions & 27 deletions compiler/rustc_hir_typeck/src/coercion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,13 @@ use rustc_infer::traits::{
PredicateObligations,
};
use rustc_middle::span_bug;
use rustc_middle::traits::BuiltinImplSource;
use rustc_middle::ty::adjustment::{
Adjust, Adjustment, AllowTwoPhase, AutoBorrow, AutoBorrowMutability, PointerCoercion,
};
use rustc_middle::ty::error::TypeError;
use rustc_middle::ty::visit::TypeVisitableExt;
use rustc_middle::ty::{self, GenericArgsRef, Ty, TyCtxt};
use rustc_session::parse::feature_err;
use rustc_span::{BytePos, DUMMY_SP, DesugaringKind, Span, sym};
use rustc_span::{BytePos, DUMMY_SP, DesugaringKind, Span};
use rustc_trait_selection::infer::InferCtxtExt as _;
use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt;
use rustc_trait_selection::traits::{
Expand Down Expand Up @@ -610,8 +608,6 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
ty::TraitRef::new(self.tcx, coerce_unsized_did, [coerce_source, coerce_target])
)];

let mut has_unsized_tuple_coercion = false;

// Keep resolving `CoerceUnsized` and `Unsize` predicates to avoid
// emitting a coercion in cases like `Foo<$1>` -> `Foo<$2>`, where
// inference might unify those two inner type variables later.
Expand Down Expand Up @@ -690,31 +686,10 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
// be silent, as it causes a type mismatch later.
}

Ok(Some(impl_source)) => {
// Some builtin coercions are still unstable so we detect
// these here and emit a feature error if coercion doesn't fail
// due to another reason.
match impl_source {
traits::ImplSource::Builtin(BuiltinImplSource::TupleUnsizing, _) => {
has_unsized_tuple_coercion = true;
}
_ => {}
}
queue.extend(impl_source.nested_obligations())
}
Ok(Some(impl_source)) => queue.extend(impl_source.nested_obligations()),
}
}

if has_unsized_tuple_coercion && !self.tcx.features().unsized_tuple_coercion() {
feature_err(
&self.tcx.sess,
sym::unsized_tuple_coercion,
self.cause.span,
"unsized tuple coercion is not stable enough for use and is subject to change",
)
.emit();
}

Ok(coercion)
}

Expand Down
49 changes: 0 additions & 49 deletions compiler/rustc_next_trait_solver/src/solve/trait_goals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -787,13 +787,6 @@ where
)
}

// `(A, B, T)` -> `(A, B, U)` where `T: Unsize<U>`
(ty::Tuple(a_tys), ty::Tuple(b_tys))
if a_tys.len() == b_tys.len() && !a_tys.is_empty() =>
{
result_to_single(ecx.consider_builtin_tuple_unsize(goal, a_tys, b_tys))
}

_ => vec![],
}
})
Expand Down Expand Up @@ -1085,48 +1078,6 @@ where
.enter(|ecx| ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes))
}

/// We generate the following builtin impl for tuples of all sizes.
///
/// This impl is still unstable and we emit a feature error when it
/// when it is used by a coercion.
/// ```ignore (builtin impl example)
/// impl<T: ?Sized, U: ?Sized, V: ?Sized> Unsize<(T, V)> for (T, U)
/// where
/// U: Unsize<V>,
/// {}
/// ```
fn consider_builtin_tuple_unsize(
&mut self,
goal: Goal<I, (I::Ty, I::Ty)>,
a_tys: I::Tys,
b_tys: I::Tys,
) -> Result<Candidate<I>, NoSolution> {
let cx = self.cx();
let Goal { predicate: (_a_ty, b_ty), .. } = goal;

let (&a_last_ty, a_rest_tys) = a_tys.split_last().unwrap();
let b_last_ty = b_tys.last().unwrap();

// Instantiate just the tail field of B., and require that they're equal.
let unsized_a_ty = Ty::new_tup_from_iter(cx, a_rest_tys.iter().copied().chain([b_last_ty]));
self.eq(goal.param_env, unsized_a_ty, b_ty)?;

// Similar to ADTs, require that we can unsize the tail.
self.add_goal(
GoalSource::ImplWhereBound,
goal.with(
cx,
ty::TraitRef::new(
cx,
cx.require_lang_item(TraitSolverLangItem::Unsize),
[a_last_ty, b_last_ty],
),
),
);
self.probe_builtin_trait_candidate(BuiltinImplSource::TupleUnsizing)
.enter(|ecx| ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes))
}

// Return `Some` if there is an impl (built-in or user provided) that may
// hold for the self type of the goal, which for coherence and soundness
// purposes must disqualify the built-in auto impl assembled by considering
Expand Down
6 changes: 2 additions & 4 deletions compiler/rustc_trait_selection/src/traits/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1232,8 +1232,7 @@ fn assemble_candidates_from_impls<'cx, 'tcx>(
// why we special case object types.
false
}
ImplSource::Builtin(BuiltinImplSource::TraitUpcasting { .. }, _)
| ImplSource::Builtin(BuiltinImplSource::TupleUnsizing, _) => {
ImplSource::Builtin(BuiltinImplSource::TraitUpcasting { .. }, _) => {
// These traits have no associated types.
selcx.tcx().dcx().span_delayed_bug(
obligation.cause.span,
Expand Down Expand Up @@ -1325,8 +1324,7 @@ fn confirm_select_candidate<'cx, 'tcx>(
}
ImplSource::Builtin(BuiltinImplSource::Object { .. }, _)
| ImplSource::Param(..)
| ImplSource::Builtin(BuiltinImplSource::TraitUpcasting { .. }, _)
| ImplSource::Builtin(BuiltinImplSource::TupleUnsizing, _) => {
| ImplSource::Builtin(BuiltinImplSource::TraitUpcasting { .. }, _) => {
// we don't create Select candidates with this kind of resolution
span_bug!(
obligation.cause.span,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1017,13 +1017,6 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
}
}

// `(.., T)` -> `(.., U)`
(&ty::Tuple(tys_a), &ty::Tuple(tys_b)) => {
if tys_a.len() == tys_b.len() {
candidates.vec.push(BuiltinUnsizeCandidate);
}
}

_ => {}
};
}
Expand Down
29 changes: 0 additions & 29 deletions compiler/rustc_trait_selection/src/traits/select/confirmation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
//! [rustc dev guide]:
//! https://rustc-dev-guide.rust-lang.org/traits/resolution.html#confirmation

use std::iter;
use std::ops::ControlFlow;

use rustc_ast::Mutability;
Expand Down Expand Up @@ -1320,34 +1319,6 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
ImplSource::Builtin(BuiltinImplSource::Misc, nested)
}

// `(.., T)` -> `(.., U)`
(&ty::Tuple(tys_a), &ty::Tuple(tys_b)) => {
assert_eq!(tys_a.len(), tys_b.len());

// The last field of the tuple has to exist.
let (&a_last, a_mid) = tys_a.split_last().ok_or(Unimplemented)?;
let &b_last = tys_b.last().unwrap();

// Check that the source tuple with the target's
// last element is equal to the target.
let new_tuple =
Ty::new_tup_from_iter(tcx, a_mid.iter().copied().chain(iter::once(b_last)));
let InferOk { mut obligations, .. } = self
.infcx
.at(&obligation.cause, obligation.param_env)
.eq(DefineOpaqueTypes::Yes, target, new_tuple)
.map_err(|_| Unimplemented)?;

// Add a nested `T: Unsize<U>` predicate.
let last_unsize_obligation = obligation.with(
tcx,
ty::TraitRef::new(tcx, obligation.predicate.def_id(), [a_last, b_last]),
);
obligations.push(last_unsize_obligation);

ImplSource::Builtin(BuiltinImplSource::TupleUnsizing, obligations)
}

_ => bug!("source: {source}, target: {target}"),
})
}
Expand Down
3 changes: 1 addition & 2 deletions compiler/rustc_ty_utils/src/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -381,8 +381,7 @@ fn resolve_associated_item<'tcx>(
}
}
traits::ImplSource::Param(..)
| traits::ImplSource::Builtin(BuiltinImplSource::TraitUpcasting { .. }, _)
| traits::ImplSource::Builtin(BuiltinImplSource::TupleUnsizing, _) => None,
| traits::ImplSource::Builtin(BuiltinImplSource::TraitUpcasting { .. }, _) => None,
})
}

Expand Down
5 changes: 0 additions & 5 deletions compiler/rustc_type_ir/src/solve/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,11 +181,6 @@ pub enum BuiltinImplSource {
///
/// The index is only used for winnowing.
TraitUpcasting(usize),
/// Unsizing a tuple like `(A, B, ..., X)` to `(A, B, ..., Y)` if `X` unsizes to `Y`.
///
/// This can be removed when `feature(tuple_unsizing)` is stabilized, since we only
/// use it to detect when unsizing tuples in hir typeck.
TupleUnsizing,
}

#[derive_where(Clone, Copy, Hash, PartialEq, Eq, Debug; I: Interner)]
Expand Down
1 change: 0 additions & 1 deletion library/coretests/tests/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,6 @@
#![feature(try_find)]
#![feature(try_trait_v2)]
#![feature(unsize)]
#![feature(unsized_tuple_coercion)]
#![feature(unwrap_infallible)]
// tidy-alphabetical-end
#![allow(internal_features)]
Expand Down
10 changes: 1 addition & 9 deletions library/coretests/tests/ptr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -525,31 +525,24 @@ fn ptr_metadata() {
assert_eq!(metadata("foo"), 3_usize);
assert_eq!(metadata(&[4, 7][..]), 2_usize);

let dst_tuple: &(bool, [u8]) = &(true, [0x66, 0x6F, 0x6F]);
let dst_struct: &Pair<bool, [u8]> = &Pair(true, [0x66, 0x6F, 0x6F]);
assert_eq!(metadata(dst_tuple), 3_usize);
assert_eq!(metadata(dst_struct), 3_usize);
unsafe {
let dst_tuple: &(bool, str) = std::mem::transmute(dst_tuple);
let dst_struct: &Pair<bool, str> = std::mem::transmute(dst_struct);
assert_eq!(&dst_tuple.1, "foo");
assert_eq!(&dst_struct.1, "foo");
assert_eq!(metadata(dst_tuple), 3_usize);
assert_eq!(metadata(dst_struct), 3_usize);
}

let vtable_1: DynMetadata<dyn Debug> = metadata(&4_u16 as &dyn Debug);
let vtable_2: DynMetadata<dyn Display> = metadata(&4_u16 as &dyn Display);
let vtable_3: DynMetadata<dyn Display> = metadata(&4_u32 as &dyn Display);
let vtable_4: DynMetadata<dyn Display> = metadata(&(true, 7_u32) as &(bool, dyn Display));
let vtable_5: DynMetadata<dyn Display> =
let vtable_4: DynMetadata<dyn Display> =
metadata(&Pair(true, 7_u32) as &Pair<bool, dyn Display>);
unsafe {
let address_1: *const () = std::mem::transmute(vtable_1);
let address_2: *const () = std::mem::transmute(vtable_2);
let address_3: *const () = std::mem::transmute(vtable_3);
let address_4: *const () = std::mem::transmute(vtable_4);
let address_5: *const () = std::mem::transmute(vtable_5);
// Different trait => different vtable pointer
assert_ne!(address_1, address_2);
// Different erased type => different vtable pointer
Expand All @@ -558,7 +551,6 @@ fn ptr_metadata() {
// This is *not guaranteed*, so we skip it in Miri.
if !cfg!(miri) {
assert_eq!(address_3, address_4);
assert_eq!(address_3, address_5);
}
}
}
Expand Down

This file was deleted.

15 changes: 0 additions & 15 deletions src/tools/miri/tests/pass/unsized.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,8 @@
//@revisions: stack tree
//@[tree]compile-flags: -Zmiri-tree-borrows
#![feature(unsized_tuple_coercion)]
#![feature(unsized_fn_params)]
#![feature(custom_mir, core_intrinsics)]

use std::mem;

fn unsized_tuple() {
let x: &(i32, i32, [i32]) = &(0, 1, [2, 3]);
let y: &(i32, i32, [i32]) = &(0, 1, [2, 3, 4]);
let mut a = [y, x];
a.sort();
assert_eq!(a, [x, y]);

assert_eq!(&format!("{:?}", a), "[(0, 1, [2, 3]), (0, 1, [2, 3, 4])]");
assert_eq!(mem::size_of_val(x), 16);
}

fn unsized_params() {
pub fn f0(_f: dyn FnOnce()) {}
pub fn f1(_s: str) {}
Expand Down Expand Up @@ -56,7 +42,6 @@ fn unsized_field_projection() {
}

fn main() {
unsized_tuple();
unsized_params();
unsized_field_projection();
}
34 changes: 0 additions & 34 deletions src/tools/rust-analyzer/crates/ide-db/src/generated/lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12311,40 +12311,6 @@ fn main() {
```

will unnecessarily extend the stack frame.
"##,
default_severity: Severity::Allow,
warn_since: None,
deny_since: None,
},
Lint {
label: "unsized_tuple_coercion",
description: r##"# `unsized_tuple_coercion`

The tracking issue for this feature is: [#42877]

[#42877]: https://github.com./rust-lang/rust/issues/42877

------------------------

This is a part of [RFC0401]. According to the RFC, there should be an implementation like this:

```rust,ignore (partial-example)
impl<..., T, U: ?Sized> Unsized<(..., U)> for (..., T) where T: Unsized<U> {}
```

This implementation is currently gated behind `#[feature(unsized_tuple_coercion)]` to avoid insta-stability. Therefore you can use it like this:

```rust
#![feature(unsized_tuple_coercion)]

fn main() {
let x : ([i32; 3], [i32; 3]) = ([1, 2, 3], [4, 5, 6]);
let y : &([i32; 3], [i32]) = &x;
assert_eq!(y.1[0], 4);
}
```

[RFC0401]: https://github.com./rust-lang/rfcs/blob/master/text/0401-coercions.md
"##,
default_severity: Severity::Allow,
warn_since: None,
Expand Down
Loading
Loading