Skip to content

Commit e633fec

Browse files
authored
Merge pull request #2506 from lcnr/type-system-invariants
go over invariants again :3
2 parents e318ef6 + 3ca42fd commit e633fec

File tree

1 file changed

+48
-33
lines changed

1 file changed

+48
-33
lines changed

src/solve/invariants.md

Lines changed: 48 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,8 @@ It is important to know about the things you can assume while working on - and w
1111
type system, so here's an incomplete and unofficial list of invariants of
1212
the core type system:
1313

14-
- ✅: this invariant mostly holds, with some weird exceptions, you can rely on it outside
15-
of these cases
16-
- ❌: this invariant does not hold, either due to bugs or by design, you must not rely on
17-
it for soundness or have to be incredibly careful when doing so
14+
- ✅: this invariant mostly holds, with some weird exceptions or current bugs
15+
- ❌: this invariant does not hold, and is unlikely to do so in the future; do not rely on it for soundness or have to be incredibly careful when doing so
1816

1917
### `wf(X)` implies `wf(normalize(X))`
2018

@@ -23,6 +21,8 @@ well-formed after normalizing said aliases. We rely on this as
2321
otherwise we would have to re-check for well-formedness for these
2422
types.
2523

24+
This currently does not hold due to a type system unsoundness: [#84533](https://github.com/rust-lang/rust/issues/84533).
25+
2626
### Structural equality modulo regions implies semantic equality ✅
2727

2828
If you have a some type and equate it to itself after replacing any regions with unique
@@ -36,7 +36,7 @@ If this invariant is broken MIR typeck ends up failing with an ICE.
3636

3737
TODO: this invariant is formulated in a weird way and needs to be elaborated.
3838
Pretty much: I would like this check to only fail if there's a solver bug:
39-
https://github.com/rust-lang/rust/blob/2ffeb4636b4ae376f716dc4378a7efb37632dc2d/compiler/rustc_trait_selection/src/solve/eval_ctxt.rs#L391-L407
39+
https://github.com/rust-lang/rust/blob/2ffeb4636b4ae376f716dc4378a7efb37632dc2d/compiler/rustc_trait_selection/src/solve/eval_ctxt.rs#L391-L407 We should readd this check and see where it breaks :3
4040

4141
If we prove some goal/equate types/whatever, apply the resulting inference constraints,
4242
and then redo the original action, the result should be the same.
@@ -73,40 +73,47 @@ Many of the currently known unsound issues end up relying on this invariant bein
7373
It is however very difficult to imagine a sound type system without this invariant, so
7474
the issue is that the invariant is broken, not that we incorrectly rely on it.
7575

76-
### Generic goals and their instantiations have the same result ✅
76+
### The type system is complete ❌
77+
78+
The type system is not complete, it often adds unnecessary inference constraints, and errors
79+
even though the goal could hold.
80+
81+
- method selection
82+
- opaque type inference
83+
- handling type outlives constraints
84+
- preferring `ParamEnv` candidates over `Impl` candidates during candidate selection
85+
in the trait solver
86+
87+
### Goals keep their result from HIR typeck afterwards ✅
7788

78-
Pretty much: If we successfully typecheck a generic function concrete instantiations
79-
of that function should also typeck. We should not get errors post-monomorphization.
80-
We can however get overflow errors at that point.
89+
Having a goal which succeeds during HIR typeck but fails when being reevaluated during MIR borrowck causes ICE, e.g. [#140211](https://github.com/rust-lang/rust/issues/140211).
8190

82-
TODO: example for overflow error post-monomorphization
91+
Having a goal which succeeds during HIR typeck but fails after being instantiated is unsound, e.g. [#140212](https://github.com/rust-lang/rust/issues/140212).
92+
93+
It is interesting that we allow some incompleteness in the trait solver while still maintaining this limitation. It would be nice if there was a clear way to separate the "allowed incompleteness" from behavior which would break this invariant.
94+
95+
#### Normalization must not change results
8396

8497
This invariant is relied on to allow the normalization of generic aliases. Breaking
8598
it can easily result in unsoundness, e.g. [#57893](https://github.com/rust-lang/rust/issues/57893)
8699

100+
#### Goals may still overflow after instantiation
101+
102+
As they start to hit the recursion limit. We also have diverging aliases which are scuffed. It's unclear how these should be handled :3
103+
87104
### Trait goals in empty environments are proven by a unique impl ✅
88105

89106
If a trait goal holds with an empty environment, there should be a unique `impl`,
90107
either user-defined or builtin, which is used to prove that goal. This is
91-
necessary to select a unique method.
108+
necessary to select unique methods and associated items.
92109

93110
We do however break this invariant in few cases, some of which are due to bugs,
94111
some by design:
95112
- *marker traits* are allowed to overlap as they do not have associated items
96113
- *specialization* allows specializing impls to overlap with their parent
97114
- the builtin trait object trait implementation can overlap with a user-defined impl:
98-
[#57893]
115+
[#57893](https://github.com/rust-lang/rust/issues/57893)
99116

100-
### The type system is complete ❌
101-
102-
The type system is not complete, it often adds unnecessary inference constraints, and errors
103-
even though the goal could hold.
104-
105-
- method selection
106-
- opaque type inference
107-
- handling type outlives constraints
108-
- preferring `ParamEnv` candidates over `Impl` candidates during candidate selection
109-
in the trait solver
110117

111118
#### The type system is complete during the implicit negative overlap check in coherence ✅
112119

@@ -121,18 +128,19 @@ We have to be careful as it is quite easy to break:
121128
- generalization of aliases
122129
- generalization during subtyping binders (luckily not exploitable in coherence)
123130

124-
### Trait solving must be (free) lifetime agnostic ✅
131+
### Trait solving must not depend on lifetimes being different ✅
132+
133+
If a goal holds with lifetimes being different, it must also hold with these lifetimes being the same. We otherwise get post-monomorphization errors during codegen or unsoundness due to invalid vtables.
134+
135+
We could also just get inconsistent behavior when first proving a goal with different lifetimes which are later constrained to be equal.
125136

126-
Trait solving during codegen should have the same result as during typeck. As we erase
127-
all free regions during codegen we must not rely on them during typeck. A noteworthy example
128-
is special behavior for `'static`.
137+
### Trait solving in bodies must not depend on lifetimes being equal ✅
129138

130139
We also have to be careful with relying on equality of regions in the trait solver.
131140
This is fine for codegen, as we treat all erased regions as equal. We can however
132141
lose equality information from HIR to MIR typeck.
133142

134-
The new solver "uniquifies regions" during canonicalization, canonicalizing `u32: Trait<'x, 'x>`
135-
as `exists<'0, '1> u32: Trait<'0, '1>`, to make it harder to rely on this property.
143+
This currently does not hold with the new solver: [trait-system-refactor-initiative#27](https://github.com/rust-lang/trait-system-refactor-initiative/issues/27).
136144

137145
### Removing ambiguity makes strictly more things compile ❌
138146

@@ -146,9 +154,16 @@ changes, breaking existing projects.
146154

147155
Two types being equal in the type system must mean that they have the
148156
same `TypeId` after instantiating their generic parameters with concrete
149-
arguments. This currently does not hold: [#97156].
157+
arguments. We can otherwise use their different `TypeId`s to impact trait selection.
158+
159+
We lookup types using structural equality during codegen, but this shouldn't necessarily be unsound
160+
- may result in redundant method codegen or backend type check errors?
161+
- we also rely on it in CTFE assertions
162+
163+
### Semantically different types have different `TypeId`s ✅
164+
165+
Semantically different `'static` types need different `TypeId`s to avoid transmutes,
166+
for example `for<'a> fn(&'a str)` vs `fn(&'static str)` must have a different `TypeId`.
167+
150168

151-
[#57893]: https://github.com/rust-lang/rust/issues/57893
152-
[#97156]: https://github.com/rust-lang/rust/issues/97156
153-
[#114936]: https://github.com/rust-lang/rust/issues/114936
154-
[coherence]: ../coherence.md
169+
[coherence]: ../coherence.md

0 commit comments

Comments
 (0)