Skip to content

Commit a9fdedd

Browse files
Rollup merge of #105765 - estebank:range-typo, r=compiler-errors
Detect likely `.` -> `..` typo in method calls Fix #65015.
2 parents 4b668a1 + 7e84273 commit a9fdedd

File tree

4 files changed

+177
-7
lines changed

4 files changed

+177
-7
lines changed

compiler/rustc_hir_typeck/src/demand.rs

+66
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
7474
self.note_type_is_not_clone(err, expected, expr_ty, expr);
7575
self.note_need_for_fn_pointer(err, expected, expr_ty);
7676
self.note_internal_mutation_in_method(err, expr, expected, expr_ty);
77+
self.check_for_range_as_method_call(err, expr, expr_ty, expected);
7778
}
7879

7980
/// Requires that the two types unify, and prints an error message if
@@ -1607,4 +1608,69 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
16071608
_ => false,
16081609
}
16091610
}
1611+
1612+
/// Identify when the user has written `foo..bar()` instead of `foo.bar()`.
1613+
pub fn check_for_range_as_method_call(
1614+
&self,
1615+
err: &mut Diagnostic,
1616+
expr: &hir::Expr<'_>,
1617+
checked_ty: Ty<'tcx>,
1618+
expected_ty: Ty<'tcx>,
1619+
) {
1620+
if !hir::is_range_literal(expr) {
1621+
return;
1622+
}
1623+
let hir::ExprKind::Struct(
1624+
hir::QPath::LangItem(LangItem::Range, ..),
1625+
[start, end],
1626+
_,
1627+
) = expr.kind else { return; };
1628+
let parent = self.tcx.hir().get_parent_node(expr.hir_id);
1629+
if let Some(hir::Node::ExprField(_)) = self.tcx.hir().find(parent) {
1630+
// Ignore `Foo { field: a..Default::default() }`
1631+
return;
1632+
}
1633+
let mut expr = end.expr;
1634+
while let hir::ExprKind::MethodCall(_, rcvr, ..) = expr.kind {
1635+
// Getting to the root receiver and asserting it is a fn call let's us ignore cases in
1636+
// `src/test/ui/methods/issues/issue-90315.stderr`.
1637+
expr = rcvr;
1638+
}
1639+
let hir::ExprKind::Call(method_name, _) = expr.kind else { return; };
1640+
let ty::Adt(adt, _) = checked_ty.kind() else { return; };
1641+
if self.tcx.lang_items().range_struct() != Some(adt.did()) {
1642+
return;
1643+
}
1644+
if let ty::Adt(adt, _) = expected_ty.kind()
1645+
&& self.tcx.lang_items().range_struct() == Some(adt.did())
1646+
{
1647+
return;
1648+
}
1649+
// Check if start has method named end.
1650+
let hir::ExprKind::Path(hir::QPath::Resolved(None, p)) = method_name.kind else { return; };
1651+
let [hir::PathSegment { ident, .. }] = p.segments else { return; };
1652+
let self_ty = self.typeck_results.borrow().expr_ty(start.expr);
1653+
let Ok(_pick) = self.probe_for_name(
1654+
probe::Mode::MethodCall,
1655+
*ident,
1656+
probe::IsSuggestion(true),
1657+
self_ty,
1658+
expr.hir_id,
1659+
probe::ProbeScope::AllTraits,
1660+
) else { return; };
1661+
let mut sugg = ".";
1662+
let mut span = start.expr.span.between(end.expr.span);
1663+
if span.lo() + BytePos(2) == span.hi() {
1664+
// There's no space between the start, the range op and the end, suggest removal which
1665+
// will be more noticeable than the replacement of `..` with `.`.
1666+
span = span.with_lo(span.lo() + BytePos(1));
1667+
sugg = "";
1668+
}
1669+
err.span_suggestion_verbose(
1670+
span,
1671+
"you likely meant to write a method call instead of a range",
1672+
sugg,
1673+
Applicability::MachineApplicable,
1674+
);
1675+
}
16101676
}

compiler/rustc_resolve/src/late.rs

+33-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,28 @@ 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+
let mut sugg = ".";
3348+
let mut span = start.span.between(end.span);
3349+
if span.lo() + BytePos(2) == span.hi() {
3350+
// There's no space between the start, the range op and the end, suggest
3351+
// removal which will look better.
3352+
span = span.with_lo(span.lo() + BytePos(1));
3353+
sugg = "";
3354+
}
3355+
Some((
3356+
span,
3357+
"you might have meant to write `.` instead of `..`",
3358+
sugg.to_string(),
3359+
Applicability::MaybeIncorrect,
3360+
))
3361+
} else if res.is_none() {
3362+
this.report_missing_type_error(path)
3363+
} else {
3364+
None
3365+
};
33463366

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

0 commit comments

Comments
 (0)