diff --git a/compiler/rustc_hir_typeck/src/demand.rs b/compiler/rustc_hir_typeck/src/demand.rs index 042ff0b46a5af..91419395838f9 100644 --- a/compiler/rustc_hir_typeck/src/demand.rs +++ b/compiler/rustc_hir_typeck/src/demand.rs @@ -71,6 +71,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.note_type_is_not_clone(err, expected, expr_ty, expr); self.note_need_for_fn_pointer(err, expected, expr_ty); self.note_internal_mutation_in_method(err, expr, expected, expr_ty); + self.check_for_range_as_method_call(err, expr, expr_ty, expected); } /// Requires that the two types unify, and prints an error message if @@ -1448,4 +1449,69 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { _ => false, } } + + /// Identify when the user has written `foo..bar()` instead of `foo.bar()`. + pub fn check_for_range_as_method_call( + &self, + err: &mut Diagnostic, + expr: &hir::Expr<'_>, + checked_ty: Ty<'tcx>, + expected_ty: Ty<'tcx>, + ) { + if !hir::is_range_literal(expr) { + return; + } + let hir::ExprKind::Struct( + hir::QPath::LangItem(LangItem::Range, ..), + [start, end], + _, + ) = expr.kind else { return; }; + let parent = self.tcx.hir().get_parent_node(expr.hir_id); + if let Some(hir::Node::ExprField(_)) = self.tcx.hir().find(parent) { + // Ignore `Foo { field: a..Default::default() }` + return; + } + let mut expr = end.expr; + while let hir::ExprKind::MethodCall(_, rcvr, ..) = expr.kind { + // Getting to the root receiver and asserting it is a fn call let's us ignore cases in + // `src/test/ui/methods/issues/issue-90315.stderr`. + expr = rcvr; + } + let hir::ExprKind::Call(method_name, _) = expr.kind else { return; }; + let ty::Adt(adt, _) = checked_ty.kind() else { return; }; + if self.tcx.lang_items().range_struct() != Some(adt.did()) { + return; + } + if let ty::Adt(adt, _) = expected_ty.kind() + && self.tcx.lang_items().range_struct() == Some(adt.did()) + { + return; + } + // Check if start has method named end. + let hir::ExprKind::Path(hir::QPath::Resolved(None, p)) = method_name.kind else { return; }; + let [hir::PathSegment { ident, .. }] = p.segments else { return; }; + let self_ty = self.typeck_results.borrow().expr_ty(start.expr); + let Ok(_pick) = self.probe_for_name( + probe::Mode::MethodCall, + *ident, + probe::IsSuggestion(true), + self_ty, + expr.hir_id, + probe::ProbeScope::AllTraits, + ) else { return; }; + let mut sugg = "."; + let mut span = start.expr.span.between(end.expr.span); + if span.lo() + BytePos(2) == span.hi() { + // There's no space between the start, the range op and the end, suggest removal which + // will be more noticeable than the replacement of `..` with `.`. + span = span.with_lo(span.lo() + BytePos(1)); + sugg = ""; + } + err.span_suggestion_verbose( + span, + "you likely meant to write a method call instead of a range", + sugg, + Applicability::MachineApplicable, + ); + } } diff --git a/compiler/rustc_resolve/src/late.rs b/compiler/rustc_resolve/src/late.rs index 5b7a00101e9b7..49e069f58c979 100644 --- a/compiler/rustc_resolve/src/late.rs +++ b/compiler/rustc_resolve/src/late.rs @@ -16,7 +16,7 @@ use rustc_ast::ptr::P; use rustc_ast::visit::{self, AssocCtxt, BoundKind, FnCtxt, FnKind, Visitor}; use rustc_ast::*; use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap}; -use rustc_errors::{DiagnosticArgValue, DiagnosticId, IntoDiagnosticArg}; +use rustc_errors::{Applicability, DiagnosticArgValue, DiagnosticId, IntoDiagnosticArg}; use rustc_hir::def::Namespace::{self, *}; use rustc_hir::def::{self, CtorKind, DefKind, LifetimeRes, PartialRes, PerNS}; use rustc_hir::def_id::{DefId, LocalDefId, CRATE_DEF_ID, LOCAL_CRATE}; @@ -536,6 +536,9 @@ struct DiagnosticMetadata<'ast> { in_assignment: Option<&'ast Expr>, is_assign_rhs: bool, + /// Used to detect possible `.` -> `..` typo when calling methods. + in_range: Option<(&'ast Expr, &'ast Expr)>, + /// If we are currently in a trait object definition. Used to point at the bounds when /// encountering a struct or enum. current_trait_object: Option<&'ast [ast::GenericBound]>, @@ -3320,6 +3323,7 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> { ); } + #[instrument(level = "debug", skip(self))] fn smart_resolve_path_fragment( &mut self, qself: &Option
>,
@@ -3327,10 +3331,6 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
source: PathSource<'ast>,
finalize: Finalize,
) -> PartialRes {
- debug!(
- "smart_resolve_path_fragment(qself={:?}, path={:?}, finalize={:?})",
- qself, path, finalize,
- );
let ns = source.namespace();
let Finalize { node_id, path_span, .. } = finalize;
@@ -3341,8 +3341,28 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
let def_id = this.parent_scope.module.nearest_parent_mod();
let instead = res.is_some();
- let suggestion =
- if res.is_none() { this.report_missing_type_error(path) } else { None };
+ let suggestion = if let Some((start, end)) = this.diagnostic_metadata.in_range
+ && path[0].ident.span.lo() == end.span.lo()
+ {
+ let mut sugg = ".";
+ let mut span = start.span.between(end.span);
+ if span.lo() + BytePos(2) == span.hi() {
+ // There's no space between the start, the range op and the end, suggest
+ // removal which will look better.
+ span = span.with_lo(span.lo() + BytePos(1));
+ sugg = "";
+ }
+ Some((
+ span,
+ "you might have meant to write `.` instead of `..`",
+ sugg.to_string(),
+ Applicability::MaybeIncorrect,
+ ))
+ } else if res.is_none() {
+ this.report_missing_type_error(path)
+ } else {
+ None
+ };
this.r.use_injections.push(UseError {
err,
@@ -4005,6 +4025,12 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
self.visit_expr(rhs);
self.diagnostic_metadata.is_assign_rhs = false;
}
+ ExprKind::Range(Some(ref start), Some(ref end), RangeLimits::HalfOpen) => {
+ self.diagnostic_metadata.in_range = Some((start, end));
+ self.resolve_expr(start, Some(expr));
+ self.resolve_expr(end, Some(expr));
+ self.diagnostic_metadata.in_range = None;
+ }
_ => {
visit::walk_expr(self, expr);
}
diff --git a/src/test/ui/suggestions/method-access-to-range-literal-typo.rs b/src/test/ui/suggestions/method-access-to-range-literal-typo.rs
new file mode 100644
index 0000000000000..ac662edafe6b0
--- /dev/null
+++ b/src/test/ui/suggestions/method-access-to-range-literal-typo.rs
@@ -0,0 +1,30 @@
+fn as_ref() -> Option