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

Incorrect borrowing suggestion for range literals #54505

Closed
varkor opened this issue Sep 23, 2018 · 14 comments · Fixed by #54734
Closed

Incorrect borrowing suggestion for range literals #54505

varkor opened this issue Sep 23, 2018 · 14 comments · Fixed by #54734
Labels
A-diagnostics Area: Messages for errors, warnings, and lints E-easy Call for participation: Easy difficulty. Experience needed to fix: Not much. Good first issue. E-mentor Call for participation: This issue has a mentor. Use #t-compiler/help on Zulip for discussion.

Comments

@varkor
Copy link
Member

varkor commented Sep 23, 2018

use std::ops::RangeInclusive;

fn take_range(r: &RangeInclusive<i8>) {}

fn main() {
    take_range(0..=1);
}
error[E0308]: mismatched types
 --> src/main.rs:6:16
  |
6 |     take_range(0..=1);
  |                ^^^^^
  |                |
  |                expected reference, found struct `std::ops::RangeInclusive`
  |                help: consider borrowing here: `&0..=1`
  |
  = note: expected type `&std::ops::RangeInclusive<i8>`
             found type `std::ops::RangeInclusive<{integer}>`

&0..=1 is an incorrect suggestion because & has higher precedence than ..=. It should suggest &(0..=1).

@varkor varkor added the A-diagnostics Area: Messages for errors, warnings, and lints label Sep 23, 2018
@varkor
Copy link
Member Author

varkor commented Sep 23, 2018

If someone wants to tackle this issue, it should be straightforward to fix. In:
https://github.com/rust-lang/rust/blob/f0bae412e97db7ef26e24546bce5199c6a086152/src/librustc_typeck/check/demand.rs#L308-L312
there should be another branch for ExprKind::Call, where the path of the call is ops::RangeInclusive.
This code is a good reference:
https://github.com/rust-lang/rust/blob/f0bae412e97db7ef26e24546bce5199c6a086152/src/librustc/hir/lowering.rs#L3777-L3788


This issue affects the other range syntaxes too, so those should also be fixed (although these are not ExprKind::Call — look at hir/lowering.rs for clues).

@varkor varkor added E-easy Call for participation: Easy difficulty. Experience needed to fix: Not much. Good first issue. E-mentor Call for participation: This issue has a mentor. Use #t-compiler/help on Zulip for discussion. labels Sep 23, 2018
@pawroman
Copy link
Contributor

pawroman commented Sep 23, 2018

Hi @varkor, I'd like to tackle this and take the plunge into the Rust compiler code :)

@varkor
Copy link
Member Author

varkor commented Sep 23, 2018

@pawroman: great! Let me know if you hit any snags or need any more tips. You'll want to add tests for these (in src/test/ui). The rustc guide is a good place for general information about the compiler. When you're done, include r? @varkor in the pull request and I'll review it!

@pawroman
Copy link
Contributor

pawroman commented Sep 24, 2018

@varkor you've anticipated most of my newbie questions already :) Thanks for that info, very useful! I'm now in the process of stressing my PC with lots of compiling to actually get started.

Skimming the test code I'm really impressed by how easy it is to add "UI" tests for these sorts of things (compile this, expect this error on this line). Snazzy!

That's all great, but do you anticipate I should also add "deeper" tests? I guess I'm trying to understand if the scope of this is just pure UI or do I need to touch some syntax parsing/immediate representations, since you mentioned code in lowering.rs.

Because the compiler is emitting a syntax suggestion, we might want to assert that the suggestion is correct syntax in the first place. I don't know if that's how deep the rabbit hole goes and if there's a facility to help out, but my hunch is that since we have all of the compiler at hand, why not use it to validate the suggestion syntax?

EDIT:
OK, reading up on adding new tests to the compiler I encountered mention of rust-runfix, and it seems to be sort-of what I was after.

@varkor
Copy link
Member Author

varkor commented Sep 24, 2018

That's all great, but do you anticipate I should also add "deeper" tests? I guess I'm trying to understand if the scope of this is just pure UI or do I need to touch some syntax parsing/immediate representations, since you mentioned code in lowering.rs.

The issue here is just a diagnostic issue, so checking that we're getting the updated hint is enough. The code in lowering.rs is just helpful to see what ranges like ..= and .. are desugared into, so you can catch them in demand.rs — you shouldn't need to modify anything (apart from the test) outside of demand.rs.

Because the compiler is emitting a syntax suggestion, we might want to assert that the suggestion is correct syntax in the first place. I don't know if that's how deep the rabbit hole goes and if there's a facility to help out, but my hunch is that since we have all of the compiler at hand, why not use it to validate the suggestion syntax?

