-
Notifications
You must be signed in to change notification settings - Fork 12.8k
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
Forbid type ascriptions of place expressions in lvalue contexts #80788
Conversation
Thanks for the pull request, and welcome! The Rust team is excited to review your changes, and you should hear from @lcnr (or someone else) soon. If any changes to this PR are deemed necessary, please add them as extra commits. This ensures that the reviewer can see what has changed since they last reviewed the code. Due to the way GitHub handles out-of-date commits, this should also make it reasonably obvious what issues have or haven't been addressed. Large or tricky changes may require several passes of review and changes. Please see the contribution instructions for more information. |
This comment has been minimized.
This comment has been minimized.
no worries 👍 this looks impressive for a first 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.
Here are some scenarios to test.
First, we want to test that every kind of borrow errors. Here are the ones I can think of:
let ref x = expr: T
should error(expr:T).as_ref()
should error (invokingAsRef::as_ref
from the standard library, which does auto-ref)&(expr:T)
should error
Next, for each kind of borrow, we want to try both a regular coercion and a coercion that is part of some larger replace. I would say cover these three scenarios:
&(expr:T).f
should error&(expr:T)[3]
should error&*(expr:T)
It's probably enough to cover those three scenarios for one kind of borrow, and then test that the other kind of borrows each handle (expr:T).f
or one of the more complex cases, but ideal would be to test all for all.
@@ -45,12 +45,18 @@ pub trait Delegate<'tcx> { | |||
&mut self, | |||
place_with_id: &PlaceWithHirId<'tcx>, | |||
diag_expr_id: hir::HirId, | |||
borrow_expr: Option<&hir::Expr<'_>>, |
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, I see, you added this parameter. I don't think we should do that. I think that place_with_id
already carries a HirId
and we ought to be able to use that to recover the expression.
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 tried to use 'place_with_id' initially, but the mem_categorization
functionality returns only the place expression id of a type ascription in some contexts (or rather in all contexts I tested) in diag_expr_id
, i.e. for (x: T)
diag_expr_id
only contains the HirId
of x
, so I had to introduce this parameter to get information to decide whether we're dealing with a type ascription. I didn't look into the mem_categorization
part to see why diag_expr_id
only corresponds to the place expression part of a type ascription expression.
place_with_id.place, borrow_expr, bk | ||
); | ||
|
||
if let Some(borrow) = borrow_expr { |
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 the right general idea, but I'm worried it's not quite covering all the bases. This will work for something like
&(x:T)
but I don't think it would work for &(x:T).f
. I'll try to create what seem to me to be a good set of test cases and link them below.
I'm also not sure if looking at borrow_expr
is the right thing; the Place
has a HirId
, maybe we want to look at that. I have to check under what conditions borrow_expr
is None.
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.
Update: OK, I see you added borrow_expr
. I think instead of doing that, you can use expect_expr
to get the hir::Expr
.
To manage the cases I was talking about, you'll need to use a HIR visitor or something to recursively walk the HIR structure.
I am, however, pondering whether the right fix here is to modify the Place
structure a bit. Let me take a look at that.
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 yes, what I think is that we should modify place construction -- I suspect we should
- modify
PlaceBase::Rvalue
to contain aHirId
- treat ascription as an rvalue, during place construction but...probably just everywhere -- that means modifying this function and perhaps this one, I haven't read that much into that code
- check in this code where
place.base
is an rvalue and, if so, check whether its an ascription and report an 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.
See comment above for why I introduced borrow_expr
. I also tried to somehow (currently don't remember what exactly I tried) to get access to a parent expression (the type ascription expression) for diag_expr_id
, but wasn't able to do that.
I will take a look at your suggestions later.
253f849
to
a37d16d
Compare
This comment has been minimized.
This comment has been minimized.
a37d16d
to
b0e1733
Compare
This comment has been minimized.
This comment has been minimized.
LL | let r: &Foo = &mut foo; | ||
| ^^^^^^^^^^^ | ||
|
||
error: type ascriptions are not allowed in lvalue contexts |
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 can fix these repetitions of errors caused by nested delegate.borrow
calls using the hir visitor. I want to see whether the general logic is correct now, before doing that.
b0e1733
to
d2485ca
Compare
This comment has been minimized.
This comment has been minimized.
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 looking pretty good-- I think it's missing a few things, though.
Specifically, I think we want to categorize place expressions as rvalues consistenly. That means we need to edit this function:
rust/compiler/rustc_hir/src/hir.rs
Line 1478 in 399b645
ExprKind::Type(ref e, _) => e.is_place_expr(allow_projections_from), |
I was reading into the THIR/MIR construction code to see if any changes are needed there, but I don't think so.
let place_with_id = return_if_err!(self.mc.cat_expr(expr)); | ||
|
||
self.delegate_consume(&place_with_id, place_with_id.hir_id); | ||
self.walk_expr(subexpr); |
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, this is interesting. It surprises me a bit but I actually think it's correct -- still, I would expect self.consume_expr(subexpr)
.
9007ab3
to
597cb28
Compare
@@ -26,4 +24,5 @@ fn main() { | |||
let tup = (9, (3, &arr : &[u32]), &mut bar); |
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.
There is no error here, but two lines down there is, in what looks to be the same expression. Is this correct?
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.
Looking good! Here are some thoughts/comments. Sorry about the delay. Thanks for pushing on this, @b-naber! I am beginning to think we'll be able to get this over the finish line soon.
// operand. See: | ||
// https://github.com/rust-lang/rfcs/blob/master/text/0803-type-ascription.md#type-ascription-and-temporaries | ||
ExprKind::Type(ref e, _) => e.is_place_expr(allow_projections_from), | ||
// We always treat type ascriptions as rvalues |
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.
probably worth adding a comment here like ", but if a type ascription is used in a reference, it will be an error"
@@ -18,7 +18,7 @@ use rustc_target::abi::VariantIdx; | |||
)] | |||
pub enum PlaceBase { | |||
/// A temporary 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.
Can we extend this comment to indicate what the HirId
is?
let possible_ancestor_proj_kinds = | ||
capture.place.projections.iter().map(|proj| proj.kind).collect(); | ||
is_ancestor_or_same_capture(&possible_ancestor_proj_kinds, &hir_projections) | ||
let possible_ancestor_proj_kinds = |
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's going on with these changes :) they seem like random whitespace changes. Are they a side-effect of running rustfmt or something?
block.and(place_builder) | ||
} | ||
ExprKind::ValueTypeAscription { source, user_ty } => { | ||
ExprKind::TypeAscription { source, user_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.
Huzzah for code being deleted! 🎉
@@ -282,7 +277,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { | |||
ExprKind::Cast(ref e, ref t) => self.check_expr_cast(e, t, expr), | |||
ExprKind::Type(ref e, ref t) => { |
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.
Pre-existing, but ExprKind::Type
is really unclear to me. TypeAscription
would be much better.
if let Some(expr) = self.fcx.tcx.hir().maybe_expr(id) { | ||
debug!("expr behind place: {:?}", expr); | ||
self.finder.visit_expr(expr); | ||
if let Some(ascr_expr) = self.finder.found_type_ascr() { |
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: found_type_ascription
-- spell it out
debug!("expr behind place: {:?}", expr); | ||
self.finder.visit_expr(expr); | ||
if let Some(ascr_expr) = self.finder.found_type_ascr() { | ||
if let hir::ExprKind::Type(ref e, ref t) = ascr_expr.kind { |
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 probably an assertion, right? maybe we can make found_type_ascr
return the e
and t
directly instead?
err.emit(); | ||
} | ||
} | ||
self.finder.reset(); |
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 would be part of the method I suggested above
assignee_place, diag_expr_id | ||
); | ||
|
||
if let PlaceBase::Rvalue(id) = assignee_place.place.base { |
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 good to have a code example of what we are detecting and forbidding
place_with_id.place, bk | ||
); | ||
|
||
if let PlaceBase::Rvalue(id) = place_with_id.place.base { |
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 good to have a code example of what we are detecting and forbidding
Ping from triage: @b-naber could you address Niko's review? Thanks! |
@camelid Landing this will require the lang team to accept the updated RFC we will write for the type ascription feature. I haven't yet addressed Niko's review, because I wanted to wait for that approval. Is that ok? |
We could close the PR and re-open it. |
OK, let's do that. |
That's totally fine! I didn't see anything in the PR about waiting on an RFC, so I was confused.
So you know for the future: that's not necessary, just indicate that the PR is waiting on something by adding |
Opening to get some feedback.
r? @nikomatsakis
edit: sorry lcnr, forgot the rust-highfive command