Rust: [NLL] E0389 error message uses the term `immutable item` inappropriately

Created on 12 Jan 2018  路  26Comments  路  Source: rust-lang/rust

The test compile-fail/E0389.rs gives the error message "cannot assign to immutable item fancy_ref.num". This is not particularly good because:

(a) "item" is probably not a very clear term for end-users but also
(b) even if it were, I would expect it to refer to "global things" like functions or statics, not fields of a local variable.

A-diagnostics E-mentor NLL-diagnostics T-compiler

Most helpful comment

@gaurikholkar this is awesome =)

A few notes:

  • Obviously, I think we'd prefer to have the suggestion inline. I am not sure why the current one is not, I guess it makes a difference precisely which methods of DiagnosticBuilder you use -- maybe @estebank knows...
  • Second, it seems like the span we are getting from MIR underlines the entire &(&mut fancy) expression, but we only want the & character. That is annoying. I think that -- to fix it -- we often do some kind of grungy "munging" of the span. Again, @estebank might know if there is a suitable helper. In this case, it's probably relatively easy, since we only want the first character, so maybe we can just do that.

All 26 comments

Mentoring instructions

The error messages in question seem to originate here:

https://github.com/rust-lang/rust/blob/da569fa9ddf8369a9809184d43c600dc06bd4b4d/src/librustc_mir/borrow_check/mod.rs#L1404-L1407

I think that to fix this, we will want to examine the Place more closely. A Place is a MIR data structure defined here

https://github.com/rust-lang/rust/blob/da569fa9ddf8369a9809184d43c600dc06bd4b4d/src/librustc/mir/mod.rs#L1231-L1243

I think we could either print "immutable path" instead of "immutable item", or else (maybe better)
examine the variant and adjust our message accordingly:

If it's a local, print "immutable local variable".

For a static, "immutable static" or "immutable static variable".

For anything else, maybe just "path"?

In general, I think I would also prefer the message to be like

error: cannot assign to `x`
  >
  > let x = 22;
  >     ^ `x` not declared `mut`

It might take a bit of tweaking to get this right, but by walking up the Place we can basically figure out why x is not mutable etc. I think the AST borrow-checker already does this to some extent?

Investigating this

Looking at possible error messages for the similar cases
Message 1

let fancy_ref = &(&mut fancy);
                ^^^^^^^^^^^^^^^^^ consider changing this to `mut`

Message2

error[E0389]: cannot assign to `fancy_ref.num`
  --> src/test/compile-fail/E0389.rs:18:5
   |
18 |     let fancy_ref = &(&mut fancy);
   |                     ^not declared `mut`

cc @estebank @nikomatsakis

I think I would prefer:

error[E0389]: cannot assign to field `num` in immutable reference `fancy_ref`
 --> src/main.rs:8:5
  |
7 |    let fancy_ref = &(&mut fancy);
  |                    - help: consider changing this to be a mutable reference: `&mut `
8 |     fancy_ref.num = 6; //~ ERROR E0389
  |     ^^^^^^^^^^^^^^^^^ cannot assign to field of immutable reference

The "immutable/mutable reference" is jargony, but consistent with other errors. We need to make it clear that fancy_ref is immutable, not fancy_ref.num.

Overall, I am feeling torn here. I like @estebank's phrasing, but I have two concerns:

First, I personally don't like term 'immutable reference'. I know it is widely used in our error messages -- and even in the Rust book -- but I think it is misleading. In particular, an &T reference does not imply that T cannot be mutated, it just means you need to use special types to do it. For example, &Cell<u32> permits mutation of the u32 value (similarly &Mutex<u32>). Our mutation story is definitely something that confuses people, and I think terminology like "immutable reference" doesn't help. However, this is to some extent a larger question.
- That said, it is also somewhat jargon-y, and it'd be nice if we could avoid it.
- My personal term is shared reference, but I realize that is not in widespread use, and I suspect it too should be avoided. =)
- I wonder if we can just say &-reference or something?

Ignoring that, I think that @estebank's message is nice, though I wonder: I feel like there should be a principle of only mentioning the things that actually matter in the error, and in fact the precise field (num, here) doesn't matter. That is, it's not that you can't assign to num -- you can't assign to any field, or any other content.

