---
# System prepended metadata

title: Deep dive fun times
tags: [deep-dive]

---

---
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<()>>>(); }
```
