Skip to content

Commit 49b584c

Browse files
authored
Rollup merge of rust-lang#48834 - ysiraichi:suggest-remove-ref, r=estebank
Suggest removing `&`s This implements the error message discussed in rust-lang#47744. We check whether removing each `&` yields a type that satisfies the requested obligation. Also, it was created a new `NodeId` field in `ObligationCause` in order to iterate through the `&`s. The way it's implemented now, it iterates through the obligation snippet and counts the number of `&`. r? @estebank
2 parents 5d5f5a0 + 736ba43 commit 49b584c

8 files changed

+205
-46
lines changed

src/librustc/traits/error_reporting.rs

+49
Original file line numberDiff line numberDiff line change
@@ -606,6 +606,7 @@ impl<'a, 'gcx, 'tcx> InferCtxt<'a, 'gcx, 'tcx> {
606606
}
607607

608608
self.suggest_borrow_on_unsized_slice(&obligation.cause.code, &mut err);
609+
self.suggest_remove_reference(&obligation, &mut err, &trait_ref);
609610

610611
// Try to report a help message
611612
if !trait_ref.has_infer_types() &&
@@ -844,6 +845,54 @@ impl<'a, 'gcx, 'tcx> InferCtxt<'a, 'gcx, 'tcx> {
844845
}
845846
}
846847

848+
/// Whenever references are used by mistake, like `for (i, e) in &vec.iter().enumerate()`,
849+
/// suggest removing these references until we reach a type that implements the trait.
850+
fn suggest_remove_reference(&self,
851+
obligation: &PredicateObligation<'tcx>,
852+
err: &mut DiagnosticBuilder<'tcx>,
853+
trait_ref: &ty::Binder<ty::TraitRef<'tcx>>) {
854+
let ty::Binder(trait_ref) = trait_ref;
855+
let span = obligation.cause.span;
856+
857+
if let Ok(snippet) = self.tcx.sess.codemap().span_to_snippet(span) {
858+
let refs_number = snippet.chars()
859+
.filter(|c| !c.is_whitespace())
860+
.take_while(|c| *c == '&')
861+
.count();
862+
863+
let mut trait_type = trait_ref.self_ty();
864+
let mut selcx = SelectionContext::new(self);
865+
866+
for refs_remaining in 0..refs_number {
867+
if let ty::TypeVariants::TyRef(_, ty::TypeAndMut{ ty: t_type, mutbl: _ }) =
868+
trait_type.sty {
869+
870+
trait_type = t_type;
871+
872+
let substs = self.tcx.mk_substs_trait(trait_type, &[]);
873+
let new_trait_ref = ty::TraitRef::new(trait_ref.def_id, substs);
874+
let new_obligation = Obligation::new(ObligationCause::dummy(),
875+
obligation.param_env,
876+
new_trait_ref.to_predicate());
877+
878+
if selcx.evaluate_obligation(&new_obligation) {
879+
let sp = self.tcx.sess.codemap()
880+
.span_take_while(span, |c| c.is_whitespace() || *c == '&');
881+
882+
let remove_refs = refs_remaining + 1;
883+
let format_str = format!("consider removing {} leading `&`-references",
884+
remove_refs);
885+
886+
err.span_suggestion_short(sp, &format_str, String::from(""));
887+
break;
888+
}
889+
} else {
890+
break;
891+
}
892+
}
893+
}
894+
}
895+
847896
/// Given some node representing a fn-like thing in the HIR map,
848897
/// returns a span and `ArgKind` information that describes the
849898
/// arguments it expects. This can be supplied to

src/libsyntax/codemap.rs

+50-46
Original file line numberDiff line numberDiff line change
@@ -597,21 +597,6 @@ impl CodeMap {
597597
self.span_to_source(sp, |src, start_index, _| src[..start_index].to_string())
598598
}
599599

