Skip to content

Commit c9381fc

Browse files
committed
Detect likely . -> .. typo in method calls
Fix #65015.
1 parent caa64e5 commit c9381fc

File tree

5 files changed

+135
-7
lines changed

5 files changed

+135
-7
lines changed

compiler/rustc_hir_typeck/src/coercion.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1604,6 +1604,7 @@ impl<'tcx, 'exprs, E: AsCoercionSite> CoerceMany<'tcx, 'exprs, E> {
16041604
None,
16051605
Some(coercion_error),
16061606
);
1607+
fcx.check_for_range_as_method_call(&mut err, expr, found, expected);
16071608
}
16081609

16091610
if visitor.ret_exprs.len() > 0 && let Some(expr) = expression {

compiler/rustc_hir_typeck/src/demand.rs

+42
Original file line numberDiff line numberDiff line change
@@ -1448,4 +1448,46 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
14481448
_ => false,
14491449
}
14501450
}
1451+
1452+
pub fn check_for_range_as_method_call(
1453+
&self,
1454+
err: &mut Diagnostic,
1455+
expr: &hir::Expr<'_>,
1456+
checked_ty: Ty<'tcx>,
1457+
// FIXME: We should do analysis to see if we can synthesize an expresion that produces
1458+
// this type for always accurate suggestions, or at least marking the suggestion as
1459+
// machine applicable.
1460+
expected_ty: Ty<'tcx>,
1461+
) {
1462+
if !hir::is_range_literal(expr) {
1463+
return;
1464+
}
1465+
let hir::ExprKind::Struct(
1466+
hir::QPath::LangItem(LangItem::Range, ..),
1467+
[start, end],
1468+
_,
1469+
) = expr.kind else { return; };
1470+
let mut expr = end.expr;
1471+
while let hir::ExprKind::MethodCall(_, rcvr, ..) = expr.kind {
1472+
// Getting to the root receiver and asserting it is a fn call let's us ignore cases in
1473+
// `src/test/ui/methods/issues/issue-90315.stderr`.
1474+
expr = rcvr;
1475+
}
1476+
let hir::ExprKind::Call(..) = expr.kind else { return; };
1477+
let ty::Adt(adt, _) = checked_ty.kind() else { return; };
1478+
if self.tcx.lang_items().range_struct() != Some(adt.did()) {
1479+
return;
1480+
}
1481+
if let ty::Adt(adt, _) = expected_ty.kind()
1482+
&& self.tcx.lang_items().range_struct() == Some(adt.did())
1483+
{
1484+
return;
1485+
}
1486+
err.span_suggestion_verbose(
1487+
start.expr.span.between(end.expr.span),
1488+
"you might have meant to write a method call instead of a range",
1489+
".".to_string(),
1490+
Applicability::MaybeIncorrect,
1491+
);
1492+
}
14511493
}

compiler/rustc_resolve/src/late.rs

+25-7
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use rustc_ast::ptr::P;
1616
use rustc_ast::visit::{self, AssocCtxt, BoundKind, FnCtxt, FnKind, Visitor};
1717
use rustc_ast::*;
1818
use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap};
19-
use rustc_errors::{DiagnosticArgValue, DiagnosticId, IntoDiagnosticArg};
19+
use rustc_errors::{Applicability, DiagnosticArgValue, DiagnosticId, IntoDiagnosticArg};
2020
use rustc_hir::def::Namespace::{self, *};
2121
use rustc_hir::def::{self, CtorKind, DefKind, LifetimeRes, PartialRes, PerNS};
2222
use rustc_hir::def_id::{DefId, LocalDefId, CRATE_DEF_ID, LOCAL_CRATE};
@@ -536,6 +536,9 @@ struct DiagnosticMetadata<'ast> {
536536
in_assignment: Option<&'ast Expr>,
537537
is_assign_rhs: bool,
538538

539+
/// Used to detect possible `.` -> `..` typo when calling methods.
540+
in_range: Option<(&'ast Expr, &'ast Expr)>,
541+
539542
/// If we are currently in a trait object definition. Used to point at the bounds when
540543
/// encountering a struct or enum.
541544
current_trait_object: Option<&'ast [ast::GenericBound]>,
@@ -3320,17 +3323,14 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
33203323
);
33213324
}
33223325

