Skip to content

Commit 1d4a767

Browse files
committed
Auto merge of #131985 - compiler-errors:const-pred, r=fee1-dead
Represent trait constness as a distinct predicate cc `@rust-lang/project-const-traits` r? `@ghost` for now Also mirrored everything that is written below on this hackmd here: https://hackmd.io/`@compiler-errors/r12zoixg1l` # Tl;dr: * This PR removes the bulk of the old effect desugaring. * This PR reimplements most of the effect desugaring as a new predicate and set of a couple queries. I believe it majorly simplifies the implementation and allows us to move forward more easily on its implementation. I'm putting this up both as a request for comments and a vibe-check, but also as a legitimate implementation that I'd like to see land (though no rush of course on that last part). ## Background ### Early days Once upon a time, we represented trait constness in the param-env and in `TraitPredicate`. This was very difficult to implement correctly; it had bugs and was also incomplete; I don't think this was anyone's fault though, it was just the limit of experimental knowledge we had at that point. Dealing with `~const` within predicates themselves meant dealing with constness all throughout the trait solver. This was difficult to keep track of, and afaict was not handled well with all the corners of candidate assembly. Specifically, we had to (in various places) remap constness according to the param-env constness: https://github.com/rust-lang/rust/blob/574b64a97f52162f965bc201e47f0af8279ca65d/compiler/rustc_trait_selection/src/traits/select/mod.rs#L1498 This was annoying and manual and also error prone. ### Beginning of the effects desugaring Later on, #113210 reimplemented a new desugaring for const traits via a `<const HOST: bool>` predicate. This essentially "reified" the const checking and separated it from any of the remapping or separate tracking in param-envs. For example, if I was in a const-if-const environment, but I wanted to call a trait that was non-const, this reification would turn the constness mismatch into a simple *type* mismatch of the effect parameter. While this was a monumental step towards straightening out const trait checking in the trait system, it had its own issues, since that meant that the constness of a trait (or any item within it, like an associated type) was *early-bound*. This essentially meant that `<T as Trait>::Assoc` was *distinct* from `<T as ~const Trait>::Assoc`, which was bad. ### Associated-type bound based effects desugaring After this, #120639 implemented a new effects desugaring. This used an associated type to more clearly represent the fact that the constness is not an input parameter of a trait, but a property that could be computed of a impl. The write-up linked in that PR explains it better than I could. However, I feel like it really reached the limits of what can comfortably be expressed in terms of associated type and trait calculus. Also, `<const HOST: bool>` remains a synthetic const parameter, which is observable in nested items like RPITs and closures, and comes with tons of its own hacks in the astconv and middle layer. For example, there are pieces of unintuitive code that are needed to represent semantics like elaboration, and eventually will be needed to make error reporting intuitive, and hopefully in the future assist us in implementing built-in traits (eventually we'll want something like `~const Fn` trait bounds!). elaboration hack: https://github.com/rust-lang/rust/blob/8069f8d17a6c86a8fd881939fcce359a90c57ff2/compiler/rustc_type_ir/src/elaborate.rs#L133-L195 trait bound remapping hack for diagnostics: https://github.com/rust-lang/rust/blob/8069f8d17a6c86a8fd881939fcce359a90c57ff2/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs#L2370-L2413 I want to be clear that I don't think this is a issue of implementation quality or anything like that; I think it's simply a very clear sign that we're using types and traits in a way that they're not fundamentally supposed to be used, especially given that constness deserves to be represented as a first-class concept. ### What now? This PR implements a new desugaring for const traits. Specifically, it introduces a `HostEffect` predicate to represent the obligation an impl is const, rather than using associated type bounds and the compat trait that exists for effects today. ### `HostEffect` predicate A `HostEffect` clause has two parts -- the `TraitRef` we're trying to prove, and a `HostPolarity::{Maybe, Const}`. `HostPolarity::Const` corresponds to `T: const Trait` bounds, which must *always* be proven as const, and which can be written in any context. These are lowered directly into the predicates of an item, since they're not "context-specific". On the other hand, `HostPolarity::Maybe` corresponds to `T: ~const Trait` bounds which must only exist in a conditionally-const context like a method in a `#[const_trait]`, or a `const fn` free function. We do not lower these immediately into the predicates of an item; instead, we collect them into a new query called the **`const_conditions`**. These are the set of trait refs that we need to prove have const implementations for an item to be const. Notably, they're represented as bare (poly) trait refs because they are meant to be paired back together with a `HostPolarity` when they're being registered in typeck (see next section). For example, given: ```rust const fn foo<T: ~const A + const B>() {} ``` `foo`'s const conditions would contain `T: A`, but not `T: B`. On the flip side, foo's predicates (`predicates_of`) query would contain `HostEffect(T: B, HostPolarity::Const)` but not `HostEffect(T: A, HostPolarity::Maybe)` since we don't need to prove that predicate in a non-const environment (and it's not even the right predicate to prove in an unconditionally const environment). ### Type checking const bodies When type checking bodies in HIR, when we encounter a call expression, we additionally register the callee item's const conditions with the `HostPolarity` from the body we're typechecking (`Const` for unconditionally const things like `const`/`static` items, and `Maybe` for conditionally const things like const fns; and we don't register `HostPolarity` predicates for non-const bodies). When type-checking a conditionally const body, we augment its param-env with `HostEffect(..., Maybe)` predicates. ### Checking that const impls are WF We extend the logic in `compare_method_predicate_entailment` to also check the const-conditions of the impl method, to make sure that we error for: ```rust #[const_trait] Bar {} #[const_trait] trait Foo { fn method<T: Bar>(); } impl Foo for () { fn method<T: ~const Bar>() {} // stronger assumption! } ``` We also extend the WF check for impls to register the const conditions of the trait that is being implemented. This is to make sure we error for: ```rust #[const_trait] trait Bar {} #[const_trait] trait Foo<T> where T: ~const Bar {} impl<T> const Foo<T> for () {} //~^ `T: ~const Bar` is missing! ``` ### Proving a `HostEffect` predicate We have several ways of proving a `HostEffect` predicate: 1. Matching a `HostEffect` predicate from the param-env 2. From an impl - we do impl selection very similar to confirming a trait goal, except we filter for only const impls, and we additionally register the impl's const conditions (i.e. the impl's `~const` where clauses). Later I expect that we will add more built-in implementations for things like `Fn`. ## What next? After this PR, I'd like to split out the work more so it can proceed in parallel and probably amongst others that are not me. * Register `HostEffect` goal for places in HIR typeck that correspond to call terminators, like autoderef. * Make traits in libstd const again. * Probably need to impl host effect preds in old solver. * Implement built-in `HostEffect` rules for traits like `Fn`. * Rip out const checking from MIR altogether. ## So what? This ends up being super convenient basically everywhere in the compiler. Due to the design of the new trait solver, we end up having an almost parallel structure to the existing trait and projection predicates for assembling `HostEffect` predicates; adding new candidates and especially new built-in implementations is now basically trivial, and it's quite straightforward to understand the confirmation logic for these predicates. Same with diagnostics reporting; since we have predicates which represent the obligation to prove an impl is const, we can simplify and make these diagnostics richer without having to write a ton of logic to intercept and rewrite the existing `Compat` trait errors. Finally, it gives us a much more straightforward path for supporting the const effect on the old trait solver. I'm personally quite passionate about getting const trait support into the hands of users without having to wait until the new solver lands[^1], so I think after this PR lands we can begin to gauge how difficult it would be to implement constness in the old trait solver too. This PR will not do this yet. [^1]: Though this is not a prerequisite or by any means the only justification for this PR.
2 parents f61306d + 0f5a47d commit 1d4a767

