From c9381fc334cb8db62fdb5a8f75807ebdff3d6e15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Thu, 15 Dec 2022 16:24:42 -0800 Subject: [PATCH] Detect likely `.` -> `..` typo in method calls Fix #65015. --- compiler/rustc_hir_typeck/src/coercion.rs | 1 + compiler/rustc_hir_typeck/src/demand.rs | 42 +++++++++++++++++ compiler/rustc_resolve/src/late.rs | 32 ++++++++++--- .../method-access-to-range-literal-typo.rs | 22 +++++++++ ...method-access-to-range-literal-typo.stderr | 45 +++++++++++++++++++ 5 files changed, 135 insertions(+), 7 deletions(-) create mode 100644 src/test/ui/suggestions/method-access-to-range-literal-typo.rs create mode 100644 src/test/ui/suggestions/method-access-to-range-literal-typo.stderr diff --git a/compiler/rustc_hir_typeck/src/coercion.rs b/compiler/rustc_hir_typeck/src/coercion.rs index b0cd4a16e98..706700bc1f4 100644 --- a/compiler/rustc_hir_typeck/src/coercion.rs +++ b/compiler/rustc_hir_typeck/src/coercion.rs @@ -1604,6 +1604,7 @@ impl<'tcx, 'exprs, E: AsCoercionSite> CoerceMany<'tcx, 'exprs, E> { None, Some(coercion_error), ); + fcx.check_for_range_as_method_call(&mut err, expr, found, expected); } if visitor.ret_exprs.len() > 0 && let Some(expr) = expression { diff --git a/compiler/rustc_hir_typeck/src/demand.rs b/compiler/rustc_hir_typeck/src/demand.rs index 042ff0b46a5..4352c50358f 100644 --- a/compiler/rustc_hir_typeck/src/demand.rs +++ b/compiler/rustc_hir_typeck/src/demand.rs @@ -1448,4 +1448,46 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { _ => false, } } + + pub fn check_for_range_as_method_call( + &self, + err: &mut Diagnostic, + expr: &hir::Expr<'_>, + checked_ty: Ty<'tcx>, + // FIXME: We should do analysis to see if we can synthesize an expresion that produces + // this type for always accurate suggestions, or at least marking the suggestion as + // machine applicable. + 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 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(..) = 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; + } + err.span_suggestion_verbose( + start.expr.span.between(end.expr.span), + "you might have meant to write a method call instead of a range", + ".".to_string(), + Applicability::MaybeIncorrect, + ); + } } diff --git a/compiler/rustc_resolve/src/late.rs b/compiler/rustc_resolve/src/late.rs index 5b7a00101e9..13b001af7ea 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,20 @@ 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() + { + Some(( + start.span.between(end.span), + "you might have meant to write a method call instead of a range", + ".".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 +4017,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 00000000000..545f9c597fd --- /dev/null +++ b/src/test/ui/suggestions/method-access-to-range-literal-typo.rs @@ -0,0 +1,22 @@ +fn as_ref() -> Option> { + None +} +struct Type { + option: Option> +} + +impl Type { + fn method(&self) -> Option> { + self.option..as_ref().map(|x| x) + //~^ ERROR E0308 + } + fn method2(&self) -> Option> { + self.option..foo().map(|x| x) + //~^ ERROR E0425 + //~| ERROR E0308 + } +} + +fn main() { + let _ = Type { option: None }.method(); +} diff --git a/src/test/ui/suggestions/method-access-to-range-literal-typo.stderr b/src/test/ui/suggestions/method-access-to-range-literal-typo.stderr new file mode 100644 index 00000000000..becc825b6cf --- /dev/null +++ b/src/test/ui/suggestions/method-access-to-range-literal-typo.stderr @@ -0,0 +1,45 @@ +error[E0425]: cannot find function `foo` in this scope + --> $DIR/method-access-to-range-literal-typo.rs:14:22 + | +LL | self.option..foo().map(|x| x) + | ^^^ not found in this scope + | +help: you might have meant to write a method call instead of a range + | +LL | self.option.foo().map(|x| x) + | ~ + +error[E0308]: mismatched types + --> $DIR/method-access-to-range-literal-typo.rs:10:9 + | +LL | fn method(&self) -> Option> { + | --------------- expected `Option>` because of return type +LL | self.option..as_ref().map(|x| x) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected enum `Option`, found struct `Range` + | + = note: expected enum `Option<_>` + found struct `std::ops::Range>` +help: you might have meant to write a method call instead of a range + | +LL | self.option.as_ref().map(|x| x) + | ~ + +error[E0308]: mismatched types + --> $DIR/method-access-to-range-literal-typo.rs:14:9 + | +LL | fn method2(&self) -> Option> { + | --------------- expected `Option>` because of return type +LL | self.option..foo().map(|x| x) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected enum `Option`, found struct `Range` + | + = note: expected enum `Option<_>` + found struct `std::ops::Range>` +help: you might have meant to write a method call instead of a range + | +LL | self.option.foo().map(|x| x) + | ~ + +error: aborting due to 3 previous errors + +Some errors have detailed explanations: E0308, E0425. +For more information about an error, try `rustc --explain E0308`.