-
Notifications
You must be signed in to change notification settings - Fork 13.9k
Support composing two autoderef adjustments #148320
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?
Conversation
|
With this PR, the following code compiles, and prints (The PR makes the compiler do the coercion of (This code ICEs on nightly.) use core::ops::Deref;
trait OneTrait {
fn method(&self);
}
struct Two(i32);
struct Three(Two);
struct OneThing;
impl OneTrait for OneThing {
fn method(&self) {
println!("OneThing method");
}
}
impl Deref for Three {
type Target = Two;
fn deref(&self) -> &Self::Target {
&self.0
}
}
// Commenting the following impl causes the code to output `Three method: 1`
impl OneTrait for Two {
fn method(&self) {
println!("Two method: {}", self.0);
}
}
// Commenting the following impl does NOT change the output
impl OneTrait for Three {
fn method(&self) {
println!("Three method: {}", self.0.0);
}
}
fn main() {
let x = match 0 {
0 => &Three(Two(1)),
// Commenting the below line causes the code to output `Three method: 1`
1 => &Two(2),
_ => &OneThing as &dyn OneTrait,
};
x.method();
} |
|
EDIT: oh, reading the original issue, is this supposed to be an intended language behavior? That feels a bit surprising to me. |
|
It does handle arbitrary steps of composition. But I may need to further restrict what composition is allowed, given the example. |
|
The following code ICEs both on nightly and with this PR: use std::ops::Deref;
trait One {}
trait Two {}
struct Thing;
impl One for Thing {}
struct Thing2;
impl Two for Thing2 {}
impl<'a> Deref for dyn One + 'a {
type Target = dyn Two + 'a;
fn deref(&self) -> &(dyn Two + 'a) {
&Thing2
}
}
fn main() {
let _ = match 0 {
0 => &Thing as &dyn One,
1 => &Thing,
_ => &Thing2 as &dyn Two,
};
} |
|
This code also ICEs both on nightly and with this PR: trait Super {}
trait Sub: Super {}
struct Thing;
impl Super for Thing {}
impl Sub for Thing {}
fn main() {
let _ = match 0 {
0 => &Thing as &dyn Sub,
1 => &Thing,
_ => &Thing as &dyn Super,
};
} |
This can be supported as well. Not so sure about whether we should. |
|
Of note: This code currently compiles on stable, and seems to rely on a special case in the compiler to treat this as a double coercion and allow it, I think. fn never() -> ! { panic!() }
fn main() {
let _ = match 1 {
0 => never(),
1 => &Box::new(2),
_ => &3,
};
} |
|
Lang-nominating, since a decision needs to be made on what exactly should LUB coercion do with double coercions, especially in cases where an |
|
These two code snippets seem to involve a double-coercion, and compiles on stable: use std::ops::Deref;
use std::marker::PhantomData;
struct Thing;
struct Wrap<T>(PhantomData<T>);
// Sub is a subtype of Super
type Sub = Wrap<for<'a> fn(&'a ()) -> &'a ()>;
type Super = Wrap<fn(&'static ()) -> &'static ()>;
impl Deref for Thing {
type Target = Sub;
fn deref(&self) -> &Sub {
&Wrap(PhantomData)
}
}
fn main() {
let _ = match 0 {
0 => &Thing,
1 => &Wrap(PhantomData) as &Super,
_ => &Wrap(PhantomData) as &Sub,
};
}use std::ops::Deref;
use std::marker::PhantomData;
struct Thing;
struct Wrap<T>(PhantomData<T>);
// Sub is a subtype of Super
type Sub = Wrap<for<'a> fn(&'a ()) -> &'a ()>;
type Super = Wrap<fn(&'static ()) -> &'static ()>;
impl Deref for Super {
type Target = Thing;
fn deref(&self) -> &Thing {
&Thing
}
}
fn main() {
let _ = match 0 {
0 => &Wrap(PhantomData) as &Super,
1 => &Wrap(PhantomData) as &Sub,
_ => &Thing,
};
}Oddly enough, swapping the order of use std::marker::PhantomData;
struct Wrap<T>(PhantomData<T>);
// Sub is a subtype of Super
type Sub = Wrap<for<'a> fn(&'a ()) -> &'a ()>;
type Super = Wrap<fn(&'static ()) -> &'static ()>;
fn main() {
let _ = match 0 {
1 => &Wrap(PhantomData) as &Sub,
_ => &Wrap(PhantomData) as &Super,
};
} |
|
My change does allow more than deref composition. I think restricting it to exactly deref composition is suitable. |
|
The question here isn't whether these double coercions can be supported. The question is whether they should be supported. |
|
Even for the case of chaining two deref coercions together, there can still be strange behavior. In the following code, the following coercions are available:
With this PR, the compiler decides to do the (This code ICEs on nightly.) use core::ops::Deref;
#[derive(Debug)]
struct Wrap<T: ?Sized>(T);
type One = Wrap<[u8]>;
struct Two;
type Three = Wrap<[u8; 0]>;
impl Deref for Three {
type Target = Two;
fn deref(&self) -> &Self::Target {
&Two
}
}
impl Deref for Two {
type Target = One;
fn deref(&self) -> &One {
&Wrap([12u8])
}
}
fn main() {
let x = match 0 {
0 => &Wrap([]) as &Three,
// Commenting the below line causes the code to output `Wrap([])`
1 => &Two,
_ => &Wrap([34u8]) as &One,
};
println!("{x:?}");
} |
|
unnominating until i get a chance to look more at this PR and probably write up a description that is less impl details. also this seems more like t-types than t-lang to me but I've yet to properly look at this |
|
In case you missed it, the reference has a section on LUB coercions. It's ambiguous on if and how double coercions should work though. |
Fixes #148283
Given a deref chain like
&C -> &B -> &A, we do can coerce&C -> &Adirectly with adjustments:But if we coerce in two steps:
&C -> &Band&B -> &A, it errs withThis PR bridges the gap.
About the FIXME, I'm not familiar with unsafe fn pointer so that's not covered.
Edited: My change allows more than deref composition. I think restricting it to exactly deref composition is suitable, to avoid surprising behavior where coercion order affects method selection.
Other unsupported composition can be reported by proper diagnostics.