File tree

221 files changed

+2364
-2749
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

221 files changed

+2364
-2749
lines changed

compiler/rustc_ast_lowering/src/item.rs

+19-186
Large diffs are not rendered by default.

compiler/rustc_ast_lowering/src/lib.rs

+3-17
Original file line numberDiff line numberDiff line change
@@ -154,17 +154,10 @@ struct LoweringContext<'a, 'hir> {
154154
/// defined on the TAIT, so we have type Foo<'a1> = ... and we establish a mapping in this
155155
/// field from the original parameter 'a to the new parameter 'a1.
156156
generics_def_id_map: Vec<LocalDefIdMap<LocalDefId>>,
157-
158-
host_param_id: Option<LocalDefId>,
159-
ast_index: &'a IndexSlice<LocalDefId, AstOwner<'a>>,
160157
}
161158

162159
impl<'a, 'hir> LoweringContext<'a, 'hir> {
163-
fn new(
164-
tcx: TyCtxt<'hir>,
165-
resolver: &'a mut ResolverAstLowering,
166-
ast_index: &'a IndexSlice<LocalDefId, AstOwner<'a>>,
167-
) -> Self {
160+
fn new(tcx: TyCtxt<'hir>, resolver: &'a mut ResolverAstLowering) -> Self {
168161
Self {
169162
// Pseudo-globals.
170163
tcx,
@@ -204,8 +197,6 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
204197
// interact with `gen`/`async gen` blocks
205198
allow_async_iterator: [sym::gen_future, sym::async_iterator].into(),
206199
generics_def_id_map: Default::default(),
207-
host_param_id: None,
208-
ast_index,
209200
}
210201
}
211202

@@ -2054,11 +2045,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
20542045
param: &GenericParam,
20552046
source: hir::GenericParamSource,
20562047
) -> hir::GenericParam<'hir> {
2057-
let (name, kind) = self.lower_generic_param_kind(
2058-
param,
2059-
source,
2060-
attr::contains_name(&param.attrs, sym::rustc_runtime),
2061-
);
2048+
let (name, kind) = self.lower_generic_param_kind(param, source);
20622049

20632050
let hir_id = self.lower_node_id(param.id);
20642051
self.lower_attrs(hir_id, &param.attrs);
@@ -2078,7 +2065,6 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
20782065
&mut self,
20792066
param: &GenericParam,
20802067
source: hir::GenericParamSource,
2081-
is_host_effect: bool,
20822068
) -> (hir::ParamName, hir::GenericParamKind<'hir>) {
20832069
match &param.kind {
20842070
GenericParamKind::Lifetime => {
@@ -2144,7 +2130,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
21442130

21452131
(
21462132
hir::ParamName::Plain(self.lower_ident(param.ident)),
2147-
hir::GenericParamKind::Const { ty, default, is_host_effect, synthetic: false },
2133+
hir::GenericParamKind::Const { ty, default, synthetic: false },
21482134
)
21492135
}
21502136
}

compiler/rustc_feature/src/builtin_attrs.rs

-4
Original file line numberDiff line numberDiff line change
@@ -838,10 +838,6 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
838838
rustc_const_panic_str, Normal, template!(Word), WarnFollowing,
839839
EncodeCrossCrate::Yes, INTERNAL_UNSTABLE
840840
),
841-
rustc_attr!(
842-
rustc_runtime, Normal, template!(Word), WarnFollowing,
843-
EncodeCrossCrate::No, INTERNAL_UNSTABLE
844-
),
845841

