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
Fix cycle error with existential types #62423
Conversation
r? @cramertj (rust_highfive has picked a reviewer for you, use r? to override) |
r? @oli-obk |
cc @rust-lang/lang |
My initial solution didn't work properly when an existential type was used inside another type (e.g. |
Since you added a commit mid my review some suggestions are sorta outdated now but the basic idea isn't so you can "translate" the changes to the new location... ;) |
@Centril: I've addressed your comments |
Fixes rust-lang#61863 We now allow uses of 'existential type's that aren't defining uses - that is, uses which don't constrain the underlying concrete type. To make this work correctly, we also modify eq_opaque_type_and_type to not try to apply additional constraints to an opaque type. If we have code like this: ``` existential type Foo; fn foo1() -> Foo { ... } fn foo2() -> Foo { foo1() } ``` then 'foo2' doesn't end up constraining 'Foo', which means that 'foo2' will end up using the type 'Foo' internally - that is, an actual 'TyKind::Opaque'. We don't want to equate this to the underlying concrete type - we just need to enforce the basic equality constraint between the two types (here, the return type of 'foo1' and the return type of 'foo2')
Previously, types like (Foo, u8) would not be handled correctly (where Foo is an 'existential type')
Co-Authored-By: Mazdak Farrokhzad <twingoow@gmail.com>
90fa14f
to
2f41962
Compare
@@ -1268,15 +1268,43 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { | |||
let opaque_defn_ty = tcx.type_of(opaque_def_id); | |||
let opaque_defn_ty = opaque_defn_ty.subst(tcx, opaque_decl.substs); | |||
let opaque_defn_ty = renumber::renumber_regions(infcx, &opaque_defn_ty); | |||
let concrete_is_opaque = infcx |
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'm not sure if this is quite the right check, or at least it doesn't match the behavior specified by the RFC. In the RFC, it's expected that the following would compile:
existential type Foo: Debug;
fn foo() -> Foo {
5u32
}
fn bar(cond: bool) -> Foo {
if cond { foo() } else { 5u32 }
}
In this situation, the type of the variable returned by foo
is an impl trait variable, but since the function is within the same module as the definition of the existential type
, it can constrain the type further and itself become a defining use. The RFC says you can even do this:
existential type Foo: Debug;
fn foo() -> Foo {
5u32
}
fn bar() -> Foo {
let x: u32 = foo();
x + 5
}
I realize this isn't at all what's implemented today, but it'd be good to talk about how the behavior being implemented here interacts with the desired behavior we want to end up with.
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 believe that this check is still compatible with implementing the full behavior specified in the RFC. This check occurs after we've attempted to instantiate the opaque type. If we're still left with an opaque type after instantiation, then there's nothing else we can do.
Once the rest of the RFC is implemented, this check should be hit only when the opaque type is being used outside the defining module.
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 makes sense-- thanks for the clarification!
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've got a local build and I'm taking a look. I'm concerned that the current check doesn't feel sufficient -- i.e., just testing that you have some opaque type doesn't mean you have the same opaque type, for starters... and obviously this isn't as flexible as we might like, as @cramertj pointed out. I'd like to put a bit of thought into whether we can implement the "proper" semantics -- not necessarily in this PR, though. Once my build completes and I finish a few other things I'll do some testing and try to give a bit more detailed feedback! Thanks @Aaron1011 for looking into this!
// not to the opaque type itself. Therefore, it's enough | ||
// to simply equate the output and opque 'revealed_type', | ||
// as we do above | ||
if revealed_ty_is_opaque { |
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 testing that the revealed type is opaque doesn't seem like enough
@nikomatsakis: We always check that the opaque type is equal to the 'output' type here. We only skip applying the obligations that should only apply to the underlying revealed type. As I mentioned to @cramertj, I believe this change is fully forwards-compatible with 'revealing' opaque types under more circumstances (e.g. within the defining module). My checks runs during MIR typecheck - at this stage of compilation, any opaque type usages that have not been revealed are never going to be revealed (i.e. usages outside the defining module). Future changes may affect under what circumstances my check is hit, but I believe that it's always correct to skip the 'revealed' obligations for an opaque type that we cannot reveal. |
@nikomatsakis: Are there any changes that you'd like me to make? |
r? @oli-obk |
// without actually looking at the underlying type it | ||
// gets 'revealed' into | ||
|
||
if !concrete_is_opaque { |
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.
if the concrete type is opaque, don't we need an additional check to figure out if the opaque types are equal? Essentially what I'm asking is whether
pub trait MyTrait {}
#[derive(Debug)]
pub struct MyStruct {
v: u64
}
impl MyTrait for MyStruct {}
mod foo {
pub fn bla() -> TE {
return crate::MyStruct {v:1}
}
existential type TE: crate::MyTrait;
}
mod bar {
pub fn bla2() -> TE2 {
crate::foo::bla()
}
pub existential type TE2: crate::MyTrait;
}
pub fn bla2() -> bar::TE2 {
crate::foo::bla()
}
still errors after your PR
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.
@oli-obk See my reply to @nikomatsakis above
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 code you linked is to the master branch (or at least before your PR) right here. So we aren't always checking that as you wrote.
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 - that line is unchanged in my PR - that is, we still equate the opaque types.
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.
🤦♂️ sorry, These two obligations looked so similar, I confused the two.
@bors r+ |
📌 Commit 2f41962 has been approved by |
…oli-obk Fix cycle error with existential types Fixes rust-lang#61863 We now allow uses of `existential type`'s that aren't defining uses - that is, uses which don't constrain the underlying concrete type. To make this work correctly, we also modify `eq_opaque_type_and_type` to not try to apply additional constraints to an opaque type. If we have code like this: ```rust existential type Foo; fn foo1() -> Foo { ... } fn foo2() -> Foo { foo1() } ``` then `foo2` doesn't end up constraining `Foo`, which means that `foo2` will end up using the type `Foo` internally - that is, an actual `TyKind::Opaque`. We don't want to equate this to the underlying concrete type - we just need to enforce the basic equality constraint between the two types (here, the return type of `foo1` and the return type of `foo2`)
Rollup of 6 pull requests Successful merges: - #62423 (Fix cycle error with existential types) - #62979 (Cleanup save-analysis JsonDumper) - #62982 (Don't access a static just for its size and alignment) - #63013 (add `repr(transparent)` to `IoSliceMut` where missing) - #63014 (Stop bare trait lint applying to macro call sites) - #63036 (Add lib section to rustc_lexer's Cargo.toml) Failed merges: r? @ghost
Fixes #61863
We now allow uses of
existential type
's that aren't defining uses - that is, uses which don't constrain the underlying concrete type.To make this work correctly, we also modify
eq_opaque_type_and_type
to not try to apply additional constraints to an opaque type. If we have code like this:then
foo2
doesn't end up constrainingFoo
, which means thatfoo2
will end up using the typeFoo
internally - that is, an actualTyKind::Opaque
. We don't want to equate this to the underlying concrete type - we just need to enforce the basic equality constraint between the two types (here, the return type offoo1
and the return type offoo2
)