600-
/// Given a `Span`, try to get a shorter span ending before the first occurrence of `c` `char`
601-
pub fn span_until_char(&self, sp: Span, c: char) -> Span {
602-
match self.span_to_snippet(sp) {
603-
Ok(snippet) => {
604-
let snippet = snippet.split(c).nth(0).unwrap_or("").trim_right();
605-
if !snippet.is_empty() && !snippet.contains('\n') {
606-
sp.with_hi(BytePos(sp.lo().0 + snippet.len() as u32))
607-
} else {
608-
sp
609-
}
610-
}
611-
_ => sp,
612-
}
613-
}
614-
615600
/// Extend the given `Span` to just after the previous occurrence of `c`. Return the same span
616601
/// if no character could be found or if an error occurred while retrieving the code snippet.
617602
pub fn span_extend_to_prev_char(&self, sp: Span, c: char) -> Span {
@@ -646,55 +631,74 @@ impl CodeMap {
646631
sp
647632
}
648633

634+
/// Given a `Span`, try to get a shorter span ending before the first occurrence of `c` `char`
635+
pub fn span_until_char(&self, sp: Span, c: char) -> Span {
636+
match self.span_to_snippet(sp) {
637+
Ok(snippet) => {
638+
let snippet = snippet.split(c).nth(0).unwrap_or("").trim_right();
639+
if !snippet.is_empty() && !snippet.contains('\n') {
640+
sp.with_hi(BytePos(sp.lo().0 + snippet.len() as u32))
641+
} else {
642+
sp
643+
}
644+
}
645+
_ => sp,
646+
}
647+
}
648+
649+
/// Given a `Span`, try to get a shorter span ending just after the first occurrence of `char`
650+
/// `c`.
651+
pub fn span_through_char(&self, sp: Span, c: char) -> Span {
652+
if let Ok(snippet) = self.span_to_snippet(sp) {
653+
if let Some(offset) = snippet.find(c) {
654+
return sp.with_hi(BytePos(sp.lo().0 + (offset + c.len_utf8()) as u32));
655+
}
656+
}
657+
sp
658+
}
659+
649660
/// Given a `Span`, get a new `Span` covering the first token and all its trailing whitespace or
650661
/// the original `Span`.
651662
///
652663
/// If `sp` points to `"let mut x"`, then a span pointing at `"let "` will be returned.
653664
pub fn span_until_non_whitespace(&self, sp: Span) -> Span {
654-
if let Ok(snippet) = self.span_to_snippet(sp) {
655-
let mut offset = 0;
656-
// get the bytes width of all the non-whitespace characters
657-
for c in snippet.chars().take_while(|c| !c.is_whitespace()) {
658-
offset += c.len_utf8();
659-
}
660-
// get the bytes width of all the whitespace characters after that
661-
for c in snippet[offset..].chars().take_while(|c| c.is_whitespace()) {
662-
offset += c.len_utf8();
665+
let mut whitespace_found = false;
666+
667+
self.span_take_while(sp, |c| {
668+
if !whitespace_found && c.is_whitespace() {
669+
whitespace_found = true;
663670
}
664-
if offset > 1 {
665-
return sp.with_hi(BytePos(sp.lo().0 + offset as u32));
671+
672+
if whitespace_found && !c.is_whitespace() {
673+
false
674+
} else {
675+
true
666676
}
667-
}
668-
sp
677+
})
669678
}
670679

671680
/// Given a `Span`, get a new `Span` covering the first token without its trailing whitespace or
672681
/// the original `Span` in case of error.
673682
///
674683
/// If `sp` points to `"let mut x"`, then a span pointing at `"let"` will be returned.
675684
pub fn span_until_whitespace(&self, sp: Span) -> Span {
676-
if let Ok(snippet) = self.span_to_snippet(sp) {
677-
let mut offset = 0;
678-
// Get the bytes width of all the non-whitespace characters
679-
for c in snippet.chars().take_while(|c| !c.is_whitespace()) {
680-
offset += c.len_utf8();
681-
}
682-
if offset > 1 {
683-
return sp.with_hi(BytePos(sp.lo().0 + offset as u32));
684-
}
685-
}
686-
sp
685+
self.span_take_while(sp, |c| !c.is_whitespace())
687686
}
688687

689-
/// Given a `Span`, try to get a shorter span ending just after the first occurrence of `char`
690-
/// `c`.
691-
pub fn span_through_char(&self, sp: Span, c: char) -> Span {
688+
/// Given a `Span`, get a shorter one until `predicate` yields false.
689+
pub fn span_take_while<P>(&self, sp: Span, predicate: P) -> Span
690+
where P: for <'r> FnMut(&'r char) -> bool
691+
{
692692
if let Ok(snippet) = self.span_to_snippet(sp) {
693-
if let Some(offset) = snippet.find(c) {
694-
return sp.with_hi(BytePos(sp.lo().0 + (offset + c.len_utf8()) as u32));
695-
}
693+
let offset = snippet.chars()
694+
.take_while(predicate)
695+
.map(|c| c.len_utf8())
696+
.sum::<usize>();
697+
698+
sp.with_hi(BytePos(sp.lo().0 + (offset as u32)))
699+
} else {
700+
sp
696701
}
697-
sp
698702
}
699703

700704
pub fn def_span(&self, sp: Span) -> Span {

src/test/ui/suggest-remove-refs-1.rs

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
fn main() {
12+
let v = vec![0, 1, 2, 3];
13+
14+
for (i, n) in &v.iter().enumerate() {
15+
//~^ ERROR the trait bound
16+
println!("{}", i);
17+
}
18+
}
+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
error[E0277]: the trait bound `&std::iter::Enumerate<std::slice::Iter<'_, {integer}>>: std::iter::Iterator` is not satisfied
2+
--> $DIR/suggest-remove-refs-1.rs:14:19
3+
|
4+
LL | for (i, n) in &v.iter().enumerate() {
5+
| -^^^^^^^^^^^^^^^^^^^^
6+
| |
7+
| `&std::iter::Enumerate<std::slice::Iter<'_, {integer}>>` is not an iterator; maybe try calling `.iter()` or a similar method
8+
| help: consider removing 1 leading `&`-references
9+
|
10+
= help: the trait `std::iter::Iterator` is not implemented for `&std::iter::Enumerate<std::slice::Iter<'_, {integer}>>`
11+
= note: required by `std::iter::IntoIterator::into_iter`
12+
13+
error: aborting due to previous error
14+
15+
For more information about this error, try `rustc --explain E0277`.

src/test/ui/suggest-remove-refs-2.rs

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
fn main() {
12+
let v = vec![0, 1, 2, 3];
13+
14+
for (i, n) in & & & & &v.iter().enumerate() {
15+
//~^ ERROR the trait bound
16+
println!("{}", i);
17+
}
18+
}
+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
error[E0277]: the trait bound `&&&&&std::iter::Enumerate<std::slice::Iter<'_, {integer}>>: std::iter::Iterator` is not satisfied
2+
--> $DIR/suggest-remove-refs-2.rs:14:19
3+
|
4+
LL | for (i, n) in & & & & &v.iter().enumerate() {
5+
| ---------^^^^^^^^^^^^^^^^^^^^
6+
| |
7+
| `&&&&&std::iter::Enumerate<std::slice::Iter<'_, {integer}>>` is not an iterator; maybe try calling `.iter()` or a similar method
8+
| help: consider removing 5 leading `&`-references
9+
|
10+
= help: the trait `std::iter::Iterator` is not implemented for `&&&&&std::iter::Enumerate<std::slice::Iter<'_, {integer}>>`
11+
= note: required by `std::iter::IntoIterator::into_iter`
12+
13+
error: aborting due to previous error
14+
15+
For more information about this error, try `rustc --explain E0277`.

src/test/ui/suggest-remove-refs-3.rs

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
fn main() {
12+
let v = vec![0, 1, 2, 3];
13+
14+
for (i, n) in & & &
15+
& &v
16+
.iter()
17+
.enumerate() {
18+
//~^^^^ ERROR the trait bound
19+
println!("{}", i);
20+
}
21+
}
+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
error[E0277]: the trait bound `&&&&&std::iter::Enumerate<std::slice::Iter<'_, {integer}>>: std::iter::Iterator` is not satisfied
2+
--> $DIR/suggest-remove-refs-3.rs:14:19
3+
|
4+
LL | for (i, n) in & & &
5+
| ___________________^
6+
| |___________________|
7+
| ||
8+
LL | || & &v
9+
| ||___________- help: consider removing 5 leading `&`-references
10+
LL | | .iter()
11+
LL | | .enumerate() {
12+
| |_____________________^ `&&&&&std::iter::Enumerate<std::slice::Iter<'_, {integer}>>` is not an iterator; maybe try calling `.iter()` or a similar method
13+
|
14+
= help: the trait `std::iter::Iterator` is not implemented for `&&&&&std::iter::Enumerate<std::slice::Iter<'_, {integer}>>`
15+
= note: required by `std::iter::IntoIterator::into_iter`
16+
17+
error: aborting due to previous error
18+
19+
For more information about this error, try `rustc --explain E0277`.

0 commit comments

Comments
 (0)