From 20052c83c037ee76dfc0e95f5c42c929250e6b71 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Sat, 17 Dec 2022 20:11:29 +0000 Subject: [PATCH] Suggest associated const on capitalization error --- compiler/rustc_hir_typeck/src/demand.rs | 45 ++++++---- compiler/rustc_hir_typeck/src/expr.rs | 10 +-- .../src/fn_ctxt/suggestions.rs | 84 ++++++++++++++++++- .../suggestions/assoc-ct-for-assoc-method.rs | 25 ++++++ .../assoc-ct-for-assoc-method.stderr | 47 +++++++++++ 5 files changed, 188 insertions(+), 23 deletions(-) create mode 100644 src/test/ui/suggestions/assoc-ct-for-assoc-method.rs create mode 100644 src/test/ui/suggestions/assoc-ct-for-assoc-method.stderr diff --git a/compiler/rustc_hir_typeck/src/demand.rs b/compiler/rustc_hir_typeck/src/demand.rs index 479aaf2e1a7..e68bd1297c8 100644 --- a/compiler/rustc_hir_typeck/src/demand.rs +++ b/compiler/rustc_hir_typeck/src/demand.rs @@ -23,6 +23,35 @@ use std::iter; impl<'a, 'tcx> FnCtxt<'a, 'tcx> { + pub fn emit_type_mismatch_suggestions( + &self, + err: &mut Diagnostic, + expr: &hir::Expr<'tcx>, + expr_ty: Ty<'tcx>, + expected: Ty<'tcx>, + expected_ty_expr: Option<&'tcx hir::Expr<'tcx>>, + _error: Option>, + ) { + if expr_ty == expected { + return; + } + + // Use `||` to give these suggestions a precedence + let _ = self.suggest_missing_parentheses(err, expr) + || self.suggest_associated_const(err, expr, expected) + || self.suggest_deref_ref_or_into(err, expr, expected, expr_ty, expected_ty_expr) + || self.suggest_option_to_bool(err, expr, expr_ty, expected) + || self.suggest_compatible_variants(err, expr, expected, expr_ty) + || self.suggest_non_zero_new_unwrap(err, expr, expected, expr_ty) + || self.suggest_calling_boxed_future_when_appropriate(err, expr, expected, expr_ty) + || self.suggest_no_capture_closure(err, expected, expr_ty) + || self.suggest_boxing_when_appropriate(err, expr, expected, expr_ty) + || self.suggest_block_to_brackets_peeling_refs(err, expr, expr_ty, expected) + || self.suggest_copied_or_cloned(err, expr, expr_ty, expected) + || self.suggest_into(err, expr, expr_ty, expected) + || self.suggest_floating_point_literal(err, expr, expected); + } + pub fn emit_coerce_suggestions( &self, err: &mut Diagnostic, @@ -37,21 +66,7 @@ pub fn emit_coerce_suggestions( } self.annotate_expected_due_to_let_ty(err, expr, error); - - // Use `||` to give these suggestions a precedence - let _ = self.suggest_missing_parentheses(err, expr) - || self.suggest_deref_ref_or_into(err, expr, expected, expr_ty, expected_ty_expr) - || self.suggest_compatible_variants(err, expr, expected, expr_ty) - || self.suggest_non_zero_new_unwrap(err, expr, expected, expr_ty) - || self.suggest_calling_boxed_future_when_appropriate(err, expr, expected, expr_ty) - || self.suggest_no_capture_closure(err, expected, expr_ty) - || self.suggest_boxing_when_appropriate(err, expr, expected, expr_ty) - || self.suggest_block_to_brackets_peeling_refs(err, expr, expr_ty, expected) - || self.suggest_copied_or_cloned(err, expr, expr_ty, expected) - || self.suggest_into(err, expr, expr_ty, expected) - || self.suggest_option_to_bool(err, expr, expr_ty, expected) - || self.suggest_floating_point_literal(err, expr, expected); - + self.emit_type_mismatch_suggestions(err, expr, expr_ty, expected, expected_ty_expr, error); 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); diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index edbbb7272ac..ae641b26eee 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -104,16 +104,14 @@ fn check_expr_meets_expectation_or_error( } if let Some(mut err) = self.demand_suptype_diag(expr.span, expected_ty, ty) { - // FIXME(compiler-errors): We probably should fold some of the - // `suggest_` functions from `emit_coerce_suggestions` into here, - // since some of those aren't necessarily just coerce suggestions. - let _ = self.suggest_deref_ref_or_into( + let _ = self.emit_type_mismatch_suggestions( &mut err, expr.peel_drop_temps(), - expected_ty, ty, + expected_ty, None, - ) || self.suggest_option_to_bool(&mut err, expr, ty, expected_ty); + None, + ); extend_err(&mut err); err.emit(); } diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs index c9d179de39f..efec0244633 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs @@ -1,6 +1,7 @@ use super::FnCtxt; use crate::errors::{AddReturnTypeSuggestion, ExpectedReturnTypeLabel}; +use crate::method::probe::{IsSuggestion, Mode, ProbeScope}; use rustc_ast::util::parser::{ExprPrecedence, PREC_POSTFIX}; use rustc_errors::{Applicability, Diagnostic, MultiSpan}; use rustc_hir as hir; @@ -15,10 +16,11 @@ use rustc_middle::lint::in_external_macro; use rustc_middle::ty::{ self, suggest_constraining_type_params, Binder, DefIdTree, IsSuggestable, ToPredicate, Ty, + TypeVisitable, }; use rustc_session::errors::ExprParenthesesNeeded; -use rustc_span::symbol::sym; -use rustc_span::Span; +use rustc_span::symbol::{sym, Ident}; +use rustc_span::{Span, Symbol}; use rustc_trait_selection::infer::InferCtxtExt; use rustc_trait_selection::traits::error_reporting::DefIdOrName; use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _; @@ -1236,6 +1238,84 @@ pub(crate) fn suggest_floating_point_literal( } } + pub(crate) fn suggest_associated_const( + &self, + err: &mut Diagnostic, + expr: &hir::Expr<'_>, + expected_ty: Ty<'tcx>, + ) -> bool { + let Some((DefKind::AssocFn, old_def_id)) = self.typeck_results.borrow().type_dependent_def(expr.hir_id) else { + return false; + }; + let old_item_name = self.tcx.item_name(old_def_id); + let capitalized_name = Symbol::intern(&old_item_name.as_str().to_uppercase()); + if old_item_name == capitalized_name { + return false; + } + let (item, segment) = match expr.kind { + hir::ExprKind::Path(QPath::Resolved( + Some(ty), + hir::Path { segments: [segment], .. }, + )) + | hir::ExprKind::Path(QPath::TypeRelative(ty, segment)) => { + let self_ty = >::ast_ty_to_ty(self, ty); + if let Ok(pick) = self.probe_for_name( + Mode::Path, + Ident::new(capitalized_name, segment.ident.span), + IsSuggestion(true), + self_ty, + expr.hir_id, + ProbeScope::TraitsInScope, + ) { + (pick.item, segment) + } else { + return false; + } + } + hir::ExprKind::Path(QPath::Resolved( + None, + hir::Path { segments: [.., segment], .. }, + )) => { + // we resolved through some path that doesn't end in the item name, + // better not do a bad suggestion by accident. + if old_item_name != segment.ident.name { + return false; + } + if let Some(item) = self + .tcx + .associated_items(self.tcx.parent(old_def_id)) + .filter_by_name_unhygienic(capitalized_name) + .next() + { + (*item, segment) + } else { + return false; + } + } + _ => return false, + }; + if item.def_id == old_def_id || self.tcx.def_kind(item.def_id) != DefKind::AssocConst { + // Same item + return false; + } + let item_ty = self.tcx.type_of(item.def_id); + // FIXME(compiler-errors): This check is *so* rudimentary + if item_ty.needs_subst() { + return false; + } + if self.can_coerce(item_ty, expected_ty) { + err.span_suggestion_verbose( + segment.ident.span, + format!("try referring to the associated const `{capitalized_name}` instead",), + capitalized_name, + Applicability::MachineApplicable, + ); + true + } else { + false + } + } + fn is_loop(&self, id: hir::HirId) -> bool { let node = self.tcx.hir().get(id); matches!(node, Node::Expr(Expr { kind: ExprKind::Loop(..), .. })) diff --git a/src/test/ui/suggestions/assoc-ct-for-assoc-method.rs b/src/test/ui/suggestions/assoc-ct-for-assoc-method.rs new file mode 100644 index 00000000000..fe222776989 --- /dev/null +++ b/src/test/ui/suggestions/assoc-ct-for-assoc-method.rs @@ -0,0 +1,25 @@ +struct MyS; + +impl MyS { + const FOO: i32 = 1; + fn foo() -> MyS { + MyS + } +} + +fn main() { + let x: i32 = MyS::foo; + //~^ ERROR mismatched types + //~| HELP try referring to the + + let z: i32 = i32::max; + //~^ ERROR mismatched types + //~| HELP try referring to the + + // This example is still broken though... This is a hard suggestion to make, + // because we don't have access to the associated const probing code to make + // this suggestion where it's emitted, i.e. in trait selection. + let y: i32 = i32::max - 42; + //~^ ERROR cannot subtract + //~| HELP use parentheses +} diff --git a/src/test/ui/suggestions/assoc-ct-for-assoc-method.stderr b/src/test/ui/suggestions/assoc-ct-for-assoc-method.stderr new file mode 100644 index 00000000000..afef38f1296 --- /dev/null +++ b/src/test/ui/suggestions/assoc-ct-for-assoc-method.stderr @@ -0,0 +1,47 @@ +error[E0308]: mismatched types + --> $DIR/assoc-ct-for-assoc-method.rs:11:18 + | +LL | let x: i32 = MyS::foo; + | --- ^^^^^^^^ expected `i32`, found fn item + | | + | expected due to this + | + = note: expected type `i32` + found fn item `fn() -> MyS {MyS::foo}` +help: try referring to the associated const `FOO` instead + | +LL | let x: i32 = MyS::FOO; + | ~~~ + +error[E0308]: mismatched types + --> $DIR/assoc-ct-for-assoc-method.rs:15:18 + | +LL | let z: i32 = i32::max; + | --- ^^^^^^^^ expected `i32`, found fn item + | | + | expected due to this + | + = note: expected type `i32` + found fn item `fn(i32, i32) -> i32 {::max}` +help: try referring to the associated const `MAX` instead + | +LL | let z: i32 = i32::MAX; + | ~~~ + +error[E0369]: cannot subtract `{integer}` from `fn(i32, i32) -> i32 {::max}` + --> $DIR/assoc-ct-for-assoc-method.rs:22:27 + | +LL | let y: i32 = i32::max - 42; + | -------- ^ -- {integer} + | | + | fn(i32, i32) -> i32 {::max} + | +help: use parentheses to call this associated function + | +LL | let y: i32 = i32::max(/* i32 */, /* i32 */) - 42; + | ++++++++++++++++++++++ + +error: aborting due to 3 previous errors + +Some errors have detailed explanations: E0308, E0369. +For more information about an error, try `rustc --explain E0308`.