Skip to content

Commit 5dd44d4

Browse files
committed
fix #102396, suggest parentheses for possible range methods
1 parent d9f8b4b commit 5dd44d4

File tree

5 files changed

+339
-15
lines changed

5 files changed

+339
-15
lines changed

compiler/rustc_error_messages/locales/en-US/hir_analysis.ftl

+5
Original file line numberDiff line numberDiff line change
@@ -133,3 +133,8 @@ hir_analysis_extern_crate_not_idiomatic =
133133
.suggestion = convert it to a `{$msg_code}`
134134
135135
hir_analysis_expected_used_symbol = expected `used`, `used(compiler)` or `used(linker)`
136+
hir_analysis_expected_used_symbol = expected `used`, `used(compiler)` or `used(linker)`
137+
138+
hir_analysis_missing_parentheses_in_range = `{$ty_str}` is not an iterator
139+
140+
hir_analysis_add_missing_parentheses_in_range = you must surround the range in parentheses to call the `{$func_name}` function

compiler/rustc_hir_analysis/src/check/method/suggest.rs

+70-5
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
//! found or is otherwise invalid.
33
44
use crate::check::FnCtxt;
5+
use crate::errors;
56
use rustc_ast::ast::Mutability;
67
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
78
use rustc_errors::{
@@ -12,7 +13,7 @@ use rustc_hir as hir;
1213
use rustc_hir::def::DefKind;
1314
use rustc_hir::def_id::DefId;
1415
use rustc_hir::lang_items::LangItem;
15-
use rustc_hir::{ExprKind, Node, QPath};
16+
use rustc_hir::{is_range_literal, ExprKind, Node, QPath};
1617
use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
1718
use rustc_middle::traits::util::supertraits;
1819
use rustc_middle::ty::fast_reject::{simplify_type, TreatParams};
@@ -271,9 +272,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
271272
}
272273
};
273274

274-
if self.suggest_constraining_numerical_ty(
275-
tcx, actual, source, span, item_kind, item_name, &ty_str,
276-
) {
275+
if self.suggest_range_for_iter(tcx, actual, source, span, item_name, &ty_str)
276+
|| self.suggest_constraining_numerical_ty(
277+
tcx, actual, source, span, item_kind, item_name, &ty_str,
278+
)
279+
{
277280
return None;
278281
}
279282

@@ -1201,6 +1204,69 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
12011204
false
12021205
}
12031206

