owned this note
owned this note
Published
Linked with GitHub
# function calls on opaques
## General constraints
We need to eagerly handle function and method calls because we need the expected signature when checking the arguments of the call.
## Approach from https://github.com/rust-lang/rust/pull/145993
We iterate over the autoderef steps for the type of the called exprs.
We then check whether the current type may implement one of the `Fn`-traits by checking `predicate_may_hold`. We eagerly error when that type is an inference variables unless it been sub-unified with an opaque type.
If they've been sub-unified with an opaque type, we also walk over the `Fn`-traits in order of `Fn`, `FnMut`, `FnOnce` as normal and select the first one which is in the item bounds of the opaque. Checking `predicate_may_hold` for opaques is somewhat pointless as opaques implement all the `Fn`-traits
## Handling `Box<opaque>`
```rust
fn fn_once_ref() -> impl FnOnce() {
if false {
let mut f = Box::new(fn_once_ref());
f();
}
let string = String::new();
move || drop(string)
//~^ ERROR expected a closure that implements the `Fn` trait, but this closure only implements `FnOnce`
}
```
The issue is that we first consider the `Fn`-traits for `Box<opaque>`. Due to the `impl<F: Fn<Args>> Fn<Args> for Box<F>` impl, we always stop here and use the `Fn` trait.
## Handling `impl Deref`
```rust
use std::ops::Deref;
fn foo() -> impl Deref<Target = impl Fn()> {
if false {
let func = foo();
func();
}
&|| ()
}
```
This fails due to 2 reasons:
- computing `Autoderef::next` returns `None` if the self type is an infer var, even if it's an opaque type
- if we encounter an infer var sub-unified with an opaque type and the opaque does not impl any of the `Fn`-traits, we fail with a hard error and don't recurse further
## A decent solution
The following change:
```rust
pub enum Certainty {
Yes,
Maybe {
cause: MaybeCause,
opaque_types_jank: OpaqueTypesJank,
},
}
enum OpaqueTypesJank {
AllGood,
ErrorIfRigidSelfTy,
}
impl OpaqueTypesJank {
// AllGood > ErrorIfRigidSelfTy
fn and(self, other: OpaqueTypesJank) -> OpaqueTypesJank;
// AllGood < ErrorIfRigidSelfTy
fn or(self, other: OpaqueTypesJank) -> OpaqueTypesJank;
}
```
Somehow expose a way to check "do these goals hold without any goal returning `ErrorIfRigidSelfTy`". Could just add some hacked in `root_goal_may_hold_err_if_rigid_opaque` or whatever.
We could alternatively use a proof tree visitor to check whether any trait/normalizes-to goal ended up with forced ambig for an opaque type. That avoids having to change the trait solver, but seems significantly worse for perf and also less maintainable.
We then use this instead of `predicate_may_hold` in `lookup_method_for_operator`. This means we don't have to specialcase anything else about opaques, except:
- autoderef steps until we're at an infer var which is not an opaque
- or if `root_goal_may_hold_err_if_rigid_opaque` fails
- rewrite autoderef steps to not eagerly return `None` if the self tpye is an infer var, accept if its an opaque
the separate question is what to do if method selection or call resolution constrains an opaque self type.
### How would method resolution work?
we again autoderef steps through opaques and don't error if we've encountered an opaque type as as infer var
we do remember the infer vars which are equal to opaque tpyes and check that they have not been constrained when confirming candidates.
### feels iffy
Introduces new type system concept of `OpaqueTypesJank` and need to expose `root_goal_may_hold_err_if_rigid_opaque`. Having infer vars *which should actually be rigid* is just kinda ass in general and something I would like to avoid.
## Alternative proposal: placeholders
If we encounter opaques as the self type in autoderef, we replace it with a placeholder and add the item bounds of the opaque into the env.
Issues
- we want to still prove stuff for the actual hidden type, so if stuff holds for the placeholder we still need to return the requirements for the infer type
- what if there are multiple sub unified opaques
- the item bounds aren't sufficient if we end up with type outlives bounds on the placeholder, we'd need to ignore any region constraints involving the placeholder
- we generally don't want to leak any constraints involving these placeholders
- only ever prove things using the placeholder in a probe and then redo all of it using the infer var (what if we still end up incompletely constraining the infer var that way)
## Do we need this is to soften the breakage or as a longterm solution
It's permanently desirable :sob:
## Do these two appraoches differ meaningfully in their behavior
Not really, at least not in ways which would be widespread enough to prevent changing it in the future. This is an inference change and we're free to break stuff for it.