-
Notifications
You must be signed in to change notification settings - Fork 13k
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
NLL should identify and respect the lifetime annotations that the user wrote #48482
Conversation
As of writing, this PR is not complete. It doesn't actually solve the issue yet, it just contains what I've got so far. It adds the majority of what was included in the mentoring instructions (I have not yet added the special case in the renumbering code as I wasn't sure about that). |
@davidtwco nice! will look soon |
src/librustc_mir/build/block.rs
Outdated
}) | ||
})); | ||
} else { | ||
this.visit_bindings(&pattern, &mut |this, _, _, node, span, _| { | ||
this.storage_live_binding(block, node, span); | ||
if let Some(ty) = 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.
So, this won't work for something like let (a, b): (u32, u32) = (22, 44);
-- it will assert that both a
and b
have type (u32, u32)
, I believe.
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 how most elegantly to handle this case right now -- one way would be to track the ty
as we visit the bindings, so that we can destructure it appropriately. Another would be to "build back up" the value from the types of the bindings. I don't like either one, unfortunately :(
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 wonder if we can push the problem to typeck, actually, since it already has to do "destructure" the type hint, I think?)
@@ -145,8 +145,18 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> { | |||
end_block.unit() | |||
} | |||
|
|||
pub fn user_assert_ty(&mut self, block: BasicBlock, ty: Ty<'tcx>, var: NodeId, span: Span) { |
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 been debating whether my idea to make this a statement was good or not -- but I do see some advantages. For example, it neatly addresses the question of where this subtyping needs to be enforced.
statement: &mut Statement<'tcx>, | ||
location: Location) { | ||
if let StatementKind::UserAssertTy(..) = statement.kind { | ||
statement.kind = StatementKind::Nop; |
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.
Nit: statement.make_nop();
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.
Fixed this.
src/librustc/session/config.rs
Outdated
@@ -1117,6 +1117,8 @@ options! {DebuggingOptions, DebuggingSetter, basic_debugging_options, | |||
"make unnamed regions display as '# (where # is some non-ident unique id)"), | |||
emit_end_regions: bool = (false, parse_bool, [UNTRACKED], | |||
"emit EndRegion as part of MIR; enable transforms that solely process EndRegion"), | |||
include_user_assert_ty: bool = (false, parse_bool, [UNTRACKED], | |||
"include UserAssertTy as part of MIR dumps"), |
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 think we should just always include 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.
Will remove this flag in a future commit.
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.
Removed this.
@@ -54,8 +54,10 @@ impl MirPass for CleanupPostBorrowck { | |||
delete.visit_mir(mir); | |||
} | |||
|
|||
let mut delete = DeleteUserAssertTy; | |||
delete.visit_mir(mir); | |||
if !tcx.sess.opts.debugging_opts.include_user_assert_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.
Also, it seems like this flag isn't quite what it suggests -- that is, it doesn't "include it" in the dump, it "stops deleting it". Can't you just use -Ζdump-mir
and inspect the results from earlier passes? For example, I generally do -Zdump-mir=nll
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 completely forgot I could view the MIR from earlier passes, so yeah, I'll remove this flag.
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.
Removed this.
@@ -776,6 +776,7 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> { | |||
| StatementKind::InlineAsm { .. } | |||
| StatementKind::EndRegion(_) | |||
| StatementKind::Validate(..) | |||
| StatementKind::UserAssertTy(..) |
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 think we want to do a check here -- something pretty similar to what Assign
does. i.e., get the type of the place
whose type is being asserted and invoke self.sub_types()
(or actually eq_type
perhaps).
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 intended to add a check here in a future commit, I initially included the variant here just so I could see if being included in the MIR without it doing anything.
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.
seems fine
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.
Added something that uses eq_type
, not sure it's doing 100% of what it needs to though.
386ddec
to
12d6706
Compare
let local_ty = mir.local_decls()[*local].ty; | ||
debug!("check_stmt: user_assert_ty ty={:?} local_ty={:?}", ty, local_ty); | ||
if let Err(terr) = | ||
self.eq_types(ty, local_ty, location.at_successor_within_block()) |
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 think this should be at_self_within_block
, though it probably doesn't matter. But this basically means that the type must be equal "on entry" to the statement, which feels "intuitively" right to me.
So, with everything that is implemented at current, I'm not seeing the error from Currently, the eq_type call is comparing the following:
I've experimented with ways to support the I'm uncertain what the next steps for this PR are? @nikomatsakis |
☔ The latest upstream changes (presumably #48208) made this pull request unmergeable. Please resolve the merge conflicts. |
☔ The latest upstream changes (presumably #46882) made this pull request unmergeable. Please resolve the merge conflicts. |
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 are also missing changes to the renumberer. In particular, I think we want to override visit_user_assert_ty
in that impl to do nothing, so that we preserve the user's types.
I think this should fix your test case!
Sorry for the super delayed feedback.
@@ -42,15 +42,15 @@ impl MirPass for CleanEndRegions { | |||
tcx: TyCtxt<'a, 'tcx, 'tcx>, | |||
_source: MirSource, | |||
mir: &mut Mir<'tcx>) { | |||
if !tcx.emit_end_regions() { return; } | |||
if tcx.emit_end_regions() { |
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.
any particular reason for this hunk, or just cleanup? (I sort of prefer the "return early" variant)
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.
So do I actually, this was just left-over from when I had CleanEndRegions
and CleanUserAssertTy
combined as a single pass and I didn't want an early return to stop the deletion of the UserAssertTy
. I can change it back.
src/librustc/mir/visit.rs
Outdated
@@ -619,6 +629,11 @@ macro_rules! make_mir_visitor { | |||
} | |||
} | |||
|
|||
fn super_user_assert_ty(&mut self, | |||
_ty: & $($mutability)* Ty<'tcx>, | |||
_local: & $($mutability)* Local) { |
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 are missing some things here:
self.visit_ty(ty);
self.visit_local(local);
d062708
to
e66fa1b
Compare
It's now causing an error as we would expect (though, almost certainly not the error we want in the end). If I'm not mistaken, we still need to handle the
|
src/test/ui/issue-47184.rs
Outdated
|
||
fn main() { | ||
let vec: Vec<&'static String> = vec![&String::new()]; | ||
//~^ ERROR |
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.
What error do we get now?
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 far as I can remember (not able to run this at the moment), we got an ICE produced by this section of the code.
Though, strangely, Travis isn't showing this. Perhaps I got mixed up and saw something else.
} | ||
} | ||
|
||
pub struct CleanUserAssertTy; |
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.
why are these two distinct things?
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 you are referring to the separation of the passes, in my experimentation locally, I found that the passes had to happen at different times otherwise the UserAssertTy
statement wouldn't be in the MIR when the NLL type check code gets it (since the type check pass doesn't run when #![feature(nll)]
was used - as far as I could tell - ordering it relative to that wasn't viable). Therefore they had to be separate. If this doesn't sound right, I can take another look.
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.
Hmm. I didn't realize that these passes were running at distinct times. At minimum, could use a comment clarifying what is happening here =)
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'll add one with my next push.
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 discussion should be resolved - I updated the comment on the module to mention this. It doesn't affect these lines specifically so this isn't marked as "outdated".
@@ -118,6 +118,11 @@ impl<'a, 'gcx, 'tcx> MutVisitor<'tcx> for NLLVisitor<'a, 'gcx, 'tcx> { | |||
debug!("visit_closure_substs: substs={:?}", substs); | |||
} | |||
|
|||
fn visit_user_assert_ty(&mut self, _ty: &mut Ty<'tcx>, _local: &mut Local, | |||
_location: Location) { | |||
debug!("visit_user_assert_ty: skipping renumber"); |
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.
It'd be nice to have a comment here. Something like:
User-assert-ty statements represent types that the user added explicitly.
We don't want to erase the regions from these types: rather, we want to
add them as constraints at type-check time.
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.
Will add this as soon as I get a chance.
I've updated the test in this PR with the expected error that we see in the AST borrowck. What was happening is the following: We were seeing an ICE occuring in the This is just because it is visiting the type that didn't get renumbered, which we don't want, so I've overridden the I'm not entirely sure where our Upon doing this, I expected to see no error be reported - as I previously wasn't seeing the ICE that I thought would be thrown where our user type assertion is performed. Digging in a little, it seems I made a bad assumption regarding all that Looking over initial instructions and previous comments, this still doesn't handle the |
|
@davidtwco turbofish covers more cases than that. Example: let x = SomeStruct::<'static> { field: u32 }; So really it's most paths. I guess it'd be useful though to try and go over the kinds of places paths can appear that matter:
(Also worth noting is that these paths can all include inference variables like |
that said, can we move this list to the issue, not this 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.
Awesome. Left a few nits but this is basically good to go.
/// let (a, b): (T, U) = y; | ||
/// | ||
/// Here we would insert a `UserAssertTy<(T, U)>(y)` instruction to check that the type of `y` | ||
/// is the right thing. |
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.
It is worth adding to the docs that the Canonical
is used to capture "inference variables" from user's types.
For example:
let x: Vec<_> = ...;
let y: &u32 = ...;
would result in Vec<?0>
and &'?0 u32
respectively (where ?0
is a canonicalized variable).
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 has been added but it is slightly below the reviewed line so GitHub hasn't noticed.
src/librustc/ty/context.rs
Outdated
@@ -344,6 +345,9 @@ pub struct TypeckTables<'tcx> { | |||
/// method calls, including those of overloaded operators. | |||
type_dependent_defs: ItemLocalMap<Def>, | |||
|
|||
/// Stores the canonicalized types provided by the user. | |||
user_provided_tys: ItemLocalMap<CanonicalTy<'tcx>>, |
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.
Maybe add a "see also: the UserAssertTy
statement in MIR" or something like that?
let (ty, _) = self.infcx.instantiate_canonical_with_fresh_inference_vars( | ||
stmt.source_info.span, c_ty); | ||
debug!("check_stmt: user_assert_ty ty={:?} local_ty={:?}", ty, local_ty); | ||
if let Err(terr) = self.eq_types(ty, local_ty, location.at_self()) { |
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.
👍
src/librustc_mir/build/block.rs
Outdated
}) | ||
})); | ||
} else { | ||
// FIXME: We currently only insert `UserAssertTy` statements for patterns |
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.
Can we say FIXME(#47184)
? We no longer mandate it, but I still prefer to have issue labels where we can.
@bors r+ |
📌 Commit 4161ae7 has been approved by |
NLL should identify and respect the lifetime annotations that the user wrote Part of rust-lang#47184. r? @nikomatsakis
NLL should identify and respect the lifetime annotations that the user wrote Part of #47184. r? @nikomatsakis
☀️ Test successful - status-appveyor, status-travis |
Part of #47184.
r? @nikomatsakis