1207+
fn suggest_range_for_iter(
1208+
&self,
1209+
tcx: TyCtxt<'tcx>,
1210+
actual: Ty<'tcx>,
1211+
source: SelfSource<'tcx>,
1212+
span: Span,
1213+
item_name: Ident,
1214+
ty_str: &str,
1215+
) -> bool {
1216+
if let SelfSource::MethodCall(expr) = source {
1217+
let mut search_limit = 5;
1218+
for (_, parent) in tcx.hir().parent_iter(expr.hir_id) {
1219+
search_limit -= 1;
1220+
if search_limit == 0 {
1221+
break;
1222+
}
1223+
1224+
if let Node::Expr(parent_expr) = parent && is_range_literal(parent_expr) {
1225+
let span_included = match parent_expr.kind {
1226+
hir::ExprKind::Struct(_, eps, _) =>
1227+
eps.len() > 0 && eps.last().map_or(false, |ep| ep.span.contains(span)),
1228+
// `..=` desugars into `::std::ops::RangeInclusive::new(...)`.
1229+
hir::ExprKind::Call(ref func, ..) => func.span.contains(span),
1230+
_ => false,
1231+
};
1232+
1233+
if !span_included {
1234+
continue;
1235+
}
1236+
1237+
let range_def_id = self.tcx.lang_items().range_struct().unwrap();
1238+
let range_ty = self.tcx.bound_type_of(range_def_id).subst(self.tcx, &[actual.into()]);
1239+
1240+
// avoid suggesting when the method name is not implemented for a `range`
1241+
let pick = self.lookup_probe(
1242+
span,
1243+
item_name,
1244+
range_ty,
1245+
expr,
1246+
ProbeScope::AllTraits
1247+
);
1248+
1249+
if pick.is_ok() {
1250+
let range_span = parent_expr.span.with_hi(expr.span.hi());
1251+
tcx.sess.emit_err(errors::MissingParentheseInRange {
1252+
span: span,
1253+
ty_str: ty_str.to_string(),
1254+
add_missing_parentheses: Some(
1255+
errors::AddMissingParenthesesInRange {
1256+
func_name: item_name.name.as_str().to_string(),
1257+
left: range_span.shrink_to_lo(),
1258+
right: range_span.shrink_to_hi(),
1259+
}
1260+
)
1261+
});
1262+
return true;
1263+
}
1264+
}
1265+
}
1266+
}
1267+
false
1268+
}
1269+
12041270
fn suggest_constraining_numerical_ty(
12051271
&self,
12061272
tcx: TyCtxt<'tcx>,
@@ -1263,7 +1329,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
12631329
// If this is a floating point literal that ends with '.',
12641330
// get rid of it to stop this from becoming a member access.
12651331
let snippet = snippet.strip_suffix('.').unwrap_or(&snippet);
1266-
12671332
err.span_suggestion(
12681333
lit.span,
12691334
&format!(

compiler/rustc_hir_analysis/src/errors.rs

+25
Original file line numberDiff line numberDiff line change
@@ -346,3 +346,28 @@ pub struct ExpectedUsedSymbol {
346346
#[primary_span]
347347
pub span: Span,
348348
}
349+
350+
#[derive(Diagnostic)]
351+
#[diag(hir_analysis::missing_parentheses_in_range, code = "E0599")]
352+
pub struct MissingParentheseInRange {
353+
#[primary_span]
354+
#[label(hir_analysis::missing_parentheses_in_range)]
355+
pub span: Span,
356+
pub ty_str: String,
357+
358+
#[subdiagnostic]
359+
pub add_missing_parentheses: Option<AddMissingParenthesesInRange>,
360+
}
361+
362+
#[derive(Subdiagnostic)]
363+
#[multipart_suggestion_verbose(
364+
hir_analysis::add_missing_parentheses_in_range,
365+
applicability = "maybe-incorrect"
366+
)]
367+
pub struct AddMissingParenthesesInRange {
368+
pub func_name: String,
369+
#[suggestion_part(code = "(")]
370+
pub left: Span,
371+
#[suggestion_part(code = ")")]
372+
pub right: Span,
373+
}
+68-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,70 @@
1+
#![allow(unused)]
12
fn main() {
2-
let arr = &[0,1,2,3];
3-
for _i in 0..arr.len().rev() { //~ERROR not an iterator
4-
// The above error used to say “the method `rev` exists for type `usize`”.
5-
// This regression test ensures it doesn't say that any more.
6-
}
3+
let arr = &[0, 1, 2, 3];
4+
for _i in 0..arr.len().rev() {
5+
//~^ ERROR not an iterator
6+
//~| surround the range in parentheses
7+
// The above error used to say “the method `rev` exists for type `usize`”.
8+
// This regression test ensures it doesn't say that any more.
9+
}
10+
11+
// Test for #102396
12+
for i in 1..11.rev() {
13+
//~^ ERROR not an iterator
14+
//~| HELP surround the range in parentheses
15+
}
16+
17+
let end: usize = 10;
18+
for i in 1..end.rev() {
19+
//~^ ERROR not an iterator
20+
//~| HELP surround the range in parentheses
21+
}
22+
23+
for i in 1..(end + 1).rev() {
24+
//~^ ERROR not an iterator
25+
//~| HELP surround the range in parentheses
26+
}
27+
28+
if 1..(end + 1).is_empty() {
29+
//~^ ERROR not an iterator
30+
//~| ERROR mismatched types [E0308]
31+
//~| HELP surround the range in parentheses
32+
}
33+
34+
if 1..(end + 1).is_sorted() {
35+
//~^ ERROR mismatched types [E0308]
36+
//~| ERROR `usize` is not an iterator [E0599]
37+
//~| HELP surround the range in parentheses
38+
}
39+
40+
let _res: i32 = 3..6.take(2).sum();
41+
//~^ ERROR `{integer}` is not an iterator [E0599]
42+
//~| ERROR mismatched types [E0308]
43+
//~| HELP surround the range in parentheses
44+
45+
let _sum: i32 = 3..6.sum();
46+
//~^ ERROR `{integer}` is not an iterator [E0599]
47+
//~| ERROR mismatched types [E0308]
48+
//~| HELP surround the range in parentheses
49+
50+
let a = 1 as usize;
51+
let b = 10 as usize;
52+
53+
for _a in a..=b.rev() {
54+
//~^ ERROR not an iterator
55+
//~| HELP surround the range in parentheses
56+
}
57+
58+
let _res = ..10.contains(3);
59+
//~^ ERROR not an iterator
60+
//~| HELP surround the range in parentheses
61+
62+
if 1..end.error_method() {
63+
//~^ ERROR no method named `error_method`
64+
//~| ERROR mismatched types [E0308]
65+
// Won't suggest
66+
}
67+
68+
let _res = b.take(1)..a;
69+
//~^ ERROR not an iterator
770
}

0 commit comments

Comments
 (0)