Putting it all together, maybe something like this?

error[E0389]: cannot assign through `&`-reference `fancy_ref`
 --> src/main.rs:8:5
  |
7 |    let fancy_ref = &(&mut fancy);
  |                    - help: consider changing this to be a mutable reference: `&mut `
8 |     fancy_ref.num = 6; //~ ERROR E0389
  |     ^^^^^^^^^^^^^^^^^ cannot assign through `&`-reference

But I'm not sure if it's really an improvement on what @estebank wrote. In some ways, e.g. the use of the word "through" instead of a field name, it may be less clear. Curious what others think.

(I'm also not sure about &-reference vs "immutable reference". I'm willing to go with the latter for now since it is used broadly, though I do want to revisit it at some point.)

@nikomatsakis I feel the term shared reference might be confusing. &-reference sounds better! I do agree that cannot assign through &-reference fancy_ref is short and clear and the mention of the field num might not be much needed.

@gaurikholkar ok, if @estebank is happy with that, let's try for that for now. This is going to require a bit of analysis of the Place which is being assigned.

I disagree slightly that num is irrelevant because it's the users intention to assign to it, but given that the span is already point to it might not need to be explicitly mentioned. Otherwise I'm fine with the proposed phrasing.

I disagree slightly that num is irrelevant because it's the users intention to assign to it

Yeah, irrelevant is too strong. But it's not that there is anything special about the num field. I suppose something like:

error[E0389]: cannot assign through `&`-reference `fancy_ref`
 --> src/main.rs:8:5
  |
7 |    let fancy_ref = &(&mut fancy);
  |                    - help: consider changing this to be a mutable reference: `&mut `
8 |     fancy_ref.num = 6; //~ ERROR E0389
  |     ^^^^^^^^^^^^^^^^^ `num` field is located through an `&`-reference, cannot be assigned

but that seems a bit verbose (and likely to run off the side of the screen).

<bikeshed>What about?

error[E0389]: cannot assign to `num` through `&`-reference `fancy_ref`
 --> src/main.rs:8:5
  |
7 |    let fancy_ref = &(&mut fancy);
  |                    - help: consider changing this to be a mutable reference: `&mut `
8 |     fancy_ref.num = 6; //~ ERROR E0389
  |     ^^^^^^^^^^^^^^^^^ cannot assign to field of `&`-reference

@estebank that seems really good. We should consider all the other cases that will arise I suppose. We can probably fallback to my simpler variant when it is not a field being assigned?

We can probably fallback to my simpler variant when it is not a field being assigned?

That would be my preference, yes.

@gaurikholkar ok so let me leave you a few more tips. I mentioned already that we will want to examine the Place that is being assigned, and how we can special case local variables and so forth. A path like foo.bar will typically be a projection:

https://github.com/rust-lang/rust/blob/da569fa9ddf8369a9809184d43c600dc06bd4b4d/src/librustc/mir/mod.rs#L1241-L1242

A projection consists of an "element" and a base Place:

https://github.com/rust-lang/rust/blob/da569fa9ddf8369a9809184d43c600dc06bd4b4d/src/librustc/mir/mod.rs#L1258-L1266

Elements consist of things like fields or dereferences:

https://github.com/rust-lang/rust/blob/da569fa9ddf8369a9809184d43c600dc06bd4b4d/src/librustc/mir/mod.rs#L1268-L1269

In this case, where foo is a reference, foo.bar is short-hand for (*foo).bar. So the place would be like:

I think, for error messages purposes, we want to consider things like this:

  • If the outermost layer of the Place is a Projection of a field, then we can extract the field name and use it in the error.
  • If somewhere in the place there is a deref of a borrowed reference (&T type), this is the "through an &-reference" case.

    • However, there is already code that looks for that, it's called the function is_mutable

https://github.com/rust-lang/rust/blob/da569fa9ddf8369a9809184d43c600dc06bd4b4d/src/librustc_mir/borrow_check/mod.rs#L1471-L1476