3326+
#[instrument(level = "debug", skip(self))]
33233327
fn smart_resolve_path_fragment(
33243328
&mut self,
33253329
qself: &Option<P<QSelf>>,
33263330
path: &[Segment],
33273331
source: PathSource<'ast>,
33283332
finalize: Finalize,
33293333
) -> PartialRes {
3330-
debug!(
3331-
"smart_resolve_path_fragment(qself={:?}, path={:?}, finalize={:?})",
3332-
qself, path, finalize,
3333-
);
33343334
let ns = source.namespace();
33353335

33363336
let Finalize { node_id, path_span, .. } = finalize;
@@ -3341,8 +3341,20 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
33413341

33423342
let def_id = this.parent_scope.module.nearest_parent_mod();
33433343
let instead = res.is_some();
3344-
let suggestion =
3345-
if res.is_none() { this.report_missing_type_error(path) } else { None };
3344+
let suggestion = if let Some((start, end)) = this.diagnostic_metadata.in_range
3345+
&& path[0].ident.span.lo() == end.span.lo()
3346+
{
3347+
Some((
3348+
start.span.between(end.span),
3349+
"you might have meant to write a method call instead of a range",
3350+
".".to_string(),
3351+
Applicability::MaybeIncorrect,
3352+
))
3353+
} else if res.is_none() {
3354+
this.report_missing_type_error(path)
3355+
} else {
3356+
None
3357+
};
33463358

33473359
this.r.use_injections.push(UseError {
33483360
err,
@@ -4005,6 +4017,12 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
40054017
self.visit_expr(rhs);
40064018
self.diagnostic_metadata.is_assign_rhs = false;
40074019
}
4020+
ExprKind::Range(Some(ref start), Some(ref end), RangeLimits::HalfOpen) => {
4021+
self.diagnostic_metadata.in_range = Some((start, end));
4022+
self.resolve_expr(start, Some(expr));
4023+
self.resolve_expr(end, Some(expr));
4024+
self.diagnostic_metadata.in_range = None;
4025+
}
40084026
_ => {
40094027
visit::walk_expr(self, expr);
40104028
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
fn as_ref() -> Option<Vec<u8>> {
2+
None
3+
}
4+
struct Type {
5+
option: Option<Vec<u8>>
6+
}
7+
8+
impl Type {
9+
fn method(&self) -> Option<Vec<u8>> {
10+
self.option..as_ref().map(|x| x)
11+
//~^ ERROR E0308
12+
}
13+
fn method2(&self) -> Option<Vec<u8>> {
14+
self.option..foo().map(|x| x)
15+
//~^ ERROR E0425
16+
//~| ERROR E0308
17+
}
18+
}
19+
20+
fn main() {
21+
let _ = Type { option: None }.method();
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
error[E0425]: cannot find function `foo` in this scope
2+
--> $DIR/method-access-to-range-literal-typo.rs:14:22
3+
|
4+
LL | self.option..foo().map(|x| x)
5+
| ^^^ not found in this scope
6+
|
7+
help: you might have meant to write a method call instead of a range
8+
|
9+
LL | self.option.foo().map(|x| x)
10+
| ~
11+
12+
error[E0308]: mismatched types
13+
--> $DIR/method-access-to-range-literal-typo.rs:10:9
14+
|
15+
LL | fn method(&self) -> Option<Vec<u8>> {
16+
| --------------- expected `Option<Vec<u8>>` because of return type
17+
LL | self.option..as_ref().map(|x| x)
18+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected enum `Option`, found struct `Range`
19+
|
20+
= note: expected enum `Option<_>`
21+
found struct `std::ops::Range<Option<_>>`
22+
help: you might have meant to write a method call instead of a range
23+
|
24+
LL | self.option.as_ref().map(|x| x)
25+
| ~
26+
27+
error[E0308]: mismatched types
28+
--> $DIR/method-access-to-range-literal-typo.rs:14:9
29+
|
30+
LL | fn method2(&self) -> Option<Vec<u8>> {
31+
| --------------- expected `Option<Vec<u8>>` because of return type
32+
LL | self.option..foo().map(|x| x)
33+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected enum `Option`, found struct `Range`
34+
|
35+
= note: expected enum `Option<_>`
36+
found struct `std::ops::Range<Option<_>>`
37+
help: you might have meant to write a method call instead of a range
38+
|
39+
LL | self.option.foo().map(|x| x)
40+
| ~
41+
42+
error: aborting due to 3 previous errors
43+
44+
Some errors have detailed explanations: E0308, E0425.
45+
For more information about an error, try `rustc --explain E0308`.

0 commit comments

Comments
 (0)