It would technically be possible to do something like this, but it would probably be more trouble than it's worth. (I'm not aware of a facility to do this easily already.) I think manually going through the possible ExprKinds and checking that the parenthesisation behaviour is correct in each case would be a lot simpler: if we handle the different ExprKinds in an exhaustive match now (it's likely that if the ranges were handled incorrectly, there could be some others that are also missed at the moment), then this should be a one-time fix and the tests should prevent it regressing.

OK, reading up on adding new tests to the compiler I encountered mention of rust-runfix, and it seems to be sort-of what I was after.

A simple UI test should be sufficient here: --bless will generate the output files, which will include all the relevant notes.

@estebank
Copy link
Contributor

There was a similar problem with the .into() suggestions, and it was fixed in #47702. Further use of the operator precedence list can be seen in #47247 (which introduced the error fixed in the other PR).

@pawroman
Copy link
Contributor

pawroman commented Sep 25, 2018

Thanks for your guidance @varkor and @estebank, much appreciated. I have dug into HIR and built-in ranges a bit more.

Decided to check all the built-in range types and concluded they are all indeed affected by this problem.

The following test I came up with demonstrates my intended fix (fails with nightly as well as stage 1 compiled from master): https://gist.github.com/pawroman/6ed825d66c0f8d910b99ce3cf22cd4d9

None of the fix suggestions, when applied, constitute valid syntax. My test checks for syntax I believe should be suggested by the compiler.

I'm keen to tackle all of the range types at once in the scope of this issue. The problem I see though is just how wildly different each of the range types is represented in HIR. Adding a debug! call just above this line:

let sugg_expr = match expr.node { // parenthesize if needed (Issue #46756)

Revealed the following:

  • expr.node for 0..1 is Struct(Resolved(None, path(::std::ops::Range)), [Field { id: NodeId(116), ident: start#0, expr: expr(74: 0), span: rust/src/test/ui/range/issue-54505.rs:31:16: 31:17, is_shorthand: false }, Field { id: NodeId(117), ident: end#0, expr: expr(75: 1), span: rust/src/test/ui/range/issue-54505.rs:31:19: 31:20, is_shorthand: false }], None)

  • expr.node for 1.. is Struct(Resolved(None, path(::std::ops::RangeFrom)), [Field { id: NodeId(118), ident: start#0, expr: expr(79: 1), span: rust/src/test/ui/range/issue-54505.rs:36:21: 36:22, is_shorthand: false }], None)

  • expr.node for .. is Path(Resolved(None, path(::std::ops::RangeFull)))

  • expr.node for 0..=1 is Call(expr(120: <::std::ops::RangeInclusive>::new), [expr(86: 0), expr(87: 1)])

  • expr.node for ..5 is Struct(Resolved(None, path(::std::ops::RangeTo)), [Field { id: NodeId(121), ident: end#0, expr: expr(91: 5), span: rust/src/test/ui/range/issue-54505.rs:51:21: 51:22, is_shorthand: false }], None)

  • expr.node for ..=42 is Struct(Resolved(None, path(::std::ops::RangeToInclusive)), [Field { id: NodeId(122), ident: end#0, expr: expr(95: 42), span: rust/src/test/ui/range/issue-54505.rs:56:32: 56:34, is_shorthand: false }], None)

I'm quite bewildered by this non-uniformity and I guess I need some help unifying all of these into a elegant condition to tell "is this a built-in range?". I could probably brute-force it in a match with explicit checks for path, but maybe we can do better.

Thanks in advance!

EDIT realized that this non-uniformity was actually mentioned in the second comment from @varkor , but my plea still stands. Thanks for your patience.

@estebank
Copy link
Contributor

I think you can match on all of these with something along the lines of (pseudocode):

match expr.node{
    Struct(Resolved(None, path, ..), _) if path.startswith("std::ops::Range") => {
    }
    _ => {}
}

@pawroman
Copy link
Contributor

pawroman commented Sep 26, 2018

Right, that's what I had in mind, but was also wondering if something like a check for RangeBounds trait would work better.

I guess we want to match against built-in range literals though, and explicitly. Here's how I rationalize it: because they are parsed and desugared by the compiler, the check needs to be explicit and hardcode all paths to builtin ranges. Handle a special case with a special case.

@varkor
Copy link
Member Author

varkor commented Sep 26, 2018

The problem I see though is just how wildly different each of the range types is represented in HIR.

Yes, this is where the lowering code comes in handy, because you can see exactly how the different syntax is desugared differently, to make sure you really are catching all the cases.

https://github.com/rust-lang/rust/blob/f0bae412e97db7ef26e24546bce5199c6a086152/src/librustc/hir/lowering.rs#L3789-L3834

Here's how I rationalize it: because they are parsed and desugared by the compiler, the check needs to be explicit and hardcode all paths to builtin ranges. Handle a special case with a special case.

Yes, I agree. It'd be nice if there was a more uniform way to handle them, but it's not convenient at the moment. You could include a reference back to lowering.rs in a comment to where the desugaring takes place, which will provide some motivation for matching in demand.rs.

@pawroman
Copy link
Contributor

pawroman commented Sep 27, 2018

All clear now, thanks. I have succeeded in matching all range types and getting the test to pass. Needs some cleanups, but hopefully should be ready for PR soon. I have a few more questions before that though 😄

I have noticed that most of my code would actually be irrelevant if I re-used functions and constants defined in clippy, e.g.:

https://github.com/rust-lang-nursery/rust-clippy/blob/a72e786c5d8866d554effd246c30fb553b63fa06/clippy_lints/src/utils/mod.rs#L154-L173

https://github.com/rust-lang-nursery/rust-clippy/blob/a72e786c5d8866d554effd246c30fb553b63fa06/clippy_lints/src/utils/higher.rs#L92

https://github.com/rust-lang-nursery/rust-clippy/blob/a72e786c5d8866d554effd246c30fb553b63fa06/clippy_lints/src/utils/paths.rs#L64-L75

Can't deny that I have used these as inspiration / guideline (albeit I did not do a stupid copy-paste). What's the policy on using clippy code (clippy is a submodule of main repo)? This is purely in the interest of pursuing DRY.

The diff is not that big (52 new lines added to demand.rs, fairly specific to matching Range paths) -- but still, my clean code sense tingles because of possible DRY violation.

Another conundrum I have is whether we should address core and std paths (and if so, how to work out we're not using std at compile time)?

@varkor
Copy link
Member Author

varkor commented Sep 27, 2018

I have noticed that most of my code would actually be irrelevant if I re-used functions and constants defined in clippy

Unfortunately, there's not much sharing from clippy to rustc. I wouldn't worry about it. If you can encapsulate it easily, it's possible that clippy could make use of the code in rustc instead.

Another conundrum I have is whether we should address core and std paths (and if so, how to work out we're not using std at compile time)?

The std_path function in lowering.rs picks the correct path for core/path — you should be able to use that.

pawroman added a commit to pawroman/rust that referenced this issue Sep 29, 2018
@pawroman
Copy link
Contributor

Thanks for the suggestion for std_path, unfortunately I can't really make the association of FnCtxt and LoweringContext.

It seems to me they are disjoint -- in driver.rs: lowering happens in phase2_configure_and_expand_inner, whereas typechecking happens in phase_3_run_analysis_passes. std_path in LoweringContext takes &mut self:

/// Given suffix ["b","c","d"], returns path `::std::b::c::d` when
/// `fld.cx.use_std`, and `::core::b::c::d` otherwise.
/// The path is also resolved according to `is_value`.
fn std_path(
&mut self,
span: Span,
components: &[&str],
params: Option<P<hir::GenericArgs>>,
is_value: bool
) -> hir::Path {
self.resolver
.resolve_str_path(span, self.crate_root, components, params, is_value)
}

The Resolver it uses needs an awful lot of things to be instantiated. Not sure if I can get the hold of it all in FnCtxt.

Another problem I encountered while writing tests is that the changes I have made will affect suggestions for code like this:

use std::ops::RangeBounds;

fn take_range(_r: &impl RangeBounds<i8>) {}

fn main() {
    take_range(::std::ops::Range { start: 0, end: 1 });
}

With my changes, the suggestion would be &(::std::ops::RangeToInclusive { end: 5 }). I believe the correct suggestion would not involve the needless parentheses.

This is due to not differentiating between "de-sugared" form coming from hir and between these de-sugared forms being supplied explicitly as input source code.

With this in mind, I think current code is not PR worthy just yet. If of interest, here's the branch: https://github.com/pawroman/rust/tree/fix_range_borrowing_suggestion

@estebank
Copy link
Contributor

estebank commented Oct 1, 2018

Left a couple of comments on possible approaches on that commit. The code looks fine.

pietroalbini added a commit to pietroalbini/rust that referenced this issue Oct 3, 2018
…tion, r=varkor

Fix range literals borrowing suggestions

Fixes rust-lang#54505. The compiler issued incorrect range borrowing suggestions (missing `()` around borrows of range literals). This was not correct syntax (see the issue for an example).

With changes in this PR, this is fixed for all types of `Range` literals.

Thanks again to @varkor and @estebank for their invaluable help and guidance.

r? @varkor
pawroman added a commit to pawroman/rust that referenced this issue Oct 8, 2018
bors added a commit that referenced this issue Oct 9, 2018
Fix range literals borrowing suggestions

Fixes #54505. The compiler issued incorrect range borrowing suggestions (missing `()` around borrows of range literals). This was not correct syntax (see the issue for an example).

With changes in this PR, this is fixed for all types of `Range` literals.

Thanks again to @varkor and @estebank for their invaluable help and guidance.

r? @varkor
bors added a commit that referenced this issue Oct 9, 2018
Fix range literals borrowing suggestions

Fixes #54505. The compiler issued incorrect range borrowing suggestions (missing `()` around borrows of range literals). This was not correct syntax (see the issue for an example).

With changes in this PR, this is fixed for all types of `Range` literals.

Thanks again to @varkor and @estebank for their invaluable help and guidance.

r? @varkor
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-diagnostics Area: Messages for errors, warnings, and lints E-easy Call for participation: Easy difficulty. Experience needed to fix: Not much. Good first issue. E-mentor Call for participation: This issue has a mentor. Use #t-compiler/help on Zulip for discussion.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants