---
tags: deep-dive
---
# Deep dive fun times
## 2023-11-06
lcnr: Been experimenting with the new `AliasRelate`, hit some interesting questions.
WIP PR: https://github.com/rust-lang/rust/pull/117278
basic idea: try to normalize both sides as much as possible and then relate the resulting types
old approach: one-step-normalize-lhs-to-rhs, one-step-normalize-rhs-to-lhs, args-relate
#### The issue discussed below, summarized:
- structurally equal aliases have to be semantically equal
- because of MIR borrowck: structurally equal aliases *modulo regions* have to be semantically equal *potentially resulting in region constraints*
- simply equating the generic arguments is incomplete and unsound in coherence as normalization could cause them to be unused
=> for aliases, whose normalization result in ambiguity or overflow, semantic equality is not idempotent, meaning that these 3 requirements cannot be all be satisfied.
---
https://github.com/rust-lang/trait-system-refactor-initiative/issues/74
https://hackmd.io/RxTOQYl9Tr2DM2tCNsfNtw#QUESTION-alias-relate-for-cyclic-aliases
> Given `T: Trait<Assoc = T::Assoc>`, should `<T as Trait>::Assoc == <T as Trait>::Assoc` hold?
the where bound can trivially proven, but is used by normalization https://github.com/rust-lang/trait-system-refactor-initiative/issues/1
pretty much: should "structurally equal" imply "semantically equal": YES
with `normalizes(lhs) == normalize(rhs)` for alias-relate in the new solver, it does not hold for
`<T as Trait>::Assoc == <T as Trait>::Assoc` given the where-bound `T: Trait<Assoc = T::Assoc>`.
three scenarios:
* `forall<T>` -- not well formec
* `forall<T> if (T: Trait)` -- on PR, works
* `forall<T> if (T: Trait<Assoc = T::Assoc>)` -- on PR, does not work
* why? normalization overflows
how MIR formality works
* normalization computes a *set* of equivalent spellings for A and B
* and looks for intersection between them
* I *think* what would happen is that normalizing with `T: Trait<Assoc = T::Assoc>` would eventually get inductive overflow
* but it would succeed because we always assume `T normalizes-to T` is an option
`<T as Trait<'a>>::Assoc == <T as Trait<'b>>::Assoc` given the where-bound `T: for<'c> Trait<'c, Assoc = <T as Trait<'c>::Assoc>`.
* the "non-normalizing" case now has non-trivial region constraints
This case is equivalent to the exact structurally equal case, because of MIR typeck.
HIR typeck tries
```rust
let x: Invariant<<T as Trait<'a>>::Assoc> = mk();
let y: Invariant<<T as Trait<'a>>::Assoc> = x;
```
^ this should compile.
we erase regions during mir building.
at the start of mir typeck we replace each instantance of these erased regions with a new **unique** region var
now checking the assignment requires `<T as Trait<'n>>::Assoc == <T as Trait<'m>>::Assoc`, which would still overflow ~> MIR typeck fails
@lcnr invariant: "structural equality modulo free regions has to imply semantic equality (potentially resulting in region constraints in the root)"
* `Vec<&'a u32>` and `Vec<&'b u32>` -- structurally equal modulo free regions, but not semantically equal?? -- addressed by adding "potentially resulting in region constraints"
@lcnr believes: with current WF types there is no alias-relate which keeps this invariant while being complete.
new WF rule: all aliases must either be rigid themselves or can be normalized to something that is.
```rust
impl<A, B> Foo for A
where
A: Iterator,
B: Foo<Bar = A::Item>, // this is ok because we can normalize to a rigid alias
{}
```
```rust
impl<A, B> Foo for A
where
A: Iterator<Item = A::Item>, // nikomatsakis thinks maybe OK, because syntactic equality has no constraints
B: Foo<Bar = A::Item>,
{}
```
```rust
impl<'x, A, B> Foo for A
where
A: IteratorLike<'x, Item = <A as IteratorLike<'x>>::Item>, // syntactic equality implies region constraints, fails above test for sure
B: Foo<Bar = A::Item>, // this is ok because we can normalize to a rigid alias
{}
```
TAIT issue with this:
https://github.com/rust-lang/trait-system-refactor-initiative/issues/74 this program would be forbidden.
more explicitly...
```rust
#![feature(type_alias_impl_trait)]
type Tait<'a> = impl Sized;
#[defines(Tait)]
fn define<'a>(x: &'a ()) -> Tait<'a> {
()
}
#[defines(Tait)]
fn x<'a>(x: &'a (), t: Tait<'a>) -> Tait<'a> {
t // Problem case!
}
```
nikomatsakis vaguely thinks it'd be ok to require...
```rust
#![feature(type_alias_impl_trait)]
type Tait<'a> = impl Sized;
fn define<'a>(x: &'a ()) -> Tait<'a> {
()
}
#[defines()] // explicit declaration
fn x<'a>(x: &'a (), t: Tait<'a>) -> Tait<'a> {
t
}
```
...but this it not lang-team consensus.
Sub-question: to what extent can Rust infer when we could have been universally quantified over an inference variable (let-generalization) in the face of erased regions?
## 2023-10-30
Reviewing [this branch](https://github.com/nikomatsakis/a-mir-formality-ndm/blob/b9d77d1ec5c4eb7d12a8a8f5c4a679c10a35cf63/crates/formality-prove/src/prove/is_local.rs)
rename `prove_not_goal` to `disprove` and add some comments about how universal/existential works etc
Observation:
* `is_local_parameter` -- currently disables coherence-mode, that seems goofy, we want to do better, but how do we define "unambiguously true"
* write some tests about these rules and check how it works
* `not_downstream`
Examining alias types, ignoring normalization:
* `T::Item`
* could be downstream
* not known to be local
* `<MyType<T> as Iterator>::Item`
* could be downstream
* not known to be local
* `<u32 as Trait>::Foo`
* cannot be downstream
* not known to be local
* a-mir-formality doesn't handle this case (neither does rustc)
Good target to fuzz
* generate impls and compare rustc vs a-mir-formality
* how to focus on coherence?
* can we fuzz the orphan rules?
* e.g., check that the impls in a downstream crate cannot be instantiated with types in upstream crates
* or check for a trait with a single impl that is downstream and check that it cannot be instantiated for upstream types (as a starting point)
* fuzz for overlap
* generate pairs of impls
* use first impl to guide fuzzer to generate matching types
* and then check if they match the second impl
* or generate random types (that are not obviously wrong) and fuzz it against the second impl
More interesting eample:
* Neither impl is a subtype of the other but they have an intersection
* add an Example 2 that covers this case
Why do compute intersections with coherence mode? Create examples
* Normalization can introduce "unnecessary" constraints that are
* can we have an example where trying to prove the assumptions will result in *extra* constraints that we can use?
* would have to lean on the inference rules
```rust
trait Trait<T> {}
trait NegImpl {}
impl<T, U> Trait<U> for T
where
(): ConstrainToU32<U>, // Needs to avoid the trait_ref_is_local check
{}
impl<T, U: NegImpl> Trait<U> for T {}
impl NegImpl for u32 {}
```
## 2023-09-11
Covering issues with the provisional cache https://github.com/rust-lang/trait-system-refactor-initiative/issues/54
broken test: https://github.com/rust-lang/rust/blob/68c2f5ba0ffb6f7f0724dd62c7562daa634caaec/tests/ui/traits/new-solver/cycles/inductive-not-on-stack.rs#L15-L25C22
Root goal A and two cycles
RESULT: rip out the provisional cache
* may add caches again in the future, caches are probably not fundamentally unusable
* need to cache the fact that X is only true if Y, instead of just assuming X is true
* store in prov result, saying whether there was an inductive or coinductive cycle further up the stack
* lots of complexity, ripping it out for now since it doesn't seem strictly necessary
caching roots of cycles and reusing it from other cycle participants is broken https://rust.godbolt.org/z/MoTrvjvY6
* lcnr explains, niko understands, oli doesn't :laughing:
cycle `A -> B -> A`: ideal: finish, then cache both the final result of `A` and `B`. This deosn't work because of query system reasons.
instead (previously): put only `A` into the global cache, dropping `B`. When proving `B` the next time, we access `A` from the cache, still avoiding having to reprove the whole cycle.
test in rust test suite https://github.com/rust-lang/rust/blob/master/tests/ui/traits/new-solver/cycles/coinduction/incompleteness-unstable-result.rs
## 2023-06-19
Plan: walk through some of the examples to compare/contrast how different solvers work.
- https://hackmd.io/N0rzMn94QZOfbaAVOl4f6A#2023-05-26
#### how the solvers work
```rust
trait Foo { }
impl Foo for () { }
impl<T: Foo> Foo for Option<T> { }
fn test<X: Foo>() { }
fn main() { test::<Option<()>>(); }
```
* 'evaluate':
* go from an obligation to a "yes/no/maybe" result, more or less
evaluate status quo
- entry point: [`evaluate_root_obligation`](https://github.com/rust-lang/rust/blob/18a6d911caba59605eb03db1452848a85d2e5879/compiler/rustc_trait_selection/src/traits/select/mod.rs#L524) with a goal `Option<()>: Foo`
- [`evaluate_predicate_recursively`](https://github.com/rust-lang/rust/blob/18a6d911caba59605eb03db1452848a85d2e5879/compiler/rustc_trait_selection/src/traits/select/mod.rs#LL629C19-L629C19) also called with `Option<()>: Foo`
- matches on `PredicateKind`, calls [`evaluate_trait_predicate_recursively`](https://github.com/rust-lang/rust/blob/18a6d911caba59605eb03db1452848a85d2e5879/compiler/rustc_trait_selection/src/traits/select/mod.rs#L995)
- calls [`evaluate_stack`](https://github.com/rust-lang/rust/blob/18a6d911caba59605eb03db1452848a85d2e5879/compiler/rustc_trait_selection/src/traits/select/mod.rs#L1045) which actually delegates to selection -> confirmation
- [`select_from_obligation`](https://github.com/rust-lang/rust/blob/18a6d911caba59605eb03db1452848a85d2e5879/compiler/rustc_trait_selection/src/traits/select/mod.rs#L306): compute a single candidate which should be used to prove the obligation, here this is an `ImplCandidate`
- confirm: apply the constraints and "nested obligations" of that candidate
- nested obligations include `(): Foo` which is proven via [`evaluate_predicate_recursively`](https://github.com/rust-lang/rust/blob/18a6d911caba59605eb03db1452848a85d2e5879/compiler/rustc_trait_selection/src/traits/select/mod.rs#LL629C19-L629C19) again.
a-mir-formality impl:
- via macros parses syntax fairly close to surface Rust
- lowered to an IR in `decls` via `to_prove_decls`
- IR is then checked via `check_current_crate`
- checks each item in the crate
- ends up using `prove_goal` in an `Env` (`InferCtxt` in rustc without constraints (inference and region constraints are tracked separately)) and given some `assumptions` (`where`-clauses).
- returns set of ways the goal can be proven `Set<Constraints>`, pretty much an OR of candidates
- query cycles are avoided by running queries to a fixpoint (afaict propagate errors to the root and rerun until the root doesn't change)
---
```rust
trait Foo: Bar { }
fn is_foo<X: Foo>() { is_bar::<X>()}
fn is_bar<X: Bar>() { }
```
---
```rust
#[rustc_coinductive]
trait Coinductive { }
impl Coinductive for () { }
impl<T> Coinductive for Option<T>
where
T: Coinductive
{ }
impl<T> Coinductive for List<T>
where
Option<List<T>>: Coinductive
{ }
struct List<T> { next: Option<List<T>> }
fn test<X: Coinductive>() { }
fn main() { test::<Option<List<()>>>(); }
```