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

Forbid type ascriptions of place expressions in lvalue contexts #80788

Closed
wants to merge 7 commits into from

Conversation

b-naber
Copy link
Contributor

@b-naber b-naber commented Jan 7, 2021

Opening to get some feedback.

r? @nikomatsakis

edit: sorry lcnr, forgot the rust-highfive command

@rust-highfive
Copy link
Collaborator

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.

@rust-highfive rust-highfive added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Jan 7, 2021
@rust-log-analyzer

This comment has been minimized.

@lcnr
Copy link
Contributor

lcnr commented Jan 7, 2021

no worries 👍 this looks impressive for a first pr

r? @nikomatsakis

Copy link
Contributor

@nikomatsakis nikomatsakis left a 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 (invoking AsRef::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<'_>>,
Copy link
Contributor

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.

Copy link
Contributor Author

@b-naber b-naber Jan 16, 2021

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 {
Copy link
Contributor

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.

Copy link
Contributor

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.

Copy link
Contributor

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 a HirId
  • 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

Copy link
Contributor Author

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.

@b-naber b-naber force-pushed the type_ascr_lvalue branch 2 times, most recently from 253f849 to a37d16d Compare January 22, 2021 20:33
@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

LL | let r: &Foo = &mut foo;
| ^^^^^^^^^^^

error: type ascriptions are not allowed in lvalue contexts
Copy link
Contributor Author

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.

@rust-log-analyzer

This comment has been minimized.

Copy link
Contributor

@nikomatsakis nikomatsakis left a 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:

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);
Copy link
Contributor

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).

@@ -26,4 +24,5 @@ fn main() {
let tup = (9, (3, &arr : &[u32]), &mut bar);
Copy link
Contributor Author

@b-naber b-naber Feb 11, 2021

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?

@b-naber b-naber changed the title [WIP] Forbid type ascriptions of place expressions in lvalue contexts Forbid type ascriptions of place expressions in lvalue contexts Feb 17, 2021
@b-naber b-naber marked this pull request as ready for review February 17, 2021 16:18
Copy link
Contributor

@nikomatsakis nikomatsakis left a 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
Copy link
Contributor

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.
Copy link
Contributor

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 =
Copy link
Contributor

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 } => {
Copy link
Contributor

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) => {
Copy link
Contributor

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() {
Copy link
Contributor

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 {
Copy link
Contributor

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();
Copy link
Contributor

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 {
Copy link
Contributor

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 {
Copy link
Contributor

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

@camelid camelid added the A-type-system Area: Type system label Mar 12, 2021
@camelid
Copy link
Member

camelid commented Mar 12, 2021

Ping from triage: @b-naber could you address Niko's review? Thanks!

@camelid camelid added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Mar 12, 2021
@b-naber
Copy link
Contributor Author

b-naber commented Mar 12, 2021

@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?

@nikomatsakis
Copy link
Contributor

We could close the PR and re-open it.

@b-naber
Copy link
Contributor Author

b-naber commented Mar 13, 2021

OK, let's do that.

@b-naber b-naber closed this Mar 13, 2021
@camelid
Copy link
Member

camelid commented Mar 13, 2021

@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?

That's totally fine! I didn't see anything in the PR about waiting on an RFC, so I was confused.

We could close the PR and re-open it.

So you know for the future: that's not necessary, just indicate that the PR is waiting on something by adding S-blocked.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-type-system Area: Type system S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants