Skip to content
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

MIR dead code analysis affects TAIT inference #99490

Closed
compiler-errors opened this issue Jul 20, 2022 · 12 comments · Fixed by #102700
Closed

MIR dead code analysis affects TAIT inference #99490

compiler-errors opened this issue Jul 20, 2022 · 12 comments · Fixed by #102700
Assignees
Labels
A-impl-trait Area: `impl Trait`. Universally / existentially quantified anonymous types with static dispatch. C-bug Category: This is a bug. F-type_alias_impl_trait `#[feature(type_alias_impl_trait)]` T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@compiler-errors
Copy link
Member

compiler-errors commented Jul 20, 2022

Given the code:

#![feature(type_alias_impl_trait)]

type Tait = impl Sized;

struct One;
fn one() -> Tait { One }

struct Two<T>(T);
fn two() -> Tait { Two::<()>(todo!()) }

If you run the code, you can see that the compiler ends up "successfully" inferencing Tait = One and succeeding compilation, even though upon visual inspection, the compiler should be raising an error since fn two should cause the conflicting inference Tait = Two<()>.

The key here is the todo!() macro, which causes the SimplifyCfg MIR pass to remove MIR that actually causes us to make this second inference.

Is this expected? How would we even change this behavior, since it would require us to disentangle TAIT inference from borrowck..?

@compiler-errors
Copy link
Member Author

I want to fix this but have no idea how I would begin, lol.

@compiler-errors compiler-errors added T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. A-impl-trait Area: `impl Trait`. Universally / existentially quantified anonymous types with static dispatch. C-bug Category: This is a bug. F-type_alias_impl_trait `#[feature(type_alias_impl_trait)]` labels Jul 22, 2022
@oli-obk
Copy link
Contributor

oli-obk commented Jul 22, 2022

I'm fairly certain we don't run simplify cfg on MIR before doing borrowck.

Borrowck still does enough dataflow analysis to realize that everything after the panic is dead. I guess typeck will to it correctly... so maybe we have to return all hidden types from typeck and seed the borrowck hidden types with it (so that borrowck only fills in the lifetimes instead of also figuring out the types)

@compiler-errors
Copy link
Member Author

@oli-obk
Copy link
Contributor

oli-obk commented Jul 22, 2022

Oh... huh... i was sure I've seems dead code in borrowck before, guess not. Either way, the "grab things from typeck" plan would work here, too

@compiler-errors
Copy link
Member Author

Either way, the "grab things from typeck" plan would work here, too

I'm finding a few issues with this strategy. Firstly, since the typeck results have their non-late regions erased, I'm not exactly sure how we're supposed to use the erased hidden type to seed the borrowck hidden types.

One thought I had was that we could capture the opaque type key and hidden type's lifetimes in a binder, like generator interior does to avoid being erased in the typeck resuls.
So if we had inferred Tait<'a> = &'a () during typeck, the typeck results would then store Binder(OpaqueTypeKey { def_id: Tait, substs: [ReLateBound#1] }, HiddenTy { ty: &ReLateBound#1 () }, [BrAnon(1)]) or something, if that makes sense.

But I found a problem that it's not even guaranteed there's a useful relationship between the regions present in the hidden type and those present in the substs of the opaque key...
That is, I'm finding that all we have in opaque type storage during writeback is something more like Tait<'a> = &'_#1r () -- maybe there's a relationship between that '_#1r, but it seems impossible to extract that relationship during writeback... we don't even do lexical region resolution anymore it seems, so fully_resolve on that region just gives us ReErased 😓.

@oli-obk
Copy link
Contributor

oli-obk commented Jul 24, 2022

Yes, that's why we actually need to do opaque type resolution in borrowck. The typeck hidden type would only be used as the initial value for the opaque types constrained by this item. All erased regions can be replaced by inference vars (which is effectively what borrowck does for all regions in the MIR). Then, the each time the hidden type is constrained again, the inference lifetimes from the seed hidden type are compared against the lifetimes of the new hidden type. If the only constraining happens in dead code, you'll end up with a lifetime error (unless the type has no lifetimes)

@compiler-errors
Copy link
Member Author

I'm mostly writing this out for my understanding, and to express some continuing confusion.

So I've gathered that we handle opaque types in two stages during borrowck -- first during type_check, where we collect a list of opaques opaque_type_values: VecMap<OpaqueTypeKey, OpaqueTypeDecl> , and then later on in RegionCtxt::infer_opaque_types, where squash all of the OpaqueTypeKeys with different substs into a VecMap<DefId, OpaqueHiddenTy> by mapping each key's substs back to the opaque's substs...

Between those two places, I have no idea where we'd put this new logic. I don't think we can just stick typeck's erased copy of these hidden types into the opaque_type_values mapping collected during mir type check, since we don't have any substs that go with this opaque type to make an OpaqueTypeKey out of.

On the other hand, I don't know where in infer_opaque_types I would put this logic either. Seems like it would have to live after all of the infer_opaque_definition_from_instantiation calls, since only then do we have a VecMap<LocalDefId, OpaqueHiddenType<'tcx>> to actually compare against the VecMap<LocalDefId, Ty<'tcx>> we get from typeck, to perhaps fill in any missing types that didn't get constrained during borrowck.

@oli-obk
Copy link
Contributor

oli-obk commented Jul 25, 2022

since we don't have any substs that go with this opaque type to make an OpaqueTypeKey out of.

You have identity substs, so yea, we could do it at the end where we convert everything to identity substs anyway

@compiler-errors
Copy link
Member Author

we could do it at the end where we convert everything to identity substs anyway

yup i can see this being the right way to do it. working on it now.

@oli-obk
Copy link
Contributor

oli-obk commented Sep 22, 2022

we could do it at the end where we convert everything to identity substs anyway

This sounds straight forward, and the implementation is, but now libcore doesn't compile anymore, because lifetime resolution is already done and thus the lifetimes of the identity-substituted type can't be equated to the other types that were already found.

Maybe we should only assert at the end that the hidden type found by typeck is the same as the one found by borrowck (modulo lifetimes). If borrowck found no hidden type, but typeck did, use the typeck type (with erased regions) to check that all other hidden types that were found by other functions at least match (again, modulo regions)

@compiler-errors
Copy link
Member Author

Yeah, I was curious if we could move all of this logic into find_opaque_ty_constraints_for_tait actually. We could make a fresh infcx there and equate all the different hidden types we find both during typeck (with erased regions -> fresh regions) and the ones we find during borrowck?

@oli-obk
Copy link
Contributor

oli-obk commented Sep 27, 2022

Yea, that seems more reasonable. Started doing that an hour ago and ironically I immediately found dead code in the opaque type processing logic 😆

@oli-obk oli-obk moved this from Todo to In Progress in type alias impl trait stabilization Oct 1, 2022
@compiler-errors compiler-errors removed their assignment Oct 5, 2022
@bors bors closed this as completed in 60bd3f9 Oct 14, 2022
Repository owner moved this from In Progress to Done in type alias impl trait stabilization Oct 14, 2022
Aaron1011 pushed a commit to Aaron1011/rust that referenced this issue Jan 6, 2023
Check hidden types in dead code

fixes rust-lang#99490

r? `@compiler-errors`

best reviewed commit by commit
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-impl-trait Area: `impl Trait`. Universally / existentially quantified anonymous types with static dispatch. C-bug Category: This is a bug. F-type_alias_impl_trait `#[feature(type_alias_impl_trait)]` T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Development

Successfully merging a pull request may close this issue.

2 participants