Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
9595e03
fallible type ops
nia-e Jun 15, 2026
12e2d6a
move trait
nia-e Jun 15, 2026
b6dff63
Fix: removed Move from being printed at all
zannabianca1997 Aug 5, 2026
0c55a40
Fix: assume Move when the `move_trait` feature is not added
zannabianca1997 Aug 16, 2026
51548a7
Fix: preferred printing of `?Move` instead of `Move`
zannabianca1997 Aug 21, 2026
94e0cd9
Feat: removed Move from mangled symbols
zannabianca1997 Aug 22, 2026
37f046f
Feat: mangling of `?Move` bounds
zannabianca1997 Aug 23, 2026
5acdf81
Fix: legacy mangler (Fn printer)
zannabianca1997 Aug 23, 2026
f6149f3
Fix: normalized test
zannabianca1997 Aug 23, 2026
0556839
Fix: correctly refusing trait objects that have only Move as a bound
zannabianca1997 Aug 23, 2026
4c040ec
Fix: line number drift
zannabianca1997 Aug 23, 2026
1c74e76
Fix: added now present Move clause
zannabianca1997 Aug 23, 2026
68930dd
Fix: missing Move pattern
zannabianca1997 Aug 23, 2026
757e345
Fix: removed the move clause from explicit clauses when the feature is
zannabianca1997 Aug 24, 2026
c1271cc
Cleanup
zannabianca1997 Aug 28, 2026
9f2ad53
Fix: expanded boolean into a more comprehensible enum
zannabianca1997 Aug 28, 2026
97fc218
aligned to boolean values
zannabianca1997 Aug 28, 2026
bca55d8
Fix: helper to avoid repeating printing pattern
zannabianca1997 Aug 29, 2026
ae6089b
Fix: missing prints
zannabianca1997 Aug 29, 2026
e8b8473
reborrow! yay.
zannabianca1997 Aug 29, 2026
b8a227b
Fix: documented error in feature gate test
zannabianca1997 Aug 30, 2026
cb26587
Fix: documented test being fixed under the next solver
zannabianca1997 Aug 30, 2026
a068654
Chore: new test use the debug too
zannabianca1997 Aug 30, 2026
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
10 changes: 9 additions & 1 deletion compiler/rustc_ast_lowering/src/item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -501,13 +501,21 @@ impl<'hir> LoweringContext<'_, 'hir> {
let constness = self.lower_constness(attrs, *constness);
let impl_restriction = self.lower_impl_restriction(impl_restriction, hir_id);
let ident = self.lower_ident(*ident);
// FIXME(move_trait): We likely want to not add an implicit `Move` super trait
// at which point we shouldn't allow relaxed bounds here. Even if we do, we should
// make sure to only allow `?Move`.
let policy = if self.tcx.features().move_trait() {
RelaxedBoundPolicy::Allowed(&mut Default::default())
} else {
RelaxedBoundPolicy::Forbidden(RelaxedBoundForbiddenReason::SuperTrait)
};
let (generics, (safety, items, bounds)) = self.lower_generics(
generics,
ImplTraitContext::Disallowed(ImplTraitPosition::Generic),
|this| {
let bounds = this.lower_param_bounds(
bounds,
RelaxedBoundPolicy::Forbidden(RelaxedBoundForbiddenReason::SuperTrait),
policy,
ImplTraitContext::Disallowed(ImplTraitPosition::Bound),
);
let items = this.arena.alloc_from_iter(
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_attr_ir/src/lang_items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,8 @@ language_item_table! {
Unpin, sym::unpin, unpin_trait, Target::Trait, GenericRequirement::None;
Pin, kw::Pin, pin_type, Target::Struct, GenericRequirement::None;

Move, sym::move_trait, move_trait, Target::Trait, GenericRequirement::None;

OrderingEnum, sym::Ordering, ordering_enum, Target::Enum, GenericRequirement::Exact(0);
PartialEq, sym::eq, eq_trait, Target::Trait, GenericRequirement::Exact(1);
PartialOrd, sym::partial_ord, partial_ord_trait, Target::Trait, GenericRequirement::Exact(1);
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_borrowck/src/diagnostics/region_errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ impl<'tcx> ConstraintDescription for ConstraintCategory<'tcx> {
ConstraintCategory::TypeAnnotation(AnnotationSource::GenericArg) => "generic argument ",
ConstraintCategory::TypeAnnotation(_) => "type annotation ",
ConstraintCategory::SizedBound => "proving this value is `Sized` ",
ConstraintCategory::MoveBound => "proving this value is `Move` ",
ConstraintCategory::CopyBound => "copying this value ",
ConstraintCategory::OpaqueType => "opaque type ",
ConstraintCategory::ClosureUpvar(_) => "closure capture ",
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_borrowck/src/region_infer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1759,6 +1759,7 @@ impl<'tcx> RegionInferenceContext<'tcx> {
| ConstraintCategory::CallArgument(_)
| ConstraintCategory::CopyBound
| ConstraintCategory::SizedBound
| ConstraintCategory::MoveBound
| ConstraintCategory::Assignment
| ConstraintCategory::Usage
| ConstraintCategory::ClosureUpvar(_) => 2,
Expand Down
28 changes: 27 additions & 1 deletion compiler/rustc_borrowck/src/type_check/canonical.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ use std::fmt;
use rustc_errors::ErrorGuaranteed;
use rustc_infer::infer::canonical::Canonical;
use rustc_infer::infer::outlives::env::RegionBoundPairs;
use rustc_infer::traits::{Obligation, ObligationCause};
use rustc_middle::bug;
use rustc_middle::mir::{Body, ConstraintCategory};
use rustc_middle::ty::{self, Ty, TyCtxt, TypeFoldable, Unnormalized, Upcast};
use rustc_span::Span;
use rustc_span::def_id::DefId;
use rustc_trait_selection::traits::ObligationCause;
use rustc_trait_selection::traits::query::type_op::custom::FallibleCustomTypeOp;
use rustc_trait_selection::traits::query::type_op::{self, TypeOpOutput};
use tracing::{debug, instrument};

Expand Down Expand Up @@ -185,6 +186,31 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
);
}

/// Certain proofs (e.g. `Move`) may error during MIR typeck, so handle them separately.
pub(super) fn prove_fallible_predicate(
&mut self,
predicate: impl Upcast<TyCtxt<'tcx>, ty::Predicate<'tcx>> + std::fmt::Debug,
locations: Locations,
category: ConstraintCategory<'tcx>,
) {
let span = self.last_span;
let predicate = predicate.upcast(self.tcx());
let op = FallibleCustomTypeOp::new(
|ocx| {
ocx.register_obligation(Obligation::new(
ocx.infcx.tcx,
ObligationCause::dummy_with_span(span),
self.infcx.param_env,
predicate,
));
Ok(())
},
"fallible type op",
);

let _: Result<_, ErrorGuaranteed> = self.fully_perform_op(locations, category, op);
}

pub(super) fn normalize<T>(
&mut self,
value: Unnormalized<'tcx, T>,
Expand Down
56 changes: 50 additions & 6 deletions compiler/rustc_borrowck/src/type_check/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ use rustc_infer::infer::region_constraints::RegionConstraintData;
use rustc_infer::infer::{
BoundRegionConversionTime, InferCtxt, NllRegionVariableOrigin, RegionVariableOrigin,
};
use rustc_infer::traits::{Obligation, ObligationCause, PredicateObligations};
use rustc_infer::traits::{Obligation, ObligationCause, PredicateObligations, ScrubbedTraitError};
use rustc_middle::bug;
use rustc_middle::mir::visit::{NonMutatingUseContext, PlaceContext, Visitor};
use rustc_middle::mir::visit::{MutatingUseContext, NonMutatingUseContext, PlaceContext, Visitor};
use rustc_middle::mir::*;
use rustc_middle::traits::query::NoSolution;
use rustc_middle::ty::adjustment::PointerCoercion;
Expand Down Expand Up @@ -1881,6 +1881,44 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
// it must.
self.prove_trait_ref(trait_ref, location.to_locations(), ConstraintCategory::CopyBound);
}

if tcx.features().move_trait() {
match context {
PlaceContext::NonMutatingUse(NonMutatingUseContext::Copy)
| PlaceContext::NonMutatingUse(NonMutatingUseContext::Move) => {
let trait_ref = ty::TraitRef::new(
tcx,
tcx.require_lang_item(LangItem::Move, self.last_span),
[place_ty.ty],
);
self.prove_fallible_predicate(
trait_ref,
location.to_locations(),
ConstraintCategory::MoveBound,
);
}
PlaceContext::NonUse(_)
| PlaceContext::NonMutatingUse(
NonMutatingUseContext::FakeBorrow
| NonMutatingUseContext::Inspect
| NonMutatingUseContext::PlaceMention
| NonMutatingUseContext::Projection
| NonMutatingUseContext::RawBorrow
| NonMutatingUseContext::SharedBorrow,
)
| PlaceContext::MutatingUse(
MutatingUseContext::Store
| MutatingUseContext::SetDiscriminant
| MutatingUseContext::AsmOutput
| MutatingUseContext::Call
| MutatingUseContext::Yield
| MutatingUseContext::Drop
| MutatingUseContext::Borrow
| MutatingUseContext::RawBorrow
| MutatingUseContext::Projection,
) => {}
}
}
}

fn visit_projection_elem(
Expand Down Expand Up @@ -2800,10 +2838,16 @@ impl<'tcx> TypeOp<'tcx> for InstantiateOpaqueType<'tcx> {
span: Span,
) -> Result<TypeOpOutput<'tcx, Self>, ErrorGuaranteed> {
let (mut output, region_constraints) =
scrape_region_constraints(infcx, root_def_id, "InstantiateOpaqueType", span, |ocx| {
ocx.register_obligations(self.obligations.clone());
Ok(())
})?;
scrape_region_constraints::<_, _, ScrubbedTraitError<'tcx>>(
infcx,
root_def_id,
"InstantiateOpaqueType",
span,
|ocx| {
ocx.register_obligations(self.obligations.clone());
Ok(())
},
)?;
self.region_constraints = Some(region_constraints);
output.error_info = Some(self);
Ok(output)
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_feature/src/unstable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -649,6 +649,8 @@ declare_features! (
(unstable, more_qualified_paths, "1.54.0", Some(86935)),
/// Allows `move(expr)` in closures.
(incomplete, move_expr, "1.97.0", Some(155050)),
/// The `Move` autotrait.
(incomplete, move_trait, "CURRENT_RUSTC_VERSION", Some(149607)),
/// The `movrs` target feature on x86.
(unstable, movrs_target_feature, "1.88.0", Some(137976)),
/// Allows the `multiple_supertrait_upcastable` lint.
Expand Down
5 changes: 4 additions & 1 deletion compiler/rustc_hir_analysis/src/check/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -405,7 +405,10 @@ fn bounds_from_generic_clauses<'tcx>(
ty::ClauseKind::Trait(trait_predicate) => {
let entry = types.entry(trait_predicate.self_ty()).or_default();
let def_id = trait_predicate.def_id();
if !tcx.is_default_trait(def_id) && !tcx.is_lang_item(def_id, LangItem::Sized) {
// nia: fixme: metasized
if !tcx.is_implicit_trait(def_id, ty::IncludingSized::No)
&& !tcx.is_lang_item(def_id, LangItem::Sized)
{
// Do not add that restriction to the list if it is a positive requirement.
entry.push(trait_predicate.def_id());
}
Expand Down
69 changes: 34 additions & 35 deletions compiler/rustc_hir_analysis/src/collect/clauses_of.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ use std::assert_matches;
use hir::Node;
use rustc_data_structures::fx::FxIndexSet;
use rustc_hir as hir;
use rustc_hir::attrs::lang_items::LangItem;
use rustc_hir::def::DefKind;
use rustc_hir::def_id::{DefId, LocalDefId};
use rustc_hir::find_attr;
use rustc_middle::ty::{
self, GenericClauses, ImplTraitInTraitData, RegionExt, Ty, TyCtxt, TypeVisitable, TypeVisitor,
Upcast,
self, ClausePolarity, GenericClauses, ImplTraitInTraitData, RegionExt, Ty, TyCtxt,
TypeVisitable, TypeVisitor, Upcast,
};
use rustc_middle::{bug, span_bug};
use rustc_span::{DUMMY_SP, Ident, Span};
Expand Down Expand Up @@ -74,6 +75,15 @@ pub(super) fn clauses_of(tcx: TyCtxt<'_>, def_id: DefId) -> ty::GenericClauses<'
);
}

if !tcx.features().move_trait() {
result.clauses = tcx.arena.alloc_from_iter(result.clauses.iter().copied().filter(|p| {
!p.0.as_trait_clause().is_some_and(|p| {
p.polarity() == ClausePolarity::Positive
&& matches!(tcx.as_lang_item(p.def_id()), Some(LangItem::Move))
})
}));
}

debug!("clauses_of({:?}) = {:?}", def_id, result);
result
}
Expand Down Expand Up @@ -197,19 +207,13 @@ fn gather_explicit_clauses_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Generi
PredicateFilter::All,
OverlappingAsssocItemConstraints::Allowed,
);
icx.lowerer().add_implicit_sizedness_bounds(
&mut bounds,
tcx.types.self_param,
self_bounds,
ImpliedBoundsContext::TraitDef(def_id),
span,
);
icx.lowerer().add_default_traits(
icx.lowerer().add_implicit_bounds(
&mut bounds,
tcx.types.self_param,
self_bounds,
ImpliedBoundsContext::TraitDef(def_id),
span,
true,
);
clauses.extend(bounds);
}
Expand All @@ -236,19 +240,13 @@ fn gather_explicit_clauses_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Generi
let param_ty = icx.lowerer().lower_ty_param(param.hir_id);
let mut bounds = Vec::new();
// Implicit bounds are added to type params unless a `?Trait` bound is found
icx.lowerer().add_implicit_sizedness_bounds(
&mut bounds,
param_ty,
&[],
ImpliedBoundsContext::TyParam(param.def_id, hir_generics.predicates),
param.span,
);
icx.lowerer().add_default_traits(
icx.lowerer().add_implicit_bounds(
&mut bounds,
param_ty,
&[],
ImpliedBoundsContext::TyParam(param.def_id, hir_generics.predicates),
param.span,
true,
);
trace!(?bounds);
clauses.extend(bounds);
Expand Down Expand Up @@ -341,7 +339,20 @@ fn gather_explicit_clauses_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Generi
clauses.insert((ty::ClauseKind::UnstableFeature(*feat_name).upcast(tcx), *span));
}

let mut clauses: Vec<_> = clauses.into_iter().collect();
// Filter out the move trait if the feature is not active
let mut clauses: Vec<_> = if !tcx.features().move_trait() {
clauses
.into_iter()
.filter(|(clause, _)| {
!clause.as_trait_clause().is_some_and(|p| {
p.polarity() == ClausePolarity::Positive
&& matches!(tcx.as_lang_item(p.def_id()), Some(LangItem::Move))
})
})
.collect()
} else {
clauses.into_iter().collect()
};

@lcnr lcnr Aug 28, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that one is unfortunately somewhat problematic. gather_explicit_clauses_of is used by a query whose result we write to crate metadata, so this would erase the Move bound from upstream crates which don't have the feature enabled.

Why is this needed

View changes since the review

@zannabianca1997 zannabianca1997 Aug 28, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Of the changes that I made this is the one i was less sure of

I was trying to fix this test

The original test expected

error[E0091]: type parameter `N` is never used
  --> $DIR/unused-type-param-suggestion.rs:25:8
   |
LL | type D<N: ?Sized> = ();
   |        ^ unused type parameter
   |
   = help: consider removing `N` or referring to it in the body of the type alias

and after the change with Move it appeared a new help message = help: if you intended Nto be a const parameter, useconst N: /* Type */ instead

now, the help should not appear as there is a bound - so i checked from where it came, and got to read the check_type_alias_type_params_are_used that uses some sort of euristic to split huser written bounds and the sized hierarchy (?)

I honestly lost the plot there, but noticed that it was calling the _explicit_ version. And I saw the doc comment of gather_explicit_clauses_of noting that implied and inferred constraints should not appear there, and Move is indeed implicit in that case, so I just aligned it

@fmease fmease Aug 30, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And I saw the doc comment of gather_explicit_clauses_of noting that implied and inferred constraints should not appear there, and Move is indeed implicit in that case, so I just aligned it

Default bounds (or what this PR now also calls "implicit bounds") are excluded from that. Without knowing all the places where we'll perform Move elaboration I would probably revert that.

The comment on gather_explicit_clauses_of doesn't consider these bounds to be potential implied bounds (in the sense of implied supertrait bounds, implicit outlives-bounds, etc.). You can see that gather_explicit_clauses_of does elaborate sizedness & other default bounds.

That's because default bounds plus relaxed bounds are just considered syntax sugar / an early syntactic transformation.

@fmease fmease Aug 30, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

now, the help should not appear as there is a bound - so i checked from where it came, and got to read the check_type_alias_type_params_are_used that uses some sort of euristic to split huser written bounds and the sized hierarchy (?)

Regarding the heuristic, it utilizes the fact that implicitly added Sized (etc.) bounds have the same span as the type parameter they annotate (in order to identify which bounds to "count").

So for (pseudo) <T>, the span of the implicit T: Sized is identical to the span of type parameter T. The span that's used comes from explicit_clauses_of / gather_explicit_clauses_of.

I would check what the span we use for implicit T: Move bounds (T type param). If it indeed shares the span with the type param, then there must be something else going on.


// Subtle: before we store the clauses into the tcx, we
// sort them so that clauses like `T: Foo<Item=U>` come
Expand Down Expand Up @@ -684,19 +695,13 @@ pub(super) fn implied_clauses_with_filter<'tcx>(
| PredicateFilter::SelfOnly
| PredicateFilter::SelfTraitThatDefines(_)
| PredicateFilter::SelfAndAssociatedTypeBounds => {
icx.lowerer().add_implicit_sizedness_bounds(
&mut bounds,
self_param_ty,
superbounds,
ImpliedBoundsContext::TraitDef(trait_def_id),
item.span,
);
icx.lowerer().add_default_traits(
icx.lowerer().add_implicit_bounds(
&mut bounds,
self_param_ty,
superbounds,
ImpliedBoundsContext::TraitDef(trait_def_id),
item.span,
true,
);
}
//`ConstIfConst` is only interested in `[const]` bounds.
Expand Down Expand Up @@ -986,19 +991,13 @@ impl<'tcx> ItemCtxt<'tcx> {
match param.kind {
hir::GenericParamKind::Type { .. } => {
let param_ty = self.lowerer().lower_ty_param(param.hir_id);
self.lowerer().add_implicit_sizedness_bounds(
&mut bounds,
param_ty,
&[],
ImpliedBoundsContext::TyParam(param.def_id, hir_generics.predicates),
param.span,
);
self.lowerer().add_default_traits(
self.lowerer().add_implicit_bounds(
&mut bounds,
param_ty,
&[],
ImpliedBoundsContext::TyParam(param.def_id, hir_generics.predicates),
param.span,
true,
);
}
hir::GenericParamKind::Lifetime { .. }
Expand Down
20 changes: 4 additions & 16 deletions compiler/rustc_hir_analysis/src/collect/item_bounds.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,19 +55,13 @@ fn associated_type_bounds<'tcx>(
| PredicateFilter::SelfTraitThatDefines(_)
| PredicateFilter::SelfAndAssociatedTypeBounds => {
// Implicit bounds are added to associated types unless a `?Trait` bound is found.
icx.lowerer().add_implicit_sizedness_bounds(
&mut bounds,
item_ty,
hir_bounds,
ImpliedBoundsContext::AssociatedTypeOrImplTrait,
span,
);
icx.lowerer().add_default_traits(
icx.lowerer().add_implicit_bounds(
&mut bounds,
item_ty,
hir_bounds,
ImpliedBoundsContext::AssociatedTypeOrImplTrait,
span,
true,
);

// Also collect `where Self::Assoc: Trait` from the parent trait's where clauses.
Expand Down Expand Up @@ -382,19 +376,13 @@ fn opaque_type_bounds<'tcx>(
| PredicateFilter::SelfOnly
| PredicateFilter::SelfTraitThatDefines(_)
| PredicateFilter::SelfAndAssociatedTypeBounds => {
icx.lowerer().add_implicit_sizedness_bounds(
&mut bounds,
item_ty,
hir_bounds,
ImpliedBoundsContext::AssociatedTypeOrImplTrait,
span,
);
icx.lowerer().add_default_traits(
icx.lowerer().add_implicit_bounds(
&mut bounds,
item_ty,
hir_bounds,
ImpliedBoundsContext::AssociatedTypeOrImplTrait,
span,
true,
);
}
//`ConstIfConst` is only interested in `[const]` bounds.
Expand Down
Loading
Loading