and it seems like is_mutable already returns the path that indicates why the field is not mutable. So I guess most of what we need is there. You'll have to read into a bit to figure out just what to change though, or shoot me a few questions later on.

Thanks @nikomatsakis

@gaurikholkar how goes ? Just checking in here =)

This is the output for now.

error[E0594]: cannot assign to `&`-reference *fancy_ref
  --> src/test/compile-fail/E0389.rs:18:5
   |
18 |     fancy_ref.num = 6; //~ ERROR E0389
   |     ^^^^^^^^^^^^^^^^^ cannot assign through `&`-reference

error: aborting due to previous error

cc @nikomatsakis @estebank

@gaurikholkar making progress! One nit: should be "cannot assign through &-reference", and also *fancy_ref ought to be embedded in ticks. Also, we probably want to strip the *, I imagine. That leaves us with:

error[E0594]: cannot assign through `&`-reference `fancy_ref`

Current status. The error message labels are inverted, will fix that and open a PR.

error[E0594]: cannot assign through `&`-reference fancy_ref
  --> src/test/compile-fail/E0389.rs:18:5
   |
18 |     fancy_ref.num = 6; //~ ERROR E0389
   |     ^^^^^^^^^^^^^^^^^ cannot assign to field of `&`-reference
   |
help: consider changing this to be a mutable reference: `&mut `
  --> src/test/compile-fail/E0389.rs:17:21
   |
17 |     let fancy_ref = &(&mut fancy);
   |                     ^^^^^^^^^^^^^

error: aborting due to previous error

@gaurikholkar this is awesome =)

A few notes:

  • Obviously, I think we'd prefer to have the suggestion inline. I am not sure why the current one is not, I guess it makes a difference precisely which methods of DiagnosticBuilder you use -- maybe @estebank knows...
  • Second, it seems like the span we are getting from MIR underlines the entire &(&mut fancy) expression, but we only want the & character. That is annoying. I think that -- to fix it -- we often do some kind of grungy "munging" of the span. Again, @estebank might know if there is a suitable helper. In this case, it's probably relatively easy, since we only want the first character, so maybe we can just do that.
error[E0594]: cannot assign through `&`-reference fancy_ref
  --> src/test/compile-fail/E0389.rs:18:5
   |
17 |     let fancy_ref = &(&mut fancy);
   |                     ------------- help: consider changing this to be a mutable reference: `&mut`
18 |     fancy_ref.num = 6; //~ ERROR E0389
   |     ^^^^^^^^^^^^^^^^^ cannot assign to field of `&`-reference

@nikomatsakis the suggestion is inline now

I am not sure why the current one is not

I'm guessing that it wasn't a span_suggestion, but rather a span_help. Regardless, span suggestions do not appear inline if the text is too long (IIRC, over 10 words) or there're multiple suggestions in the same diagnostic.

In this case, it's probably relatively easy, since we only want the first character, so maybe we can just do that.

There's already a method to get a span pointing at the end of a given span (tcx.sess.codemap().end_point(span)), but the equivalent for the beginning was removed due to lack of use, it could be brought back in.

@gaurikholkar be careful with suggestions, everything underlined will be replaced with the content of the suggestion string, in this case the resulting code would be let fancy_ref = &mut;, which is not what we want. If you use a span pointing only at &, you don't need to change anything else. Really like the change!

I'm guessing that it wasn't a span_suggestion, but rather a span_help

Yeah I was using span_help instead before

If you use a span pointing only at &, you don't need to change anything else. Really like the change!

@estebank are you saying we should alter the suggestion/error message or get back the old method?

@gaurikholkar I'm saying that the only change needed to fix the incorrect suggestion result is changing the span to only point to the &.

Clearing assignment since #48914 was closed for inactivity. It'd be good to dust off the PR and get those changes landed though! We were getting close.

Reopening the PR @nikomatsakis @estebank

Was this page helpful?
0 / 5 - 0 ratings

Related issues

alexcrichton picture alexcrichton  路  240Comments

nikomatsakis picture nikomatsakis  路  236Comments

Leo1003 picture Leo1003  路  898Comments

nikomatsakis picture nikomatsakis  路  412Comments

withoutboats picture withoutboats  路  213Comments