Skip to content

Commit edcdab0

Browse files
authored
Rollup merge of #108033 - lcnr:coinductive-attr, r=compiler-errors
add an unstable `#[rustc_coinductive]` attribute useful to test coinduction, especially in the new solver. as this attribute should remain permanently unstable I don't think this needs any official approval. cc ``@rust-lang/types`` had to weaken the check for stable query results in the solver to prevent an ICE if there's a coinductive cycle with constraints. r? ``@compiler-errors``
2 parents 7e0127b + a2f0303 commit edcdab0

File tree

10 files changed

+130
-37
lines changed

10 files changed

+130
-37
lines changed

compiler/rustc_feature/src/builtin_attrs.rs

+11-4
Original file line numberDiff line numberDiff line change
@@ -414,7 +414,10 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
414414
),
415415

416416
// Linking:
417-
gated!(naked, Normal, template!(Word), WarnFollowing, @only_local: true, naked_functions, experimental!(naked)),
417+
gated!(
418+
naked, Normal, template!(Word), WarnFollowing, @only_local: true,
419+
naked_functions, experimental!(naked)
420+
),
418421

419422
// Plugins:
420423
BuiltinAttribute {
@@ -441,7 +444,8 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
441444
),
442445
// RFC #1268
443446
gated!(
444-
marker, Normal, template!(Word), WarnFollowing, marker_trait_attr, experimental!(marker)
447+
marker, Normal, template!(Word), WarnFollowing, @only_local: true,
448+
marker_trait_attr, experimental!(marker)
445449
),
446450
gated!(
447451
thread_local, Normal, template!(Word), WarnFollowing,
@@ -682,14 +686,17 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
682686
"language items are subject to change",
683687
),
684688
rustc_attr!(
685-
rustc_pass_by_value, Normal,
686-
template!(Word), ErrorFollowing,
689+
rustc_pass_by_value, Normal, template!(Word), ErrorFollowing,
687690
"#[rustc_pass_by_value] is used to mark types that must be passed by value instead of reference."
688691
),
689692
rustc_attr!(
690693
rustc_coherence_is_core, AttributeType::CrateLevel, template!(Word), ErrorFollowing, @only_local: true,
691694
"#![rustc_coherence_is_core] allows inherent methods on builtin types, only intended to be used in `core`."
692695
),
696+
rustc_attr!(
697+
rustc_coinductive, AttributeType::Normal, template!(Word), WarnFollowing, @only_local: true,
698+
"#![rustc_coinductive] changes a trait to be coinductive, allowing cycles in the trait solver."
699+
),
693700
rustc_attr!(
694701
rustc_allow_incoherent_impl, AttributeType::Normal, template!(Word), ErrorFollowing, @only_local: true,
695702
"#[rustc_allow_incoherent_impl] has to be added to all impl items of an incoherent inherent impl."

compiler/rustc_hir_analysis/src/collect.rs

+7-5
Original file line numberDiff line numberDiff line change
@@ -934,9 +934,10 @@ fn trait_def(tcx: TyCtxt<'_>, def_id: DefId) -> ty::TraitDef {
934934
}
935935

936936
let is_marker = tcx.has_attr(def_id, sym::marker);
937+
let rustc_coinductive = tcx.has_attr(def_id, sym::rustc_coinductive);
937938
let skip_array_during_method_dispatch =
938939
tcx.has_attr(def_id, sym::rustc_skip_array_during_method_dispatch);
939-
let spec_kind = if tcx.has_attr(def_id, sym::rustc_unsafe_specialization_marker) {
940+
let specialization_kind = if tcx.has_attr(def_id, sym::rustc_unsafe_specialization_marker) {
940941
ty::trait_def::TraitSpecializationKind::Marker
941942
} else if tcx.has_attr(def_id, sym::rustc_specialization_trait) {
942943
ty::trait_def::TraitSpecializationKind::AlwaysApplicable
@@ -1036,16 +1037,17 @@ fn trait_def(tcx: TyCtxt<'_>, def_id: DefId) -> ty::TraitDef {
10361037
no_dups.then_some(list)
10371038
});
10381039

1039-
ty::TraitDef::new(
1040+
ty::TraitDef {
10401041
def_id,
10411042
unsafety,
10421043
paren_sugar,
1043-
is_auto,
1044+
has_auto_impl: is_auto,
10441045
is_marker,
1046+
is_coinductive: rustc_coinductive || is_auto,
10451047
skip_array_during_method_dispatch,
1046-
spec_kind,
1048+
specialization_kind,
10471049
must_implement_one_of,
1048-
)
1050+
}
10491051
}
10501052

10511053
fn are_suggestable_generic_args(generic_args: &[hir::GenericArg<'_>]) -> bool {

compiler/rustc_middle/src/ty/mod.rs

+6-4
Original file line numberDiff line numberDiff line change
@@ -2388,15 +2388,17 @@ impl<'tcx> TyCtxt<'tcx> {
23882388
self.trait_def(trait_def_id).has_auto_impl
23892389
}
23902390

2391+
/// Returns `true` if this is coinductive, either because it is
2392+
/// an auto trait or because it has the `#[rustc_coinductive]` attribute.
2393+
pub fn trait_is_coinductive(self, trait_def_id: DefId) -> bool {
2394+
self.trait_def(trait_def_id).is_coinductive
2395+
}
2396+
23912397
/// Returns `true` if this is a trait alias.
23922398
pub fn trait_is_alias(self, trait_def_id: DefId) -> bool {
23932399
self.def_kind(trait_def_id) == DefKind::TraitAlias
23942400
}
23952401

2396-
pub fn trait_is_coinductive(self, trait_def_id: DefId) -> bool {
2397-
self.trait_is_auto(trait_def_id) || self.lang_items().sized_trait() == Some(trait_def_id)
2398-
}
2399-
24002402
/// Returns layout of a generator. Layout might be unavailable if the
24012403
/// generator is tainted by errors.
24022404
pub fn generator_layout(self, def_id: DefId) -> Option<&'tcx GeneratorLayout<'tcx>> {

compiler/rustc_middle/src/ty/trait_def.rs

+9-22
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,15 @@ pub struct TraitDef {
3131
/// and thus `impl`s of it are allowed to overlap.
3232
pub is_marker: bool,
3333

34+
/// If `true`, then this trait has to `#[rustc_coinductive]` attribute or
35+
/// is an auto trait. This indicates that trait solver cycles involving an
36+
/// `X: ThisTrait` goal are accepted.
37+
///
38+
/// In the future all traits should be coinductive, but we need a better
39+
/// formal understanding of what exactly that means and should probably
40+
/// also have already switched to the new trait solver.
41+
pub is_coinductive: bool,
42+
3443
/// If `true`, then this trait has the `#[rustc_skip_array_during_method_dispatch]`
3544
/// attribute, indicating that editions before 2021 should not consider this trait
3645
/// during method dispatch if the receiver is an array.
@@ -81,28 +90,6 @@ impl TraitImpls {
8190
}
8291

8392
impl<'tcx> TraitDef {
84-
pub fn new(
85-
def_id: DefId,
86-
unsafety: hir::Unsafety,
87-
paren_sugar: bool,
88-
has_auto_impl: bool,
89-
is_marker: bool,
90-
skip_array_during_method_dispatch: bool,
91-
specialization_kind: TraitSpecializationKind,
92-
must_implement_one_of: Option<Box<[Ident]>>,
93-
) -> TraitDef {
94-
TraitDef {
95-
def_id,
96-
unsafety,
97-
paren_sugar,
98-
has_auto_impl,
99-
is_marker,
100-
skip_array_during_method_dispatch,
101-
specialization_kind,
102-
must_implement_one_of,
103-
}
104-
}
105-
10693
pub fn ancestors(
10794
&self,
10895
tcx: TyCtxt<'tcx>,

compiler/rustc_passes/src/check_attr.rs

+15
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ impl CheckAttrVisitor<'_> {
156156
| sym::rustc_dirty
157157
| sym::rustc_if_this_changed
158158
| sym::rustc_then_this_would_need => self.check_rustc_dirty_clean(&attr),
159+
sym::rustc_coinductive => self.check_rustc_coinductive(&attr, span, target),
159160
sym::cmse_nonsecure_entry => {
160161
self.check_cmse_nonsecure_entry(hir_id, attr, span, target)
161162
}
@@ -1608,6 +1609,20 @@ impl CheckAttrVisitor<'_> {
16081609
}
16091610
}
16101611

1612+
/// Checks if the `#[rustc_coinductive]` attribute is applied to a trait.
1613+
fn check_rustc_coinductive(&self, attr: &Attribute, span: Span, target: Target) -> bool {
1614+
match target {
1615+
Target::Trait => true,
1616+
_ => {
1617+
self.tcx.sess.emit_err(errors::AttrShouldBeAppliedToTrait {
1618+
attr_span: attr.span,
1619+
defn_span: span,
1620+
});
1621+
false
1622+
}
1623+
}
1624+
}
1625+
16111626
/// Checks if `#[link_section]` is applied to a function or static.
16121627
fn check_link_section(&self, hir_id: HirId, attr: &Attribute, span: Span, target: Target) {
16131628
match target {

compiler/rustc_span/src/symbol.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1224,6 +1224,7 @@ symbols! {
12241224
rustc_capture_analysis,
12251225
rustc_clean,
12261226
rustc_coherence_is_core,
1227+
rustc_coinductive,
12271228
rustc_const_stable,
12281229
rustc_const_unstable,
12291230
rustc_conversion_suggestion,

compiler/rustc_trait_selection/src/solve/mod.rs

+8-2
Original file line numberDiff line numberDiff line change
@@ -265,12 +265,18 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
265265
// call `exists<U> <T as Trait>::Assoc == U` to enable better caching. This goal
266266
// could constrain `U` to `u32` which would cause this check to result in a
267267
// solver cycle.
268-
if cfg!(debug_assertions) && has_changed && !self.in_projection_eq_hack {
268+
if cfg!(debug_assertions)
269+
&& has_changed
270+
&& !self.in_projection_eq_hack
271+
&& !self.search_graph.in_cycle()
272+
{
269273
let mut orig_values = OriginalQueryValues::default();
270274
let canonical_goal = self.infcx.canonicalize_query(goal, &mut orig_values);
271275
let canonical_response =
272276
EvalCtxt::evaluate_canonical_goal(self.tcx(), self.search_graph, canonical_goal)?;
273-
assert!(canonical_response.value.var_values.is_identity());
277+
if !canonical_response.value.var_values.is_identity() {
278+
bug!("unstable result: {goal:?} {canonical_goal:?} {canonical_response:?}");
279+
}
274280
assert_eq!(certainty, canonical_response.value.certainty);
275281
}
276282

compiler/rustc_trait_selection/src/solve/search_graph/mod.rs

+18
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,24 @@ impl<'tcx> SearchGraph<'tcx> {
4242
&& !self.overflow_data.did_overflow()
4343
}
4444

45+
/// Whether we're currently in a cycle. This should only be used
46+
/// for debug assertions.
47+
pub(super) fn in_cycle(&self) -> bool {
48+
if let Some(stack_depth) = self.stack.last() {
49+
// Either the current goal on the stack is the root of a cycle...
50+
if self.stack[stack_depth].has_been_used {
51+
return true;
52+
}
53+
54+
// ...or it depends on a goal with a lower depth.
55+
let current_goal = self.stack[stack_depth].goal;
56+
let entry_index = self.provisional_cache.lookup_table[&current_goal];
57+
self.provisional_cache.entries[entry_index].depth != stack_depth
58+
} else {
59+
false
60+
}
61+
}
62+
4563
/// Tries putting the new goal on the stack, returning an error if it is already cached.
4664
///
4765
/// This correctly updates the provisional cache if there is a cycle.

library/core/src/marker.rs

+1
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ unsafe impl<T: Sync + ?Sized> Send for &T {}
9797
#[fundamental] // for Default, for example, which requires that `[T]: !Default` be evaluatable
9898
#[rustc_specialization_trait]
9999
#[rustc_deny_explicit_impl]
100+
#[cfg_attr(not(bootstrap), rustc_coinductive)]
100101
pub trait Sized {
101102
// Empty.
102103
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// check-pass
2+
// revisions: old new
3+
//[new] compile-flags: -Ztrait-solver=next
4+
5+
// If we use canonical goals during trait solving we have to reevaluate
6+
// the root goal of a cycle until we hit a fixpoint.
7+
//
8+
// Here `main` has a goal `(?0, ?1): Trait` which is canonicalized to
9+
// `exists<^0, ^1> (^0, ^1): Trait`.
10+
//
11+
// - `exists<^0, ^1> (^0, ^1): Trait` -instantiate-> `(?0, ?1): Trait`
12+
// -`(?1, ?0): Trait` -canonicalize-> `exists<^0, ^1> (^0, ^1): Trait`
13+
// - COINDUCTIVE CYCLE OK (no constraints)
14+
// - `(): ConstrainToU32<?0>` -canonicalize-> `exists<^0> (): ConstrainToU32<^0>`
15+
// - OK (^0 = u32 -apply-> ?0 = u32)
16+
// - OK (?0 = u32 -canonicalize-> ^0 = u32)
17+
// - coinductive cycle with provisional result != final result, rerun
18+
//
19+
// - `exists<^0, ^1> (^0, ^1): Trait` -instantiate-> `(?0, ?1): Trait`
20+
// -`(?1, ?0): Trait` -canonicalize-> `exists<^0, ^1> (^0, ^1): Trait`
21+
// - COINDUCTIVE CYCLE OK (^0 = u32 -apply-> ?1 = u32)
22+
// - `(): ConstrainToU32<?0>` -canonicalize-> `exists<^0> (): ConstrainToU32<^0>`
23+
// - OK (^0 = u32 -apply-> ?1 = u32)
24+
// - OK (?0 = u32, ?1 = u32 -canonicalize-> ^0 = u32, ^1 = u32)
25+
// - coinductive cycle with provisional result != final result, rerun
26+
//
27+
// - `exists<^0, ^1> (^0, ^1): Trait` -instantiate-> `(?0, ?1): Trait`
28+
// -`(?1, ?0): Trait` -canonicalize-> `exists<^0, ^1> (^0, ^1): Trait`
29+
// - COINDUCTIVE CYCLE OK (^0 = u32, ^1 = u32 -apply-> ?1 = u32, ?0 = u32)
30+
// - `(): ConstrainToU32<?0>` -canonicalize-> `exists<^0> (): ConstrainToU32<^0>`
31+
// - OK (^0 = u32 -apply-> ?1 = u32)
32+
// - OK (?0 = u32, ?1 = u32 -canonicalize-> ^0 = u32, ^1 = u32)
33+
// - coinductive cycle with provisional result == final result, DONE
34+
#![feature(rustc_attrs)]
35+
#[rustc_coinductive]
36+
trait Trait {}
37+
38+
impl<T, U> Trait for (T, U)
39+
where
40+
(U, T): Trait,
41+
(): ConstrainToU32<T>,
42+
{}
43+
44+
trait ConstrainToU32<T> {}
45+
impl ConstrainToU32<u32> for () {}
46+
47+
fn impls_trait<T, U>()
48+
where
49+
(T, U): Trait,
50+
{}
51+
52+
fn main() {
53+
impls_trait::<_, _>();
54+
}

0 commit comments

Comments
 (0)