Concrete trait impl checks#137
Conversation
|
@JonasAlaif Do we really want to go with existentials for the generics? It makes sense semantically but (apart from vaguely worrying about performance) I wonder if we need this at all? From our previous discussion about this I assumed it suffices to do, e.g., with
|
I started implementing it that way, but then ran into some issues in more complicated cases. For example this "legal" impl block: impl<U, V: Foo<U>> Foo<U> for V {}I would try to encode it as: Even to get to this one has to do some "thinking" and simplifying. Additionally, the discriminator + destructor style encoding makes it much more verbose for more complex types, for example Lastly, I have simply found this easier to implement 😞. |
|
One other issue that popped up is the |
e4708f8 to
14daf0b
Compare
f9ed15a to
b89b922
Compare
|
I believe this is now a mostly final version for the encoding. Ready for review. |
There was a problem hiding this comment.
As the
Sizedtrait is auto-implemented for all types by default, there are no actual impls forSizedmaking all our types!Sizedby default.
Is this the case only for Sized? What about other (user-defined) auto traits? What about the other built-in ones, like Send or Sync?
Besides the minor comments, I worry a bit about:
- The fact that we have so much machinery for dealing with
Sizedtrait specifically; theCanonicalTyseems to only exist for that, right? - All the type disassembling and reassembling going on in
trait_implsis quite a lot of logic. - How much of this is rebase-able on #127 (if you know)? Some of the changes in
traits.rsandtrait_impls.rsseem quite similar to that PR's.
It's most certainly the case for other auto-traits too. I am implementing the
Have mostly merged the logic of
It's quite complicated indeed. Without existentials, we need to "discover" an expression to which we can bind each generic instead and then assert equivalences between subsequent occurrences of the generic. A way to do this differently would be to introduce the generics using let-expressions. This would reduce the visual clutter of repeating long accessor paths at the cost of more equality assertions. Additionally, with let-bindings for the impl block generics, we could now process the predicates in any order, and would avoid the dependencies between the projections.
I tried to mimic or copy #127 where I have found it to do similar things. This could be probably improved. |
dea0177 to
2f1cd2f
Compare
| let decl = match idx { | ||
| Result::Ok(idx) => impl_params.ty_decls()[idx].upcast_ty(), | ||
| Result::Err(idx) => impl_params.const_decls()[idx].upcast_ty(), | ||
| }; |
There was a problem hiding this comment.
This bit I find rather messy. Is the invariant here that only type declarations will be in the impl_params map? Can there not be conflicts? Can you add a comment to explain this? Either way matching on a Result (and why Result::Ok instead of just Ok?) here seems strange, I would expect Option maybe? There is no actual error occurring here...
There was a problem hiding this comment.
The matching on Result stems from how GenericParams map each generic to a generic type or const (probably it should have been a custom enum there in the first place to avoid confusion).
|
@Aurel300 the main things to review are:
The test failures are because it spots the Clone impls for types declared in |
| /// `spec_block` builtins: recursively encodes the closure body, reifying | ||
| /// the closure itself (closure argument 1) and the given quantified | ||
| /// variables (closure arguments 2..). | ||
| fn encode_spec_closure( |
There was a problem hiding this comment.
Quantifiers still differ in that they will have to have their triggers encoded. Add a TODO?
There was a problem hiding this comment.
(Also this change doesn't really belong in this PR.)
There was a problem hiding this comment.
I'll add a TODO, yeah. And sure it doesn't really belong, but I'm not going to do a separate PR just for this and there were clear issues with the existing code (e.g. for the SpecBlock it would still report forall in the error).
| .borrow_mut() | ||
| .insert(task_key, TaskEncoderCacheState::Enqueued,) | ||
| .is_none())); | ||
| triggers::fire_watchers::<Self>(&task_key); |
There was a problem hiding this comment.
Here the watchers are fired before the cache is updated, in encode below it is done after. Firing after seems more reasonable to me (maybe avoiding some weird cycles which expect the key to be in the cache already...?).
There was a problem hiding this comment.
Firing after would require a clone of the task_key. It doesn't actually matter when they fire (maybe the wrong term), since that only means that the closures get moved from one queue to another (from a "waiting for key and won't run if key not seen" queue to a "waiting for regular encoding to be done and will definitely run after that" queue). The latter queue only gets run after all the regular encoding is done (see prusti-encoder/src/lib.rs:72). I'm happy to add the extra clone if that makes it less confusing though, or maybe rename fire_watchers?
|
I think that this is basically ready then, it just needs #188 so that there are no regressions |
Instead of using axiomatized version of the trait check encoding, we will instead use a function with a concrete body.
This makes it clearer which types implement a specific trait without having to resort to making "negative implementations" for types that do not.