-
Notifications
You must be signed in to change notification settings - Fork 13.6k
[WIP] Add downcast_trait and downcast_trait_mut #144363
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
[WIP] Add downcast_trait and downcast_trait_mut #144363
Conversation
This comment has been minimized.
This comment has been minimized.
#[must_use] | ||
#[unstable(feature = "downcast_trait", issue = "144361")] | ||
pub const fn downcast_trait_mut< | ||
T: Any + 'static, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
does the Any
bound actually prevent anything?
We'll need some ui tests that actually error when downcast_trait
gets mis-used.
Some general test ideas:
- passing a
dyn Trait
for theT
whereU
is alsodyn Trait
- passing a
dyn UnrelatedTrait
forT
whenU
isdyn Trait
- using a not dyn-safe trait for
U
- using a
dyn Trait<AssocTy = u32>
when the type only implementsTrait<AssocTy = bool>
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also: Test out:
- downcasting from two disjoint supertrait impls (
trait Foo<T, U>: Super<T> + Super<U>
). - Downcasting when the type doesn't implement an
Auto
trait.
This is also I think still unsound because we don't (and cannot) enforce that the lifetime in the input and output are equal. This should allow you to unsoundly cast dyn Foo + 'a
to dyn Foo + 'static
, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The 'static bound maybe prevents potential unsoundness.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, then that really limits the usefulness of this intrinsic as an alternative to specialization as noted in the tracking issue 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As @oli-obk noted in another comment the intrinsic doesn't necessarily need 'static
, but downcast_trait*
does.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, yeah, that is kind of similar... but the existing downcast
is not, not at all.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doesn't really warrant a new module either, right? Even with type_name
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just for clarity @RalfJung I just plopped it in any
so I could keep going. Was the best candidate I saw at a glance, I'm very open to moving it :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding it to unresolved questions in the tracking issue
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about std::convert
?
} | ||
} | ||
|
||
#[allow(missing_docs)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need some docs before merging
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Absolutely. Just appeasing the commit hook
.build_with_typing_env(ty::TypingEnv::fully_monomorphized()); | ||
|
||
let type_impls_trait = preds.iter().all(|pred| { | ||
let trait_ref = ty::TraitRef::new(tcx, pred.def_id(), [tp_ty]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should definitely be matching on the ExistentialPredicate
and using .with_self_ty
instead of just constructing a TraitRef
here.
As far as I can tell, right now if I try to downcast to dyn Iterator<Item = ()>
, I'm only checking that the type implements Iterator
, not that it implements the projection predicate of <Self as Iterator>::Item == ()
.
That should also allow you to remove that def_id
function you added to ExistentialPredicate
too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's great!
This is the first time I've even seen these concepts so I'm glad you can detect mistakes like these hehe
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I noticed that when dealing with TraitRef
s and .with_self_ty
there is often a call to tcx.erase_regions(trait_ref)
following.
Inspected the code. Had a look at the documentation. Still not fully certain but it seems to be simplifying the definition you pass in without changing it's actual meaning - is that correct?
I guess it would help if I knew exactly what a region was in this context hehe
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, you can erase regions as we already checked that all regions are 'static
via the bounds.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is an interesting idea, but I think before we commit to this experiment or anything, we should definitely do a vibe check about whether this is ever possible to implement in our current codegen backends.
If not, then this doesn't really serve as an alternative to specialization, right?
edit: Oh wait, I understand. This is a constant intrinsic, so we should never need to impl the intrinsic at runtime. I think I see. This whole scheme should be documented regardless, since that's a subtle difference from the way that upcasting is implemented today, which sometimes happens at runtime and thus has backend support.
Co-authored-by: Oli Scherer <github35764891676564198441@oli-obk.de>
Co-authored-by: Oli Scherer <github35764891676564198441@oli-obk.de>
Co-authored-by: Oli Scherer <github35764891676564198441@oli-obk.de>
Co-authored-by: Oli Scherer <github35764891676564198441@oli-obk.de>
Co-authored-by: Oli Scherer <github35764891676564198441@oli-obk.de>
Yeah, the key point is that everything is static. You can't take an That also makes it extremely confusing to put this near the existing |
This comment has been minimized.
This comment has been minimized.
Does overflow in the trait solver cause a post-mono error? |
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
Edit: Same behavior in 8e9b17b At commit 0da23e7 (the last commit that builds on my machine), this PR has weird behavior with overflows in the trait solver, seemingly causing an error only if a function makes it to codegen. Code and error#![feature(downcast_trait)]
#![recursion_limit = "8"]
use std::any::downcast_trait;
struct Wrap<T>(T);
struct Indirect<T>(T);
trait Trait {}
impl<T> Trait for Wrap<T> where Indirect<Indirect<T>>: Trait {}
impl<T> Trait for Indirect<T> where T: Trait {}
impl Trait for i32 {}
// Uncommenting the below line makes the code compile
// #[inline]
pub fn weird() {
let x = Wrap(Wrap(Wrap(Wrap(1i32))));
let b = downcast_trait::<Wrap<Wrap<Wrap<Wrap<i32>>>>, dyn Trait>(&x).is_some();
println!("{b}");
}
|
@theemathas it's not compiling because I used the gh commit Great catch wrt the trait solver overflow. I'm hoping the suggestions pointed out by @compiler-errors and @oli-obk will solve that. |
☔ The latest upstream changes (presumably #116707) made this pull request unmergeable. Please resolve the merge conflicts. |
This comment was marked as resolved.
This comment was marked as resolved.
I think the recent changes have addressed the unsoundness pointed out by @theemathas, but I still need to address the recursion issue that causes trait solver overflow. |
#[rustc_intrinsic] | ||
pub const fn vtable_for<T, U: ptr::Pointee<Metadata = ptr::DynMetadata<U>> + ?Sized>() | ||
-> Option<ptr::DynMetadata<U>>; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since you replaced the erased lifetimes with 'static
, I believe that this function needs a T: 'static
and a U: 'static
bound.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could make it unsafe to allow for best-effort checks for optimizations like the Fuse
specializations
I just realized that the weirdness with |
Tracking issue: #144361