846842
// ==========================================================================
847843
// Internal attributes, Layout related:

compiler/rustc_hir/src/hir.rs

-1
Original file line numberDiff line numberDiff line change
@@ -580,7 +580,6 @@ pub enum GenericParamKind<'hir> {
580580
ty: &'hir Ty<'hir>,
581581
/// Optional default value for the const generic param
582582
default: Option<&'hir ConstArg<'hir>>,
583-
is_host_effect: bool,
584583
synthetic: bool,
585584
},
586585
}

compiler/rustc_hir/src/intravisit.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -935,7 +935,7 @@ pub fn walk_generic_param<'v, V: Visitor<'v>>(
935935
match param.kind {
936936
GenericParamKind::Lifetime { .. } => {}
937937
GenericParamKind::Type { ref default, .. } => visit_opt!(visitor, visit_ty, default),
938-
GenericParamKind::Const { ref ty, ref default, is_host_effect: _, synthetic: _ } => {
938+
GenericParamKind::Const { ref ty, ref default, synthetic: _ } => {
939939
try_visit!(visitor.visit_ty(ty));
940940
if let Some(ref default) = default {
941941
try_visit!(visitor.visit_const_param_default(param.hir_id, default));

compiler/rustc_hir/src/lang_items.rs

-8
Original file line numberDiff line numberDiff line change
@@ -415,14 +415,6 @@ language_item_table! {
415415

416416
String, sym::String, string, Target::Struct, GenericRequirement::None;
417417
CStr, sym::CStr, c_str, Target::Struct, GenericRequirement::None;
418-
419-
EffectsRuntime, sym::EffectsRuntime, effects_runtime, Target::Struct, GenericRequirement::None;
420-
EffectsNoRuntime, sym::EffectsNoRuntime, effects_no_runtime, Target::Struct, GenericRequirement::None;
421-
EffectsMaybe, sym::EffectsMaybe, effects_maybe, Target::Struct, GenericRequirement::None;
422-
EffectsIntersection, sym::EffectsIntersection, effects_intersection, Target::Trait, GenericRequirement::None;
423-
EffectsIntersectionOutput, sym::EffectsIntersectionOutput, effects_intersection_output, Target::AssocTy, GenericRequirement::None;
424-
EffectsCompat, sym::EffectsCompat, effects_compat, Target::Trait, GenericRequirement::Exact(1);
425-
EffectsTyCompat, sym::EffectsTyCompat, effects_ty_compat, Target::Trait, GenericRequirement::Exact(1);
426418
}
427419

428420
pub enum GenericRequirement {

compiler/rustc_hir_analysis/src/bounds.rs

+15-139
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,8 @@
33
44
use rustc_data_structures::fx::FxIndexMap;
55
use rustc_hir::LangItem;
6-
use rustc_hir::def::DefKind;
7-
use rustc_middle::ty::fold::FnMutDelegate;
86
use rustc_middle::ty::{self, Ty, TyCtxt, Upcast};
97
use rustc_span::Span;
10-
use rustc_span::def_id::DefId;
11-
12-
use crate::hir_ty_lowering::PredicateFilter;
138

149
/// Collects together a list of type bounds. These lists of bounds occur in many places
1510
/// in Rust's syntax:
@@ -47,12 +42,9 @@ impl<'tcx> Bounds<'tcx> {
4742
pub(crate) fn push_trait_bound(
4843
&mut self,
4944
tcx: TyCtxt<'tcx>,
50-
defining_def_id: DefId,
5145
bound_trait_ref: ty::PolyTraitRef<'tcx>,
5246
span: Span,
5347
polarity: ty::PredicatePolarity,
54-
constness: Option<ty::BoundConstness>,
55-
predicate_filter: PredicateFilter,
5648
) {
5749
let clause = (
5850
bound_trait_ref
@@ -68,137 +60,6 @@ impl<'tcx> Bounds<'tcx> {
6860
} else {
6961
self.clauses.push(clause);
7062
}
71-
72-
// FIXME(effects): Lift this out of `push_trait_bound`, and move it somewhere else.
73-
// Perhaps moving this into `lower_poly_trait_ref`, just like we lower associated
74-
// type bounds.
75-
if !tcx.features().effects() {
76-
return;
77-
}
78-
match predicate_filter {
79-
PredicateFilter::SelfOnly | PredicateFilter::SelfThatDefines(_) => {
80-
return;
81-
}
82-
PredicateFilter::All | PredicateFilter::SelfAndAssociatedTypeBounds => {
83-
// Ok.
84-
}
85-
}
86-
87-
// For `T: ~const Tr` or `T: const Tr`, we need to add an additional bound on the
88-
// associated type of `<T as Tr>` and make sure that the effect is compatible.
89-
let compat_val = match (tcx.def_kind(defining_def_id), constness) {
90-
// FIXME(effects): revisit the correctness of this
91-
(_, Some(ty::BoundConstness::Const)) => tcx.consts.false_,
92-
// body owners that can have trait bounds
93-
(
94-
DefKind::Const | DefKind::Fn | DefKind::AssocFn,
95-
Some(ty::BoundConstness::ConstIfConst),
96-
) => tcx.expected_host_effect_param_for_body(defining_def_id),
97-
98-
(_, None) => {
99-
if !tcx.is_const_trait(bound_trait_ref.def_id()) {
100-
return;
101-
}
102-
tcx.consts.true_
103-
}
104-
(DefKind::Trait, Some(ty::BoundConstness::ConstIfConst)) => {
105-
// we are in a trait, where `bound_trait_ref` could be:
106-
// (1) a super trait `trait Foo: ~const Bar`.
107-
// - This generates `<Self as Foo>::Effects: TyCompat<<Self as Bar>::Effects>`
108-
//
109-
// (2) a where clause `where for<..> Something: ~const Bar`.
110-
// - This generates `for<..> <Self as Foo>::Effects: TyCompat<<Something as Bar>::Effects>`
111-
let Some(own_fx) = tcx.associated_type_for_effects(defining_def_id) else {
112-
tcx.dcx().span_delayed_bug(span, "should not have allowed `~const` on a trait that doesn't have `#[const_trait]`");
113-
return;
114-
};
115-
let own_fx_ty = Ty::new_projection(
116-
tcx,
117-
own_fx,
118-
ty::GenericArgs::identity_for_item(tcx, own_fx),
119-
);
120-
let Some(their_fx) = tcx.associated_type_for_effects(bound_trait_ref.def_id())
121-
else {
122-
tcx.dcx().span_delayed_bug(span, "`~const` on trait without Effects assoc");
123-
return;
124-
};
125-
let their_fx_ty =
126-
Ty::new_projection(tcx, their_fx, bound_trait_ref.skip_binder().args);
127-
let compat = tcx.require_lang_item(LangItem::EffectsTyCompat, Some(span));
128-
let clause = bound_trait_ref
129-
.map_bound(|_| {
130-
let trait_ref = ty::TraitRef::new(tcx, compat, [own_fx_ty, their_fx_ty]);
131-
ty::ClauseKind::Trait(ty::TraitPredicate {
132-
trait_ref,
133-
polarity: ty::PredicatePolarity::Positive,
134-
})
135-
})
136-
.upcast(tcx);
137-
138-
self.clauses.push((clause, span));
139-
return;
140-
}
141-
142-
(DefKind::Impl { of_trait: true }, Some(ty::BoundConstness::ConstIfConst)) => {
143-
// this is a where clause on an impl header.
144-
// push `<T as Tr>::Effects` into the set for the `Min` bound.
145-
let Some(assoc) = tcx.associated_type_for_effects(bound_trait_ref.def_id()) else {
146-
tcx.dcx().span_delayed_bug(span, "`~const` on trait without Effects assoc");
147-
return;
148-
};
149-
150-
let ty = bound_trait_ref
151-
.map_bound(|trait_ref| Ty::new_projection(tcx, assoc, trait_ref.args));
152-
153-
// When the user has written `for<'a, T> X<'a, T>: ~const Foo`, replace the
154-
// binders to dummy ones i.e. `X<'static, ()>` so they can be referenced in
155-
// the `Min` associated type properly (which doesn't allow using `for<>`)
156-
// This should work for any bound variables as long as they don't have any
157-
// bounds e.g. `for<T: Trait>`.
158-
// FIXME(effects) reconsider this approach to allow compatibility with `for<T: Tr>`
159-
let ty = tcx.replace_bound_vars_uncached(ty, FnMutDelegate {
160-
regions: &mut |_| tcx.lifetimes.re_static,
161-
types: &mut |_| tcx.types.unit,
162-
consts: &mut |_| unimplemented!("`~const` does not support const binders"),
163-
});
164-
165-
self.effects_min_tys.insert(ty, span);
166-
return;
167-
}
168-
// for
169-
// ```
170-
// trait Foo { type Bar: ~const Trait }
171-
// ```
172-
// ensure that `<Self::Bar as Trait>::Effects: TyCompat<Self::Effects>`.
173-
//
174-
// FIXME(effects) this is equality for now, which wouldn't be helpful for a non-const implementor
175-
// that uses a `Bar` that implements `Trait` with `Maybe` effects.
176-
(DefKind::AssocTy, Some(ty::BoundConstness::ConstIfConst)) => {
177-
// FIXME(effects): implement this
178-
return;
179-
}
180-
// probably illegal in this position.
181-
(_, Some(ty::BoundConstness::ConstIfConst)) => {
182-
tcx.dcx().span_delayed_bug(span, "invalid `~const` encountered");
183-
return;
184-
}
185-
};
186-
// create a new projection type `<T as Tr>::Effects`
187-
let Some(assoc) = tcx.associated_type_for_effects(bound_trait_ref.def_id()) else {
188-
tcx.dcx().span_delayed_bug(
189-
span,
190-
"`~const` trait bound has no effect assoc yet no errors encountered?",
191-
);
192-
return;
193-
};
194-
let self_ty = Ty::new_projection(tcx, assoc, bound_trait_ref.skip_binder().args);
195-
// make `<T as Tr>::Effects: Compat<runtime>`
196-
let new_trait_ref =
197-
ty::TraitRef::new(tcx, tcx.require_lang_item(LangItem::EffectsCompat, Some(span)), [
198-
ty::GenericArg::from(self_ty),
199-
compat_val.into(),
200-
]);
201-
self.clauses.push((bound_trait_ref.rebind(new_trait_ref).upcast(tcx), span));
20263
}
20364

20465
pub(crate) fn push_projection_bound(
@@ -220,6 +81,21 @@ impl<'tcx> Bounds<'tcx> {
22081
self.clauses.insert(0, (trait_ref.upcast(tcx), span));
22182
}
22283

84+
/// Push a `const` or `~const` bound as a `HostEffect` predicate.
85+
pub(crate) fn push_const_bound(
86+
&mut self,
87+
tcx: TyCtxt<'tcx>,
88+
bound_trait_ref: ty::PolyTraitRef<'tcx>,
89+
host: ty::HostPolarity,
90+
span: Span,
91+
) {
92+
if tcx.is_const_trait(bound_trait_ref.def_id()) {
93+
self.clauses.push((bound_trait_ref.to_host_effect_clause(tcx, host), span));
94+
} else {
95+
tcx.dcx().span_delayed_bug(span, "tried to lower {host:?} bound for non-const trait");
96+
}
97+
}
98+
22399
pub(crate) fn clauses(
224100
&self,
225101
// FIXME(effects): remove tcx

0 commit comments

Comments
 (0)