diff options
Diffstat (limited to '')
-rw-r--r-- | compiler/rustc_hir_typeck/src/_match.rs (renamed from compiler/rustc_typeck/src/check/_match.rs) | 131 | ||||
-rw-r--r-- | compiler/rustc_hir_typeck/src/autoderef.rs (renamed from compiler/rustc_typeck/src/check/autoderef.rs) | 0 | ||||
-rw-r--r-- | compiler/rustc_hir_typeck/src/callee.rs | 831 | ||||
-rw-r--r-- | compiler/rustc_hir_typeck/src/cast.rs (renamed from compiler/rustc_typeck/src/check/cast.rs) | 139 | ||||
-rw-r--r-- | compiler/rustc_hir_typeck/src/check.rs | 324 | ||||
-rw-r--r-- | compiler/rustc_hir_typeck/src/closure.rs | 824 | ||||
-rw-r--r-- | compiler/rustc_hir_typeck/src/coercion.rs (renamed from compiler/rustc_typeck/src/check/coercion.rs) | 172 | ||||
-rw-r--r-- | compiler/rustc_hir_typeck/src/demand.rs (renamed from compiler/rustc_typeck/src/check/demand.rs) | 218 | ||||
-rw-r--r-- | compiler/rustc_hir_typeck/src/diverges.rs (renamed from compiler/rustc_typeck/src/check/diverges.rs) | 0 | ||||
-rw-r--r-- | compiler/rustc_hir_typeck/src/errors.rs | 126 | ||||
-rw-r--r-- | compiler/rustc_hir_typeck/src/expectation.rs (renamed from compiler/rustc_typeck/src/check/expectation.rs) | 0 | ||||
-rw-r--r-- | compiler/rustc_hir_typeck/src/expr.rs | 2896 | ||||
-rw-r--r-- | compiler/rustc_hir_typeck/src/expr_use_visitor.rs (renamed from compiler/rustc_typeck/src/expr_use_visitor.rs) | 20 | ||||
-rw-r--r-- | compiler/rustc_hir_typeck/src/fallback.rs (renamed from compiler/rustc_typeck/src/check/fallback.rs) | 4 | ||||
-rw-r--r-- | compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs (renamed from compiler/rustc_typeck/src/check/fn_ctxt/_impl.rs) | 198 | ||||
-rw-r--r-- | compiler/rustc_hir_typeck/src/fn_ctxt/arg_matrix.rs (renamed from compiler/rustc_typeck/src/check/fn_ctxt/arg_matrix.rs) | 25 | ||||
-rw-r--r-- | compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs | 2236 | ||||
-rw-r--r-- | compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs | 312 | ||||
-rw-r--r-- | compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs | 1250 | ||||
-rw-r--r-- | compiler/rustc_hir_typeck/src/gather_locals.rs (renamed from compiler/rustc_typeck/src/check/gather_locals.rs) | 3 | ||||
-rw-r--r-- | compiler/rustc_hir_typeck/src/generator_interior/drop_ranges/cfg_build.rs (renamed from compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs) | 7 | ||||
-rw-r--r-- | compiler/rustc_hir_typeck/src/generator_interior/drop_ranges/cfg_propagate.rs (renamed from compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_propagate.rs) | 0 | ||||
-rw-r--r-- | compiler/rustc_hir_typeck/src/generator_interior/drop_ranges/cfg_visualize.rs (renamed from compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_visualize.rs) | 0 | ||||
-rw-r--r-- | compiler/rustc_hir_typeck/src/generator_interior/drop_ranges/mod.rs | 309 | ||||
-rw-r--r-- | compiler/rustc_hir_typeck/src/generator_interior/drop_ranges/record_consumed_borrow.rs (renamed from compiler/rustc_typeck/src/check/generator_interior/drop_ranges/record_consumed_borrow.rs) | 19 | ||||
-rw-r--r-- | compiler/rustc_hir_typeck/src/generator_interior/mod.rs | 647 | ||||
-rw-r--r-- | compiler/rustc_hir_typeck/src/inherited.rs | 213 | ||||
-rw-r--r-- | compiler/rustc_hir_typeck/src/intrinsicck.rs | 108 | ||||
-rw-r--r-- | compiler/rustc_hir_typeck/src/lib.rs | 507 | ||||
-rw-r--r-- | compiler/rustc_hir_typeck/src/mem_categorization.rs (renamed from compiler/rustc_typeck/src/mem_categorization.rs) | 24 | ||||
-rw-r--r-- | compiler/rustc_hir_typeck/src/method/confirm.rs (renamed from compiler/rustc_typeck/src/check/method/confirm.rs) | 20 | ||||
-rw-r--r-- | compiler/rustc_hir_typeck/src/method/mod.rs | 625 | ||||
-rw-r--r-- | compiler/rustc_hir_typeck/src/method/prelude2021.rs (renamed from compiler/rustc_typeck/src/check/method/prelude2021.rs) | 122 | ||||
-rw-r--r-- | compiler/rustc_hir_typeck/src/method/probe.rs (renamed from compiler/rustc_typeck/src/check/method/probe.rs) | 180 | ||||
-rw-r--r-- | compiler/rustc_hir_typeck/src/method/suggest.rs (renamed from compiler/rustc_typeck/src/check/method/suggest.rs) | 663 | ||||
-rw-r--r-- | compiler/rustc_hir_typeck/src/op.rs | 994 | ||||
-rw-r--r-- | compiler/rustc_hir_typeck/src/pat.rs (renamed from compiler/rustc_typeck/src/check/pat.rs) | 77 | ||||
-rw-r--r-- | compiler/rustc_hir_typeck/src/place_op.rs (renamed from compiler/rustc_typeck/src/check/place_op.rs) | 4 | ||||
-rw-r--r-- | compiler/rustc_hir_typeck/src/rvalue_scopes.rs (renamed from compiler/rustc_typeck/src/check/rvalue_scopes.rs) | 0 | ||||
-rw-r--r-- | compiler/rustc_hir_typeck/src/upvar.rs (renamed from compiler/rustc_typeck/src/check/upvar.rs) | 34 | ||||
-rw-r--r-- | compiler/rustc_hir_typeck/src/writeback.rs (renamed from compiler/rustc_typeck/src/check/writeback.rs) | 118 |
41 files changed, 13619 insertions, 761 deletions
diff --git a/compiler/rustc_typeck/src/check/_match.rs b/compiler/rustc_hir_typeck/src/_match.rs index 1b13c98e4..2b15d4dcd 100644 --- a/compiler/rustc_typeck/src/check/_match.rs +++ b/compiler/rustc_hir_typeck/src/_match.rs @@ -1,10 +1,10 @@ -use crate::check::coercion::{AsCoercionSite, CoerceMany}; -use crate::check::{Diverges, Expectation, FnCtxt, Needs}; +use crate::coercion::{AsCoercionSite, CoerceMany}; +use crate::{Diverges, Expectation, FnCtxt, Needs}; use rustc_errors::{Applicability, MultiSpan}; use rustc_hir::{self as hir, ExprKind}; use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind}; use rustc_infer::traits::Obligation; -use rustc_middle::ty::{self, ToPredicate, Ty, TypeVisitable}; +use rustc_middle::ty::{self, ToPredicate, Ty}; use rustc_span::Span; use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt; use rustc_trait_selection::traits::{ @@ -12,7 +12,7 @@ use rustc_trait_selection::traits::{ }; impl<'a, 'tcx> FnCtxt<'a, 'tcx> { - #[instrument(skip(self), level = "debug")] + #[instrument(skip(self), level = "debug", ret)] pub fn check_match( &self, expr: &'tcx hir::Expr<'tcx>, @@ -94,7 +94,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let arm_ty = self.check_expr_with_expectation(&arm.body, expected); all_arms_diverge &= self.diverges.get(); - let opt_suggest_box_span = self.opt_suggest_box_span(arm_ty, orig_expected); + let opt_suggest_box_span = prior_arm.and_then(|(_, prior_arm_ty, _)| { + self.opt_suggest_box_span(prior_arm_ty, arm_ty, orig_expected) + }); let (arm_block_id, arm_span) = if let hir::ExprKind::Block(blk, _) = arm.body.kind { (Some(blk.hir_id), self.find_block_span(blk)) @@ -135,9 +137,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { Some(&arm.body), arm_ty, Some(&mut |err| { - let Some(ret) = self.ret_type_span else { - return; - }; + let Some(ret) = self + .tcx + .hir() + .find_by_def_id(self.body_id.owner.def_id) + .and_then(|owner| owner.fn_decl()) + .map(|decl| decl.output.span()) + else { return; }; let Expectation::IsLast(stmt) = orig_expected else { return }; @@ -210,9 +216,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // We won't diverge unless the scrutinee or all arms diverge. self.diverges.set(scrut_diverges | all_arms_diverge); - let match_ty = coercion.complete(self); - debug!(?match_ty); - match_ty + coercion.complete(self) } /// When the previously checked expression (the scrutinee) diverges, @@ -255,7 +259,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { err.help("consider adding an `else` block that evaluates to the expected type"); error = true; }, - ret_reason.is_none(), + false, ); error } @@ -468,53 +472,80 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } - // When we have a `match` as a tail expression in a `fn` with a returned `impl Trait` - // we check if the different arms would work with boxed trait objects instead and - // provide a structured suggestion in that case. + /// When we have a `match` as a tail expression in a `fn` with a returned `impl Trait` + /// we check if the different arms would work with boxed trait objects instead and + /// provide a structured suggestion in that case. pub(crate) fn opt_suggest_box_span( &self, - outer_ty: Ty<'tcx>, + first_ty: Ty<'tcx>, + second_ty: Ty<'tcx>, orig_expected: Expectation<'tcx>, ) -> Option<Span> { + // FIXME(compiler-errors): This really shouldn't need to be done during the + // "good" path of typeck, but here we are. match orig_expected { - Expectation::ExpectHasType(expected) - if self.in_tail_expr - && self.ret_coercion.as_ref()?.borrow().merged_ty().has_opaque_types() - && self.can_coerce(outer_ty, expected) => - { - let obligations = self.fulfillment_cx.borrow().pending_obligations(); - let mut suggest_box = !obligations.is_empty(); - for o in obligations { - match o.predicate.kind().skip_binder() { - ty::PredicateKind::Trait(t) => { - let pred = - ty::Binder::dummy(ty::PredicateKind::Trait(ty::TraitPredicate { - trait_ref: ty::TraitRef { - def_id: t.def_id(), - substs: self.tcx.mk_substs_trait(outer_ty, &[]), - }, - constness: t.constness, - polarity: t.polarity, - })); - let obl = Obligation::new( - o.cause.clone(), - self.param_env, - pred.to_predicate(self.tcx), - ); - suggest_box &= self.predicate_must_hold_modulo_regions(&obl); - if !suggest_box { - // We've encountered some obligation that didn't hold, so the - // return expression can't just be boxed. We don't need to - // evaluate the rest of the obligations. - break; + Expectation::ExpectHasType(expected) => { + let TypeVariableOrigin { + span, + kind: TypeVariableOriginKind::OpaqueTypeInference(rpit_def_id), + .. + } = self.type_var_origin(expected)? else { return None; }; + + let sig = *self + .typeck_results + .borrow() + .liberated_fn_sigs() + .get(hir::HirId::make_owner(self.body_id.owner.def_id))?; + + let substs = sig.output().walk().find_map(|arg| { + if let ty::GenericArgKind::Type(ty) = arg.unpack() + && let ty::Opaque(def_id, substs) = *ty.kind() + && def_id == rpit_def_id + { + Some(substs) + } else { + None + } + })?; + let opaque_ty = self.tcx.mk_opaque(rpit_def_id, substs); + + if !self.can_coerce(first_ty, expected) || !self.can_coerce(second_ty, expected) { + return None; + } + + for ty in [first_ty, second_ty] { + for (pred, _) in self + .tcx + .bound_explicit_item_bounds(rpit_def_id) + .subst_iter_copied(self.tcx, substs) + { + let pred = match pred.kind().skip_binder() { + ty::PredicateKind::Trait(mut trait_pred) => { + assert_eq!(trait_pred.trait_ref.self_ty(), opaque_ty); + trait_pred.trait_ref.substs = + self.tcx.mk_substs_trait(ty, &trait_pred.trait_ref.substs[1..]); + pred.kind().rebind(trait_pred).to_predicate(self.tcx) } + ty::PredicateKind::Projection(mut proj_pred) => { + assert_eq!(proj_pred.projection_ty.self_ty(), opaque_ty); + proj_pred.projection_ty.substs = self + .tcx + .mk_substs_trait(ty, &proj_pred.projection_ty.substs[1..]); + pred.kind().rebind(proj_pred).to_predicate(self.tcx) + } + _ => continue, + }; + if !self.predicate_must_hold_modulo_regions(&Obligation::new( + ObligationCause::misc(span, self.body_id), + self.param_env, + pred, + )) { + return None; } - _ => {} } } - // If all the obligations hold (or there are no obligations) the tail expression - // we can suggest to return a boxed trait object instead of an opaque type. - if suggest_box { self.ret_type_span } else { None } + + Some(span) } _ => None, } diff --git a/compiler/rustc_typeck/src/check/autoderef.rs b/compiler/rustc_hir_typeck/src/autoderef.rs index 59c366ad7..59c366ad7 100644 --- a/compiler/rustc_typeck/src/check/autoderef.rs +++ b/compiler/rustc_hir_typeck/src/autoderef.rs diff --git a/compiler/rustc_hir_typeck/src/callee.rs b/compiler/rustc_hir_typeck/src/callee.rs new file mode 100644 index 000000000..1b33f2f02 --- /dev/null +++ b/compiler/rustc_hir_typeck/src/callee.rs @@ -0,0 +1,831 @@ +use super::method::probe::{IsSuggestion, Mode, ProbeScope}; +use super::method::MethodCallee; +use super::{Expectation, FnCtxt, TupleArgumentsFlag}; + +use crate::type_error_struct; +use rustc_ast::util::parser::PREC_POSTFIX; +use rustc_errors::{struct_span_err, Applicability, Diagnostic, StashKey}; +use rustc_hir as hir; +use rustc_hir::def::{self, Namespace, Res}; +use rustc_hir::def_id::DefId; +use rustc_infer::{ + infer, + traits::{self, Obligation}, +}; +use rustc_infer::{ + infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind}, + traits::ObligationCause, +}; +use rustc_middle::ty::adjustment::{ + Adjust, Adjustment, AllowTwoPhase, AutoBorrow, AutoBorrowMutability, +}; +use rustc_middle::ty::SubstsRef; +use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitable}; +use rustc_span::def_id::LocalDefId; +use rustc_span::symbol::{sym, Ident}; +use rustc_span::Span; +use rustc_target::spec::abi; +use rustc_trait_selection::autoderef::Autoderef; +use rustc_trait_selection::infer::InferCtxtExt as _; +use rustc_trait_selection::traits::error_reporting::DefIdOrName; +use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _; + +use std::iter; + +/// Checks that it is legal to call methods of the trait corresponding +/// to `trait_id` (this only cares about the trait, not the specific +/// method that is called). +pub fn check_legal_trait_for_method_call( + tcx: TyCtxt<'_>, + span: Span, + receiver: Option<Span>, + expr_span: Span, + trait_id: DefId, +) { + if tcx.lang_items().drop_trait() == Some(trait_id) { + let mut err = struct_span_err!(tcx.sess, span, E0040, "explicit use of destructor method"); + err.span_label(span, "explicit destructor calls not allowed"); + + let (sp, suggestion) = receiver + .and_then(|s| tcx.sess.source_map().span_to_snippet(s).ok()) + .filter(|snippet| !snippet.is_empty()) + .map(|snippet| (expr_span, format!("drop({snippet})"))) + .unwrap_or_else(|| (span, "drop".to_string())); + + err.span_suggestion( + sp, + "consider using `drop` function", + suggestion, + Applicability::MaybeIncorrect, + ); + + err.emit(); + } +} + +#[derive(Debug)] +enum CallStep<'tcx> { + Builtin(Ty<'tcx>), + DeferredClosure(LocalDefId, ty::FnSig<'tcx>), + /// E.g., enum variant constructors. + Overloaded(MethodCallee<'tcx>), +} + +impl<'a, 'tcx> FnCtxt<'a, 'tcx> { + pub fn check_call( + &self, + call_expr: &'tcx hir::Expr<'tcx>, + callee_expr: &'tcx hir::Expr<'tcx>, + arg_exprs: &'tcx [hir::Expr<'tcx>], + expected: Expectation<'tcx>, + ) -> Ty<'tcx> { + let original_callee_ty = match &callee_expr.kind { + hir::ExprKind::Path(hir::QPath::Resolved(..) | hir::QPath::TypeRelative(..)) => self + .check_expr_with_expectation_and_args( + callee_expr, + Expectation::NoExpectation, + arg_exprs, + ), + _ => self.check_expr(callee_expr), + }; + + let expr_ty = self.structurally_resolved_type(call_expr.span, original_callee_ty); + + let mut autoderef = self.autoderef(callee_expr.span, expr_ty); + let mut result = None; + while result.is_none() && autoderef.next().is_some() { + result = self.try_overloaded_call_step(call_expr, callee_expr, arg_exprs, &autoderef); + } + self.register_predicates(autoderef.into_obligations()); + + let output = match result { + None => { + // this will report an error since original_callee_ty is not a fn + self.confirm_builtin_call( + call_expr, + callee_expr, + original_callee_ty, + arg_exprs, + expected, + ) + } + + Some(CallStep::Builtin(callee_ty)) => { + self.confirm_builtin_call(call_expr, callee_expr, callee_ty, arg_exprs, expected) + } + + Some(CallStep::DeferredClosure(def_id, fn_sig)) => { + self.confirm_deferred_closure_call(call_expr, arg_exprs, expected, def_id, fn_sig) + } + + Some(CallStep::Overloaded(method_callee)) => { + self.confirm_overloaded_call(call_expr, arg_exprs, expected, method_callee) + } + }; + + // we must check that return type of called functions is WF: + self.register_wf_obligation(output.into(), call_expr.span, traits::WellFormed(None)); + + output + } + + fn try_overloaded_call_step( + &self, + call_expr: &'tcx hir::Expr<'tcx>, + callee_expr: &'tcx hir::Expr<'tcx>, + arg_exprs: &'tcx [hir::Expr<'tcx>], + autoderef: &Autoderef<'a, 'tcx>, + ) -> Option<CallStep<'tcx>> { + let adjusted_ty = + self.structurally_resolved_type(autoderef.span(), autoderef.final_ty(false)); + debug!( + "try_overloaded_call_step(call_expr={:?}, adjusted_ty={:?})", + call_expr, adjusted_ty + ); + + // If the callee is a bare function or a closure, then we're all set. + match *adjusted_ty.kind() { + ty::FnDef(..) | ty::FnPtr(_) => { + let adjustments = self.adjust_steps(autoderef); + self.apply_adjustments(callee_expr, adjustments); + return Some(CallStep::Builtin(adjusted_ty)); + } + + ty::Closure(def_id, substs) => { + let def_id = def_id.expect_local(); + + // Check whether this is a call to a closure where we + // haven't yet decided on whether the closure is fn vs + // fnmut vs fnonce. If so, we have to defer further processing. + if self.closure_kind(substs).is_none() { + let closure_sig = substs.as_closure().sig(); + let closure_sig = self.replace_bound_vars_with_fresh_vars( + call_expr.span, + infer::FnCall, + closure_sig, + ); + let adjustments = self.adjust_steps(autoderef); + self.record_deferred_call_resolution( + def_id, + DeferredCallResolution { + call_expr, + callee_expr, + adjusted_ty, + adjustments, + fn_sig: closure_sig, + closure_substs: substs, + }, + ); + return Some(CallStep::DeferredClosure(def_id, closure_sig)); + } + } + + // Hack: we know that there are traits implementing Fn for &F + // where F:Fn and so forth. In the particular case of types + // like `x: &mut FnMut()`, if there is a call `x()`, we would + // normally translate to `FnMut::call_mut(&mut x, ())`, but + // that winds up requiring `mut x: &mut FnMut()`. A little + // over the top. The simplest fix by far is to just ignore + // this case and deref again, so we wind up with + // `FnMut::call_mut(&mut *x, ())`. + ty::Ref(..) if autoderef.step_count() == 0 => { + return None; + } + + ty::Error(_) => { + return None; + } + + _ => {} + } + + // Now, we look for the implementation of a Fn trait on the object's type. + // We first do it with the explicit instruction to look for an impl of + // `Fn<Tuple>`, with the tuple `Tuple` having an arity corresponding + // to the number of call parameters. + // If that fails (or_else branch), we try again without specifying the + // shape of the tuple (hence the None). This allows to detect an Fn trait + // is implemented, and use this information for diagnostic. + self.try_overloaded_call_traits(call_expr, adjusted_ty, Some(arg_exprs)) + .or_else(|| self.try_overloaded_call_traits(call_expr, adjusted_ty, None)) + .map(|(autoref, method)| { + let mut adjustments = self.adjust_steps(autoderef); + adjustments.extend(autoref); + self.apply_adjustments(callee_expr, adjustments); + CallStep::Overloaded(method) + }) + } + + fn try_overloaded_call_traits( + &self, + call_expr: &hir::Expr<'_>, + adjusted_ty: Ty<'tcx>, + opt_arg_exprs: Option<&'tcx [hir::Expr<'tcx>]>, + ) -> Option<(Option<Adjustment<'tcx>>, MethodCallee<'tcx>)> { + // Try the options that are least restrictive on the caller first. + for (opt_trait_def_id, method_name, borrow) in [ + (self.tcx.lang_items().fn_trait(), Ident::with_dummy_span(sym::call), true), + (self.tcx.lang_items().fn_mut_trait(), Ident::with_dummy_span(sym::call_mut), true), + (self.tcx.lang_items().fn_once_trait(), Ident::with_dummy_span(sym::call_once), false), + ] { + let Some(trait_def_id) = opt_trait_def_id else { continue }; + + let opt_input_types = opt_arg_exprs.map(|arg_exprs| { + [self.tcx.mk_tup(arg_exprs.iter().map(|e| { + self.next_ty_var(TypeVariableOrigin { + kind: TypeVariableOriginKind::TypeInference, + span: e.span, + }) + }))] + }); + let opt_input_types = opt_input_types.as_ref().map(AsRef::as_ref); + + if let Some(ok) = self.lookup_method_in_trait( + call_expr.span, + method_name, + trait_def_id, + adjusted_ty, + opt_input_types, + ) { + let method = self.register_infer_ok_obligations(ok); + let mut autoref = None; + if borrow { + // Check for &self vs &mut self in the method signature. Since this is either + // the Fn or FnMut trait, it should be one of those. + let ty::Ref(region, _, mutbl) = method.sig.inputs()[0].kind() else { + // The `fn`/`fn_mut` lang item is ill-formed, which should have + // caused an error elsewhere. + self.tcx + .sess + .delay_span_bug(call_expr.span, "input to call/call_mut is not a ref?"); + return None; + }; + + let mutbl = match mutbl { + hir::Mutability::Not => AutoBorrowMutability::Not, + hir::Mutability::Mut => AutoBorrowMutability::Mut { + // For initial two-phase borrow + // deployment, conservatively omit + // overloaded function call ops. + allow_two_phase_borrow: AllowTwoPhase::No, + }, + }; + autoref = Some(Adjustment { + kind: Adjust::Borrow(AutoBorrow::Ref(*region, mutbl)), + target: method.sig.inputs()[0], + }); + } + return Some((autoref, method)); + } + } + + None + } + + /// Give appropriate suggestion when encountering `||{/* not callable */}()`, where the + /// likely intention is to call the closure, suggest `(||{})()`. (#55851) + fn identify_bad_closure_def_and_call( + &self, + err: &mut Diagnostic, + hir_id: hir::HirId, + callee_node: &hir::ExprKind<'_>, + callee_span: Span, + ) { + let hir = self.tcx.hir(); + let parent_hir_id = hir.get_parent_node(hir_id); + let parent_node = hir.get(parent_hir_id); + if let ( + hir::Node::Expr(hir::Expr { + kind: hir::ExprKind::Closure(&hir::Closure { fn_decl_span, body, .. }), + .. + }), + hir::ExprKind::Block(..), + ) = (parent_node, callee_node) + { + let fn_decl_span = if hir.body(body).generator_kind + == Some(hir::GeneratorKind::Async(hir::AsyncGeneratorKind::Closure)) + { + // Actually need to unwrap a few more layers of HIR to get to + // the _real_ closure... + let async_closure = hir.get_parent_node(hir.get_parent_node(parent_hir_id)); + if let hir::Node::Expr(hir::Expr { + kind: hir::ExprKind::Closure(&hir::Closure { fn_decl_span, .. }), + .. + }) = hir.get(async_closure) + { + fn_decl_span + } else { + return; + } + } else { + fn_decl_span + }; + + let start = fn_decl_span.shrink_to_lo(); + let end = callee_span.shrink_to_hi(); + err.multipart_suggestion( + "if you meant to create this closure and immediately call it, surround the \ + closure with parentheses", + vec![(start, "(".to_string()), (end, ")".to_string())], + Applicability::MaybeIncorrect, + ); + } + } + + /// Give appropriate suggestion when encountering `[("a", 0) ("b", 1)]`, where the + /// likely intention is to create an array containing tuples. + fn maybe_suggest_bad_array_definition( + &self, + err: &mut Diagnostic, + call_expr: &'tcx hir::Expr<'tcx>, + callee_expr: &'tcx hir::Expr<'tcx>, + ) -> bool { + let hir_id = self.tcx.hir().get_parent_node(call_expr.hir_id); + let parent_node = self.tcx.hir().get(hir_id); + if let ( + hir::Node::Expr(hir::Expr { kind: hir::ExprKind::Array(_), .. }), + hir::ExprKind::Tup(exp), + hir::ExprKind::Call(_, args), + ) = (parent_node, &callee_expr.kind, &call_expr.kind) + && args.len() == exp.len() + { + let start = callee_expr.span.shrink_to_hi(); + err.span_suggestion( + start, + "consider separating array elements with a comma", + ",", + Applicability::MaybeIncorrect, + ); + return true; + } + false + } + + fn confirm_builtin_call( + &self, + call_expr: &'tcx hir::Expr<'tcx>, + callee_expr: &'tcx hir::Expr<'tcx>, + callee_ty: Ty<'tcx>, + arg_exprs: &'tcx [hir::Expr<'tcx>], + expected: Expectation<'tcx>, + ) -> Ty<'tcx> { + let (fn_sig, def_id) = match *callee_ty.kind() { + ty::FnDef(def_id, subst) => { + let fn_sig = self.tcx.bound_fn_sig(def_id).subst(self.tcx, subst); + + // Unit testing: function items annotated with + // `#[rustc_evaluate_where_clauses]` trigger special output + // to let us test the trait evaluation system. + if self.tcx.has_attr(def_id, sym::rustc_evaluate_where_clauses) { + let predicates = self.tcx.predicates_of(def_id); + let predicates = predicates.instantiate(self.tcx, subst); + for (predicate, predicate_span) in + predicates.predicates.iter().zip(&predicates.spans) + { + let obligation = Obligation::new( + ObligationCause::dummy_with_span(callee_expr.span), + self.param_env, + *predicate, + ); + let result = self.evaluate_obligation(&obligation); + self.tcx + .sess + .struct_span_err( + callee_expr.span, + &format!("evaluate({:?}) = {:?}", predicate, result), + ) + .span_label(*predicate_span, "predicate") + .emit(); + } + } + (fn_sig, Some(def_id)) + } + ty::FnPtr(sig) => (sig, None), + _ => { + if let hir::ExprKind::Path(hir::QPath::Resolved(_, path)) = &callee_expr.kind + && let [segment] = path.segments + && let Some(mut diag) = self + .tcx + .sess + .diagnostic() + .steal_diagnostic(segment.ident.span, StashKey::CallIntoMethod) + { + // Try suggesting `foo(a)` -> `a.foo()` if possible. + if let Some(ty) = + self.suggest_call_as_method( + &mut diag, + segment, + arg_exprs, + call_expr, + expected + ) + { + diag.emit(); + return ty; + } else { + diag.emit(); + } + } + + self.report_invalid_callee(call_expr, callee_expr, callee_ty, arg_exprs); + + // This is the "default" function signature, used in case of error. + // In that case, we check each argument against "error" in order to + // set up all the node type bindings. + ( + ty::Binder::dummy(self.tcx.mk_fn_sig( + self.err_args(arg_exprs.len()).into_iter(), + self.tcx.ty_error(), + false, + hir::Unsafety::Normal, + abi::Abi::Rust, + )), + None, + ) + } + }; + + // Replace any late-bound regions that appear in the function + // signature with region variables. We also have to + // renormalize the associated types at this point, since they + // previously appeared within a `Binder<>` and hence would not + // have been normalized before. + let fn_sig = self.replace_bound_vars_with_fresh_vars(call_expr.span, infer::FnCall, fn_sig); + let fn_sig = self.normalize_associated_types_in(call_expr.span, fn_sig); + + // Call the generic checker. + let expected_arg_tys = self.expected_inputs_for_expected_output( + call_expr.span, + expected, + fn_sig.output(), + fn_sig.inputs(), + ); + self.check_argument_types( + call_expr.span, + call_expr, + fn_sig.inputs(), + expected_arg_tys, + arg_exprs, + fn_sig.c_variadic, + TupleArgumentsFlag::DontTupleArguments, + def_id, + ); + + fn_sig.output() + } + + /// Attempts to reinterpret `method(rcvr, args...)` as `rcvr.method(args...)` + /// and suggesting the fix if the method probe is successful. + fn suggest_call_as_method( + &self, + diag: &mut Diagnostic, + segment: &'tcx hir::PathSegment<'tcx>, + arg_exprs: &'tcx [hir::Expr<'tcx>], + call_expr: &'tcx hir::Expr<'tcx>, + expected: Expectation<'tcx>, + ) -> Option<Ty<'tcx>> { + if let [callee_expr, rest @ ..] = arg_exprs { + let callee_ty = self.check_expr(callee_expr); + // First, do a probe with `IsSuggestion(true)` to avoid emitting + // any strange errors. If it's successful, then we'll do a true + // method lookup. + let Ok(pick) = self + .probe_for_name( + call_expr.span, + Mode::MethodCall, + segment.ident, + IsSuggestion(true), + callee_ty, + call_expr.hir_id, + // We didn't record the in scope traits during late resolution + // so we need to probe AllTraits unfortunately + ProbeScope::AllTraits, + ) else { + return None; + }; + + let pick = self.confirm_method( + call_expr.span, + callee_expr, + call_expr, + callee_ty, + pick, + segment, + ); + if pick.illegal_sized_bound.is_some() { + return None; + } + + let up_to_rcvr_span = segment.ident.span.until(callee_expr.span); + let rest_span = callee_expr.span.shrink_to_hi().to(call_expr.span.shrink_to_hi()); + let rest_snippet = if let Some(first) = rest.first() { + self.tcx + .sess + .source_map() + .span_to_snippet(first.span.to(call_expr.span.shrink_to_hi())) + } else { + Ok(")".to_string()) + }; + + if let Ok(rest_snippet) = rest_snippet { + let sugg = if callee_expr.precedence().order() >= PREC_POSTFIX { + vec![ + (up_to_rcvr_span, "".to_string()), + (rest_span, format!(".{}({rest_snippet}", segment.ident)), + ] + } else { + vec![ + (up_to_rcvr_span, "(".to_string()), + (rest_span, format!(").{}({rest_snippet}", segment.ident)), + ] + }; + let self_ty = self.resolve_vars_if_possible(pick.callee.sig.inputs()[0]); + diag.multipart_suggestion( + format!( + "use the `.` operator to call the method `{}{}` on `{self_ty}`", + self.tcx + .associated_item(pick.callee.def_id) + .trait_container(self.tcx) + .map_or_else( + || String::new(), + |trait_def_id| self.tcx.def_path_str(trait_def_id) + "::" + ), + segment.ident + ), + sugg, + Applicability::MaybeIncorrect, + ); + + // Let's check the method fully now + let return_ty = self.check_method_argument_types( + segment.ident.span, + call_expr, + Ok(pick.callee), + rest, + TupleArgumentsFlag::DontTupleArguments, + expected, + ); + + return Some(return_ty); + } + } + + None + } + + fn report_invalid_callee( + &self, + call_expr: &'tcx hir::Expr<'tcx>, + callee_expr: &'tcx hir::Expr<'tcx>, + callee_ty: Ty<'tcx>, + arg_exprs: &'tcx [hir::Expr<'tcx>], + ) { + let mut unit_variant = None; + if let hir::ExprKind::Path(qpath) = &callee_expr.kind + && let Res::Def(def::DefKind::Ctor(kind, def::CtorKind::Const), _) + = self.typeck_results.borrow().qpath_res(qpath, callee_expr.hir_id) + // Only suggest removing parens if there are no arguments + && arg_exprs.is_empty() + { + let descr = match kind { + def::CtorOf::Struct => "struct", + def::CtorOf::Variant => "enum variant", + }; + let removal_span = callee_expr.span.shrink_to_hi().to(call_expr.span.shrink_to_hi()); + unit_variant = Some((removal_span, descr, rustc_hir_pretty::qpath_to_string(qpath))); + } + + let callee_ty = self.resolve_vars_if_possible(callee_ty); + let mut err = type_error_struct!( + self.tcx.sess, + callee_expr.span, + callee_ty, + E0618, + "expected function, found {}", + match &unit_variant { + Some((_, kind, path)) => format!("{kind} `{path}`"), + None => format!("`{callee_ty}`"), + } + ); + + self.identify_bad_closure_def_and_call( + &mut err, + call_expr.hir_id, + &callee_expr.kind, + callee_expr.span, + ); + + if let Some((removal_span, kind, path)) = &unit_variant { + err.span_suggestion_verbose( + *removal_span, + &format!( + "`{path}` is a unit {kind}, and does not take parentheses to be constructed", + ), + "", + Applicability::MachineApplicable, + ); + } + + let mut inner_callee_path = None; + let def = match callee_expr.kind { + hir::ExprKind::Path(ref qpath) => { + self.typeck_results.borrow().qpath_res(qpath, callee_expr.hir_id) + } + hir::ExprKind::Call(ref inner_callee, _) => { + // If the call spans more than one line and the callee kind is + // itself another `ExprCall`, that's a clue that we might just be + // missing a semicolon (Issue #51055) + let call_is_multiline = self.tcx.sess.source_map().is_multiline(call_expr.span); + if call_is_multiline { + err.span_suggestion( + callee_expr.span.shrink_to_hi(), + "consider using a semicolon here", + ";", + Applicability::MaybeIncorrect, + ); + } + if let hir::ExprKind::Path(ref inner_qpath) = inner_callee.kind { + inner_callee_path = Some(inner_qpath); + self.typeck_results.borrow().qpath_res(inner_qpath, inner_callee.hir_id) + } else { + Res::Err + } + } + _ => Res::Err, + }; + + if !self.maybe_suggest_bad_array_definition(&mut err, call_expr, callee_expr) { + if let Some((maybe_def, output_ty, _)) = + self.extract_callable_info(callee_expr, callee_ty) + && !self.type_is_sized_modulo_regions(self.param_env, output_ty, callee_expr.span) + { + let descr = match maybe_def { + DefIdOrName::DefId(def_id) => self.tcx.def_kind(def_id).descr(def_id), + DefIdOrName::Name(name) => name, + }; + err.span_label( + callee_expr.span, + format!("this {descr} returns an unsized value `{output_ty}`, so it cannot be called") + ); + if let DefIdOrName::DefId(def_id) = maybe_def + && let Some(def_span) = self.tcx.hir().span_if_local(def_id) + { + err.span_label(def_span, "the callable type is defined here"); + } + } else { + err.span_label(call_expr.span, "call expression requires function"); + } + } + + if let Some(span) = self.tcx.hir().res_span(def) { + let callee_ty = callee_ty.to_string(); + let label = match (unit_variant, inner_callee_path) { + (Some((_, kind, path)), _) => Some(format!("{kind} `{path}` defined here")), + (_, Some(hir::QPath::Resolved(_, path))) => self + .tcx + .sess + .source_map() + .span_to_snippet(path.span) + .ok() + .map(|p| format!("`{p}` defined here returns `{callee_ty}`")), + _ => { + match def { + // Emit a different diagnostic for local variables, as they are not + // type definitions themselves, but rather variables *of* that type. + Res::Local(hir_id) => Some(format!( + "`{}` has type `{}`", + self.tcx.hir().name(hir_id), + callee_ty + )), + Res::Def(kind, def_id) if kind.ns() == Some(Namespace::ValueNS) => { + Some(format!("`{}` defined here", self.tcx.def_path_str(def_id),)) + } + _ => Some(format!("`{callee_ty}` defined here")), + } + } + }; + if let Some(label) = label { + err.span_label(span, label); + } + } + err.emit(); + } + + fn confirm_deferred_closure_call( + &self, + call_expr: &'tcx hir::Expr<'tcx>, + arg_exprs: &'tcx [hir::Expr<'tcx>], + expected: Expectation<'tcx>, + closure_def_id: LocalDefId, + fn_sig: ty::FnSig<'tcx>, + ) -> Ty<'tcx> { + // `fn_sig` is the *signature* of the closure being called. We + // don't know the full details yet (`Fn` vs `FnMut` etc), but we + // do know the types expected for each argument and the return + // type. + + let expected_arg_tys = self.expected_inputs_for_expected_output( + call_expr.span, + expected, + fn_sig.output(), + fn_sig.inputs(), + ); + + self.check_argument_types( + call_expr.span, + call_expr, + fn_sig.inputs(), + expected_arg_tys, + arg_exprs, + fn_sig.c_variadic, + TupleArgumentsFlag::TupleArguments, + Some(closure_def_id.to_def_id()), + ); + + fn_sig.output() + } + + fn confirm_overloaded_call( + &self, + call_expr: &'tcx hir::Expr<'tcx>, + arg_exprs: &'tcx [hir::Expr<'tcx>], + expected: Expectation<'tcx>, + method_callee: MethodCallee<'tcx>, + ) -> Ty<'tcx> { + let output_type = self.check_method_argument_types( + call_expr.span, + call_expr, + Ok(method_callee), + arg_exprs, + TupleArgumentsFlag::TupleArguments, + expected, + ); + + self.write_method_call(call_expr.hir_id, method_callee); + output_type + } +} + +#[derive(Debug)] +pub struct DeferredCallResolution<'tcx> { + call_expr: &'tcx hir::Expr<'tcx>, + callee_expr: &'tcx hir::Expr<'tcx>, + adjusted_ty: Ty<'tcx>, + adjustments: Vec<Adjustment<'tcx>>, + fn_sig: ty::FnSig<'tcx>, + closure_substs: SubstsRef<'tcx>, +} + +impl<'a, 'tcx> DeferredCallResolution<'tcx> { + pub fn resolve(self, fcx: &FnCtxt<'a, 'tcx>) { + debug!("DeferredCallResolution::resolve() {:?}", self); + + // we should not be invoked until the closure kind has been + // determined by upvar inference + assert!(fcx.closure_kind(self.closure_substs).is_some()); + + // We may now know enough to figure out fn vs fnmut etc. + match fcx.try_overloaded_call_traits(self.call_expr, self.adjusted_ty, None) { + Some((autoref, method_callee)) => { + // One problem is that when we get here, we are going + // to have a newly instantiated function signature + // from the call trait. This has to be reconciled with + // the older function signature we had before. In + // principle we *should* be able to fn_sigs(), but we + // can't because of the annoying need for a TypeTrace. + // (This always bites me, should find a way to + // refactor it.) + let method_sig = method_callee.sig; + + debug!("attempt_resolution: method_callee={:?}", method_callee); + + for (method_arg_ty, self_arg_ty) in + iter::zip(method_sig.inputs().iter().skip(1), self.fn_sig.inputs()) + { + fcx.demand_eqtype(self.call_expr.span, *self_arg_ty, *method_arg_ty); + } + + fcx.demand_eqtype(self.call_expr.span, method_sig.output(), self.fn_sig.output()); + + let mut adjustments = self.adjustments; + adjustments.extend(autoref); + fcx.apply_adjustments(self.callee_expr, adjustments); + + fcx.write_method_call(self.call_expr.hir_id, method_callee); + } + None => { + // This can happen if `#![no_core]` is used and the `fn/fn_mut/fn_once` + // lang items are not defined (issue #86238). + let mut err = fcx.inh.tcx.sess.struct_span_err( + self.call_expr.span, + "failed to find an overloaded call trait for closure call", + ); + err.help( + "make sure the `fn`/`fn_mut`/`fn_once` lang items are defined \ + and have associated `call`/`call_mut`/`call_once` functions", + ); + err.emit(); + } + } + } +} diff --git a/compiler/rustc_typeck/src/check/cast.rs b/compiler/rustc_hir_typeck/src/cast.rs index 7aaddc2bd..d1dab0540 100644 --- a/compiler/rustc_typeck/src/check/cast.rs +++ b/compiler/rustc_hir_typeck/src/cast.rs @@ -30,35 +30,38 @@ use super::FnCtxt; -use crate::hir::def_id::DefId; use crate::type_error_struct; -use rustc_errors::{struct_span_err, Applicability, DiagnosticBuilder, ErrorGuaranteed}; +use rustc_errors::{struct_span_err, Applicability, DelayDm, DiagnosticBuilder, ErrorGuaranteed}; use rustc_hir as hir; -use rustc_hir::lang_items::LangItem; use rustc_middle::mir::Mutability; use rustc_middle::ty::adjustment::AllowTwoPhase; use rustc_middle::ty::cast::{CastKind, CastTy}; use rustc_middle::ty::error::TypeError; use rustc_middle::ty::subst::SubstsRef; -use rustc_middle::ty::{self, Ty, TypeAndMut, TypeVisitable}; +use rustc_middle::ty::{self, Ty, TypeAndMut, TypeVisitable, VariantDef}; use rustc_session::lint; use rustc_session::Session; +use rustc_span::def_id::{DefId, LOCAL_CRATE}; use rustc_span::symbol::sym; use rustc_span::Span; use rustc_trait_selection::infer::InferCtxtExt; -use rustc_trait_selection::traits; use rustc_trait_selection::traits::error_reporting::report_object_safety_error; /// Reifies a cast check to be checked once we have full type information for /// a function context. #[derive(Debug)] pub struct CastCheck<'tcx> { + /// The expression whose value is being casted expr: &'tcx hir::Expr<'tcx>, + /// The source type for the cast expression expr_ty: Ty<'tcx>, expr_span: Span, + /// The target type. That is, the type we are casting to. cast_ty: Ty<'tcx>, cast_span: Span, span: Span, + /// whether the cast is made in a const context or not. + pub constness: hir::Constness, } /// The kind of pointer and associated metadata (thin, length or vtable) - we @@ -96,13 +99,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { return Err(reported); } - if self.type_is_known_to_be_sized_modulo_regions(t, span) { + if self.type_is_sized_modulo_regions(self.param_env, t, span) { return Ok(Some(PointerKind::Thin)); } Ok(match *t.kind() { ty::Slice(_) | ty::Str => Some(PointerKind::Length), - ty::Dynamic(ref tty, ..) => Some(PointerKind::VTable(tty.principal_def_id())), + ty::Dynamic(ref tty, _, ty::Dyn) => Some(PointerKind::VTable(tty.principal_def_id())), ty::Adt(def, substs) if def.is_struct() => match def.non_enum_variant().fields.last() { None => Some(PointerKind::Thin), Some(f) => { @@ -139,6 +142,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { | ty::Generator(..) | ty::Adt(..) | ty::Never + | ty::Dynamic(_, _, ty::DynStar) | ty::Error(_) => { let reported = self .tcx @@ -173,6 +177,7 @@ pub enum CastError { /// or "a length". If this argument is None, then the metadata is unknown, for example, /// when we're typechecking a type parameter with a ?Sized bound. IntToFatCast(Option<&'static str>), + ForeignNonExhaustiveAdt, } impl From<ErrorGuaranteed> for CastError { @@ -207,15 +212,16 @@ impl<'a, 'tcx> CastCheck<'tcx> { cast_ty: Ty<'tcx>, cast_span: Span, span: Span, + constness: hir::Constness, ) -> Result<CastCheck<'tcx>, ErrorGuaranteed> { let expr_span = expr.span.find_ancestor_inside(span).unwrap_or(expr.span); - let check = CastCheck { expr, expr_ty, expr_span, cast_ty, cast_span, span }; + let check = CastCheck { expr, expr_ty, expr_span, cast_ty, cast_span, span, constness }; // For better error messages, check for some obviously unsized // cases now. We do a more thorough check at the end, once // inference is more completely known. match cast_ty.kind() { - ty::Dynamic(..) | ty::Slice(..) => { + ty::Dynamic(_, _, ty::Dyn) | ty::Slice(..) => { let reported = check.report_cast_to_unsized_type(fcx); Err(reported) } @@ -523,7 +529,9 @@ impl<'a, 'tcx> CastCheck<'tcx> { err.emit(); } CastError::SizedUnsizedCast => { - use crate::structured_errors::{SizedUnsizedCast, StructuredDiagnostic}; + use rustc_hir_analysis::structured_errors::{ + SizedUnsizedCast, StructuredDiagnostic, + }; SizedUnsizedCast { sess: &fcx.tcx.sess, @@ -591,6 +599,17 @@ impl<'a, 'tcx> CastCheck<'tcx> { } err.emit(); } + CastError::ForeignNonExhaustiveAdt => { + make_invalid_casting_error( + fcx.tcx.sess, + self.span, + self.expr_ty, + self.cast_ty, + fcx, + ) + .note("cannot cast an enum with a non-exhaustive variant when it's defined in another crate") + .emit(); + } } } @@ -670,19 +689,25 @@ impl<'a, 'tcx> CastCheck<'tcx> { } else { ("", lint::builtin::TRIVIAL_CASTS) }; - fcx.tcx.struct_span_lint_hir(lint, self.expr.hir_id, self.span, |err| { - err.build(&format!( - "trivial {}cast: `{}` as `{}`", - adjective, - fcx.ty_to_string(t_expr), - fcx.ty_to_string(t_cast) - )) - .help(&format!( - "cast can be replaced by coercion; this might \ - require {type_asc_or}a temporary variable" - )) - .emit(); - }); + fcx.tcx.struct_span_lint_hir( + lint, + self.expr.hir_id, + self.span, + DelayDm(|| { + format!( + "trivial {}cast: `{}` as `{}`", + adjective, + fcx.ty_to_string(t_expr), + fcx.ty_to_string(t_cast) + ) + }), + |lint| { + lint.help(format!( + "cast can be replaced by coercion; this might \ + require {type_asc_or}a temporary variable" + )) + }, + ); } #[instrument(skip(fcx), level = "debug")] @@ -692,7 +717,7 @@ impl<'a, 'tcx> CastCheck<'tcx> { debug!("check_cast({}, {:?} as {:?})", self.expr.hir_id, self.expr_ty, self.cast_ty); - if !fcx.type_is_known_to_be_sized_modulo_regions(self.cast_ty, self.span) + if !fcx.type_is_sized_modulo_regions(fcx.param_env, self.cast_ty, self.span) && !self.cast_ty.has_infer_types() { self.report_cast_to_unsized_type(fcx); @@ -789,6 +814,14 @@ impl<'a, 'tcx> CastCheck<'tcx> { _ => return Err(CastError::NonScalar), }; + if let ty::Adt(adt_def, _) = *self.expr_ty.kind() { + if adt_def.did().krate != LOCAL_CRATE { + if adt_def.variants().iter().any(VariantDef::is_field_list_non_exhaustive) { + return Err(CastError::ForeignNonExhaustiveAdt); + } + } + } + match (t_from, t_cast) { // These types have invariants! can't cast into them. (_, Int(CEnum) | FnPtr) => Err(CastError::NonScalar), @@ -835,6 +868,14 @@ impl<'a, 'tcx> CastCheck<'tcx> { (Int(Char) | Int(Bool), Int(_)) => Ok(CastKind::PrimIntCast), (Int(_) | Float, Int(_) | Float) => Ok(CastKind::NumericCast), + + (_, DynStar) | (DynStar, _) => { + if fcx.tcx.features().dyn_star { + bug!("should be handled by `try_coerce`") + } else { + Err(CastError::IllegalCast) + } + } } } @@ -976,12 +1017,12 @@ impl<'a, 'tcx> CastCheck<'tcx> { lint::builtin::CENUM_IMPL_DROP_CAST, self.expr.hir_id, self.span, - |err| { - err.build(&format!( - "cannot cast enum `{}` into integer `{}` because it implements `Drop`", - self.expr_ty, self.cast_ty - )) - .emit(); + DelayDm(|| format!( + "cannot cast enum `{}` into integer `{}` because it implements `Drop`", + self.expr_ty, self.cast_ty + )), + |lint| { + lint }, ); } @@ -992,12 +1033,11 @@ impl<'a, 'tcx> CastCheck<'tcx> { lint::builtin::LOSSY_PROVENANCE_CASTS, self.expr.hir_id, self.span, - |err| { - let mut err = err.build(&format!( + DelayDm(|| format!( "under strict provenance it is considered bad style to cast pointer `{}` to integer `{}`", self.expr_ty, self.cast_ty - )); - + )), + |lint| { let msg = "use `.addr()` to obtain the address of a pointer"; let expr_prec = self.expr.precedence().order(); @@ -1016,9 +1056,9 @@ impl<'a, 'tcx> CastCheck<'tcx> { (cast_span, format!(").addr(){scalar_cast}")), ]; - err.multipart_suggestion(msg, suggestions, Applicability::MaybeIncorrect); + lint.multipart_suggestion(msg, suggestions, Applicability::MaybeIncorrect); } else { - err.span_suggestion( + lint.span_suggestion( cast_span, msg, format!(".addr(){scalar_cast}"), @@ -1026,12 +1066,12 @@ impl<'a, 'tcx> CastCheck<'tcx> { ); } - err.help( + lint.help( "if you can't comply with strict provenance and need to expose the pointer \ provenance you can use `.expose_addr()` instead" ); - err.emit(); + lint }, ); } @@ -1041,32 +1081,25 @@ impl<'a, 'tcx> CastCheck<'tcx> { lint::builtin::FUZZY_PROVENANCE_CASTS, self.expr.hir_id, self.span, - |err| { - let mut err = err.build(&format!( - "strict provenance disallows casting integer `{}` to pointer `{}`", - self.expr_ty, self.cast_ty - )); + DelayDm(|| format!( + "strict provenance disallows casting integer `{}` to pointer `{}`", + self.expr_ty, self.cast_ty + )), + |lint| { let msg = "use `.with_addr()` to adjust a valid pointer in the same allocation, to this address"; let suggestions = vec![ (self.expr_span.shrink_to_lo(), String::from("(...).with_addr(")), (self.expr_span.shrink_to_hi().to(self.cast_span), String::from(")")), ]; - err.multipart_suggestion(msg, suggestions, Applicability::MaybeIncorrect); - err.help( + lint.multipart_suggestion(msg, suggestions, Applicability::MaybeIncorrect); + lint.help( "if you can't comply with strict provenance and don't have a pointer with \ the correct provenance you can use `std::ptr::from_exposed_addr()` instead" ); - err.emit(); + lint }, ); } } - -impl<'a, 'tcx> FnCtxt<'a, 'tcx> { - fn type_is_known_to_be_sized_modulo_regions(&self, ty: Ty<'tcx>, span: Span) -> bool { - let lang_item = self.tcx.require_lang_item(LangItem::Sized, None); - traits::type_known_to_meet_bound_modulo_regions(self, self.param_env, ty, lang_item, span) - } -} diff --git a/compiler/rustc_hir_typeck/src/check.rs b/compiler/rustc_hir_typeck/src/check.rs new file mode 100644 index 000000000..7f76364e1 --- /dev/null +++ b/compiler/rustc_hir_typeck/src/check.rs @@ -0,0 +1,324 @@ +use crate::coercion::CoerceMany; +use crate::gather_locals::GatherLocalsVisitor; +use crate::{FnCtxt, Inherited}; +use crate::{GeneratorTypes, UnsafetyState}; +use rustc_hir as hir; +use rustc_hir::def::DefKind; +use rustc_hir::intravisit::Visitor; +use rustc_hir::lang_items::LangItem; +use rustc_hir::{ImplicitSelfKind, ItemKind, Node}; +use rustc_hir_analysis::check::fn_maybe_err; +use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind}; +use rustc_infer::infer::RegionVariableOrigin; +use rustc_middle::ty::{self, Ty, TyCtxt}; +use rustc_span::def_id::LocalDefId; +use rustc_target::spec::abi::Abi; +use rustc_trait_selection::traits; +use std::cell::RefCell; + +/// Helper used for fns and closures. Does the grungy work of checking a function +/// body and returns the function context used for that purpose, since in the case of a fn item +/// there is still a bit more to do. +/// +/// * ... +/// * inherited: other fields inherited from the enclosing fn (if any) +#[instrument(skip(inherited, body), level = "debug")] +pub(super) fn check_fn<'a, 'tcx>( + inherited: &'a Inherited<'tcx>, + param_env: ty::ParamEnv<'tcx>, + fn_sig: ty::FnSig<'tcx>, + decl: &'tcx hir::FnDecl<'tcx>, + fn_id: hir::HirId, + body: &'tcx hir::Body<'tcx>, + can_be_generator: Option<hir::Movability>, + return_type_pre_known: bool, +) -> (FnCtxt<'a, 'tcx>, Option<GeneratorTypes<'tcx>>) { + // Create the function context. This is either derived from scratch or, + // in the case of closures, based on the outer context. + let mut fcx = FnCtxt::new(inherited, param_env, body.value.hir_id); + fcx.ps.set(UnsafetyState::function(fn_sig.unsafety, fn_id)); + fcx.return_type_pre_known = return_type_pre_known; + + let tcx = fcx.tcx; + let hir = tcx.hir(); + + let declared_ret_ty = fn_sig.output(); + + let ret_ty = + fcx.register_infer_ok_obligations(fcx.infcx.replace_opaque_types_with_inference_vars( + declared_ret_ty, + body.value.hir_id, + decl.output.span(), + param_env, + )); + // If we replaced declared_ret_ty with infer vars, then we must be inferring + // an opaque type, so set a flag so we can improve diagnostics. + fcx.return_type_has_opaque = ret_ty != declared_ret_ty; + + fcx.ret_coercion = Some(RefCell::new(CoerceMany::new(ret_ty))); + + let span = body.value.span; + + fn_maybe_err(tcx, span, fn_sig.abi); + + if fn_sig.abi == Abi::RustCall { + let expected_args = if let ImplicitSelfKind::None = decl.implicit_self { 1 } else { 2 }; + + let err = || { + let item = match tcx.hir().get(fn_id) { + Node::Item(hir::Item { kind: ItemKind::Fn(header, ..), .. }) => Some(header), + Node::ImplItem(hir::ImplItem { + kind: hir::ImplItemKind::Fn(header, ..), .. + }) => Some(header), + Node::TraitItem(hir::TraitItem { + kind: hir::TraitItemKind::Fn(header, ..), + .. + }) => Some(header), + // Closures are RustCall, but they tuple their arguments, so shouldn't be checked + Node::Expr(hir::Expr { kind: hir::ExprKind::Closure { .. }, .. }) => None, + node => bug!("Item being checked wasn't a function/closure: {:?}", node), + }; + + if let Some(header) = item { + tcx.sess.span_err(header.span, "functions with the \"rust-call\" ABI must take a single non-self argument that is a tuple"); + } + }; + + if fn_sig.inputs().len() != expected_args { + err() + } else { + // FIXME(CraftSpider) Add a check on parameter expansion, so we don't just make the ICE happen later on + // This will probably require wide-scale changes to support a TupleKind obligation + // We can't resolve this without knowing the type of the param + if !matches!(fn_sig.inputs()[expected_args - 1].kind(), ty::Tuple(_) | ty::Param(_)) { + err() + } + } + } + + if body.generator_kind.is_some() && can_be_generator.is_some() { + let yield_ty = fcx + .next_ty_var(TypeVariableOrigin { kind: TypeVariableOriginKind::TypeInference, span }); + fcx.require_type_is_sized(yield_ty, span, traits::SizedYieldType); + + // Resume type defaults to `()` if the generator has no argument. + let resume_ty = fn_sig.inputs().get(0).copied().unwrap_or_else(|| tcx.mk_unit()); + + fcx.resume_yield_tys = Some((resume_ty, yield_ty)); + } + + GatherLocalsVisitor::new(&fcx).visit_body(body); + + // C-variadic fns also have a `VaList` input that's not listed in `fn_sig` + // (as it's created inside the body itself, not passed in from outside). + let maybe_va_list = if fn_sig.c_variadic { + let span = body.params.last().unwrap().span; + let va_list_did = tcx.require_lang_item(LangItem::VaList, Some(span)); + let region = fcx.next_region_var(RegionVariableOrigin::MiscVariable(span)); + + Some(tcx.bound_type_of(va_list_did).subst(tcx, &[region.into()])) + } else { + None + }; + + // Add formal parameters. + let inputs_hir = hir.fn_decl_by_hir_id(fn_id).map(|decl| &decl.inputs); + let inputs_fn = fn_sig.inputs().iter().copied(); + for (idx, (param_ty, param)) in inputs_fn.chain(maybe_va_list).zip(body.params).enumerate() { + // Check the pattern. + let ty_span = try { inputs_hir?.get(idx)?.span }; + fcx.check_pat_top(¶m.pat, param_ty, ty_span, false); + + // Check that argument is Sized. + // The check for a non-trivial pattern is a hack to avoid duplicate warnings + // for simple cases like `fn foo(x: Trait)`, + // where we would error once on the parameter as a whole, and once on the binding `x`. + if param.pat.simple_ident().is_none() && !tcx.features().unsized_fn_params { + fcx.require_type_is_sized(param_ty, param.pat.span, traits::SizedArgumentType(ty_span)); + } + + fcx.write_ty(param.hir_id, param_ty); + } + + inherited.typeck_results.borrow_mut().liberated_fn_sigs_mut().insert(fn_id, fn_sig); + + fcx.in_tail_expr = true; + if let ty::Dynamic(..) = declared_ret_ty.kind() { + // FIXME: We need to verify that the return type is `Sized` after the return expression has + // been evaluated so that we have types available for all the nodes being returned, but that + // requires the coerced evaluated type to be stored. Moving `check_return_expr` before this + // causes unsized errors caused by the `declared_ret_ty` to point at the return expression, + // while keeping the current ordering we will ignore the tail expression's type because we + // don't know it yet. We can't do `check_expr_kind` while keeping `check_return_expr` + // because we will trigger "unreachable expression" lints unconditionally. + // Because of all of this, we perform a crude check to know whether the simplest `!Sized` + // case that a newcomer might make, returning a bare trait, and in that case we populate + // the tail expression's type so that the suggestion will be correct, but ignore all other + // possible cases. + fcx.check_expr(&body.value); + fcx.require_type_is_sized(declared_ret_ty, decl.output.span(), traits::SizedReturnType); + } else { + fcx.require_type_is_sized(declared_ret_ty, decl.output.span(), traits::SizedReturnType); + fcx.check_return_expr(&body.value, false); + } + fcx.in_tail_expr = false; + + // We insert the deferred_generator_interiors entry after visiting the body. + // This ensures that all nested generators appear before the entry of this generator. + // resolve_generator_interiors relies on this property. + let gen_ty = if let (Some(_), Some(gen_kind)) = (can_be_generator, body.generator_kind) { + let interior = fcx + .next_ty_var(TypeVariableOrigin { kind: TypeVariableOriginKind::MiscVariable, span }); + fcx.deferred_generator_interiors.borrow_mut().push((body.id(), interior, gen_kind)); + + let (resume_ty, yield_ty) = fcx.resume_yield_tys.unwrap(); + Some(GeneratorTypes { + resume_ty, + yield_ty, + interior, + movability: can_be_generator.unwrap(), + }) + } else { + None + }; + + // Finalize the return check by taking the LUB of the return types + // we saw and assigning it to the expected return type. This isn't + // really expected to fail, since the coercions would have failed + // earlier when trying to find a LUB. + let coercion = fcx.ret_coercion.take().unwrap().into_inner(); + let mut actual_return_ty = coercion.complete(&fcx); + debug!("actual_return_ty = {:?}", actual_return_ty); + if let ty::Dynamic(..) = declared_ret_ty.kind() { + // We have special-cased the case where the function is declared + // `-> dyn Foo` and we don't actually relate it to the + // `fcx.ret_coercion`, so just substitute a type variable. + actual_return_ty = + fcx.next_ty_var(TypeVariableOrigin { kind: TypeVariableOriginKind::DynReturnFn, span }); + debug!("actual_return_ty replaced with {:?}", actual_return_ty); + } + + // HACK(oli-obk, compiler-errors): We should be comparing this against + // `declared_ret_ty`, but then anything uninferred would be inferred to + // the opaque type itself. That again would cause writeback to assume + // we have a recursive call site and do the sadly stabilized fallback to `()`. + fcx.demand_suptype(span, ret_ty, actual_return_ty); + + // Check that a function marked as `#[panic_handler]` has signature `fn(&PanicInfo) -> !` + if let Some(panic_impl_did) = tcx.lang_items().panic_impl() + && panic_impl_did == hir.local_def_id(fn_id).to_def_id() + { + check_panic_info_fn(tcx, panic_impl_did.expect_local(), fn_sig, decl, declared_ret_ty); + } + + // Check that a function marked as `#[alloc_error_handler]` has signature `fn(Layout) -> !` + if let Some(alloc_error_handler_did) = tcx.lang_items().oom() + && alloc_error_handler_did == hir.local_def_id(fn_id).to_def_id() + { + check_alloc_error_fn(tcx, alloc_error_handler_did.expect_local(), fn_sig, decl, declared_ret_ty); + } + + (fcx, gen_ty) +} + +fn check_panic_info_fn( + tcx: TyCtxt<'_>, + fn_id: LocalDefId, + fn_sig: ty::FnSig<'_>, + decl: &hir::FnDecl<'_>, + declared_ret_ty: Ty<'_>, +) { + let Some(panic_info_did) = tcx.lang_items().panic_info() else { + tcx.sess.err("language item required, but not found: `panic_info`"); + return; + }; + + if *declared_ret_ty.kind() != ty::Never { + tcx.sess.span_err(decl.output.span(), "return type should be `!`"); + } + + let inputs = fn_sig.inputs(); + if inputs.len() != 1 { + tcx.sess.span_err(tcx.def_span(fn_id), "function should have one argument"); + return; + } + + let arg_is_panic_info = match *inputs[0].kind() { + ty::Ref(region, ty, mutbl) => match *ty.kind() { + ty::Adt(ref adt, _) => { + adt.did() == panic_info_did && mutbl == hir::Mutability::Not && !region.is_static() + } + _ => false, + }, + _ => false, + }; + + if !arg_is_panic_info { + tcx.sess.span_err(decl.inputs[0].span, "argument should be `&PanicInfo`"); + } + + let DefKind::Fn = tcx.def_kind(fn_id) else { + let span = tcx.def_span(fn_id); + tcx.sess.span_err(span, "should be a function"); + return; + }; + + let generic_counts = tcx.generics_of(fn_id).own_counts(); + if generic_counts.types != 0 { + let span = tcx.def_span(fn_id); + tcx.sess.span_err(span, "should have no type parameters"); + } + if generic_counts.consts != 0 { + let span = tcx.def_span(fn_id); + tcx.sess.span_err(span, "should have no const parameters"); + } +} + +fn check_alloc_error_fn( + tcx: TyCtxt<'_>, + fn_id: LocalDefId, + fn_sig: ty::FnSig<'_>, + decl: &hir::FnDecl<'_>, + declared_ret_ty: Ty<'_>, +) { + let Some(alloc_layout_did) = tcx.lang_items().alloc_layout() else { + tcx.sess.err("language item required, but not found: `alloc_layout`"); + return; + }; + + if *declared_ret_ty.kind() != ty::Never { + tcx.sess.span_err(decl.output.span(), "return type should be `!`"); + } + + let inputs = fn_sig.inputs(); + if inputs.len() != 1 { + tcx.sess.span_err(tcx.def_span(fn_id), "function should have one argument"); + return; + } + + let arg_is_alloc_layout = match inputs[0].kind() { + ty::Adt(ref adt, _) => adt.did() == alloc_layout_did, + _ => false, + }; + + if !arg_is_alloc_layout { + tcx.sess.span_err(decl.inputs[0].span, "argument should be `Layout`"); + } + + let DefKind::Fn = tcx.def_kind(fn_id) else { + let span = tcx.def_span(fn_id); + tcx.sess.span_err(span, "`#[alloc_error_handler]` should be a function"); + return; + }; + + let generic_counts = tcx.generics_of(fn_id).own_counts(); + if generic_counts.types != 0 { + let span = tcx.def_span(fn_id); + tcx.sess.span_err(span, "`#[alloc_error_handler]` function should have no type parameters"); + } + if generic_counts.consts != 0 { + let span = tcx.def_span(fn_id); + tcx.sess + .span_err(span, "`#[alloc_error_handler]` function should have no const parameters"); + } +} diff --git a/compiler/rustc_hir_typeck/src/closure.rs b/compiler/rustc_hir_typeck/src/closure.rs new file mode 100644 index 000000000..a5a45f75e --- /dev/null +++ b/compiler/rustc_hir_typeck/src/closure.rs @@ -0,0 +1,824 @@ +//! Code for type-checking closure expressions. + +use super::{check_fn, Expectation, FnCtxt, GeneratorTypes}; + +use hir::def::DefKind; +use rustc_hir as hir; +use rustc_hir::def_id::DefId; +use rustc_hir::lang_items::LangItem; +use rustc_hir_analysis::astconv::AstConv; +use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind}; +use rustc_infer::infer::LateBoundRegionConversionTime; +use rustc_infer::infer::{InferOk, InferResult}; +use rustc_middle::ty::subst::InternalSubsts; +use rustc_middle::ty::visit::TypeVisitable; +use rustc_middle::ty::{self, Ty}; +use rustc_span::source_map::Span; +use rustc_target::spec::abi::Abi; +use rustc_trait_selection::traits::error_reporting::ArgKind; +use rustc_trait_selection::traits::error_reporting::InferCtxtExt as _; +use std::cmp; +use std::iter; + +/// What signature do we *expect* the closure to have from context? +#[derive(Debug)] +struct ExpectedSig<'tcx> { + /// Span that gave us this expectation, if we know that. + cause_span: Option<Span>, + sig: ty::PolyFnSig<'tcx>, +} + +struct ClosureSignatures<'tcx> { + /// The signature users of the closure see. + bound_sig: ty::PolyFnSig<'tcx>, + /// The signature within the function body. + /// This mostly differs in the sense that lifetimes are now early bound and any + /// opaque types from the signature expectation are overriden in case there are + /// explicit hidden types written by the user in the closure signature. + liberated_sig: ty::FnSig<'tcx>, +} + +impl<'a, 'tcx> FnCtxt<'a, 'tcx> { + #[instrument(skip(self, expr, _capture, decl, body_id), level = "debug")] + pub fn check_expr_closure( + &self, + expr: &hir::Expr<'_>, + _capture: hir::CaptureBy, + decl: &'tcx hir::FnDecl<'tcx>, + body_id: hir::BodyId, + gen: Option<hir::Movability>, + expected: Expectation<'tcx>, + ) -> Ty<'tcx> { + trace!("decl = {:#?}", decl); + trace!("expr = {:#?}", expr); + + // It's always helpful for inference if we know the kind of + // closure sooner rather than later, so first examine the expected + // type, and see if can glean a closure kind from there. + let (expected_sig, expected_kind) = match expected.to_option(self) { + Some(ty) => self.deduce_expectations_from_expected_type(ty), + None => (None, None), + }; + let body = self.tcx.hir().body(body_id); + self.check_closure(expr, expected_kind, decl, body, gen, expected_sig) + } + + #[instrument(skip(self, expr, body, decl), level = "debug", ret)] + fn check_closure( + &self, + expr: &hir::Expr<'_>, + opt_kind: Option<ty::ClosureKind>, + decl: &'tcx hir::FnDecl<'tcx>, + body: &'tcx hir::Body<'tcx>, + gen: Option<hir::Movability>, + expected_sig: Option<ExpectedSig<'tcx>>, + ) -> Ty<'tcx> { + trace!("decl = {:#?}", decl); + let expr_def_id = self.tcx.hir().local_def_id(expr.hir_id); + debug!(?expr_def_id); + + let ClosureSignatures { bound_sig, liberated_sig } = + self.sig_of_closure(expr.hir_id, expr_def_id.to_def_id(), decl, body, expected_sig); + + debug!(?bound_sig, ?liberated_sig); + + let return_type_pre_known = !liberated_sig.output().is_ty_infer(); + + let generator_types = check_fn( + self, + self.param_env.without_const(), + liberated_sig, + decl, + expr.hir_id, + body, + gen, + return_type_pre_known, + ) + .1; + + let parent_substs = InternalSubsts::identity_for_item( + self.tcx, + self.tcx.typeck_root_def_id(expr_def_id.to_def_id()), + ); + + let tupled_upvars_ty = self.next_ty_var(TypeVariableOrigin { + kind: TypeVariableOriginKind::ClosureSynthetic, + span: self.tcx.hir().span(expr.hir_id), + }); + + if let Some(GeneratorTypes { resume_ty, yield_ty, interior, movability }) = generator_types + { + let generator_substs = ty::GeneratorSubsts::new( + self.tcx, + ty::GeneratorSubstsParts { + parent_substs, + resume_ty, + yield_ty, + return_ty: liberated_sig.output(), + witness: interior, + tupled_upvars_ty, + }, + ); + + return self.tcx.mk_generator( + expr_def_id.to_def_id(), + generator_substs.substs, + movability, + ); + } + + // Tuple up the arguments and insert the resulting function type into + // the `closures` table. + let sig = bound_sig.map_bound(|sig| { + self.tcx.mk_fn_sig( + iter::once(self.tcx.intern_tup(sig.inputs())), + sig.output(), + sig.c_variadic, + sig.unsafety, + sig.abi, + ) + }); + + debug!(?sig, ?opt_kind); + + let closure_kind_ty = match opt_kind { + Some(kind) => kind.to_ty(self.tcx), + + // Create a type variable (for now) to represent the closure kind. + // It will be unified during the upvar inference phase (`upvar.rs`) + None => self.next_ty_var(TypeVariableOrigin { + // FIXME(eddyb) distinguish closure kind inference variables from the rest. + kind: TypeVariableOriginKind::ClosureSynthetic, + span: expr.span, + }), + }; + + let closure_substs = ty::ClosureSubsts::new( + self.tcx, + ty::ClosureSubstsParts { + parent_substs, + closure_kind_ty, + closure_sig_as_fn_ptr_ty: self.tcx.mk_fn_ptr(sig), + tupled_upvars_ty, + }, + ); + + self.tcx.mk_closure(expr_def_id.to_def_id(), closure_substs.substs) + } + + /// Given the expected type, figures out what it can about this closure we + /// are about to type check: + #[instrument(skip(self), level = "debug")] + fn deduce_expectations_from_expected_type( + &self, + expected_ty: Ty<'tcx>, + ) -> (Option<ExpectedSig<'tcx>>, Option<ty::ClosureKind>) { + match *expected_ty.kind() { + ty::Opaque(def_id, substs) => { + let bounds = self.tcx.bound_explicit_item_bounds(def_id); + let sig = + bounds.subst_iter_copied(self.tcx, substs).find_map(|(pred, span)| match pred + .kind() + .skip_binder() + { + ty::PredicateKind::Projection(proj_predicate) => self + .deduce_sig_from_projection( + Some(span), + pred.kind().rebind(proj_predicate), + ), + _ => None, + }); + + let kind = bounds + .0 + .iter() + .filter_map(|(pred, _)| match pred.kind().skip_binder() { + ty::PredicateKind::Trait(tp) => { + self.tcx.fn_trait_kind_from_lang_item(tp.def_id()) + } + _ => None, + }) + .fold(None, |best, cur| Some(best.map_or(cur, |best| cmp::min(best, cur)))); + trace!(?sig, ?kind); + (sig, kind) + } + ty::Dynamic(ref object_type, ..) => { + let sig = object_type.projection_bounds().find_map(|pb| { + let pb = pb.with_self_ty(self.tcx, self.tcx.types.trait_object_dummy_self); + self.deduce_sig_from_projection(None, pb) + }); + let kind = object_type + .principal_def_id() + .and_then(|did| self.tcx.fn_trait_kind_from_lang_item(did)); + (sig, kind) + } + ty::Infer(ty::TyVar(vid)) => self.deduce_expectations_from_obligations(vid), + ty::FnPtr(sig) => { + let expected_sig = ExpectedSig { cause_span: None, sig }; + (Some(expected_sig), Some(ty::ClosureKind::Fn)) + } + _ => (None, None), + } + } + + fn deduce_expectations_from_obligations( + &self, + expected_vid: ty::TyVid, + ) -> (Option<ExpectedSig<'tcx>>, Option<ty::ClosureKind>) { + let expected_sig = + self.obligations_for_self_ty(expected_vid).find_map(|(_, obligation)| { + debug!(?obligation.predicate); + + let bound_predicate = obligation.predicate.kind(); + if let ty::PredicateKind::Projection(proj_predicate) = + obligation.predicate.kind().skip_binder() + { + // Given a Projection predicate, we can potentially infer + // the complete signature. + self.deduce_sig_from_projection( + Some(obligation.cause.span), + bound_predicate.rebind(proj_predicate), + ) + } else { + None + } + }); + + // Even if we can't infer the full signature, we may be able to + // infer the kind. This can occur when we elaborate a predicate + // like `F : Fn<A>`. Note that due to subtyping we could encounter + // many viable options, so pick the most restrictive. + let expected_kind = self + .obligations_for_self_ty(expected_vid) + .filter_map(|(tr, _)| self.tcx.fn_trait_kind_from_lang_item(tr.def_id())) + .fold(None, |best, cur| Some(best.map_or(cur, |best| cmp::min(best, cur)))); + + (expected_sig, expected_kind) + } + + /// Given a projection like "<F as Fn(X)>::Result == Y", we can deduce + /// everything we need to know about a closure or generator. + /// + /// The `cause_span` should be the span that caused us to + /// have this expected signature, or `None` if we can't readily + /// know that. + #[instrument(level = "debug", skip(self, cause_span), ret)] + fn deduce_sig_from_projection( + &self, + cause_span: Option<Span>, + projection: ty::PolyProjectionPredicate<'tcx>, + ) -> Option<ExpectedSig<'tcx>> { + let tcx = self.tcx; + + let trait_def_id = projection.trait_def_id(tcx); + + let is_fn = tcx.fn_trait_kind_from_lang_item(trait_def_id).is_some(); + let gen_trait = tcx.require_lang_item(LangItem::Generator, cause_span); + let is_gen = gen_trait == trait_def_id; + if !is_fn && !is_gen { + debug!("not fn or generator"); + return None; + } + + if is_gen { + // Check that we deduce the signature from the `<_ as std::ops::Generator>::Return` + // associated item and not yield. + let return_assoc_item = self.tcx.associated_item_def_ids(gen_trait)[1]; + if return_assoc_item != projection.projection_def_id() { + debug!("not return assoc item of generator"); + return None; + } + } + + let input_tys = if is_fn { + let arg_param_ty = projection.skip_binder().projection_ty.substs.type_at(1); + let arg_param_ty = self.resolve_vars_if_possible(arg_param_ty); + debug!(?arg_param_ty); + + match arg_param_ty.kind() { + &ty::Tuple(tys) => tys, + _ => return None, + } + } else { + // Generators with a `()` resume type may be defined with 0 or 1 explicit arguments, + // else they must have exactly 1 argument. For now though, just give up in this case. + return None; + }; + + // Since this is a return parameter type it is safe to unwrap. + let ret_param_ty = projection.skip_binder().term.ty().unwrap(); + let ret_param_ty = self.resolve_vars_if_possible(ret_param_ty); + debug!(?ret_param_ty); + + let sig = projection.rebind(self.tcx.mk_fn_sig( + input_tys.iter(), + ret_param_ty, + false, + hir::Unsafety::Normal, + Abi::Rust, + )); + + Some(ExpectedSig { cause_span, sig }) + } + + fn sig_of_closure( + &self, + hir_id: hir::HirId, + expr_def_id: DefId, + decl: &hir::FnDecl<'_>, + body: &hir::Body<'_>, + expected_sig: Option<ExpectedSig<'tcx>>, + ) -> ClosureSignatures<'tcx> { + if let Some(e) = expected_sig { + self.sig_of_closure_with_expectation(hir_id, expr_def_id, decl, body, e) + } else { + self.sig_of_closure_no_expectation(hir_id, expr_def_id, decl, body) + } + } + + /// If there is no expected signature, then we will convert the + /// types that the user gave into a signature. + #[instrument(skip(self, hir_id, expr_def_id, decl, body), level = "debug")] + fn sig_of_closure_no_expectation( + &self, + hir_id: hir::HirId, + expr_def_id: DefId, + decl: &hir::FnDecl<'_>, + body: &hir::Body<'_>, + ) -> ClosureSignatures<'tcx> { + let bound_sig = self.supplied_sig_of_closure(hir_id, expr_def_id, decl, body); + + self.closure_sigs(expr_def_id, body, bound_sig) + } + + /// Invoked to compute the signature of a closure expression. This + /// combines any user-provided type annotations (e.g., `|x: u32| + /// -> u32 { .. }`) with the expected signature. + /// + /// The approach is as follows: + /// + /// - Let `S` be the (higher-ranked) signature that we derive from the user's annotations. + /// - Let `E` be the (higher-ranked) signature that we derive from the expectations, if any. + /// - If we have no expectation `E`, then the signature of the closure is `S`. + /// - Otherwise, the signature of the closure is E. Moreover: + /// - Skolemize the late-bound regions in `E`, yielding `E'`. + /// - Instantiate all the late-bound regions bound in the closure within `S` + /// with fresh (existential) variables, yielding `S'` + /// - Require that `E' = S'` + /// - We could use some kind of subtyping relationship here, + /// I imagine, but equality is easier and works fine for + /// our purposes. + /// + /// The key intuition here is that the user's types must be valid + /// from "the inside" of the closure, but the expectation + /// ultimately drives the overall signature. + /// + /// # Examples + /// + /// ```ignore (illustrative) + /// fn with_closure<F>(_: F) + /// where F: Fn(&u32) -> &u32 { .. } + /// + /// with_closure(|x: &u32| { ... }) + /// ``` + /// + /// Here: + /// - E would be `fn(&u32) -> &u32`. + /// - S would be `fn(&u32) -> + /// - E' is `&'!0 u32 -> &'!0 u32` + /// - S' is `&'?0 u32 -> ?T` + /// + /// S' can be unified with E' with `['?0 = '!0, ?T = &'!10 u32]`. + /// + /// # Arguments + /// + /// - `expr_def_id`: the `DefId` of the closure expression + /// - `decl`: the HIR declaration of the closure + /// - `body`: the body of the closure + /// - `expected_sig`: the expected signature (if any). Note that + /// this is missing a binder: that is, there may be late-bound + /// regions with depth 1, which are bound then by the closure. + #[instrument(skip(self, hir_id, expr_def_id, decl, body), level = "debug")] + fn sig_of_closure_with_expectation( + &self, + hir_id: hir::HirId, + expr_def_id: DefId, + decl: &hir::FnDecl<'_>, + body: &hir::Body<'_>, + expected_sig: ExpectedSig<'tcx>, + ) -> ClosureSignatures<'tcx> { + // Watch out for some surprises and just ignore the + // expectation if things don't see to match up with what we + // expect. + if expected_sig.sig.c_variadic() != decl.c_variadic { + return self.sig_of_closure_no_expectation(hir_id, expr_def_id, decl, body); + } else if expected_sig.sig.skip_binder().inputs_and_output.len() != decl.inputs.len() + 1 { + return self.sig_of_closure_with_mismatched_number_of_arguments( + expr_def_id, + decl, + body, + expected_sig, + ); + } + + // Create a `PolyFnSig`. Note the oddity that late bound + // regions appearing free in `expected_sig` are now bound up + // in this binder we are creating. + assert!(!expected_sig.sig.skip_binder().has_vars_bound_above(ty::INNERMOST)); + let bound_sig = expected_sig.sig.map_bound(|sig| { + self.tcx.mk_fn_sig( + sig.inputs().iter().cloned(), + sig.output(), + sig.c_variadic, + hir::Unsafety::Normal, + Abi::RustCall, + ) + }); + + // `deduce_expectations_from_expected_type` introduces + // late-bound lifetimes defined elsewhere, which we now + // anonymize away, so as not to confuse the user. + let bound_sig = self.tcx.anonymize_late_bound_regions(bound_sig); + + let closure_sigs = self.closure_sigs(expr_def_id, body, bound_sig); + + // Up till this point, we have ignored the annotations that the user + // gave. This function will check that they unify successfully. + // Along the way, it also writes out entries for types that the user + // wrote into our typeck results, which are then later used by the privacy + // check. + match self.merge_supplied_sig_with_expectation( + hir_id, + expr_def_id, + decl, + body, + closure_sigs, + ) { + Ok(infer_ok) => self.register_infer_ok_obligations(infer_ok), + Err(_) => self.sig_of_closure_no_expectation(hir_id, expr_def_id, decl, body), + } + } + + fn sig_of_closure_with_mismatched_number_of_arguments( + &self, + expr_def_id: DefId, + decl: &hir::FnDecl<'_>, + body: &hir::Body<'_>, + expected_sig: ExpectedSig<'tcx>, + ) -> ClosureSignatures<'tcx> { + let hir = self.tcx.hir(); + let expr_map_node = hir.get_if_local(expr_def_id).unwrap(); + let expected_args: Vec<_> = expected_sig + .sig + .skip_binder() + .inputs() + .iter() + .map(|ty| ArgKind::from_expected_ty(*ty, None)) + .collect(); + let (closure_span, found_args) = match self.get_fn_like_arguments(expr_map_node) { + Some((sp, args)) => (Some(sp), args), + None => (None, Vec::new()), + }; + let expected_span = + expected_sig.cause_span.unwrap_or_else(|| hir.span_if_local(expr_def_id).unwrap()); + self.report_arg_count_mismatch( + expected_span, + closure_span, + expected_args, + found_args, + true, + ) + .emit(); + + let error_sig = self.error_sig_of_closure(decl); + + self.closure_sigs(expr_def_id, body, error_sig) + } + + /// Enforce the user's types against the expectation. See + /// `sig_of_closure_with_expectation` for details on the overall + /// strategy. + #[instrument(level = "debug", skip(self, hir_id, expr_def_id, decl, body, expected_sigs))] + fn merge_supplied_sig_with_expectation( + &self, + hir_id: hir::HirId, + expr_def_id: DefId, + decl: &hir::FnDecl<'_>, + body: &hir::Body<'_>, + mut expected_sigs: ClosureSignatures<'tcx>, + ) -> InferResult<'tcx, ClosureSignatures<'tcx>> { + // Get the signature S that the user gave. + // + // (See comment on `sig_of_closure_with_expectation` for the + // meaning of these letters.) + let supplied_sig = self.supplied_sig_of_closure(hir_id, expr_def_id, decl, body); + + debug!(?supplied_sig); + + // FIXME(#45727): As discussed in [this comment][c1], naively + // forcing equality here actually results in suboptimal error + // messages in some cases. For now, if there would have been + // an obvious error, we fallback to declaring the type of the + // closure to be the one the user gave, which allows other + // error message code to trigger. + // + // However, I think [there is potential to do even better + // here][c2], since in *this* code we have the precise span of + // the type parameter in question in hand when we report the + // error. + // + // [c1]: https://github.com/rust-lang/rust/pull/45072#issuecomment-341089706 + // [c2]: https://github.com/rust-lang/rust/pull/45072#issuecomment-341096796 + self.commit_if_ok(|_| { + let mut all_obligations = vec![]; + let inputs: Vec<_> = iter::zip( + decl.inputs, + supplied_sig.inputs().skip_binder(), // binder moved to (*) below + ) + .map(|(hir_ty, &supplied_ty)| { + // Instantiate (this part of..) S to S', i.e., with fresh variables. + self.replace_bound_vars_with_fresh_vars( + hir_ty.span, + LateBoundRegionConversionTime::FnCall, + // (*) binder moved to here + supplied_sig.inputs().rebind(supplied_ty), + ) + }) + .collect(); + + // The liberated version of this signature should be a subtype + // of the liberated form of the expectation. + for ((hir_ty, &supplied_ty), expected_ty) in iter::zip( + iter::zip(decl.inputs, &inputs), + expected_sigs.liberated_sig.inputs(), // `liberated_sig` is E'. + ) { + // Check that E' = S'. + let cause = self.misc(hir_ty.span); + let InferOk { value: (), obligations } = + self.at(&cause, self.param_env).eq(*expected_ty, supplied_ty)?; + all_obligations.extend(obligations); + } + + let supplied_output_ty = self.replace_bound_vars_with_fresh_vars( + decl.output.span(), + LateBoundRegionConversionTime::FnCall, + supplied_sig.output(), + ); + let cause = &self.misc(decl.output.span()); + let InferOk { value: (), obligations } = self + .at(cause, self.param_env) + .eq(expected_sigs.liberated_sig.output(), supplied_output_ty)?; + all_obligations.extend(obligations); + + let inputs = inputs.into_iter().map(|ty| self.resolve_vars_if_possible(ty)); + + expected_sigs.liberated_sig = self.tcx.mk_fn_sig( + inputs, + supplied_output_ty, + expected_sigs.liberated_sig.c_variadic, + hir::Unsafety::Normal, + Abi::RustCall, + ); + + Ok(InferOk { value: expected_sigs, obligations: all_obligations }) + }) + } + + /// If there is no expected signature, then we will convert the + /// types that the user gave into a signature. + /// + /// Also, record this closure signature for later. + #[instrument(skip(self, decl, body), level = "debug", ret)] + fn supplied_sig_of_closure( + &self, + hir_id: hir::HirId, + expr_def_id: DefId, + decl: &hir::FnDecl<'_>, + body: &hir::Body<'_>, + ) -> ty::PolyFnSig<'tcx> { + let astconv: &dyn AstConv<'_> = self; + + trace!("decl = {:#?}", decl); + debug!(?body.generator_kind); + + let bound_vars = self.tcx.late_bound_vars(hir_id); + + // First, convert the types that the user supplied (if any). + let supplied_arguments = decl.inputs.iter().map(|a| astconv.ast_ty_to_ty(a)); + let supplied_return = match decl.output { + hir::FnRetTy::Return(ref output) => astconv.ast_ty_to_ty(&output), + hir::FnRetTy::DefaultReturn(_) => match body.generator_kind { + // In the case of the async block that we create for a function body, + // we expect the return type of the block to match that of the enclosing + // function. + Some(hir::GeneratorKind::Async(hir::AsyncGeneratorKind::Fn)) => { + debug!("closure is async fn body"); + self.deduce_future_output_from_obligations(expr_def_id, body.id().hir_id) + .unwrap_or_else(|| { + // AFAIK, deducing the future output + // always succeeds *except* in error cases + // like #65159. I'd like to return Error + // here, but I can't because I can't + // easily (and locally) prove that we + // *have* reported an + // error. --nikomatsakis + astconv.ty_infer(None, decl.output.span()) + }) + } + + _ => astconv.ty_infer(None, decl.output.span()), + }, + }; + + let result = ty::Binder::bind_with_vars( + self.tcx.mk_fn_sig( + supplied_arguments, + supplied_return, + decl.c_variadic, + hir::Unsafety::Normal, + Abi::RustCall, + ), + bound_vars, + ); + // Astconv can't normalize inputs or outputs with escaping bound vars, + // so normalize them here, after we've wrapped them in a binder. + let result = self.normalize_associated_types_in(self.tcx.hir().span(hir_id), result); + + let c_result = self.inh.infcx.canonicalize_response(result); + self.typeck_results.borrow_mut().user_provided_sigs.insert(expr_def_id, c_result); + + result + } + + /// Invoked when we are translating the generator that results + /// from desugaring an `async fn`. Returns the "sugared" return + /// type of the `async fn` -- that is, the return type that the + /// user specified. The "desugared" return type is an `impl + /// Future<Output = T>`, so we do this by searching through the + /// obligations to extract the `T`. + #[instrument(skip(self), level = "debug", ret)] + fn deduce_future_output_from_obligations( + &self, + expr_def_id: DefId, + body_id: hir::HirId, + ) -> Option<Ty<'tcx>> { + let ret_coercion = self.ret_coercion.as_ref().unwrap_or_else(|| { + span_bug!(self.tcx.def_span(expr_def_id), "async fn generator outside of a fn") + }); + + let ret_ty = ret_coercion.borrow().expected_ty(); + let ret_ty = self.inh.infcx.shallow_resolve(ret_ty); + + let get_future_output = |predicate: ty::Predicate<'tcx>, span| { + // Search for a pending obligation like + // + // `<R as Future>::Output = T` + // + // where R is the return type we are expecting. This type `T` + // will be our output. + let bound_predicate = predicate.kind(); + if let ty::PredicateKind::Projection(proj_predicate) = bound_predicate.skip_binder() { + self.deduce_future_output_from_projection( + span, + bound_predicate.rebind(proj_predicate), + ) + } else { + None + } + }; + + let output_ty = match *ret_ty.kind() { + ty::Infer(ty::TyVar(ret_vid)) => { + self.obligations_for_self_ty(ret_vid).find_map(|(_, obligation)| { + get_future_output(obligation.predicate, obligation.cause.span) + })? + } + ty::Opaque(def_id, substs) => self + .tcx + .bound_explicit_item_bounds(def_id) + .subst_iter_copied(self.tcx, substs) + .find_map(|(p, s)| get_future_output(p, s))?, + ty::Error(_) => return None, + ty::Projection(proj) + if self.tcx.def_kind(proj.item_def_id) == DefKind::ImplTraitPlaceholder => + { + self.tcx + .bound_explicit_item_bounds(proj.item_def_id) + .subst_iter_copied(self.tcx, proj.substs) + .find_map(|(p, s)| get_future_output(p, s))? + } + _ => span_bug!( + self.tcx.def_span(expr_def_id), + "async fn generator return type not an inference variable: {ret_ty}" + ), + }; + + // async fn that have opaque types in their return type need to redo the conversion to inference variables + // as they fetch the still opaque version from the signature. + let InferOk { value: output_ty, obligations } = self + .replace_opaque_types_with_inference_vars( + output_ty, + body_id, + self.tcx.def_span(expr_def_id), + self.param_env, + ); + self.register_predicates(obligations); + + Some(output_ty) + } + + /// Given a projection like + /// + /// `<X as Future>::Output = T` + /// + /// where `X` is some type that has no late-bound regions, returns + /// `Some(T)`. If the projection is for some other trait, returns + /// `None`. + fn deduce_future_output_from_projection( + &self, + cause_span: Span, + predicate: ty::PolyProjectionPredicate<'tcx>, + ) -> Option<Ty<'tcx>> { + debug!("deduce_future_output_from_projection(predicate={:?})", predicate); + + // We do not expect any bound regions in our predicate, so + // skip past the bound vars. + let Some(predicate) = predicate.no_bound_vars() else { + debug!("deduce_future_output_from_projection: has late-bound regions"); + return None; + }; + + // Check that this is a projection from the `Future` trait. + let trait_def_id = predicate.projection_ty.trait_def_id(self.tcx); + let future_trait = self.tcx.require_lang_item(LangItem::Future, Some(cause_span)); + if trait_def_id != future_trait { + debug!("deduce_future_output_from_projection: not a future"); + return None; + } + + // The `Future` trait has only one associated item, `Output`, + // so check that this is what we see. + let output_assoc_item = self.tcx.associated_item_def_ids(future_trait)[0]; + if output_assoc_item != predicate.projection_ty.item_def_id { + span_bug!( + cause_span, + "projecting associated item `{:?}` from future, which is not Output `{:?}`", + predicate.projection_ty.item_def_id, + output_assoc_item, + ); + } + + // Extract the type from the projection. Note that there can + // be no bound variables in this type because the "self type" + // does not have any regions in it. + let output_ty = self.resolve_vars_if_possible(predicate.term); + debug!("deduce_future_output_from_projection: output_ty={:?}", output_ty); + // This is a projection on a Fn trait so will always be a type. + Some(output_ty.ty().unwrap()) + } + + /// Converts the types that the user supplied, in case that doing + /// so should yield an error, but returns back a signature where + /// all parameters are of type `TyErr`. + fn error_sig_of_closure(&self, decl: &hir::FnDecl<'_>) -> ty::PolyFnSig<'tcx> { + let astconv: &dyn AstConv<'_> = self; + + let supplied_arguments = decl.inputs.iter().map(|a| { + // Convert the types that the user supplied (if any), but ignore them. + astconv.ast_ty_to_ty(a); + self.tcx.ty_error() + }); + + if let hir::FnRetTy::Return(ref output) = decl.output { + astconv.ast_ty_to_ty(&output); + } + + let result = ty::Binder::dummy(self.tcx.mk_fn_sig( + supplied_arguments, + self.tcx.ty_error(), + decl.c_variadic, + hir::Unsafety::Normal, + Abi::RustCall, + )); + + debug!("supplied_sig_of_closure: result={:?}", result); + + result + } + + fn closure_sigs( + &self, + expr_def_id: DefId, + body: &hir::Body<'_>, + bound_sig: ty::PolyFnSig<'tcx>, + ) -> ClosureSignatures<'tcx> { + let liberated_sig = self.tcx().liberate_late_bound_regions(expr_def_id, bound_sig); + let liberated_sig = self.inh.normalize_associated_types_in( + body.value.span, + body.value.hir_id, + self.param_env, + liberated_sig, + ); + ClosureSignatures { bound_sig, liberated_sig } + } +} diff --git a/compiler/rustc_typeck/src/check/coercion.rs b/compiler/rustc_hir_typeck/src/coercion.rs index 2ed5f569b..86597a703 100644 --- a/compiler/rustc_typeck/src/check/coercion.rs +++ b/compiler/rustc_hir_typeck/src/coercion.rs @@ -35,13 +35,15 @@ //! // and are then unable to coerce `&7i32` to `&mut i32`. //! ``` -use crate::astconv::AstConv; -use crate::check::FnCtxt; +use crate::FnCtxt; use rustc_errors::{ - struct_span_err, Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed, + struct_span_err, Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed, MultiSpan, }; use rustc_hir as hir; use rustc_hir::def_id::DefId; +use rustc_hir::intravisit::{self, Visitor}; +use rustc_hir::Expr; +use rustc_hir_analysis::astconv::AstConv; use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind}; use rustc_infer::infer::{Coercion, InferOk, InferResult}; use rustc_infer::traits::{Obligation, TraitEngine, TraitEngineExt}; @@ -59,7 +61,7 @@ use rustc_span::symbol::sym; use rustc_span::{self, BytePos, DesugaringKind, Span}; use rustc_target::spec::abi::Abi; use rustc_trait_selection::infer::InferCtxtExt as _; -use rustc_trait_selection::traits::error_reporting::InferCtxtExt as _; +use rustc_trait_selection::traits::error_reporting::TypeErrCtxtExt as _; use rustc_trait_selection::traits::{self, ObligationCause, ObligationCauseCode}; use smallvec::{smallvec, SmallVec}; @@ -87,6 +89,19 @@ impl<'a, 'tcx> Deref for Coerce<'a, 'tcx> { type CoerceResult<'tcx> = InferResult<'tcx, (Vec<Adjustment<'tcx>>, Ty<'tcx>)>; +struct CollectRetsVisitor<'tcx> { + ret_exprs: Vec<&'tcx hir::Expr<'tcx>>, +} + +impl<'tcx> Visitor<'tcx> for CollectRetsVisitor<'tcx> { + fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { + if let hir::ExprKind::Ret(_) = expr.kind { + self.ret_exprs.push(expr); + } + intravisit::walk_expr(self, expr); + } +} + /// Coercing a mutable reference to an immutable works, while /// coercing `&T` to `&mut T` should be forbidden. fn coerce_mutbls<'tcx>( @@ -201,6 +216,9 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> { ty::Ref(r_b, _, mutbl_b) => { return self.coerce_borrowed_pointer(a, b, r_b, mutbl_b); } + ty::Dynamic(predicates, region, ty::DynStar) if self.tcx.features().dyn_star => { + return self.coerce_dyn_star(a, b, predicates, region); + } _ => {} } @@ -687,7 +705,12 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> { // Object safety violations or miscellaneous. Err(err) => { - self.report_selection_error(obligation.clone(), &obligation, &err, false); + self.err_ctxt().report_selection_error( + obligation.clone(), + &obligation, + &err, + false, + ); // Treat this like an obligation and follow through // with the unsizing - the lack of a coercion should // be silent, as it causes a type mismatch later. @@ -725,6 +748,63 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> { Ok(coercion) } + fn coerce_dyn_star( + &self, + a: Ty<'tcx>, + b: Ty<'tcx>, + predicates: &'tcx ty::List<ty::Binder<'tcx, ty::ExistentialPredicate<'tcx>>>, + b_region: ty::Region<'tcx>, + ) -> CoerceResult<'tcx> { + if !self.tcx.features().dyn_star { + return Err(TypeError::Mismatch); + } + + if let ty::Dynamic(a_data, _, _) = a.kind() + && let ty::Dynamic(b_data, _, _) = b.kind() + { + if a_data.principal_def_id() == b_data.principal_def_id() { + return self.unify_and(a, b, |_| vec![]); + } else if !self.tcx().features().trait_upcasting { + let mut err = feature_err( + &self.tcx.sess.parse_sess, + sym::trait_upcasting, + self.cause.span, + &format!( + "cannot cast `{a}` to `{b}`, trait upcasting coercion is experimental" + ), + ); + err.emit(); + } + } + + // Check the obligations of the cast -- for example, when casting + // `usize` to `dyn* Clone + 'static`: + let obligations = predicates + .iter() + .map(|predicate| { + // For each existential predicate (e.g., `?Self: Clone`) substitute + // the type of the expression (e.g., `usize` in our example above) + // and then require that the resulting predicate (e.g., `usize: Clone`) + // holds (it does). + let predicate = predicate.with_self_ty(self.tcx, a); + Obligation::new(self.cause.clone(), self.param_env, predicate) + }) + // Enforce the region bound (e.g., `usize: 'static`, in our example). + .chain([Obligation::new( + self.cause.clone(), + self.param_env, + self.tcx.mk_predicate(ty::Binder::dummy(ty::PredicateKind::TypeOutlives( + ty::OutlivesPredicate(a, b_region), + ))), + )]) + .collect(); + + Ok(InferOk { + value: (vec![Adjustment { kind: Adjust::DynStar, target: b }], b), + obligations, + }) + } + fn coerce_from_safe_fn<F, G>( &self, a: Ty<'tcx>, @@ -1464,23 +1544,29 @@ impl<'tcx, 'exprs, E: AsCoercionSite> CoerceMany<'tcx, 'exprs, E> { } } Err(coercion_error) => { + // Mark that we've failed to coerce the types here to suppress + // any superfluous errors we might encounter while trying to + // emit or provide suggestions on how to fix the initial error. + fcx.set_tainted_by_errors(); let (expected, found) = if label_expression_as_expected { // In the case where this is a "forced unit", like // `break`, we want to call the `()` "expected" // since it is implied by the syntax. // (Note: not all force-units work this way.)" - (expression_ty, self.final_ty.unwrap_or(self.expected_ty)) + (expression_ty, self.merged_ty()) } else { // Otherwise, the "expected" type for error // reporting is the current unification type, // which is basically the LUB of the expressions // we've seen so far (combined with the expected // type) - (self.final_ty.unwrap_or(self.expected_ty), expression_ty) + (self.merged_ty(), expression_ty) }; + let (expected, found) = fcx.resolve_vars_if_possible((expected, found)); let mut err; let mut unsized_return = false; + let mut visitor = CollectRetsVisitor { ret_exprs: vec![] }; match *cause.code() { ObligationCauseCode::ReturnNoExpression => { err = struct_span_err!( @@ -1506,6 +1592,10 @@ impl<'tcx, 'exprs, E: AsCoercionSite> CoerceMany<'tcx, 'exprs, E> { if !fcx.tcx.features().unsized_locals { unsized_return = self.is_return_ty_unsized(fcx, blk_id); } + if let Some(expression) = expression + && let hir::ExprKind::Loop(loop_blk, ..) = expression.kind { + intravisit::walk_block(& mut visitor, loop_blk); + } } ObligationCauseCode::ReturnValue(id) => { err = self.report_return_mismatched_types( @@ -1524,7 +1614,7 @@ impl<'tcx, 'exprs, E: AsCoercionSite> CoerceMany<'tcx, 'exprs, E> { } } _ => { - err = fcx.report_mismatched_types( + err = fcx.err_ctxt().report_mismatched_types( cause, expected, found, @@ -1551,12 +1641,47 @@ impl<'tcx, 'exprs, E: AsCoercionSite> CoerceMany<'tcx, 'exprs, E> { ); } + if visitor.ret_exprs.len() > 0 && let Some(expr) = expression { + self.note_unreachable_loop_return(&mut err, &expr, &visitor.ret_exprs); + } err.emit_unless(unsized_return); self.final_ty = Some(fcx.tcx.ty_error()); } } } + fn note_unreachable_loop_return( + &self, + err: &mut Diagnostic, + expr: &hir::Expr<'tcx>, + ret_exprs: &Vec<&'tcx hir::Expr<'tcx>>, + ) { + let hir::ExprKind::Loop(_, _, _, loop_span) = expr.kind else { return;}; + let mut span: MultiSpan = vec![loop_span].into(); + span.push_span_label(loop_span, "this might have zero elements to iterate on"); + const MAXITER: usize = 3; + let iter = ret_exprs.iter().take(MAXITER); + for ret_expr in iter { + span.push_span_label( + ret_expr.span, + "if the loop doesn't execute, this value would never get returned", + ); + } + err.span_note( + span, + "the function expects a value to always be returned, but loops might run zero times", + ); + if MAXITER < ret_exprs.len() { + err.note(&format!( + "if the loop doesn't execute, {} other values would never get returned", + ret_exprs.len() - MAXITER + )); + } + err.help( + "return a value for the case when the loop has zero elements to iterate on, or \ + consider changing the return type to account for that possibility", + ); + } fn report_return_mismatched_types<'a>( &self, @@ -1569,7 +1694,7 @@ impl<'tcx, 'exprs, E: AsCoercionSite> CoerceMany<'tcx, 'exprs, E> { expression: Option<&'tcx hir::Expr<'tcx>>, blk_id: Option<hir::HirId>, ) -> DiagnosticBuilder<'a, ErrorGuaranteed> { - let mut err = fcx.report_mismatched_types(cause, expected, found, ty_err); + let mut err = fcx.err_ctxt().report_mismatched_types(cause, expected, found, ty_err); let mut pointing_at_return_type = false; let mut fn_output = None; @@ -1623,7 +1748,7 @@ impl<'tcx, 'exprs, E: AsCoercionSite> CoerceMany<'tcx, 'exprs, E> { expected, found, can_suggest, - fcx.tcx.hir().local_def_id_to_hir_id(fcx.tcx.hir().get_parent_item(id)), + fcx.tcx.hir().get_parent_item(id).into(), ); } if !pointing_at_return_type { @@ -1632,7 +1757,7 @@ impl<'tcx, 'exprs, E: AsCoercionSite> CoerceMany<'tcx, 'exprs, E> { } let parent_id = fcx.tcx.hir().get_parent_item(id); - let parent_item = fcx.tcx.hir().get_by_def_id(parent_id); + let parent_item = fcx.tcx.hir().get_by_def_id(parent_id.def_id); if let (Some(expr), Some(_), Some((fn_decl, _, _))) = (expression, blk_id, fcx.get_node_fn_decl(parent_item)) @@ -1644,13 +1769,34 @@ impl<'tcx, 'exprs, E: AsCoercionSite> CoerceMany<'tcx, 'exprs, E> { expected, found, id, - fcx.tcx.hir().local_def_id_to_hir_id(parent_id), + parent_id.into(), + ); + } + + let ret_coercion_span = fcx.ret_coercion_span.get(); + + if let Some(sp) = ret_coercion_span + // If the closure has an explicit return type annotation, or if + // the closure's return type has been inferred from outside + // requirements (such as an Fn* trait bound), then a type error + // may occur at the first return expression we see in the closure + // (if it conflicts with the declared return type). Skip adding a + // note in this case, since it would be incorrect. + && !fcx.return_type_pre_known + { + err.span_note( + sp, + &format!( + "return type inferred to be `{}` here", + expected + ), ); } - if let (Some(sp), Some(fn_output)) = (fcx.ret_coercion_span.get(), fn_output) { + if let (Some(sp), Some(fn_output)) = (ret_coercion_span, fn_output) { self.add_impl_trait_explanation(&mut err, cause, fcx, expected, sp, fn_output); } + err } diff --git a/compiler/rustc_typeck/src/check/demand.rs b/compiler/rustc_hir_typeck/src/demand.rs index 4de48dc5b..16febfc46 100644 --- a/compiler/rustc_typeck/src/check/demand.rs +++ b/compiler/rustc_hir_typeck/src/demand.rs @@ -1,21 +1,20 @@ -use crate::check::FnCtxt; -use rustc_infer::infer::InferOk; -use rustc_middle::middle::stability::EvalResult; -use rustc_trait_selection::infer::InferCtxtExt as _; -use rustc_trait_selection::traits::ObligationCause; - +use crate::FnCtxt; use rustc_ast::util::parser::PREC_POSTFIX; use rustc_errors::{Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed}; use rustc_hir as hir; use rustc_hir::lang_items::LangItem; use rustc_hir::{is_range_literal, Node}; +use rustc_infer::infer::InferOk; use rustc_middle::lint::in_external_macro; +use rustc_middle::middle::stability::EvalResult; use rustc_middle::ty::adjustment::AllowTwoPhase; use rustc_middle::ty::error::{ExpectedFound, TypeError}; use rustc_middle::ty::print::with_no_trimmed_paths; use rustc_middle::ty::{self, Article, AssocItem, Ty, TypeAndMut}; use rustc_span::symbol::{sym, Symbol}; use rustc_span::{BytePos, Span}; +use rustc_trait_selection::infer::InferCtxtExt as _; +use rustc_trait_selection::traits::ObligationCause; use super::method::probe; @@ -32,20 +31,22 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { error: Option<TypeError<'tcx>>, ) { self.annotate_expected_due_to_let_ty(err, expr, error); - 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); - if self.suggest_calling_boxed_future_when_appropriate(err, expr, expected, expr_ty) { - return; - } - self.suggest_no_capture_closure(err, expected, expr_ty); - self.suggest_boxing_when_appropriate(err, expr, expected, expr_ty); - self.suggest_missing_parentheses(err, expr); - self.suggest_block_to_brackets_peeling_refs(err, expr, expr_ty, expected); + + // 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.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.report_closure_inferred_return_type(err, expected); } // Requires that the two types unify, and prints an error message if @@ -77,7 +78,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.register_predicates(obligations); None } - Err(e) => Some(self.report_mismatched_types(&cause, expected, actual, e)), + Err(e) => Some(self.err_ctxt().report_mismatched_types(&cause, expected, actual, e)), } } @@ -107,7 +108,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.register_predicates(obligations); None } - Err(e) => Some(self.report_mismatched_types(cause, expected, actual, e)), + Err(e) => Some(self.err_ctxt().report_mismatched_types(cause, expected, actual, e)), } } @@ -131,7 +132,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { /// /// N.B., this code relies on `self.diverges` to be accurate. In particular, assignments to `!` /// will be permitted if the diverges flag is currently "always". - #[tracing::instrument(level = "debug", skip(self, expr, expected_ty_expr, allow_two_phase))] + #[instrument(level = "debug", skip(self, expr, expected_ty_expr, allow_two_phase))] pub fn demand_coerce_diag( &self, expr: &hir::Expr<'tcx>, @@ -151,7 +152,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let expr = expr.peel_drop_temps(); let cause = self.misc(expr.span); let expr_ty = self.resolve_vars_with_obligations(checked_ty); - let mut err = self.report_mismatched_types(&cause, expected, expr_ty, e.clone()); + let mut err = self.err_ctxt().report_mismatched_types(&cause, expected, expr_ty, e.clone()); let is_insufficiently_polymorphic = matches!(e, TypeError::RegionsInsufficientlyPolymorphic(..)); @@ -286,7 +287,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { expr: &hir::Expr<'_>, expected: Ty<'tcx>, expr_ty: Ty<'tcx>, - ) { + ) -> bool { if let ty::Adt(expected_adt, substs) = expected.kind() { if let hir::ExprKind::Field(base, ident) = expr.kind { let base_ty = self.typeck_results.borrow().expr_ty(base); @@ -299,7 +300,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { "", Applicability::MaybeIncorrect, ); - return + return true; } } @@ -338,7 +339,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } else if self.tcx.is_diagnostic_item(sym::Option, expected_adt.did()) { vec!["None", "Some(())"] } else { - return; + return false; }; if let Some(indent) = self.tcx.sess.source_map().indentation_before(span.shrink_to_lo()) @@ -358,7 +359,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { Applicability::MaybeIncorrect, ); } - return; + return true; } } } @@ -375,7 +376,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let field_is_local = sole_field.did.is_local(); let field_is_accessible = - sole_field.vis.is_accessible_from(expr.hir_id.owner.to_def_id(), self.tcx) + sole_field.vis.is_accessible_from(expr.hir_id.owner.def_id, self.tcx) // Skip suggestions for unstable public fields (for example `Pin::pointer`) && matches!(self.tcx.eval_stability(sole_field.did, None, expr.span, None), EvalResult::Allow | EvalResult::Unmarked); @@ -417,6 +418,16 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { hir::def::CtorKind::Const => unreachable!(), }; + // Suggest constructor as deep into the block tree as possible. + // This fixes https://github.com/rust-lang/rust/issues/101065, + // and also just helps make the most minimal suggestions. + let mut expr = expr; + while let hir::ExprKind::Block(block, _) = &expr.kind + && let Some(expr_) = &block.expr + { + expr = expr_ + } + vec![ (expr.span.shrink_to_lo(), format!("{prefix}{variant}{open}")), (expr.span.shrink_to_hi(), close.to_owned()), @@ -435,6 +446,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { suggestions_for(&**variant, *ctor_kind, *field_name), Applicability::MaybeIncorrect, ); + return true; } _ => { // More than one matching variant. @@ -450,9 +462,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ), Applicability::MaybeIncorrect, ); + return true; } } } + + false } fn suggest_non_zero_new_unwrap( @@ -461,19 +476,19 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { expr: &hir::Expr<'_>, expected: Ty<'tcx>, expr_ty: Ty<'tcx>, - ) { + ) -> bool { let tcx = self.tcx; let (adt, unwrap) = match expected.kind() { // In case Option<NonZero*> is wanted, but * is provided, suggest calling new ty::Adt(adt, substs) if tcx.is_diagnostic_item(sym::Option, adt.did()) => { // Unwrap option - let ty::Adt(adt, _) = substs.type_at(0).kind() else { return }; + let ty::Adt(adt, _) = substs.type_at(0).kind() else { return false; }; (adt, "") } // In case NonZero* is wanted, but * is provided also add `.unwrap()` to satisfy types ty::Adt(adt, _) => (adt, ".unwrap()"), - _ => return, + _ => return false, }; let map = [ @@ -492,7 +507,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let Some((s, _)) = map .iter() .find(|&&(s, t)| self.tcx.is_diagnostic_item(s, adt.did()) && self.can_coerce(expr_ty, t)) - else { return }; + else { return false; }; let path = self.tcx.def_path_str(adt.non_enum_variant().def_id); @@ -504,6 +519,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ], Applicability::MaybeIncorrect, ); + + true } pub fn get_conversion_methods( @@ -513,24 +530,29 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { checked_ty: Ty<'tcx>, hir_id: hir::HirId, ) -> Vec<AssocItem> { - let mut methods = - self.probe_for_return_type(span, probe::Mode::MethodCall, expected, checked_ty, hir_id); - methods.retain(|m| { - self.has_only_self_parameter(m) - && self - .tcx - // This special internal attribute is used to permit - // "identity-like" conversion methods to be suggested here. - // - // FIXME (#46459 and #46460): ideally - // `std::convert::Into::into` and `std::borrow:ToOwned` would - // also be `#[rustc_conversion_suggestion]`, if not for - // method-probing false-positives and -negatives (respectively). - // - // FIXME? Other potential candidate methods: `as_ref` and - // `as_mut`? - .has_attr(m.def_id, sym::rustc_conversion_suggestion) - }); + let methods = self.probe_for_return_type( + span, + probe::Mode::MethodCall, + expected, + checked_ty, + hir_id, + |m| { + self.has_only_self_parameter(m) + && self + .tcx + // This special internal attribute is used to permit + // "identity-like" conversion methods to be suggested here. + // + // FIXME (#46459 and #46460): ideally + // `std::convert::Into::into` and `std::borrow:ToOwned` would + // also be `#[rustc_conversion_suggestion]`, if not for + // method-probing false-positives and -negatives (respectively). + // + // FIXME? Other potential candidate methods: `as_ref` and + // `as_mut`? + .has_attr(m.def_id, sym::rustc_conversion_suggestion) + }, + ); methods } @@ -590,7 +612,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let closure_params_len = closure_fn_decl.inputs.len(); let ( Some(Node::Expr(hir::Expr { - kind: hir::ExprKind::MethodCall(method_path, method_expr, _), + kind: hir::ExprKind::MethodCall(method_path, receiver, ..), .. })), 1, @@ -598,14 +620,16 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { return None; }; - let self_ty = self.typeck_results.borrow().expr_ty(&method_expr[0]); - let self_ty = format!("{:?}", self_ty); + let self_ty = self.typeck_results.borrow().expr_ty(receiver); let name = method_path.ident.name; - let is_as_ref_able = (self_ty.starts_with("&std::option::Option") - || self_ty.starts_with("&std::result::Result") - || self_ty.starts_with("std::option::Option") - || self_ty.starts_with("std::result::Result")) - && (name == sym::map || name == sym::and_then); + let is_as_ref_able = match self_ty.peel_refs().kind() { + ty::Adt(def, _) => { + (self.tcx.is_diagnostic_item(sym::Option, def.did()) + || self.tcx.is_diagnostic_item(sym::Result, def.did())) + && (name == sym::map || name == sym::and_then) + } + _ => false, + }; match (is_as_ref_able, self.sess().source_map().span_to_snippet(method_path.ident.span)) { (true, Ok(src)) => { let suggestion = format!("as_ref().{}", src); @@ -637,11 +661,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { }?; match hir.find(hir.get_parent_node(expr.hir_id))? { - Node::Expr(hir::Expr { kind: hir::ExprKind::Struct(_, fields, ..), .. }) => { - for field in *fields { - if field.ident.name == local.name && field.is_shorthand { - return Some(local.name); - } + Node::ExprField(field) => { + if field.ident.name == local.name && field.is_shorthand { + return Some(local.name); } } _ => {} @@ -697,7 +719,14 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { expr: &hir::Expr<'tcx>, checked_ty: Ty<'tcx>, expected: Ty<'tcx>, - ) -> Option<(Span, String, String, Applicability, bool /* verbose */)> { + ) -> Option<( + Span, + String, + String, + Applicability, + bool, /* verbose */ + bool, /* suggest `&` or `&mut` type annotation */ + )> { let sess = self.sess(); let sp = expr.span; @@ -729,6 +758,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { String::new(), Applicability::MachineApplicable, true, + false, )); } } @@ -743,6 +773,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { "b".to_string(), Applicability::MachineApplicable, true, + false, )); } } @@ -767,22 +798,21 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { }; if self.can_coerce(ref_ty, expected) { let mut sugg_sp = sp; - if let hir::ExprKind::MethodCall(ref segment, ref args, _) = expr.kind { + if let hir::ExprKind::MethodCall(ref segment, receiver, args, _) = expr.kind { let clone_trait = self.tcx.require_lang_item(LangItem::Clone, Some(segment.ident.span)); - if let ([arg], Some(true), sym::clone) = ( - &args[..], - self.typeck_results.borrow().type_dependent_def_id(expr.hir_id).map( + if args.is_empty() + && self.typeck_results.borrow().type_dependent_def_id(expr.hir_id).map( |did| { let ai = self.tcx.associated_item(did); ai.trait_container(self.tcx) == Some(clone_trait) }, - ), - segment.ident.name, - ) { + ) == Some(true) + && segment.ident.name == sym::clone + { // If this expression had a clone call when suggesting borrowing // we want to suggest removing it because it'd now be unnecessary. - sugg_sp = arg.span; + sugg_sp = receiver.span; } } if let Ok(src) = sm.span_to_snippet(sugg_sp) { @@ -793,7 +823,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { _ if is_range_literal(expr) => true, _ => false, }; - let sugg_expr = if needs_parens { format!("({src})") } else { src }; if let Some(sugg) = self.can_use_as_ref(expr) { return Some(( @@ -802,6 +831,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { sugg.2, Applicability::MachineApplicable, false, + false, )); } @@ -821,6 +851,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } + let sugg_expr = if needs_parens { format!("({src})") } else { src }; return Some(match mutability { hir::Mutability::Mut => ( sp, @@ -828,6 +859,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { format!("{prefix}&mut {sugg_expr}"), Applicability::MachineApplicable, false, + false, ), hir::Mutability::Not => ( sp, @@ -835,6 +867,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { format!("{prefix}&{sugg_expr}"), Applicability::MachineApplicable, false, + false, ), }); } @@ -864,6 +897,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { String::new(), Applicability::MachineApplicable, true, + true )); } return None; @@ -877,6 +911,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { String::new(), Applicability::MachineApplicable, true, + true, )); } } @@ -943,6 +978,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { src, applicability, true, + false, )); } } @@ -983,6 +1019,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { Applicability::MachineApplicable }, true, + false, )); } @@ -1034,6 +1071,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { suggestion, Applicability::MachineApplicable, true, + false, )); } } @@ -1072,21 +1110,16 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let mut sugg = vec![]; - if let Some(hir::Node::Expr(hir::Expr { - kind: hir::ExprKind::Struct(_, fields, _), .. - })) = self.tcx.hir().find(self.tcx.hir().get_parent_node(expr.hir_id)) + if let Some(hir::Node::ExprField(field)) = + self.tcx.hir().find(self.tcx.hir().get_parent_node(expr.hir_id)) { // `expr` is a literal field for a struct, only suggest if appropriate - match (*fields) - .iter() - .find(|field| field.expr.hir_id == expr.hir_id && field.is_shorthand) - { + if field.is_shorthand { // This is a field literal - Some(field) => { - sugg.push((field.ident.span.shrink_to_lo(), format!("{}: ", field.ident))); - } + sugg.push((field.ident.span.shrink_to_lo(), format!("{}: ", field.ident))); + } else { // Likely a field was meant, but this field wasn't found. Do not suggest anything. - None => return false, + return false; } }; @@ -1418,25 +1451,4 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { _ => false, } } - - // Report the type inferred by the return statement. - fn report_closure_inferred_return_type(&self, err: &mut Diagnostic, expected: Ty<'tcx>) { - if let Some(sp) = self.ret_coercion_span.get() - // If the closure has an explicit return type annotation, or if - // the closure's return type has been inferred from outside - // requirements (such as an Fn* trait bound), then a type error - // may occur at the first return expression we see in the closure - // (if it conflicts with the declared return type). Skip adding a - // note in this case, since it would be incorrect. - && !self.return_type_pre_known - { - err.span_note( - sp, - &format!( - "return type inferred to be `{}` here", - self.resolve_vars_if_possible(expected) - ), - ); - } - } } diff --git a/compiler/rustc_typeck/src/check/diverges.rs b/compiler/rustc_hir_typeck/src/diverges.rs index 963a93a95..963a93a95 100644 --- a/compiler/rustc_typeck/src/check/diverges.rs +++ b/compiler/rustc_hir_typeck/src/diverges.rs diff --git a/compiler/rustc_hir_typeck/src/errors.rs b/compiler/rustc_hir_typeck/src/errors.rs new file mode 100644 index 000000000..175037f9b --- /dev/null +++ b/compiler/rustc_hir_typeck/src/errors.rs @@ -0,0 +1,126 @@ +//! Errors emitted by `rustc_hir_analysis`. +use rustc_macros::{Diagnostic, Subdiagnostic}; +use rustc_middle::ty::Ty; +use rustc_span::{symbol::Ident, Span}; + +#[derive(Diagnostic)] +#[diag(hir_analysis_field_multiply_specified_in_initializer, code = "E0062")] +pub struct FieldMultiplySpecifiedInInitializer { + #[primary_span] + #[label] + pub span: Span, + #[label(previous_use_label)] + pub prev_span: Span, + pub ident: Ident, +} + +#[derive(Diagnostic)] +#[diag(hir_analysis_return_stmt_outside_of_fn_body, code = "E0572")] +pub struct ReturnStmtOutsideOfFnBody { + #[primary_span] + pub span: Span, + #[label(encl_body_label)] + pub encl_body_span: Option<Span>, + #[label(encl_fn_label)] + pub encl_fn_span: Option<Span>, +} + +#[derive(Diagnostic)] +#[diag(hir_analysis_yield_expr_outside_of_generator, code = "E0627")] +pub struct YieldExprOutsideOfGenerator { + #[primary_span] + pub span: Span, +} + +#[derive(Diagnostic)] +#[diag(hir_analysis_struct_expr_non_exhaustive, code = "E0639")] +pub struct StructExprNonExhaustive { + #[primary_span] + pub span: Span, + pub what: &'static str, +} + +#[derive(Diagnostic)] +#[diag(hir_analysis_method_call_on_unknown_type, code = "E0699")] +pub struct MethodCallOnUnknownType { + #[primary_span] + pub span: Span, +} + +#[derive(Diagnostic)] +#[diag(hir_analysis_functional_record_update_on_non_struct, code = "E0436")] +pub struct FunctionalRecordUpdateOnNonStruct { + #[primary_span] + pub span: Span, +} + +#[derive(Diagnostic)] +#[diag(hir_analysis_address_of_temporary_taken, code = "E0745")] +pub struct AddressOfTemporaryTaken { + #[primary_span] + #[label] + pub span: Span, +} + +#[derive(Subdiagnostic)] +pub enum AddReturnTypeSuggestion { + #[suggestion( + hir_analysis_add_return_type_add, + code = "-> {found} ", + applicability = "machine-applicable" + )] + Add { + #[primary_span] + span: Span, + found: String, + }, + #[suggestion( + hir_analysis_add_return_type_missing_here, + code = "-> _ ", + applicability = "has-placeholders" + )] + MissingHere { + #[primary_span] + span: Span, + }, +} + +#[derive(Subdiagnostic)] +pub enum ExpectedReturnTypeLabel<'tcx> { + #[label(hir_analysis_expected_default_return_type)] + Unit { + #[primary_span] + span: Span, + }, + #[label(hir_analysis_expected_return_type)] + Other { + #[primary_span] + span: Span, + expected: Ty<'tcx>, + }, +} + +#[derive(Diagnostic)] +#[diag(hir_analysis_missing_parentheses_in_range, code = "E0689")] +pub struct MissingParentheseInRange { + #[primary_span] + #[label(hir_analysis_missing_parentheses_in_range)] + pub span: Span, + pub ty_str: String, + pub method_name: String, + #[subdiagnostic] + pub add_missing_parentheses: Option<AddMissingParenthesesInRange>, +} + +#[derive(Subdiagnostic)] +#[multipart_suggestion_verbose( + hir_analysis_add_missing_parentheses_in_range, + applicability = "maybe-incorrect" +)] +pub struct AddMissingParenthesesInRange { + pub func_name: String, + #[suggestion_part(code = "(")] + pub left: Span, + #[suggestion_part(code = ")")] + pub right: Span, +} diff --git a/compiler/rustc_typeck/src/check/expectation.rs b/compiler/rustc_hir_typeck/src/expectation.rs index e9e810344..e9e810344 100644 --- a/compiler/rustc_typeck/src/check/expectation.rs +++ b/compiler/rustc_hir_typeck/src/expectation.rs diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs new file mode 100644 index 000000000..9fde62a81 --- /dev/null +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -0,0 +1,2896 @@ +//! Type checking expressions. +//! +//! See `mod.rs` for more context on type checking in general. + +use crate::cast; +use crate::coercion::CoerceMany; +use crate::coercion::DynamicCoerceMany; +use crate::errors::{AddressOfTemporaryTaken, ReturnStmtOutsideOfFnBody, StructExprNonExhaustive}; +use crate::errors::{ + FieldMultiplySpecifiedInInitializer, FunctionalRecordUpdateOnNonStruct, + YieldExprOutsideOfGenerator, +}; +use crate::fatally_break_rust; +use crate::method::SelfSource; +use crate::type_error_struct; +use crate::Expectation::{self, ExpectCastableToType, ExpectHasType, NoExpectation}; +use crate::{ + report_unexpected_variant_res, BreakableCtxt, Diverges, FnCtxt, Needs, + TupleArgumentsFlag::DontTupleArguments, +}; +use rustc_ast as ast; +use rustc_data_structures::fx::FxHashMap; +use rustc_data_structures::stack::ensure_sufficient_stack; +use rustc_errors::{ + pluralize, struct_span_err, Applicability, Diagnostic, DiagnosticBuilder, DiagnosticId, + ErrorGuaranteed, StashKey, +}; +use rustc_hir as hir; +use rustc_hir::def::{CtorKind, DefKind, Res}; +use rustc_hir::def_id::DefId; +use rustc_hir::intravisit::Visitor; +use rustc_hir::lang_items::LangItem; +use rustc_hir::{Closure, ExprKind, HirId, QPath}; +use rustc_hir_analysis::astconv::AstConv as _; +use rustc_hir_analysis::check::ty_kind_suggestion; +use rustc_infer::infer; +use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind}; +use rustc_infer::infer::InferOk; +use rustc_infer::traits::ObligationCause; +use rustc_middle::middle::stability; +use rustc_middle::ty::adjustment::{Adjust, Adjustment, AllowTwoPhase}; +use rustc_middle::ty::error::TypeError::FieldMisMatch; +use rustc_middle::ty::subst::SubstsRef; +use rustc_middle::ty::{self, AdtKind, Ty, TypeVisitable}; +use rustc_session::errors::ExprParenthesesNeeded; +use rustc_session::parse::feature_err; +use rustc_span::hygiene::DesugaringKind; +use rustc_span::lev_distance::find_best_match_for_name; +use rustc_span::source_map::{Span, Spanned}; +use rustc_span::symbol::{kw, sym, Ident, Symbol}; +use rustc_target::spec::abi::Abi::RustIntrinsic; +use rustc_trait_selection::infer::InferCtxtExt; +use rustc_trait_selection::traits::{self, ObligationCauseCode}; + +impl<'a, 'tcx> FnCtxt<'a, 'tcx> { + fn check_expr_eq_type(&self, expr: &'tcx hir::Expr<'tcx>, expected: Ty<'tcx>) { + let ty = self.check_expr_with_hint(expr, expected); + self.demand_eqtype(expr.span, expected, ty); + } + + pub fn check_expr_has_type_or_error( + &self, + expr: &'tcx hir::Expr<'tcx>, + expected: Ty<'tcx>, + extend_err: impl FnMut(&mut Diagnostic), + ) -> Ty<'tcx> { + self.check_expr_meets_expectation_or_error(expr, ExpectHasType(expected), extend_err) + } + + fn check_expr_meets_expectation_or_error( + &self, + expr: &'tcx hir::Expr<'tcx>, + expected: Expectation<'tcx>, + mut extend_err: impl FnMut(&mut Diagnostic), + ) -> Ty<'tcx> { + let expected_ty = expected.to_option(&self).unwrap_or(self.tcx.types.bool); + let mut ty = self.check_expr_with_expectation(expr, expected); + + // While we don't allow *arbitrary* coercions here, we *do* allow + // coercions from ! to `expected`. + if ty.is_never() { + if let Some(adjustments) = self.typeck_results.borrow().adjustments().get(expr.hir_id) { + self.tcx().sess.delay_span_bug( + expr.span, + "expression with never type wound up being adjusted", + ); + return if let [Adjustment { kind: Adjust::NeverToAny, target }] = &adjustments[..] { + target.to_owned() + } else { + self.tcx().ty_error() + }; + } + + let adj_ty = self.next_ty_var(TypeVariableOrigin { + kind: TypeVariableOriginKind::AdjustmentType, + span: expr.span, + }); + self.apply_adjustments( + expr, + vec![Adjustment { kind: Adjust::NeverToAny, target: adj_ty }], + ); + ty = adj_ty; + } + + if let Some(mut err) = self.demand_suptype_diag(expr.span, expected_ty, ty) { + let expr = expr.peel_drop_temps(); + self.suggest_deref_ref_or_into(&mut err, expr, expected_ty, ty, None); + extend_err(&mut err); + err.emit(); + } + ty + } + + pub(super) fn check_expr_coercable_to_type( + &self, + expr: &'tcx hir::Expr<'tcx>, + expected: Ty<'tcx>, + expected_ty_expr: Option<&'tcx hir::Expr<'tcx>>, + ) -> Ty<'tcx> { + let ty = self.check_expr_with_hint(expr, expected); + // checks don't need two phase + self.demand_coerce(expr, ty, expected, expected_ty_expr, AllowTwoPhase::No) + } + + pub(super) fn check_expr_with_hint( + &self, + expr: &'tcx hir::Expr<'tcx>, + expected: Ty<'tcx>, + ) -> Ty<'tcx> { + self.check_expr_with_expectation(expr, ExpectHasType(expected)) + } + + fn check_expr_with_expectation_and_needs( + &self, + expr: &'tcx hir::Expr<'tcx>, + expected: Expectation<'tcx>, + needs: Needs, + ) -> Ty<'tcx> { + let ty = self.check_expr_with_expectation(expr, expected); + + // If the expression is used in a place whether mutable place is required + // e.g. LHS of assignment, perform the conversion. + if let Needs::MutPlace = needs { + self.convert_place_derefs_to_mutable(expr); + } + + ty + } + + pub(super) fn check_expr(&self, expr: &'tcx hir::Expr<'tcx>) -> Ty<'tcx> { + self.check_expr_with_expectation(expr, NoExpectation) + } + + pub(super) fn check_expr_with_needs( + &self, + expr: &'tcx hir::Expr<'tcx>, + needs: Needs, + ) -> Ty<'tcx> { + self.check_expr_with_expectation_and_needs(expr, NoExpectation, needs) + } + + /// Invariant: + /// If an expression has any sub-expressions that result in a type error, + /// inspecting that expression's type with `ty.references_error()` will return + /// true. Likewise, if an expression is known to diverge, inspecting its + /// type with `ty::type_is_bot` will return true (n.b.: since Rust is + /// strict, _|_ can appear in the type of an expression that does not, + /// itself, diverge: for example, fn() -> _|_.) + /// Note that inspecting a type's structure *directly* may expose the fact + /// that there are actually multiple representations for `Error`, so avoid + /// that when err needs to be handled differently. + #[instrument(skip(self, expr), level = "debug")] + pub(super) fn check_expr_with_expectation( + &self, + expr: &'tcx hir::Expr<'tcx>, + expected: Expectation<'tcx>, + ) -> Ty<'tcx> { + self.check_expr_with_expectation_and_args(expr, expected, &[]) + } + + /// Same as `check_expr_with_expectation`, but allows us to pass in the arguments of a + /// `ExprKind::Call` when evaluating its callee when it is an `ExprKind::Path`. + pub(super) fn check_expr_with_expectation_and_args( + &self, + expr: &'tcx hir::Expr<'tcx>, + expected: Expectation<'tcx>, + args: &'tcx [hir::Expr<'tcx>], + ) -> Ty<'tcx> { + if self.tcx().sess.verbose() { + // make this code only run with -Zverbose because it is probably slow + if let Ok(lint_str) = self.tcx.sess.source_map().span_to_snippet(expr.span) { + if !lint_str.contains('\n') { + debug!("expr text: {lint_str}"); + } else { + let mut lines = lint_str.lines(); + if let Some(line0) = lines.next() { + let remaining_lines = lines.count(); + debug!("expr text: {line0}"); + debug!("expr text: ...(and {remaining_lines} more lines)"); + } + } + } + } + + // True if `expr` is a `Try::from_ok(())` that is a result of desugaring a try block + // without the final expr (e.g. `try { return; }`). We don't want to generate an + // unreachable_code lint for it since warnings for autogenerated code are confusing. + let is_try_block_generated_unit_expr = match expr.kind { + ExprKind::Call(_, args) if expr.span.is_desugaring(DesugaringKind::TryBlock) => { + args.len() == 1 && args[0].span.is_desugaring(DesugaringKind::TryBlock) + } + + _ => false, + }; + + // Warn for expressions after diverging siblings. + if !is_try_block_generated_unit_expr { + self.warn_if_unreachable(expr.hir_id, expr.span, "expression"); + } + + // Hide the outer diverging and has_errors flags. + let old_diverges = self.diverges.replace(Diverges::Maybe); + let old_has_errors = self.has_errors.replace(false); + + let ty = ensure_sufficient_stack(|| match &expr.kind { + hir::ExprKind::Path( + qpath @ hir::QPath::Resolved(..) | qpath @ hir::QPath::TypeRelative(..), + ) => self.check_expr_path(qpath, expr, args), + _ => self.check_expr_kind(expr, expected), + }); + + // Warn for non-block expressions with diverging children. + match expr.kind { + ExprKind::Block(..) + | ExprKind::If(..) + | ExprKind::Let(..) + | ExprKind::Loop(..) + | ExprKind::Match(..) => {} + // If `expr` is a result of desugaring the try block and is an ok-wrapped + // diverging expression (e.g. it arose from desugaring of `try { return }`), + // we skip issuing a warning because it is autogenerated code. + ExprKind::Call(..) if expr.span.is_desugaring(DesugaringKind::TryBlock) => {} + ExprKind::Call(callee, _) => self.warn_if_unreachable(expr.hir_id, callee.span, "call"), + ExprKind::MethodCall(segment, ..) => { + self.warn_if_unreachable(expr.hir_id, segment.ident.span, "call") + } + _ => self.warn_if_unreachable(expr.hir_id, expr.span, "expression"), + } + + // Any expression that produces a value of type `!` must have diverged + if ty.is_never() { + self.diverges.set(self.diverges.get() | Diverges::always(expr.span)); + } + + // Record the type, which applies it effects. + // We need to do this after the warning above, so that + // we don't warn for the diverging expression itself. + self.write_ty(expr.hir_id, ty); + + // Combine the diverging and has_error flags. + self.diverges.set(self.diverges.get() | old_diverges); + self.has_errors.set(self.has_errors.get() | old_has_errors); + + debug!("type of {} is...", self.tcx.hir().node_to_string(expr.hir_id)); + debug!("... {:?}, expected is {:?}", ty, expected); + + ty + } + + #[instrument(skip(self, expr), level = "debug")] + fn check_expr_kind( + &self, + expr: &'tcx hir::Expr<'tcx>, + expected: Expectation<'tcx>, + ) -> Ty<'tcx> { + trace!("expr={:#?}", expr); + + let tcx = self.tcx; + match expr.kind { + ExprKind::Box(subexpr) => self.check_expr_box(subexpr, expected), + ExprKind::Lit(ref lit) => self.check_lit(&lit, expected), + ExprKind::Binary(op, lhs, rhs) => self.check_binop(expr, op, lhs, rhs, expected), + ExprKind::Assign(lhs, rhs, span) => { + self.check_expr_assign(expr, expected, lhs, rhs, span) + } + ExprKind::AssignOp(op, lhs, rhs) => { + self.check_binop_assign(expr, op, lhs, rhs, expected) + } + ExprKind::Unary(unop, oprnd) => self.check_expr_unary(unop, oprnd, expected, expr), + ExprKind::AddrOf(kind, mutbl, oprnd) => { + self.check_expr_addr_of(kind, mutbl, oprnd, expected, expr) + } + ExprKind::Path(QPath::LangItem(lang_item, _, hir_id)) => { + self.check_lang_item_path(lang_item, expr, hir_id) + } + ExprKind::Path(ref qpath) => self.check_expr_path(qpath, expr, &[]), + ExprKind::InlineAsm(asm) => { + // We defer some asm checks as we may not have resolved the input and output types yet (they may still be infer vars). + self.deferred_asm_checks.borrow_mut().push((asm, expr.hir_id)); + self.check_expr_asm(asm) + } + ExprKind::Break(destination, ref expr_opt) => { + self.check_expr_break(destination, expr_opt.as_deref(), expr) + } + ExprKind::Continue(destination) => { + if destination.target_id.is_ok() { + tcx.types.never + } else { + // There was an error; make type-check fail. + tcx.ty_error() + } + } + ExprKind::Ret(ref expr_opt) => self.check_expr_return(expr_opt.as_deref(), expr), + ExprKind::Let(let_expr) => self.check_expr_let(let_expr), + ExprKind::Loop(body, _, source, _) => { + self.check_expr_loop(body, source, expected, expr) + } + ExprKind::Match(discrim, arms, match_src) => { + self.check_match(expr, &discrim, arms, expected, match_src) + } + ExprKind::Closure(&Closure { capture_clause, fn_decl, body, movability, .. }) => { + self.check_expr_closure(expr, capture_clause, &fn_decl, body, movability, expected) + } + ExprKind::Block(body, _) => self.check_block_with_expected(&body, expected), + ExprKind::Call(callee, args) => self.check_call(expr, &callee, args, expected), + ExprKind::MethodCall(segment, receiver, args, _) => { + self.check_method_call(expr, segment, receiver, args, expected) + } + ExprKind::Cast(e, t) => self.check_expr_cast(e, t, expr), + ExprKind::Type(e, t) => { + let ty = self.to_ty_saving_user_provided_ty(&t); + self.check_expr_eq_type(&e, ty); + ty + } + ExprKind::If(cond, then_expr, opt_else_expr) => { + self.check_then_else(cond, then_expr, opt_else_expr, expr.span, expected) + } + ExprKind::DropTemps(e) => self.check_expr_with_expectation(e, expected), + ExprKind::Array(args) => self.check_expr_array(args, expected, expr), + ExprKind::ConstBlock(ref anon_const) => { + self.check_expr_const_block(anon_const, expected, expr) + } + ExprKind::Repeat(element, ref count) => { + self.check_expr_repeat(element, count, expected, expr) + } + ExprKind::Tup(elts) => self.check_expr_tuple(elts, expected, expr), + ExprKind::Struct(qpath, fields, ref base_expr) => { + self.check_expr_struct(expr, expected, qpath, fields, base_expr) + } + ExprKind::Field(base, field) => self.check_field(expr, &base, field), + ExprKind::Index(base, idx) => self.check_expr_index(base, idx, expr), + ExprKind::Yield(value, ref src) => self.check_expr_yield(value, expr, src), + hir::ExprKind::Err => tcx.ty_error(), + } + } + + fn check_expr_box(&self, expr: &'tcx hir::Expr<'tcx>, expected: Expectation<'tcx>) -> Ty<'tcx> { + let expected_inner = expected.to_option(self).map_or(NoExpectation, |ty| match ty.kind() { + ty::Adt(def, _) if def.is_box() => Expectation::rvalue_hint(self, ty.boxed_ty()), + _ => NoExpectation, + }); + let referent_ty = self.check_expr_with_expectation(expr, expected_inner); + self.require_type_is_sized(referent_ty, expr.span, traits::SizedBoxType); + self.tcx.mk_box(referent_ty) + } + + fn check_expr_unary( + &self, + unop: hir::UnOp, + oprnd: &'tcx hir::Expr<'tcx>, + expected: Expectation<'tcx>, + expr: &'tcx hir::Expr<'tcx>, + ) -> Ty<'tcx> { + let tcx = self.tcx; + let expected_inner = match unop { + hir::UnOp::Not | hir::UnOp::Neg => expected, + hir::UnOp::Deref => NoExpectation, + }; + let mut oprnd_t = self.check_expr_with_expectation(&oprnd, expected_inner); + + if !oprnd_t.references_error() { + oprnd_t = self.structurally_resolved_type(expr.span, oprnd_t); + match unop { + hir::UnOp::Deref => { + if let Some(ty) = self.lookup_derefing(expr, oprnd, oprnd_t) { + oprnd_t = ty; + } else { + let mut err = type_error_struct!( + tcx.sess, + expr.span, + oprnd_t, + E0614, + "type `{oprnd_t}` cannot be dereferenced", + ); + let sp = tcx.sess.source_map().start_point(expr.span); + if let Some(sp) = + tcx.sess.parse_sess.ambiguous_block_expr_parse.borrow().get(&sp) + { + err.subdiagnostic(ExprParenthesesNeeded::surrounding(*sp)); + } + err.emit(); + oprnd_t = tcx.ty_error(); + } + } + hir::UnOp::Not => { + let result = self.check_user_unop(expr, oprnd_t, unop, expected_inner); + // If it's builtin, we can reuse the type, this helps inference. + if !(oprnd_t.is_integral() || *oprnd_t.kind() == ty::Bool) { + oprnd_t = result; + } + } + hir::UnOp::Neg => { + let result = self.check_user_unop(expr, oprnd_t, unop, expected_inner); + // If it's builtin, we can reuse the type, this helps inference. + if !oprnd_t.is_numeric() { + oprnd_t = result; + } + } + } + } + oprnd_t + } + + fn check_expr_addr_of( + &self, + kind: hir::BorrowKind, + mutbl: hir::Mutability, + oprnd: &'tcx hir::Expr<'tcx>, + expected: Expectation<'tcx>, + expr: &'tcx hir::Expr<'tcx>, + ) -> Ty<'tcx> { + let hint = expected.only_has_type(self).map_or(NoExpectation, |ty| { + match ty.kind() { + ty::Ref(_, ty, _) | ty::RawPtr(ty::TypeAndMut { ty, .. }) => { + if oprnd.is_syntactic_place_expr() { + // Places may legitimately have unsized types. + // For example, dereferences of a fat pointer and + // the last field of a struct can be unsized. + ExpectHasType(*ty) + } else { + Expectation::rvalue_hint(self, *ty) + } + } + _ => NoExpectation, + } + }); + let ty = + self.check_expr_with_expectation_and_needs(&oprnd, hint, Needs::maybe_mut_place(mutbl)); + + let tm = ty::TypeAndMut { ty, mutbl }; + match kind { + _ if tm.ty.references_error() => self.tcx.ty_error(), + hir::BorrowKind::Raw => { + self.check_named_place_expr(oprnd); + self.tcx.mk_ptr(tm) + } + hir::BorrowKind::Ref => { + // Note: at this point, we cannot say what the best lifetime + // is to use for resulting pointer. We want to use the + // shortest lifetime possible so as to avoid spurious borrowck + // errors. Moreover, the longest lifetime will depend on the + // precise details of the value whose address is being taken + // (and how long it is valid), which we don't know yet until + // type inference is complete. + // + // Therefore, here we simply generate a region variable. The + // region inferencer will then select a suitable value. + // Finally, borrowck will infer the value of the region again, + // this time with enough precision to check that the value + // whose address was taken can actually be made to live as long + // as it needs to live. + let region = self.next_region_var(infer::AddrOfRegion(expr.span)); + self.tcx.mk_ref(region, tm) + } + } + } + + /// Does this expression refer to a place that either: + /// * Is based on a local or static. + /// * Contains a dereference + /// Note that the adjustments for the children of `expr` should already + /// have been resolved. + fn check_named_place_expr(&self, oprnd: &'tcx hir::Expr<'tcx>) { + let is_named = oprnd.is_place_expr(|base| { + // Allow raw borrows if there are any deref adjustments. + // + // const VAL: (i32,) = (0,); + // const REF: &(i32,) = &(0,); + // + // &raw const VAL.0; // ERROR + // &raw const REF.0; // OK, same as &raw const (*REF).0; + // + // This is maybe too permissive, since it allows + // `let u = &raw const Box::new((1,)).0`, which creates an + // immediately dangling raw pointer. + self.typeck_results + .borrow() + .adjustments() + .get(base.hir_id) + .map_or(false, |x| x.iter().any(|adj| matches!(adj.kind, Adjust::Deref(_)))) + }); + if !is_named { + self.tcx.sess.emit_err(AddressOfTemporaryTaken { span: oprnd.span }); + } + } + + fn check_lang_item_path( + &self, + lang_item: hir::LangItem, + expr: &'tcx hir::Expr<'tcx>, + hir_id: Option<hir::HirId>, + ) -> Ty<'tcx> { + self.resolve_lang_item_path(lang_item, expr.span, expr.hir_id, hir_id).1 + } + + pub(crate) fn check_expr_path( + &self, + qpath: &'tcx hir::QPath<'tcx>, + expr: &'tcx hir::Expr<'tcx>, + args: &'tcx [hir::Expr<'tcx>], + ) -> Ty<'tcx> { + let tcx = self.tcx; + let (res, opt_ty, segs) = + self.resolve_ty_and_res_fully_qualified_call(qpath, expr.hir_id, expr.span); + let ty = match res { + Res::Err => { + self.set_tainted_by_errors(); + tcx.ty_error() + } + Res::Def(DefKind::Ctor(_, CtorKind::Fictive), _) => { + report_unexpected_variant_res(tcx, res, qpath, expr.span); + tcx.ty_error() + } + _ => self.instantiate_value_path(segs, opt_ty, res, expr.span, expr.hir_id).0, + }; + + if let ty::FnDef(did, ..) = *ty.kind() { + let fn_sig = ty.fn_sig(tcx); + if tcx.fn_sig(did).abi() == RustIntrinsic && tcx.item_name(did) == sym::transmute { + let from = fn_sig.inputs().skip_binder()[0]; + let to = fn_sig.output().skip_binder(); + // We defer the transmute to the end of typeck, once all inference vars have + // been resolved or we errored. This is important as we can only check transmute + // on concrete types, but the output type may not be known yet (it would only + // be known if explicitly specified via turbofish). + self.deferred_transmute_checks.borrow_mut().push((from, to, expr.hir_id)); + } + if !tcx.features().unsized_fn_params { + // We want to remove some Sized bounds from std functions, + // but don't want to expose the removal to stable Rust. + // i.e., we don't want to allow + // + // ```rust + // drop as fn(str); + // ``` + // + // to work in stable even if the Sized bound on `drop` is relaxed. + for i in 0..fn_sig.inputs().skip_binder().len() { + // We just want to check sizedness, so instead of introducing + // placeholder lifetimes with probing, we just replace higher lifetimes + // with fresh vars. + let span = args.get(i).map(|a| a.span).unwrap_or(expr.span); + let input = self.replace_bound_vars_with_fresh_vars( + span, + infer::LateBoundRegionConversionTime::FnCall, + fn_sig.input(i), + ); + self.require_type_is_sized_deferred( + input, + span, + traits::SizedArgumentType(None), + ); + } + } + // Here we want to prevent struct constructors from returning unsized types. + // There were two cases this happened: fn pointer coercion in stable + // and usual function call in presence of unsized_locals. + // Also, as we just want to check sizedness, instead of introducing + // placeholder lifetimes with probing, we just replace higher lifetimes + // with fresh vars. + let output = self.replace_bound_vars_with_fresh_vars( + expr.span, + infer::LateBoundRegionConversionTime::FnCall, + fn_sig.output(), + ); + self.require_type_is_sized_deferred(output, expr.span, traits::SizedReturnType); + } + + // We always require that the type provided as the value for + // a type parameter outlives the moment of instantiation. + let substs = self.typeck_results.borrow().node_substs(expr.hir_id); + self.add_wf_bounds(substs, expr); + + ty + } + + fn check_expr_break( + &self, + destination: hir::Destination, + expr_opt: Option<&'tcx hir::Expr<'tcx>>, + expr: &'tcx hir::Expr<'tcx>, + ) -> Ty<'tcx> { + let tcx = self.tcx; + if let Ok(target_id) = destination.target_id { + let (e_ty, cause); + if let Some(e) = expr_opt { + // If this is a break with a value, we need to type-check + // the expression. Get an expected type from the loop context. + let opt_coerce_to = { + // We should release `enclosing_breakables` before the `check_expr_with_hint` + // below, so can't move this block of code to the enclosing scope and share + // `ctxt` with the second `enclosing_breakables` borrow below. + let mut enclosing_breakables = self.enclosing_breakables.borrow_mut(); + match enclosing_breakables.opt_find_breakable(target_id) { + Some(ctxt) => ctxt.coerce.as_ref().map(|coerce| coerce.expected_ty()), + None => { + // Avoid ICE when `break` is inside a closure (#65383). + return tcx.ty_error_with_message( + expr.span, + "break was outside loop, but no error was emitted", + ); + } + } + }; + + // If the loop context is not a `loop { }`, then break with + // a value is illegal, and `opt_coerce_to` will be `None`. + // Just set expectation to error in that case. + let coerce_to = opt_coerce_to.unwrap_or_else(|| tcx.ty_error()); + + // Recurse without `enclosing_breakables` borrowed. + e_ty = self.check_expr_with_hint(e, coerce_to); + cause = self.misc(e.span); + } else { + // Otherwise, this is a break *without* a value. That's + // always legal, and is equivalent to `break ()`. + e_ty = tcx.mk_unit(); + cause = self.misc(expr.span); + } + + // Now that we have type-checked `expr_opt`, borrow + // the `enclosing_loops` field and let's coerce the + // type of `expr_opt` into what is expected. + let mut enclosing_breakables = self.enclosing_breakables.borrow_mut(); + let Some(ctxt) = enclosing_breakables.opt_find_breakable(target_id) else { + // Avoid ICE when `break` is inside a closure (#65383). + return tcx.ty_error_with_message( + expr.span, + "break was outside loop, but no error was emitted", + ); + }; + + if let Some(ref mut coerce) = ctxt.coerce { + if let Some(ref e) = expr_opt { + coerce.coerce(self, &cause, e, e_ty); + } else { + assert!(e_ty.is_unit()); + let ty = coerce.expected_ty(); + coerce.coerce_forced_unit( + self, + &cause, + &mut |mut err| { + self.suggest_mismatched_types_on_tail( + &mut err, expr, ty, e_ty, target_id, + ); + if let Some(val) = ty_kind_suggestion(ty) { + let label = destination + .label + .map(|l| format!(" {}", l.ident)) + .unwrap_or_else(String::new); + err.span_suggestion( + expr.span, + "give it a value of the expected type", + format!("break{label} {val}"), + Applicability::HasPlaceholders, + ); + } + }, + false, + ); + } + } else { + // If `ctxt.coerce` is `None`, we can just ignore + // the type of the expression. This is because + // either this was a break *without* a value, in + // which case it is always a legal type (`()`), or + // else an error would have been flagged by the + // `loops` pass for using break with an expression + // where you are not supposed to. + assert!(expr_opt.is_none() || self.tcx.sess.has_errors().is_some()); + } + + // If we encountered a `break`, then (no surprise) it may be possible to break from the + // loop... unless the value being returned from the loop diverges itself, e.g. + // `break return 5` or `break loop {}`. + ctxt.may_break |= !self.diverges.get().is_always(); + + // the type of a `break` is always `!`, since it diverges + tcx.types.never + } else { + // Otherwise, we failed to find the enclosing loop; + // this can only happen if the `break` was not + // inside a loop at all, which is caught by the + // loop-checking pass. + let err = self.tcx.ty_error_with_message( + expr.span, + "break was outside loop, but no error was emitted", + ); + + // We still need to assign a type to the inner expression to + // prevent the ICE in #43162. + if let Some(e) = expr_opt { + self.check_expr_with_hint(e, err); + + // ... except when we try to 'break rust;'. + // ICE this expression in particular (see #43162). + if let ExprKind::Path(QPath::Resolved(_, path)) = e.kind { + if path.segments.len() == 1 && path.segments[0].ident.name == sym::rust { + fatally_break_rust(self.tcx.sess); + } + } + } + + // There was an error; make type-check fail. + err + } + } + + fn check_expr_return( + &self, + expr_opt: Option<&'tcx hir::Expr<'tcx>>, + expr: &'tcx hir::Expr<'tcx>, + ) -> Ty<'tcx> { + if self.ret_coercion.is_none() { + let mut err = ReturnStmtOutsideOfFnBody { + span: expr.span, + encl_body_span: None, + encl_fn_span: None, + }; + + let encl_item_id = self.tcx.hir().get_parent_item(expr.hir_id); + + if let Some(hir::Node::Item(hir::Item { + kind: hir::ItemKind::Fn(..), + span: encl_fn_span, + .. + })) + | Some(hir::Node::TraitItem(hir::TraitItem { + kind: hir::TraitItemKind::Fn(_, hir::TraitFn::Provided(_)), + span: encl_fn_span, + .. + })) + | Some(hir::Node::ImplItem(hir::ImplItem { + kind: hir::ImplItemKind::Fn(..), + span: encl_fn_span, + .. + })) = self.tcx.hir().find_by_def_id(encl_item_id.def_id) + { + // We are inside a function body, so reporting "return statement + // outside of function body" needs an explanation. + + let encl_body_owner_id = self.tcx.hir().enclosing_body_owner(expr.hir_id); + + // If this didn't hold, we would not have to report an error in + // the first place. + assert_ne!(encl_item_id.def_id, encl_body_owner_id); + + let encl_body_id = self.tcx.hir().body_owned_by(encl_body_owner_id); + let encl_body = self.tcx.hir().body(encl_body_id); + + err.encl_body_span = Some(encl_body.value.span); + err.encl_fn_span = Some(*encl_fn_span); + } + + self.tcx.sess.emit_err(err); + + if let Some(e) = expr_opt { + // We still have to type-check `e` (issue #86188), but calling + // `check_return_expr` only works inside fn bodies. + self.check_expr(e); + } + } else if let Some(e) = expr_opt { + if self.ret_coercion_span.get().is_none() { + self.ret_coercion_span.set(Some(e.span)); + } + self.check_return_expr(e, true); + } else { + let mut coercion = self.ret_coercion.as_ref().unwrap().borrow_mut(); + if self.ret_coercion_span.get().is_none() { + self.ret_coercion_span.set(Some(expr.span)); + } + let cause = self.cause(expr.span, ObligationCauseCode::ReturnNoExpression); + if let Some((fn_decl, _)) = self.get_fn_decl(expr.hir_id) { + coercion.coerce_forced_unit( + self, + &cause, + &mut |db| { + let span = fn_decl.output.span(); + if let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(span) { + db.span_label( + span, + format!("expected `{snippet}` because of this return type"), + ); + } + }, + true, + ); + } else { + coercion.coerce_forced_unit(self, &cause, &mut |_| (), true); + } + } + self.tcx.types.never + } + + /// `explicit_return` is `true` if we're checking an explicit `return expr`, + /// and `false` if we're checking a trailing expression. + pub(super) fn check_return_expr( + &self, + return_expr: &'tcx hir::Expr<'tcx>, + explicit_return: bool, + ) { + let ret_coercion = self.ret_coercion.as_ref().unwrap_or_else(|| { + span_bug!(return_expr.span, "check_return_expr called outside fn body") + }); + + let ret_ty = ret_coercion.borrow().expected_ty(); + let return_expr_ty = self.check_expr_with_hint(return_expr, ret_ty); + let mut span = return_expr.span; + // Use the span of the trailing expression for our cause, + // not the span of the entire function + if !explicit_return { + if let ExprKind::Block(body, _) = return_expr.kind && let Some(last_expr) = body.expr { + span = last_expr.span; + } + } + ret_coercion.borrow_mut().coerce( + self, + &self.cause(span, ObligationCauseCode::ReturnValue(return_expr.hir_id)), + return_expr, + return_expr_ty, + ); + + if self.return_type_has_opaque { + // Point any obligations that were registered due to opaque type + // inference at the return expression. + self.select_obligations_where_possible(false, |errors| { + self.point_at_return_for_opaque_ty_error(errors, span, return_expr_ty); + }); + } + } + + fn point_at_return_for_opaque_ty_error( + &self, + errors: &mut Vec<traits::FulfillmentError<'tcx>>, + span: Span, + return_expr_ty: Ty<'tcx>, + ) { + // Don't point at the whole block if it's empty + if span == self.tcx.hir().span(self.body_id) { + return; + } + for err in errors { + let cause = &mut err.obligation.cause; + if let ObligationCauseCode::OpaqueReturnType(None) = cause.code() { + let new_cause = ObligationCause::new( + cause.span, + cause.body_id, + ObligationCauseCode::OpaqueReturnType(Some((return_expr_ty, span))), + ); + *cause = new_cause; + } + } + } + + pub(crate) fn check_lhs_assignable( + &self, + lhs: &'tcx hir::Expr<'tcx>, + err_code: &'static str, + op_span: Span, + adjust_err: impl FnOnce(&mut Diagnostic), + ) { + if lhs.is_syntactic_place_expr() { + return; + } + + // FIXME: Make this use Diagnostic once error codes can be dynamically set. + let mut err = self.tcx.sess.struct_span_err_with_code( + op_span, + "invalid left-hand side of assignment", + DiagnosticId::Error(err_code.into()), + ); + err.span_label(lhs.span, "cannot assign to this expression"); + + self.comes_from_while_condition(lhs.hir_id, |expr| { + err.span_suggestion_verbose( + expr.span.shrink_to_lo(), + "you might have meant to use pattern destructuring", + "let ", + Applicability::MachineApplicable, + ); + }); + + adjust_err(&mut err); + + err.emit(); + } + + // Check if an expression `original_expr_id` comes from the condition of a while loop, + // as opposed from the body of a while loop, which we can naively check by iterating + // parents until we find a loop... + pub(super) fn comes_from_while_condition( + &self, + original_expr_id: HirId, + then: impl FnOnce(&hir::Expr<'_>), + ) { + let mut parent = self.tcx.hir().get_parent_node(original_expr_id); + while let Some(node) = self.tcx.hir().find(parent) { + match node { + hir::Node::Expr(hir::Expr { + kind: + hir::ExprKind::Loop( + hir::Block { + expr: + Some(hir::Expr { + kind: + hir::ExprKind::Match(expr, ..) | hir::ExprKind::If(expr, ..), + .. + }), + .. + }, + _, + hir::LoopSource::While, + _, + ), + .. + }) => { + // Check if our original expression is a child of the condition of a while loop + let expr_is_ancestor = std::iter::successors(Some(original_expr_id), |id| { + self.tcx.hir().find_parent_node(*id) + }) + .take_while(|id| *id != parent) + .any(|id| id == expr.hir_id); + // if it is, then we have a situation like `while Some(0) = value.get(0) {`, + // where `while let` was more likely intended. + if expr_is_ancestor { + then(expr); + } + break; + } + hir::Node::Item(_) + | hir::Node::ImplItem(_) + | hir::Node::TraitItem(_) + | hir::Node::Crate(_) => break, + _ => { + parent = self.tcx.hir().get_parent_node(parent); + } + } + } + } + + // A generic function for checking the 'then' and 'else' clauses in an 'if' + // or 'if-else' expression. + fn check_then_else( + &self, + cond_expr: &'tcx hir::Expr<'tcx>, + then_expr: &'tcx hir::Expr<'tcx>, + opt_else_expr: Option<&'tcx hir::Expr<'tcx>>, + sp: Span, + orig_expected: Expectation<'tcx>, + ) -> Ty<'tcx> { + let cond_ty = self.check_expr_has_type_or_error(cond_expr, self.tcx.types.bool, |_| {}); + + self.warn_if_unreachable( + cond_expr.hir_id, + then_expr.span, + "block in `if` or `while` expression", + ); + + let cond_diverges = self.diverges.get(); + self.diverges.set(Diverges::Maybe); + + let expected = orig_expected.adjust_for_branches(self); + let then_ty = self.check_expr_with_expectation(then_expr, expected); + let then_diverges = self.diverges.get(); + self.diverges.set(Diverges::Maybe); + + // We've already taken the expected type's preferences + // into account when typing the `then` branch. To figure + // out the initial shot at a LUB, we thus only consider + // `expected` if it represents a *hard* constraint + // (`only_has_type`); otherwise, we just go with a + // fresh type variable. + let coerce_to_ty = expected.coercion_target_type(self, sp); + let mut coerce: DynamicCoerceMany<'_> = CoerceMany::new(coerce_to_ty); + + coerce.coerce(self, &self.misc(sp), then_expr, then_ty); + + if let Some(else_expr) = opt_else_expr { + let else_ty = self.check_expr_with_expectation(else_expr, expected); + let else_diverges = self.diverges.get(); + + let opt_suggest_box_span = self.opt_suggest_box_span(then_ty, else_ty, orig_expected); + let if_cause = self.if_cause( + sp, + cond_expr.span, + then_expr, + else_expr, + then_ty, + else_ty, + opt_suggest_box_span, + ); + + coerce.coerce(self, &if_cause, else_expr, else_ty); + + // We won't diverge unless both branches do (or the condition does). + self.diverges.set(cond_diverges | then_diverges & else_diverges); + } else { + self.if_fallback_coercion(sp, then_expr, &mut coerce); + + // If the condition is false we can't diverge. + self.diverges.set(cond_diverges); + } + + let result_ty = coerce.complete(self); + if cond_ty.references_error() { self.tcx.ty_error() } else { result_ty } + } + + /// Type check assignment expression `expr` of form `lhs = rhs`. + /// The expected type is `()` and is passed to the function for the purposes of diagnostics. + fn check_expr_assign( + &self, + expr: &'tcx hir::Expr<'tcx>, + expected: Expectation<'tcx>, + lhs: &'tcx hir::Expr<'tcx>, + rhs: &'tcx hir::Expr<'tcx>, + span: Span, + ) -> Ty<'tcx> { + let expected_ty = expected.coercion_target_type(self, expr.span); + if expected_ty == self.tcx.types.bool { + // The expected type is `bool` but this will result in `()` so we can reasonably + // say that the user intended to write `lhs == rhs` instead of `lhs = rhs`. + // The likely cause of this is `if foo = bar { .. }`. + let actual_ty = self.tcx.mk_unit(); + let mut err = self.demand_suptype_diag(expr.span, expected_ty, actual_ty).unwrap(); + let lhs_ty = self.check_expr(&lhs); + let rhs_ty = self.check_expr(&rhs); + let (applicability, eq) = if self.can_coerce(rhs_ty, lhs_ty) { + (Applicability::MachineApplicable, true) + } else if let ExprKind::Binary( + Spanned { node: hir::BinOpKind::And | hir::BinOpKind::Or, .. }, + _, + rhs_expr, + ) = lhs.kind + { + // if x == 1 && y == 2 { .. } + // + + let actual_lhs_ty = self.check_expr(&rhs_expr); + (Applicability::MaybeIncorrect, self.can_coerce(rhs_ty, actual_lhs_ty)) + } else if let ExprKind::Binary( + Spanned { node: hir::BinOpKind::And | hir::BinOpKind::Or, .. }, + lhs_expr, + _, + ) = rhs.kind + { + // if x == 1 && y == 2 { .. } + // + + let actual_rhs_ty = self.check_expr(&lhs_expr); + (Applicability::MaybeIncorrect, self.can_coerce(actual_rhs_ty, lhs_ty)) + } else { + (Applicability::MaybeIncorrect, false) + }; + if !lhs.is_syntactic_place_expr() + && lhs.is_approximately_pattern() + && !matches!(lhs.kind, hir::ExprKind::Lit(_)) + { + // Do not suggest `if let x = y` as `==` is way more likely to be the intention. + let hir = self.tcx.hir(); + if let hir::Node::Expr(hir::Expr { kind: ExprKind::If { .. }, .. }) = + hir.get(hir.get_parent_node(hir.get_parent_node(expr.hir_id))) + { + err.span_suggestion_verbose( + expr.span.shrink_to_lo(), + "you might have meant to use pattern matching", + "let ", + applicability, + ); + }; + } + if eq { + err.span_suggestion_verbose( + span.shrink_to_hi(), + "you might have meant to compare for equality", + '=', + applicability, + ); + } + + // If the assignment expression itself is ill-formed, don't + // bother emitting another error + if lhs_ty.references_error() || rhs_ty.references_error() { + err.delay_as_bug() + } else { + err.emit(); + } + return self.tcx.ty_error(); + } + + let lhs_ty = self.check_expr_with_needs(&lhs, Needs::MutPlace); + + let suggest_deref_binop = |err: &mut Diagnostic, rhs_ty: Ty<'tcx>| { + if let Some(lhs_deref_ty) = self.deref_once_mutably_for_diagnostic(lhs_ty) { + // Can only assign if the type is sized, so if `DerefMut` yields a type that is + // unsized, do not suggest dereferencing it. + let lhs_deref_ty_is_sized = self + .infcx + .type_implements_trait( + self.tcx.lang_items().sized_trait().unwrap(), + lhs_deref_ty, + ty::List::empty(), + self.param_env, + ) + .may_apply(); + if lhs_deref_ty_is_sized && self.can_coerce(rhs_ty, lhs_deref_ty) { + err.span_suggestion_verbose( + lhs.span.shrink_to_lo(), + "consider dereferencing here to assign to the mutably borrowed value", + "*", + Applicability::MachineApplicable, + ); + } + } + }; + + // This is (basically) inlined `check_expr_coercable_to_type`, but we want + // to suggest an additional fixup here in `suggest_deref_binop`. + let rhs_ty = self.check_expr_with_hint(&rhs, lhs_ty); + if let (_, Some(mut diag)) = + self.demand_coerce_diag(rhs, rhs_ty, lhs_ty, Some(lhs), AllowTwoPhase::No) + { + suggest_deref_binop(&mut diag, rhs_ty); + diag.emit(); + } + + self.check_lhs_assignable(lhs, "E0070", span, |err| { + if let Some(rhs_ty) = self.typeck_results.borrow().expr_ty_opt(rhs) { + suggest_deref_binop(err, rhs_ty); + } + }); + + self.require_type_is_sized(lhs_ty, lhs.span, traits::AssignmentLhsSized); + + if lhs_ty.references_error() || rhs_ty.references_error() { + self.tcx.ty_error() + } else { + self.tcx.mk_unit() + } + } + + pub(super) fn check_expr_let(&self, let_expr: &'tcx hir::Let<'tcx>) -> Ty<'tcx> { + // for let statements, this is done in check_stmt + let init = let_expr.init; + self.warn_if_unreachable(init.hir_id, init.span, "block in `let` expression"); + // otherwise check exactly as a let statement + self.check_decl(let_expr.into()); + // but return a bool, for this is a boolean expression + self.tcx.types.bool + } + + fn check_expr_loop( + &self, + body: &'tcx hir::Block<'tcx>, + source: hir::LoopSource, + expected: Expectation<'tcx>, + expr: &'tcx hir::Expr<'tcx>, + ) -> Ty<'tcx> { + let coerce = match source { + // you can only use break with a value from a normal `loop { }` + hir::LoopSource::Loop => { + let coerce_to = expected.coercion_target_type(self, body.span); + Some(CoerceMany::new(coerce_to)) + } + + hir::LoopSource::While | hir::LoopSource::ForLoop => None, + }; + + let ctxt = BreakableCtxt { + coerce, + may_break: false, // Will get updated if/when we find a `break`. + }; + + let (ctxt, ()) = self.with_breakable_ctxt(expr.hir_id, ctxt, || { + self.check_block_no_value(&body); + }); + + if ctxt.may_break { + // No way to know whether it's diverging because + // of a `break` or an outer `break` or `return`. + self.diverges.set(Diverges::Maybe); + } + + // If we permit break with a value, then result type is + // the LUB of the breaks (possibly ! if none); else, it + // is nil. This makes sense because infinite loops + // (which would have type !) are only possible iff we + // permit break with a value [1]. + if ctxt.coerce.is_none() && !ctxt.may_break { + // [1] + self.tcx.sess.delay_span_bug(body.span, "no coercion, but loop may not break"); + } + ctxt.coerce.map(|c| c.complete(self)).unwrap_or_else(|| self.tcx.mk_unit()) + } + + /// Checks a method call. + fn check_method_call( + &self, + expr: &'tcx hir::Expr<'tcx>, + segment: &hir::PathSegment<'_>, + rcvr: &'tcx hir::Expr<'tcx>, + args: &'tcx [hir::Expr<'tcx>], + expected: Expectation<'tcx>, + ) -> Ty<'tcx> { + let rcvr_t = self.check_expr(&rcvr); + // no need to check for bot/err -- callee does that + let rcvr_t = self.structurally_resolved_type(rcvr.span, rcvr_t); + let span = segment.ident.span; + + let method = match self.lookup_method(rcvr_t, segment, span, expr, rcvr, args) { + Ok(method) => { + // We could add a "consider `foo::<params>`" suggestion here, but I wasn't able to + // trigger this codepath causing `structurally_resolved_type` to emit an error. + + self.write_method_call(expr.hir_id, method); + Ok(method) + } + Err(error) => { + if segment.ident.name != kw::Empty { + if let Some(mut err) = self.report_method_error( + span, + rcvr_t, + segment.ident, + SelfSource::MethodCall(rcvr), + error, + Some((rcvr, args)), + ) { + err.emit(); + } + } + Err(()) + } + }; + + // Call the generic checker. + self.check_method_argument_types(span, expr, method, &args, DontTupleArguments, expected) + } + + fn check_expr_cast( + &self, + e: &'tcx hir::Expr<'tcx>, + t: &'tcx hir::Ty<'tcx>, + expr: &'tcx hir::Expr<'tcx>, + ) -> Ty<'tcx> { + // Find the type of `e`. Supply hints based on the type we are casting to, + // if appropriate. + let t_cast = self.to_ty_saving_user_provided_ty(t); + let t_cast = self.resolve_vars_if_possible(t_cast); + let t_expr = self.check_expr_with_expectation(e, ExpectCastableToType(t_cast)); + let t_expr = self.resolve_vars_if_possible(t_expr); + + // Eagerly check for some obvious errors. + if t_expr.references_error() || t_cast.references_error() { + self.tcx.ty_error() + } else { + // Defer other checks until we're done type checking. + let mut deferred_cast_checks = self.deferred_cast_checks.borrow_mut(); + match cast::CastCheck::new( + self, + e, + t_expr, + t_cast, + t.span, + expr.span, + self.param_env.constness(), + ) { + Ok(cast_check) => { + debug!( + "check_expr_cast: deferring cast from {:?} to {:?}: {:?}", + t_cast, t_expr, cast_check, + ); + deferred_cast_checks.push(cast_check); + t_cast + } + Err(_) => self.tcx.ty_error(), + } + } + } + + fn check_expr_array( + &self, + args: &'tcx [hir::Expr<'tcx>], + expected: Expectation<'tcx>, + expr: &'tcx hir::Expr<'tcx>, + ) -> Ty<'tcx> { + let element_ty = if !args.is_empty() { + let coerce_to = expected + .to_option(self) + .and_then(|uty| match *uty.kind() { + ty::Array(ty, _) | ty::Slice(ty) => Some(ty), + _ => None, + }) + .unwrap_or_else(|| { + self.next_ty_var(TypeVariableOrigin { + kind: TypeVariableOriginKind::TypeInference, + span: expr.span, + }) + }); + let mut coerce = CoerceMany::with_coercion_sites(coerce_to, args); + assert_eq!(self.diverges.get(), Diverges::Maybe); + for e in args { + let e_ty = self.check_expr_with_hint(e, coerce_to); + let cause = self.misc(e.span); + coerce.coerce(self, &cause, e, e_ty); + } + coerce.complete(self) + } else { + self.next_ty_var(TypeVariableOrigin { + kind: TypeVariableOriginKind::TypeInference, + span: expr.span, + }) + }; + let array_len = args.len() as u64; + self.suggest_array_len(expr, array_len); + self.tcx.mk_array(element_ty, array_len) + } + + fn suggest_array_len(&self, expr: &'tcx hir::Expr<'tcx>, array_len: u64) { + let parent_node = self.tcx.hir().parent_iter(expr.hir_id).find(|(_, node)| { + !matches!(node, hir::Node::Expr(hir::Expr { kind: hir::ExprKind::AddrOf(..), .. })) + }); + let Some((_, + hir::Node::Local(hir::Local { ty: Some(ty), .. }) + | hir::Node::Item(hir::Item { kind: hir::ItemKind::Const(ty, _), .. })) + ) = parent_node else { + return + }; + if let hir::TyKind::Array(_, length) = ty.peel_refs().kind + && let hir::ArrayLen::Body(hir::AnonConst { hir_id, .. }) = length + && let Some(span) = self.tcx.hir().opt_span(hir_id) + { + match self.tcx.sess.diagnostic().steal_diagnostic(span, StashKey::UnderscoreForArrayLengths) { + Some(mut err) => { + err.span_suggestion( + span, + "consider specifying the array length", + array_len, + Applicability::MaybeIncorrect, + ); + err.emit(); + } + None => () + } + } + } + + fn check_expr_const_block( + &self, + anon_const: &'tcx hir::AnonConst, + expected: Expectation<'tcx>, + _expr: &'tcx hir::Expr<'tcx>, + ) -> Ty<'tcx> { + let body = self.tcx.hir().body(anon_const.body); + + // Create a new function context. + let fcx = FnCtxt::new(self, self.param_env.with_const(), body.value.hir_id); + crate::GatherLocalsVisitor::new(&fcx).visit_body(body); + + let ty = fcx.check_expr_with_expectation(&body.value, expected); + fcx.require_type_is_sized(ty, body.value.span, traits::ConstSized); + fcx.write_ty(anon_const.hir_id, ty); + ty + } + + fn check_expr_repeat( + &self, + element: &'tcx hir::Expr<'tcx>, + count: &'tcx hir::ArrayLen, + expected: Expectation<'tcx>, + expr: &'tcx hir::Expr<'tcx>, + ) -> Ty<'tcx> { + let tcx = self.tcx; + let count = self.array_length_to_const(count); + if let Some(count) = count.try_eval_usize(tcx, self.param_env) { + self.suggest_array_len(expr, count); + } + + let uty = match expected { + ExpectHasType(uty) => match *uty.kind() { + ty::Array(ty, _) | ty::Slice(ty) => Some(ty), + _ => None, + }, + _ => None, + }; + + let (element_ty, t) = match uty { + Some(uty) => { + self.check_expr_coercable_to_type(&element, uty, None); + (uty, uty) + } + None => { + let ty = self.next_ty_var(TypeVariableOrigin { + kind: TypeVariableOriginKind::MiscVariable, + span: element.span, + }); + let element_ty = self.check_expr_has_type_or_error(&element, ty, |_| {}); + (element_ty, ty) + } + }; + + if element_ty.references_error() { + return tcx.ty_error(); + } + + self.check_repeat_element_needs_copy_bound(element, count, element_ty); + + tcx.mk_ty(ty::Array(t, count)) + } + + fn check_repeat_element_needs_copy_bound( + &self, + element: &hir::Expr<'_>, + count: ty::Const<'tcx>, + element_ty: Ty<'tcx>, + ) { + let tcx = self.tcx; + // Actual constants as the repeat element get inserted repeatedly instead of getting copied via Copy. + match &element.kind { + hir::ExprKind::ConstBlock(..) => return, + hir::ExprKind::Path(qpath) => { + let res = self.typeck_results.borrow().qpath_res(qpath, element.hir_id); + if let Res::Def(DefKind::Const | DefKind::AssocConst | DefKind::AnonConst, _) = res + { + return; + } + } + _ => {} + } + // If someone calls a const fn, they can extract that call out into a separate constant (or a const + // block in the future), so we check that to tell them that in the diagnostic. Does not affect typeck. + let is_const_fn = match element.kind { + hir::ExprKind::Call(func, _args) => match *self.node_ty(func.hir_id).kind() { + ty::FnDef(def_id, _) => tcx.is_const_fn(def_id), + _ => false, + }, + _ => false, + }; + + // If the length is 0, we don't create any elements, so we don't copy any. If the length is 1, we + // don't copy that one element, we move it. Only check for Copy if the length is larger. + if count.try_eval_usize(tcx, self.param_env).map_or(true, |len| len > 1) { + let lang_item = self.tcx.require_lang_item(LangItem::Copy, None); + let code = traits::ObligationCauseCode::RepeatElementCopy { is_const_fn }; + self.require_type_meets(element_ty, element.span, code, lang_item); + } + } + + fn check_expr_tuple( + &self, + elts: &'tcx [hir::Expr<'tcx>], + expected: Expectation<'tcx>, + expr: &'tcx hir::Expr<'tcx>, + ) -> Ty<'tcx> { + let flds = expected.only_has_type(self).and_then(|ty| { + let ty = self.resolve_vars_with_obligations(ty); + match ty.kind() { + ty::Tuple(flds) => Some(&flds[..]), + _ => None, + } + }); + + let elt_ts_iter = elts.iter().enumerate().map(|(i, e)| match flds { + Some(fs) if i < fs.len() => { + let ety = fs[i]; + self.check_expr_coercable_to_type(&e, ety, None); + ety + } + _ => self.check_expr_with_expectation(&e, NoExpectation), + }); + let tuple = self.tcx.mk_tup(elt_ts_iter); + if tuple.references_error() { + self.tcx.ty_error() + } else { + self.require_type_is_sized(tuple, expr.span, traits::TupleInitializerSized); + tuple + } + } + + fn check_expr_struct( + &self, + expr: &hir::Expr<'_>, + expected: Expectation<'tcx>, + qpath: &QPath<'_>, + fields: &'tcx [hir::ExprField<'tcx>], + base_expr: &'tcx Option<&'tcx hir::Expr<'tcx>>, + ) -> Ty<'tcx> { + // Find the relevant variant + let Some((variant, adt_ty)) = self.check_struct_path(qpath, expr.hir_id) else { + self.check_struct_fields_on_error(fields, base_expr); + return self.tcx.ty_error(); + }; + + // Prohibit struct expressions when non-exhaustive flag is set. + let adt = adt_ty.ty_adt_def().expect("`check_struct_path` returned non-ADT type"); + if !adt.did().is_local() && variant.is_field_list_non_exhaustive() { + self.tcx + .sess + .emit_err(StructExprNonExhaustive { span: expr.span, what: adt.variant_descr() }); + } + + self.check_expr_struct_fields( + adt_ty, + expected, + expr.hir_id, + qpath.span(), + variant, + fields, + base_expr, + expr.span, + ); + + self.require_type_is_sized(adt_ty, expr.span, traits::StructInitializerSized); + adt_ty + } + + fn check_expr_struct_fields( + &self, + adt_ty: Ty<'tcx>, + expected: Expectation<'tcx>, + expr_id: hir::HirId, + span: Span, + variant: &'tcx ty::VariantDef, + ast_fields: &'tcx [hir::ExprField<'tcx>], + base_expr: &'tcx Option<&'tcx hir::Expr<'tcx>>, + expr_span: Span, + ) { + let tcx = self.tcx; + + let expected_inputs = + self.expected_inputs_for_expected_output(span, expected, adt_ty, &[adt_ty]); + let adt_ty_hint = if let Some(expected_inputs) = expected_inputs { + expected_inputs.get(0).cloned().unwrap_or(adt_ty) + } else { + adt_ty + }; + // re-link the regions that EIfEO can erase. + self.demand_eqtype(span, adt_ty_hint, adt_ty); + + let ty::Adt(adt, substs) = adt_ty.kind() else { + span_bug!(span, "non-ADT passed to check_expr_struct_fields"); + }; + let adt_kind = adt.adt_kind(); + + let mut remaining_fields = variant + .fields + .iter() + .enumerate() + .map(|(i, field)| (field.ident(tcx).normalize_to_macros_2_0(), (i, field))) + .collect::<FxHashMap<_, _>>(); + + let mut seen_fields = FxHashMap::default(); + + let mut error_happened = false; + + // Type-check each field. + for (idx, field) in ast_fields.iter().enumerate() { + let ident = tcx.adjust_ident(field.ident, variant.def_id); + let field_type = if let Some((i, v_field)) = remaining_fields.remove(&ident) { + seen_fields.insert(ident, field.span); + self.write_field_index(field.hir_id, i); + + // We don't look at stability attributes on + // struct-like enums (yet...), but it's definitely not + // a bug to have constructed one. + if adt_kind != AdtKind::Enum { + tcx.check_stability(v_field.did, Some(expr_id), field.span, None); + } + + self.field_ty(field.span, v_field, substs) + } else { + error_happened = true; + if let Some(prev_span) = seen_fields.get(&ident) { + tcx.sess.emit_err(FieldMultiplySpecifiedInInitializer { + span: field.ident.span, + prev_span: *prev_span, + ident, + }); + } else { + self.report_unknown_field( + adt_ty, + variant, + field, + ast_fields, + adt.variant_descr(), + expr_span, + ); + } + + tcx.ty_error() + }; + + // Make sure to give a type to the field even if there's + // an error, so we can continue type-checking. + let ty = self.check_expr_with_hint(&field.expr, field_type); + let (_, diag) = + self.demand_coerce_diag(&field.expr, ty, field_type, None, AllowTwoPhase::No); + + if let Some(mut diag) = diag { + if idx == ast_fields.len() - 1 && remaining_fields.is_empty() { + self.suggest_fru_from_range(field, variant, substs, &mut diag); + } + diag.emit(); + } + } + + // Make sure the programmer specified correct number of fields. + if adt_kind == AdtKind::Union { + if ast_fields.len() != 1 { + struct_span_err!( + tcx.sess, + span, + E0784, + "union expressions should have exactly one field", + ) + .emit(); + } + } + + // If check_expr_struct_fields hit an error, do not attempt to populate + // the fields with the base_expr. This could cause us to hit errors later + // when certain fields are assumed to exist that in fact do not. + if error_happened { + return; + } + + if let Some(base_expr) = base_expr { + // FIXME: We are currently creating two branches here in order to maintain + // consistency. But they should be merged as much as possible. + let fru_tys = if self.tcx.features().type_changing_struct_update { + if adt.is_struct() { + // Make some fresh substitutions for our ADT type. + let fresh_substs = self.fresh_substs_for_item(base_expr.span, adt.did()); + // We do subtyping on the FRU fields first, so we can + // learn exactly what types we expect the base expr + // needs constrained to be compatible with the struct + // type we expect from the expectation value. + let fru_tys = variant + .fields + .iter() + .map(|f| { + let fru_ty = self.normalize_associated_types_in( + expr_span, + self.field_ty(base_expr.span, f, fresh_substs), + ); + let ident = self.tcx.adjust_ident(f.ident(self.tcx), variant.def_id); + if let Some(_) = remaining_fields.remove(&ident) { + let target_ty = self.field_ty(base_expr.span, f, substs); + let cause = self.misc(base_expr.span); + match self.at(&cause, self.param_env).sup(target_ty, fru_ty) { + Ok(InferOk { obligations, value: () }) => { + self.register_predicates(obligations) + } + Err(_) => { + // This should never happen, since we're just subtyping the + // remaining_fields, but it's fine to emit this, I guess. + self.err_ctxt() + .report_mismatched_types( + &cause, + target_ty, + fru_ty, + FieldMisMatch(variant.name, ident.name), + ) + .emit(); + } + } + } + self.resolve_vars_if_possible(fru_ty) + }) + .collect(); + // The use of fresh substs that we have subtyped against + // our base ADT type's fields allows us to guide inference + // along so that, e.g. + // ``` + // MyStruct<'a, F1, F2, const C: usize> { + // f: F1, + // // Other fields that reference `'a`, `F2`, and `C` + // } + // + // let x = MyStruct { + // f: 1usize, + // ..other_struct + // }; + // ``` + // will have the `other_struct` expression constrained to + // `MyStruct<'a, _, F2, C>`, as opposed to just `_`... + // This is important to allow coercions to happen in + // `other_struct` itself. See `coerce-in-base-expr.rs`. + let fresh_base_ty = self.tcx.mk_adt(*adt, fresh_substs); + self.check_expr_has_type_or_error( + base_expr, + self.resolve_vars_if_possible(fresh_base_ty), + |_| {}, + ); + fru_tys + } else { + // Check the base_expr, regardless of a bad expected adt_ty, so we can get + // type errors on that expression, too. + self.check_expr(base_expr); + self.tcx + .sess + .emit_err(FunctionalRecordUpdateOnNonStruct { span: base_expr.span }); + return; + } + } else { + self.check_expr_has_type_or_error(base_expr, adt_ty, |_| { + let base_ty = self.typeck_results.borrow().expr_ty(*base_expr); + let same_adt = match (adt_ty.kind(), base_ty.kind()) { + (ty::Adt(adt, _), ty::Adt(base_adt, _)) if adt == base_adt => true, + _ => false, + }; + if self.tcx.sess.is_nightly_build() && same_adt { + feature_err( + &self.tcx.sess.parse_sess, + sym::type_changing_struct_update, + base_expr.span, + "type changing struct updating is experimental", + ) + .emit(); + } + }); + match adt_ty.kind() { + ty::Adt(adt, substs) if adt.is_struct() => variant + .fields + .iter() + .map(|f| { + self.normalize_associated_types_in(expr_span, f.ty(self.tcx, substs)) + }) + .collect(), + _ => { + self.tcx + .sess + .emit_err(FunctionalRecordUpdateOnNonStruct { span: base_expr.span }); + return; + } + } + }; + self.typeck_results.borrow_mut().fru_field_types_mut().insert(expr_id, fru_tys); + } else if adt_kind != AdtKind::Union && !remaining_fields.is_empty() { + debug!(?remaining_fields); + let private_fields: Vec<&ty::FieldDef> = variant + .fields + .iter() + .filter(|field| !field.vis.is_accessible_from(tcx.parent_module(expr_id), tcx)) + .collect(); + + if !private_fields.is_empty() { + self.report_private_fields(adt_ty, span, private_fields, ast_fields); + } else { + self.report_missing_fields( + adt_ty, + span, + remaining_fields, + variant, + ast_fields, + substs, + ); + } + } + } + + fn check_struct_fields_on_error( + &self, + fields: &'tcx [hir::ExprField<'tcx>], + base_expr: &'tcx Option<&'tcx hir::Expr<'tcx>>, + ) { + for field in fields { + self.check_expr(&field.expr); + } + if let Some(base) = *base_expr { + self.check_expr(&base); + } + } + + /// Report an error for a struct field expression when there are fields which aren't provided. + /// + /// ```text + /// error: missing field `you_can_use_this_field` in initializer of `foo::Foo` + /// --> src/main.rs:8:5 + /// | + /// 8 | foo::Foo {}; + /// | ^^^^^^^^ missing `you_can_use_this_field` + /// + /// error: aborting due to previous error + /// ``` + fn report_missing_fields( + &self, + adt_ty: Ty<'tcx>, + span: Span, + remaining_fields: FxHashMap<Ident, (usize, &ty::FieldDef)>, + variant: &'tcx ty::VariantDef, + ast_fields: &'tcx [hir::ExprField<'tcx>], + substs: SubstsRef<'tcx>, + ) { + let len = remaining_fields.len(); + + let mut displayable_field_names: Vec<&str> = + remaining_fields.keys().map(|ident| ident.as_str()).collect(); + // sorting &str primitives here, sort_unstable is ok + displayable_field_names.sort_unstable(); + + let mut truncated_fields_error = String::new(); + let remaining_fields_names = match &displayable_field_names[..] { + [field1] => format!("`{}`", field1), + [field1, field2] => format!("`{field1}` and `{field2}`"), + [field1, field2, field3] => format!("`{field1}`, `{field2}` and `{field3}`"), + _ => { + truncated_fields_error = + format!(" and {} other field{}", len - 3, pluralize!(len - 3)); + displayable_field_names + .iter() + .take(3) + .map(|n| format!("`{n}`")) + .collect::<Vec<_>>() + .join(", ") + } + }; + + let mut err = struct_span_err!( + self.tcx.sess, + span, + E0063, + "missing field{} {}{} in initializer of `{}`", + pluralize!(len), + remaining_fields_names, + truncated_fields_error, + adt_ty + ); + err.span_label(span, format!("missing {remaining_fields_names}{truncated_fields_error}")); + + if let Some(last) = ast_fields.last() { + self.suggest_fru_from_range(last, variant, substs, &mut err); + } + + err.emit(); + } + + /// If the last field is a range literal, but it isn't supposed to be, then they probably + /// meant to use functional update syntax. + fn suggest_fru_from_range( + &self, + last_expr_field: &hir::ExprField<'tcx>, + variant: &ty::VariantDef, + substs: SubstsRef<'tcx>, + err: &mut Diagnostic, + ) { + // I don't use 'is_range_literal' because only double-sided, half-open ranges count. + if let ExprKind::Struct( + QPath::LangItem(LangItem::Range, ..), + &[ref range_start, ref range_end], + _, + ) = last_expr_field.expr.kind + && let variant_field = + variant.fields.iter().find(|field| field.ident(self.tcx) == last_expr_field.ident) + && let range_def_id = self.tcx.lang_items().range_struct() + && variant_field + .and_then(|field| field.ty(self.tcx, substs).ty_adt_def()) + .map(|adt| adt.did()) + != range_def_id + { + let instead = self + .tcx + .sess + .source_map() + .span_to_snippet(range_end.expr.span) + .map(|s| format!(" from `{s}`")) + .unwrap_or_default(); + err.span_suggestion( + range_start.span.shrink_to_hi(), + &format!("to set the remaining fields{instead}, separate the last named field with a comma"), + ",", + Applicability::MaybeIncorrect, + ); + } + } + + /// Report an error for a struct field expression when there are invisible fields. + /// + /// ```text + /// error: cannot construct `Foo` with struct literal syntax due to private fields + /// --> src/main.rs:8:5 + /// | + /// 8 | foo::Foo {}; + /// | ^^^^^^^^ + /// + /// error: aborting due to previous error + /// ``` + fn report_private_fields( + &self, + adt_ty: Ty<'tcx>, + span: Span, + private_fields: Vec<&ty::FieldDef>, + used_fields: &'tcx [hir::ExprField<'tcx>], + ) { + let mut err = self.tcx.sess.struct_span_err( + span, + &format!( + "cannot construct `{adt_ty}` with struct literal syntax due to private fields", + ), + ); + let (used_private_fields, remaining_private_fields): ( + Vec<(Symbol, Span, bool)>, + Vec<(Symbol, Span, bool)>, + ) = private_fields + .iter() + .map(|field| { + match used_fields.iter().find(|used_field| field.name == used_field.ident.name) { + Some(used_field) => (field.name, used_field.span, true), + None => (field.name, self.tcx.def_span(field.did), false), + } + }) + .partition(|field| field.2); + err.span_labels(used_private_fields.iter().map(|(_, span, _)| *span), "private field"); + if !remaining_private_fields.is_empty() { + let remaining_private_fields_len = remaining_private_fields.len(); + let names = match &remaining_private_fields + .iter() + .map(|(name, _, _)| name) + .collect::<Vec<_>>()[..] + { + _ if remaining_private_fields_len > 6 => String::new(), + [name] => format!("`{name}` "), + [names @ .., last] => { + let names = names.iter().map(|name| format!("`{name}`")).collect::<Vec<_>>(); + format!("{} and `{last}` ", names.join(", ")) + } + [] => unreachable!(), + }; + err.note(format!( + "... and other private field{s} {names}that {were} not provided", + s = pluralize!(remaining_private_fields_len), + were = pluralize!("was", remaining_private_fields_len), + )); + } + err.emit(); + } + + fn report_unknown_field( + &self, + ty: Ty<'tcx>, + variant: &'tcx ty::VariantDef, + field: &hir::ExprField<'_>, + skip_fields: &[hir::ExprField<'_>], + kind_name: &str, + expr_span: Span, + ) { + if variant.is_recovered() { + self.set_tainted_by_errors(); + return; + } + let mut err = self.err_ctxt().type_error_struct_with_diag( + field.ident.span, + |actual| match ty.kind() { + ty::Adt(adt, ..) if adt.is_enum() => struct_span_err!( + self.tcx.sess, + field.ident.span, + E0559, + "{} `{}::{}` has no field named `{}`", + kind_name, + actual, + variant.name, + field.ident + ), + _ => struct_span_err!( + self.tcx.sess, + field.ident.span, + E0560, + "{} `{}` has no field named `{}`", + kind_name, + actual, + field.ident + ), + }, + ty, + ); + + let variant_ident_span = self.tcx.def_ident_span(variant.def_id).unwrap(); + match variant.ctor_kind { + CtorKind::Fn => match ty.kind() { + ty::Adt(adt, ..) if adt.is_enum() => { + err.span_label( + variant_ident_span, + format!( + "`{adt}::{variant}` defined here", + adt = ty, + variant = variant.name, + ), + ); + err.span_label(field.ident.span, "field does not exist"); + err.span_suggestion_verbose( + expr_span, + &format!( + "`{adt}::{variant}` is a tuple {kind_name}, use the appropriate syntax", + adt = ty, + variant = variant.name, + ), + format!( + "{adt}::{variant}(/* fields */)", + adt = ty, + variant = variant.name, + ), + Applicability::HasPlaceholders, + ); + } + _ => { + err.span_label(variant_ident_span, format!("`{adt}` defined here", adt = ty)); + err.span_label(field.ident.span, "field does not exist"); + err.span_suggestion_verbose( + expr_span, + &format!( + "`{adt}` is a tuple {kind_name}, use the appropriate syntax", + adt = ty, + kind_name = kind_name, + ), + format!("{adt}(/* fields */)", adt = ty), + Applicability::HasPlaceholders, + ); + } + }, + _ => { + // prevent all specified fields from being suggested + let skip_fields = skip_fields.iter().map(|x| x.ident.name); + if let Some(field_name) = self.suggest_field_name( + variant, + field.ident.name, + skip_fields.collect(), + expr_span, + ) { + err.span_suggestion( + field.ident.span, + "a field with a similar name exists", + field_name, + Applicability::MaybeIncorrect, + ); + } else { + match ty.kind() { + ty::Adt(adt, ..) => { + if adt.is_enum() { + err.span_label( + field.ident.span, + format!("`{}::{}` does not have this field", ty, variant.name), + ); + } else { + err.span_label( + field.ident.span, + format!("`{ty}` does not have this field"), + ); + } + let available_field_names = + self.available_field_names(variant, expr_span); + if !available_field_names.is_empty() { + err.note(&format!( + "available fields are: {}", + self.name_series_display(available_field_names) + )); + } + } + _ => bug!("non-ADT passed to report_unknown_field"), + } + }; + } + } + err.emit(); + } + + // Return a hint about the closest match in field names + fn suggest_field_name( + &self, + variant: &'tcx ty::VariantDef, + field: Symbol, + skip: Vec<Symbol>, + // The span where stability will be checked + span: Span, + ) -> Option<Symbol> { + let names = variant + .fields + .iter() + .filter_map(|field| { + // ignore already set fields and private fields from non-local crates + // and unstable fields. + if skip.iter().any(|&x| x == field.name) + || (!variant.def_id.is_local() && !field.vis.is_public()) + || matches!( + self.tcx.eval_stability(field.did, None, span, None), + stability::EvalResult::Deny { .. } + ) + { + None + } else { + Some(field.name) + } + }) + .collect::<Vec<Symbol>>(); + + find_best_match_for_name(&names, field, None) + } + + fn available_field_names( + &self, + variant: &'tcx ty::VariantDef, + access_span: Span, + ) -> Vec<Symbol> { + variant + .fields + .iter() + .filter(|field| { + let def_scope = self + .tcx + .adjust_ident_and_get_scope(field.ident(self.tcx), variant.def_id, self.body_id) + .1; + field.vis.is_accessible_from(def_scope, self.tcx) + && !matches!( + self.tcx.eval_stability(field.did, None, access_span, None), + stability::EvalResult::Deny { .. } + ) + }) + .filter(|field| !self.tcx.is_doc_hidden(field.did)) + .map(|field| field.name) + .collect() + } + + fn name_series_display(&self, names: Vec<Symbol>) -> String { + // dynamic limit, to never omit just one field + let limit = if names.len() == 6 { 6 } else { 5 }; + let mut display = + names.iter().take(limit).map(|n| format!("`{}`", n)).collect::<Vec<_>>().join(", "); + if names.len() > limit { + display = format!("{} ... and {} others", display, names.len() - limit); + } + display + } + + // Check field access expressions + fn check_field( + &self, + expr: &'tcx hir::Expr<'tcx>, + base: &'tcx hir::Expr<'tcx>, + field: Ident, + ) -> Ty<'tcx> { + debug!("check_field(expr: {:?}, base: {:?}, field: {:?})", expr, base, field); + let base_ty = self.check_expr(base); + let base_ty = self.structurally_resolved_type(base.span, base_ty); + let mut private_candidate = None; + let mut autoderef = self.autoderef(expr.span, base_ty); + while let Some((deref_base_ty, _)) = autoderef.next() { + debug!("deref_base_ty: {:?}", deref_base_ty); + match deref_base_ty.kind() { + ty::Adt(base_def, substs) if !base_def.is_enum() => { + debug!("struct named {:?}", deref_base_ty); + let (ident, def_scope) = + self.tcx.adjust_ident_and_get_scope(field, base_def.did(), self.body_id); + let fields = &base_def.non_enum_variant().fields; + if let Some(index) = fields + .iter() + .position(|f| f.ident(self.tcx).normalize_to_macros_2_0() == ident) + { + let field = &fields[index]; + let field_ty = self.field_ty(expr.span, field, substs); + // Save the index of all fields regardless of their visibility in case + // of error recovery. + self.write_field_index(expr.hir_id, index); + let adjustments = self.adjust_steps(&autoderef); + if field.vis.is_accessible_from(def_scope, self.tcx) { + self.apply_adjustments(base, adjustments); + self.register_predicates(autoderef.into_obligations()); + + self.tcx.check_stability(field.did, Some(expr.hir_id), expr.span, None); + return field_ty; + } + private_candidate = Some((adjustments, base_def.did(), field_ty)); + } + } + ty::Tuple(tys) => { + let fstr = field.as_str(); + if let Ok(index) = fstr.parse::<usize>() { + if fstr == index.to_string() { + if let Some(&field_ty) = tys.get(index) { + let adjustments = self.adjust_steps(&autoderef); + self.apply_adjustments(base, adjustments); + self.register_predicates(autoderef.into_obligations()); + + self.write_field_index(expr.hir_id, index); + return field_ty; + } + } + } + } + _ => {} + } + } + self.structurally_resolved_type(autoderef.span(), autoderef.final_ty(false)); + + if let Some((adjustments, did, field_ty)) = private_candidate { + // (#90483) apply adjustments to avoid ExprUseVisitor from + // creating erroneous projection. + self.apply_adjustments(base, adjustments); + self.ban_private_field_access(expr, base_ty, field, did); + return field_ty; + } + + if field.name == kw::Empty { + } else if self.method_exists(field, base_ty, expr.hir_id, true) { + self.ban_take_value_of_method(expr, base_ty, field); + } else if !base_ty.is_primitive_ty() { + self.ban_nonexisting_field(field, base, expr, base_ty); + } else { + let field_name = field.to_string(); + let mut err = type_error_struct!( + self.tcx().sess, + field.span, + base_ty, + E0610, + "`{base_ty}` is a primitive type and therefore doesn't have fields", + ); + let is_valid_suffix = |field: &str| { + if field == "f32" || field == "f64" { + return true; + } + let mut chars = field.chars().peekable(); + match chars.peek() { + Some('e') | Some('E') => { + chars.next(); + if let Some(c) = chars.peek() + && !c.is_numeric() && *c != '-' && *c != '+' + { + return false; + } + while let Some(c) = chars.peek() { + if !c.is_numeric() { + break; + } + chars.next(); + } + } + _ => (), + } + let suffix = chars.collect::<String>(); + suffix.is_empty() || suffix == "f32" || suffix == "f64" + }; + let maybe_partial_suffix = |field: &str| -> Option<&str> { + let first_chars = ['f', 'l']; + if field.len() >= 1 + && field.to_lowercase().starts_with(first_chars) + && field[1..].chars().all(|c| c.is_ascii_digit()) + { + if field.to_lowercase().starts_with(['f']) { Some("f32") } else { Some("f64") } + } else { + None + } + }; + if let ty::Infer(ty::IntVar(_)) = base_ty.kind() + && let ExprKind::Lit(Spanned { + node: ast::LitKind::Int(_, ast::LitIntType::Unsuffixed), + .. + }) = base.kind + && !base.span.from_expansion() + { + if is_valid_suffix(&field_name) { + err.span_suggestion_verbose( + field.span.shrink_to_lo(), + "if intended to be a floating point literal, consider adding a `0` after the period", + '0', + Applicability::MaybeIncorrect, + ); + } else if let Some(correct_suffix) = maybe_partial_suffix(&field_name) { + err.span_suggestion_verbose( + field.span, + format!("if intended to be a floating point literal, consider adding a `0` after the period and a `{correct_suffix}` suffix"), + format!("0{correct_suffix}"), + Applicability::MaybeIncorrect, + ); + } + } + err.emit(); + } + + self.tcx().ty_error() + } + + fn suggest_await_on_field_access( + &self, + err: &mut Diagnostic, + field_ident: Ident, + base: &'tcx hir::Expr<'tcx>, + ty: Ty<'tcx>, + ) { + let output_ty = match self.get_impl_future_output_ty(ty) { + Some(output_ty) => self.resolve_vars_if_possible(output_ty), + _ => return, + }; + let mut add_label = true; + if let ty::Adt(def, _) = output_ty.skip_binder().kind() { + // no field access on enum type + if !def.is_enum() { + if def + .non_enum_variant() + .fields + .iter() + .any(|field| field.ident(self.tcx) == field_ident) + { + add_label = false; + err.span_label( + field_ident.span, + "field not available in `impl Future`, but it is available in its `Output`", + ); + err.span_suggestion_verbose( + base.span.shrink_to_hi(), + "consider `await`ing on the `Future` and access the field of its `Output`", + ".await", + Applicability::MaybeIncorrect, + ); + } + } + } + if add_label { + err.span_label(field_ident.span, &format!("field not found in `{ty}`")); + } + } + + fn ban_nonexisting_field( + &self, + ident: Ident, + base: &'tcx hir::Expr<'tcx>, + expr: &'tcx hir::Expr<'tcx>, + base_ty: Ty<'tcx>, + ) { + debug!( + "ban_nonexisting_field: field={:?}, base={:?}, expr={:?}, base_ty={:?}", + ident, base, expr, base_ty + ); + let mut err = self.no_such_field_err(ident, base_ty, base.hir_id); + + match *base_ty.peel_refs().kind() { + ty::Array(_, len) => { + self.maybe_suggest_array_indexing(&mut err, expr, base, ident, len); + } + ty::RawPtr(..) => { + self.suggest_first_deref_field(&mut err, expr, base, ident); + } + ty::Adt(def, _) if !def.is_enum() => { + self.suggest_fields_on_recordish(&mut err, def, ident, expr.span); + } + ty::Param(param_ty) => { + self.point_at_param_definition(&mut err, param_ty); + } + ty::Opaque(_, _) => { + self.suggest_await_on_field_access(&mut err, ident, base, base_ty.peel_refs()); + } + _ => {} + } + + self.suggest_fn_call(&mut err, base, base_ty, |output_ty| { + if let ty::Adt(def, _) = output_ty.kind() && !def.is_enum() { + def.non_enum_variant().fields.iter().any(|field| { + field.ident(self.tcx) == ident + && field.vis.is_accessible_from(expr.hir_id.owner.def_id, self.tcx) + }) + } else if let ty::Tuple(tys) = output_ty.kind() + && let Ok(idx) = ident.as_str().parse::<usize>() + { + idx < tys.len() + } else { + false + } + }); + + if ident.name == kw::Await { + // We know by construction that `<expr>.await` is either on Rust 2015 + // or results in `ExprKind::Await`. Suggest switching the edition to 2018. + err.note("to `.await` a `Future`, switch to Rust 2018 or later"); + err.help_use_latest_edition(); + } + + err.emit(); + } + + fn ban_private_field_access( + &self, + expr: &hir::Expr<'_>, + expr_t: Ty<'tcx>, + field: Ident, + base_did: DefId, + ) { + let struct_path = self.tcx().def_path_str(base_did); + let kind_name = self.tcx().def_kind(base_did).descr(base_did); + let mut err = struct_span_err!( + self.tcx().sess, + field.span, + E0616, + "field `{field}` of {kind_name} `{struct_path}` is private", + ); + err.span_label(field.span, "private field"); + // Also check if an accessible method exists, which is often what is meant. + if self.method_exists(field, expr_t, expr.hir_id, false) && !self.expr_in_place(expr.hir_id) + { + self.suggest_method_call( + &mut err, + &format!("a method `{field}` also exists, call it with parentheses"), + field, + expr_t, + expr, + None, + ); + } + err.emit(); + } + + fn ban_take_value_of_method(&self, expr: &hir::Expr<'_>, expr_t: Ty<'tcx>, field: Ident) { + let mut err = type_error_struct!( + self.tcx().sess, + field.span, + expr_t, + E0615, + "attempted to take value of method `{field}` on type `{expr_t}`", + ); + err.span_label(field.span, "method, not a field"); + let expr_is_call = + if let hir::Node::Expr(hir::Expr { kind: ExprKind::Call(callee, _args), .. }) = + self.tcx.hir().get(self.tcx.hir().get_parent_node(expr.hir_id)) + { + expr.hir_id == callee.hir_id + } else { + false + }; + let expr_snippet = + self.tcx.sess.source_map().span_to_snippet(expr.span).unwrap_or_default(); + let is_wrapped = expr_snippet.starts_with('(') && expr_snippet.ends_with(')'); + let after_open = expr.span.lo() + rustc_span::BytePos(1); + let before_close = expr.span.hi() - rustc_span::BytePos(1); + + if expr_is_call && is_wrapped { + err.multipart_suggestion( + "remove wrapping parentheses to call the method", + vec![ + (expr.span.with_hi(after_open), String::new()), + (expr.span.with_lo(before_close), String::new()), + ], + Applicability::MachineApplicable, + ); + } else if !self.expr_in_place(expr.hir_id) { + // Suggest call parentheses inside the wrapping parentheses + let span = if is_wrapped { + expr.span.with_lo(after_open).with_hi(before_close) + } else { + expr.span + }; + self.suggest_method_call( + &mut err, + "use parentheses to call the method", + field, + expr_t, + expr, + Some(span), + ); + } else if let ty::RawPtr(ty_and_mut) = expr_t.kind() + && let ty::Adt(adt_def, _) = ty_and_mut.ty.kind() + && let ExprKind::Field(base_expr, _) = expr.kind + && adt_def.variants().len() == 1 + && adt_def + .variants() + .iter() + .next() + .unwrap() + .fields + .iter() + .any(|f| f.ident(self.tcx) == field) + { + err.multipart_suggestion( + "to access the field, dereference first", + vec![ + (base_expr.span.shrink_to_lo(), "(*".to_string()), + (base_expr.span.shrink_to_hi(), ")".to_string()), + ], + Applicability::MaybeIncorrect, + ); + } else { + err.help("methods are immutable and cannot be assigned to"); + } + + err.emit(); + } + + fn point_at_param_definition(&self, err: &mut Diagnostic, param: ty::ParamTy) { + let generics = self.tcx.generics_of(self.body_id.owner.to_def_id()); + let generic_param = generics.type_param(¶m, self.tcx); + if let ty::GenericParamDefKind::Type { synthetic: true, .. } = generic_param.kind { + return; + } + let param_def_id = generic_param.def_id; + let param_hir_id = match param_def_id.as_local() { + Some(x) => self.tcx.hir().local_def_id_to_hir_id(x), + None => return, + }; + let param_span = self.tcx.hir().span(param_hir_id); + let param_name = self.tcx.hir().ty_param_name(param_def_id.expect_local()); + + err.span_label(param_span, &format!("type parameter '{param_name}' declared here")); + } + + fn suggest_fields_on_recordish( + &self, + err: &mut Diagnostic, + def: ty::AdtDef<'tcx>, + field: Ident, + access_span: Span, + ) { + if let Some(suggested_field_name) = + self.suggest_field_name(def.non_enum_variant(), field.name, vec![], access_span) + { + err.span_suggestion( + field.span, + "a field with a similar name exists", + suggested_field_name, + Applicability::MaybeIncorrect, + ); + } else { + err.span_label(field.span, "unknown field"); + let struct_variant_def = def.non_enum_variant(); + let field_names = self.available_field_names(struct_variant_def, access_span); + if !field_names.is_empty() { + err.note(&format!( + "available fields are: {}", + self.name_series_display(field_names), + )); + } + } + } + + fn maybe_suggest_array_indexing( + &self, + err: &mut Diagnostic, + expr: &hir::Expr<'_>, + base: &hir::Expr<'_>, + field: Ident, + len: ty::Const<'tcx>, + ) { + if let (Some(len), Ok(user_index)) = + (len.try_eval_usize(self.tcx, self.param_env), field.as_str().parse::<u64>()) + && let Ok(base) = self.tcx.sess.source_map().span_to_snippet(base.span) + { + let help = "instead of using tuple indexing, use array indexing"; + let suggestion = format!("{base}[{field}]"); + let applicability = if len < user_index { + Applicability::MachineApplicable + } else { + Applicability::MaybeIncorrect + }; + err.span_suggestion(expr.span, help, suggestion, applicability); + } + } + + fn suggest_first_deref_field( + &self, + err: &mut Diagnostic, + expr: &hir::Expr<'_>, + base: &hir::Expr<'_>, + field: Ident, + ) { + if let Ok(base) = self.tcx.sess.source_map().span_to_snippet(base.span) { + let msg = format!("`{base}` is a raw pointer; try dereferencing it"); + let suggestion = format!("(*{base}).{field}"); + err.span_suggestion(expr.span, &msg, suggestion, Applicability::MaybeIncorrect); + } + } + + fn no_such_field_err( + &self, + field: Ident, + expr_t: Ty<'tcx>, + id: HirId, + ) -> DiagnosticBuilder<'_, ErrorGuaranteed> { + let span = field.span; + debug!("no_such_field_err(span: {:?}, field: {:?}, expr_t: {:?})", span, field, expr_t); + + let mut err = type_error_struct!( + self.tcx().sess, + field.span, + expr_t, + E0609, + "no field `{field}` on type `{expr_t}`", + ); + + // try to add a suggestion in case the field is a nested field of a field of the Adt + let mod_id = self.tcx.parent_module(id).to_def_id(); + if let Some((fields, substs)) = + self.get_field_candidates_considering_privacy(span, expr_t, mod_id) + { + let candidate_fields: Vec<_> = fields + .filter_map(|candidate_field| { + self.check_for_nested_field_satisfying( + span, + &|candidate_field, _| candidate_field.ident(self.tcx()) == field, + candidate_field, + substs, + vec![], + mod_id, + ) + }) + .map(|mut field_path| { + field_path.pop(); + field_path + .iter() + .map(|id| id.name.to_ident_string()) + .collect::<Vec<String>>() + .join(".") + }) + .collect::<Vec<_>>(); + + let len = candidate_fields.len(); + if len > 0 { + err.span_suggestions( + field.span.shrink_to_lo(), + format!( + "{} of the expressions' fields {} a field of the same name", + if len > 1 { "some" } else { "one" }, + if len > 1 { "have" } else { "has" }, + ), + candidate_fields.iter().map(|path| format!("{path}.")), + Applicability::MaybeIncorrect, + ); + } + } + err + } + + pub(crate) fn get_field_candidates_considering_privacy( + &self, + span: Span, + base_ty: Ty<'tcx>, + mod_id: DefId, + ) -> Option<(impl Iterator<Item = &'tcx ty::FieldDef> + 'tcx, SubstsRef<'tcx>)> { + debug!("get_field_candidates(span: {:?}, base_t: {:?}", span, base_ty); + + for (base_t, _) in self.autoderef(span, base_ty) { + match base_t.kind() { + ty::Adt(base_def, substs) if !base_def.is_enum() => { + let tcx = self.tcx; + let fields = &base_def.non_enum_variant().fields; + // Some struct, e.g. some that impl `Deref`, have all private fields + // because you're expected to deref them to access the _real_ fields. + // This, for example, will help us suggest accessing a field through a `Box<T>`. + if fields.iter().all(|field| !field.vis.is_accessible_from(mod_id, tcx)) { + continue; + } + return Some(( + fields + .iter() + .filter(move |field| field.vis.is_accessible_from(mod_id, tcx)) + // For compile-time reasons put a limit on number of fields we search + .take(100), + substs, + )); + } + _ => {} + } + } + None + } + + /// This method is called after we have encountered a missing field error to recursively + /// search for the field + pub(crate) fn check_for_nested_field_satisfying( + &self, + span: Span, + matches: &impl Fn(&ty::FieldDef, Ty<'tcx>) -> bool, + candidate_field: &ty::FieldDef, + subst: SubstsRef<'tcx>, + mut field_path: Vec<Ident>, + mod_id: DefId, + ) -> Option<Vec<Ident>> { + debug!( + "check_for_nested_field_satisfying(span: {:?}, candidate_field: {:?}, field_path: {:?}", + span, candidate_field, field_path + ); + + if field_path.len() > 3 { + // For compile-time reasons and to avoid infinite recursion we only check for fields + // up to a depth of three + None + } else { + field_path.push(candidate_field.ident(self.tcx).normalize_to_macros_2_0()); + let field_ty = candidate_field.ty(self.tcx, subst); + if matches(candidate_field, field_ty) { + return Some(field_path); + } else if let Some((nested_fields, subst)) = + self.get_field_candidates_considering_privacy(span, field_ty, mod_id) + { + // recursively search fields of `candidate_field` if it's a ty::Adt + for field in nested_fields { + if let Some(field_path) = self.check_for_nested_field_satisfying( + span, + matches, + field, + subst, + field_path.clone(), + mod_id, + ) { + return Some(field_path); + } + } + } + None + } + } + + fn check_expr_index( + &self, + base: &'tcx hir::Expr<'tcx>, + idx: &'tcx hir::Expr<'tcx>, + expr: &'tcx hir::Expr<'tcx>, + ) -> Ty<'tcx> { + let base_t = self.check_expr(&base); + let idx_t = self.check_expr(&idx); + + if base_t.references_error() { + base_t + } else if idx_t.references_error() { + idx_t + } else { + let base_t = self.structurally_resolved_type(base.span, base_t); + match self.lookup_indexing(expr, base, base_t, idx, idx_t) { + Some((index_ty, element_ty)) => { + // two-phase not needed because index_ty is never mutable + self.demand_coerce(idx, idx_t, index_ty, None, AllowTwoPhase::No); + self.select_obligations_where_possible(false, |errors| { + self.point_at_index_if_possible(errors, idx.span) + }); + element_ty + } + None => { + let mut err = type_error_struct!( + self.tcx.sess, + expr.span, + base_t, + E0608, + "cannot index into a value of type `{base_t}`", + ); + // Try to give some advice about indexing tuples. + if let ty::Tuple(..) = base_t.kind() { + let mut needs_note = true; + // If the index is an integer, we can show the actual + // fixed expression: + if let ExprKind::Lit(ref lit) = idx.kind { + if let ast::LitKind::Int(i, ast::LitIntType::Unsuffixed) = lit.node { + let snip = self.tcx.sess.source_map().span_to_snippet(base.span); + if let Ok(snip) = snip { + err.span_suggestion( + expr.span, + "to access tuple elements, use", + format!("{snip}.{i}"), + Applicability::MachineApplicable, + ); + needs_note = false; + } + } + } + if needs_note { + err.help( + "to access tuple elements, use tuple indexing \ + syntax (e.g., `tuple.0`)", + ); + } + } + err.emit(); + self.tcx.ty_error() + } + } + } + } + + fn point_at_index_if_possible( + &self, + errors: &mut Vec<traits::FulfillmentError<'tcx>>, + span: Span, + ) { + for error in errors { + match error.obligation.predicate.kind().skip_binder() { + ty::PredicateKind::Trait(predicate) + if self.tcx.is_diagnostic_item(sym::SliceIndex, predicate.trait_ref.def_id) => { + } + _ => continue, + } + error.obligation.cause.span = span; + } + } + + fn check_expr_yield( + &self, + value: &'tcx hir::Expr<'tcx>, + expr: &'tcx hir::Expr<'tcx>, + src: &'tcx hir::YieldSource, + ) -> Ty<'tcx> { + match self.resume_yield_tys { + Some((resume_ty, yield_ty)) => { + self.check_expr_coercable_to_type(&value, yield_ty, None); + + resume_ty + } + // Given that this `yield` expression was generated as a result of lowering a `.await`, + // we know that the yield type must be `()`; however, the context won't contain this + // information. Hence, we check the source of the yield expression here and check its + // value's type against `()` (this check should always hold). + None if src.is_await() => { + self.check_expr_coercable_to_type(&value, self.tcx.mk_unit(), None); + self.tcx.mk_unit() + } + _ => { + self.tcx.sess.emit_err(YieldExprOutsideOfGenerator { span: expr.span }); + // Avoid expressions without types during writeback (#78653). + self.check_expr(value); + self.tcx.mk_unit() + } + } + } + + fn check_expr_asm_operand(&self, expr: &'tcx hir::Expr<'tcx>, is_input: bool) { + let needs = if is_input { Needs::None } else { Needs::MutPlace }; + let ty = self.check_expr_with_needs(expr, needs); + self.require_type_is_sized(ty, expr.span, traits::InlineAsmSized); + + if !is_input && !expr.is_syntactic_place_expr() { + let mut err = self.tcx.sess.struct_span_err(expr.span, "invalid asm output"); + err.span_label(expr.span, "cannot assign to this expression"); + err.emit(); + } + + // If this is an input value, we require its type to be fully resolved + // at this point. This allows us to provide helpful coercions which help + // pass the type candidate list in a later pass. + // + // We don't require output types to be resolved at this point, which + // allows them to be inferred based on how they are used later in the + // function. + if is_input { + let ty = self.structurally_resolved_type(expr.span, ty); + match *ty.kind() { + ty::FnDef(..) => { + let fnptr_ty = self.tcx.mk_fn_ptr(ty.fn_sig(self.tcx)); + self.demand_coerce(expr, ty, fnptr_ty, None, AllowTwoPhase::No); + } + ty::Ref(_, base_ty, mutbl) => { + let ptr_ty = self.tcx.mk_ptr(ty::TypeAndMut { ty: base_ty, mutbl }); + self.demand_coerce(expr, ty, ptr_ty, None, AllowTwoPhase::No); + } + _ => {} + } + } + } + + fn check_expr_asm(&self, asm: &'tcx hir::InlineAsm<'tcx>) -> Ty<'tcx> { + for (op, _op_sp) in asm.operands { + match op { + hir::InlineAsmOperand::In { expr, .. } => { + self.check_expr_asm_operand(expr, true); + } + hir::InlineAsmOperand::Out { expr: Some(expr), .. } + | hir::InlineAsmOperand::InOut { expr, .. } => { + self.check_expr_asm_operand(expr, false); + } + hir::InlineAsmOperand::Out { expr: None, .. } => {} + hir::InlineAsmOperand::SplitInOut { in_expr, out_expr, .. } => { + self.check_expr_asm_operand(in_expr, true); + if let Some(out_expr) = out_expr { + self.check_expr_asm_operand(out_expr, false); + } + } + // `AnonConst`s have their own body and is type-checked separately. + // As they don't flow into the type system we don't need them to + // be well-formed. + hir::InlineAsmOperand::Const { .. } | hir::InlineAsmOperand::SymFn { .. } => {} + hir::InlineAsmOperand::SymStatic { .. } => {} + } + } + if asm.options.contains(ast::InlineAsmOptions::NORETURN) { + self.tcx.types.never + } else { + self.tcx.mk_unit() + } + } +} diff --git a/compiler/rustc_typeck/src/expr_use_visitor.rs b/compiler/rustc_hir_typeck/src/expr_use_visitor.rs index 74a5b6e42..fce2a5888 100644 --- a/compiler/rustc_typeck/src/expr_use_visitor.rs +++ b/compiler/rustc_hir_typeck/src/expr_use_visitor.rs @@ -89,15 +89,6 @@ enum ConsumeMode { Move, } -#[derive(Copy, Clone, PartialEq, Debug)] -pub enum MutateMode { - Init, - /// Example: `x = y` - JustWrite, - /// Example: `x += y` - WriteAndRead, -} - /// The ExprUseVisitor type /// /// This is the code that actually walks the tree. @@ -134,7 +125,7 @@ impl<'a, 'tcx> ExprUseVisitor<'a, 'tcx> { /// - `typeck_results` --- typeck results for the code being analyzed pub fn new( delegate: &'a mut (dyn Delegate<'tcx> + 'a), - infcx: &'a InferCtxt<'a, 'tcx>, + infcx: &'a InferCtxt<'tcx>, body_owner: LocalDefId, param_env: ty::ParamEnv<'tcx>, typeck_results: &'a ty::TypeckResults<'tcx>, @@ -233,8 +224,9 @@ impl<'a, 'tcx> ExprUseVisitor<'a, 'tcx> { self.consume_exprs(args); } - hir::ExprKind::MethodCall(.., args, _) => { + hir::ExprKind::MethodCall(.., receiver, args, _) => { // callee.m(args) + self.consume_expr(receiver); self.consume_exprs(args); } @@ -497,7 +489,7 @@ impl<'a, 'tcx> ExprUseVisitor<'a, 'tcx> { let expr_place = return_if_err!(self.mc.cat_expr(expr)); f(self); if let Some(els) = els { - // borrowing because we need to test the descriminant + // borrowing because we need to test the discriminant self.maybe_read_scrutinee(expr, expr_place.clone(), from_ref(pat).iter()); self.walk_block(els) } @@ -582,7 +574,9 @@ impl<'a, 'tcx> ExprUseVisitor<'a, 'tcx> { for adjustment in adjustments { debug!("walk_adjustment expr={:?} adj={:?}", expr, adjustment); match adjustment.kind { - adjustment::Adjust::NeverToAny | adjustment::Adjust::Pointer(_) => { + adjustment::Adjust::NeverToAny + | adjustment::Adjust::Pointer(_) + | adjustment::Adjust::DynStar => { // Creating a closure/fn-pointer or unsizing consumes // the input and stores it into the resulting rvalue. self.delegate_consume(&place_with_id, place_with_id.hir_id); diff --git a/compiler/rustc_typeck/src/check/fallback.rs b/compiler/rustc_hir_typeck/src/fallback.rs index 4059b3403..747ecb036 100644 --- a/compiler/rustc_typeck/src/check/fallback.rs +++ b/compiler/rustc_hir_typeck/src/fallback.rs @@ -1,4 +1,4 @@ -use crate::check::FnCtxt; +use crate::FnCtxt; use rustc_data_structures::{ fx::{FxHashMap, FxHashSet}, graph::WithSuccessors, @@ -72,7 +72,7 @@ impl<'tcx> FnCtxt<'_, 'tcx> { // // - Unconstrained ints are replaced with `i32`. // - // - Unconstrained floats are replaced with with `f64`. + // - Unconstrained floats are replaced with `f64`. // // - Non-numerics may get replaced with `()` or `!`, depending on // how they were categorized by `calculate_diverging_fallback` diff --git a/compiler/rustc_typeck/src/check/fn_ctxt/_impl.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs index 3a8093345..6a1cffe3e 100644 --- a/compiler/rustc_typeck/src/check/fn_ctxt/_impl.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs @@ -1,12 +1,7 @@ -use crate::astconv::{ - AstConv, CreateSubstsForGenericArgsCtxt, ExplicitLateBound, GenericArgCountMismatch, - GenericArgCountResult, IsMethodCall, PathSeg, -}; -use crate::check::callee::{self, DeferredCallResolution}; -use crate::check::method::{self, MethodCallee, SelfSource}; -use crate::check::rvalue_scopes; -use crate::check::{BreakableCtxt, Diverges, Expectation, FnCtxt, LocalTy}; - +use crate::callee::{self, DeferredCallResolution}; +use crate::method::{self, MethodCallee, SelfSource}; +use crate::rvalue_scopes; +use crate::{BreakableCtxt, Diverges, Expectation, FnCtxt, LocalTy}; use rustc_data_structures::captures::Captures; use rustc_data_structures::fx::FxHashSet; use rustc_errors::{Applicability, Diagnostic, ErrorGuaranteed, MultiSpan}; @@ -15,26 +10,28 @@ use rustc_hir::def::{CtorOf, DefKind, Res}; use rustc_hir::def_id::DefId; use rustc_hir::lang_items::LangItem; use rustc_hir::{ExprKind, GenericArg, Node, QPath}; +use rustc_hir_analysis::astconv::{ + AstConv, CreateSubstsForGenericArgsCtxt, ExplicitLateBound, GenericArgCountMismatch, + GenericArgCountResult, IsMethodCall, PathSeg, +}; use rustc_infer::infer::canonical::{Canonical, OriginalQueryValues, QueryResponse}; use rustc_infer::infer::error_reporting::TypeAnnotationNeeded::E0282; use rustc_infer::infer::{InferOk, InferResult}; use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, AutoBorrowMutability}; use rustc_middle::ty::fold::TypeFoldable; -use rustc_middle::ty::subst::{ - self, GenericArgKind, InternalSubsts, Subst, SubstsRef, UserSelfTy, UserSubsts, -}; use rustc_middle::ty::visit::TypeVisitable; use rustc_middle::ty::{ self, AdtKind, CanonicalUserType, DefIdTree, EarlyBinder, GenericParamDefKind, ToPolyTraitRef, ToPredicate, Ty, UserType, }; +use rustc_middle::ty::{GenericArgKind, InternalSubsts, SubstsRef, UserSelfTy, UserSubsts}; use rustc_session::lint; use rustc_span::def_id::LocalDefId; use rustc_span::hygiene::DesugaringKind; use rustc_span::symbol::{kw, sym, Ident}; use rustc_span::{Span, DUMMY_SP}; use rustc_trait_selection::infer::InferCtxtExt as _; -use rustc_trait_selection::traits::error_reporting::InferCtxtExt as _; +use rustc_trait_selection::traits::error_reporting::TypeErrCtxtExt as _; use rustc_trait_selection::traits::{ self, ObligationCause, ObligationCauseCode, TraitEngine, TraitEngineExt, }; @@ -60,17 +57,20 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { debug!("warn_if_unreachable: id={:?} span={:?} kind={}", id, span, kind); - self.tcx().struct_span_lint_hir(lint::builtin::UNREACHABLE_CODE, id, span, |lint| { - let msg = format!("unreachable {}", kind); - lint.build(&msg) - .span_label(span, &msg) - .span_label( + let msg = format!("unreachable {}", kind); + self.tcx().struct_span_lint_hir( + lint::builtin::UNREACHABLE_CODE, + id, + span, + &msg, + |lint| { + lint.span_label(span, &msg).span_label( orig_span, custom_note .unwrap_or("any code following this expression is unreachable"), ) - .emit(); - }) + }, + ) } } } @@ -83,21 +83,21 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.resolve_vars_with_obligations_and_mutate_fulfillment(ty, |_| {}) } - #[instrument(skip(self, mutate_fulfillment_errors), level = "debug")] + #[instrument(skip(self, mutate_fulfillment_errors), level = "debug", ret)] pub(in super::super) fn resolve_vars_with_obligations_and_mutate_fulfillment( &self, mut ty: Ty<'tcx>, mutate_fulfillment_errors: impl Fn(&mut Vec<traits::FulfillmentError<'tcx>>), ) -> Ty<'tcx> { // No Infer()? Nothing needs doing. - if !ty.has_infer_types_or_consts() { + if !ty.has_non_region_infer() { debug!("no inference var, nothing needs doing"); return ty; } // If `ty` is a type variable, see whether we already know what it is. ty = self.resolve_vars_if_possible(ty); - if !ty.has_infer_types_or_consts() { + if !ty.has_non_region_infer() { debug!(?ty); return ty; } @@ -107,10 +107,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // indirect dependencies that don't seem worth tracking // precisely. self.select_obligations_where_possible(false, mutate_fulfillment_errors); - ty = self.resolve_vars_if_possible(ty); - - debug!(?ty); - ty + self.resolve_vars_if_possible(ty) } pub(in super::super) fn record_deferred_call_resolution( @@ -412,7 +409,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { rhs_span: opt_input_expr.map(|expr| expr.span), is_lit: opt_input_expr .map_or(false, |expr| matches!(expr.kind, ExprKind::Lit(_))), - output_pred: None, + output_ty: None, }, ), self.param_env, @@ -492,21 +489,16 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { pub fn array_length_to_const(&self, length: &hir::ArrayLen) -> ty::Const<'tcx> { match length { &hir::ArrayLen::Infer(_, span) => self.ct_infer(self.tcx.types.usize, None, span), - hir::ArrayLen::Body(anon_const) => self.to_const(anon_const), + hir::ArrayLen::Body(anon_const) => { + let const_def_id = self.tcx.hir().local_def_id(anon_const.hir_id); + let span = self.tcx.hir().span(anon_const.hir_id); + let c = ty::Const::from_anon_const(self.tcx, const_def_id); + self.register_wf_obligation(c.into(), span, ObligationCauseCode::WellFormed(None)); + self.normalize_associated_types_in(span, c) + } } } - pub fn to_const(&self, ast_c: &hir::AnonConst) -> ty::Const<'tcx> { - let const_def_id = self.tcx.hir().local_def_id(ast_c.hir_id); - let c = ty::Const::from_anon_const(self.tcx, const_def_id); - self.register_wf_obligation( - c.into(), - self.tcx.hir().span(ast_c.hir_id), - ObligationCauseCode::WellFormed(None), - ); - c - } - pub fn const_arg_to_const( &self, ast_c: &hir::AnonConst, @@ -565,7 +557,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { /// Registers an obligation for checking later, during regionck, that `arg` is well-formed. pub fn register_wf_obligation( &self, - arg: subst::GenericArg<'tcx>, + arg: ty::GenericArg<'tcx>, span: Span, code: traits::ObligationCauseCode<'tcx>, ) { @@ -610,18 +602,17 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let mut generators = self.deferred_generator_interiors.borrow_mut(); for (body_id, interior, kind) in generators.drain(..) { self.select_obligations_where_possible(false, |_| {}); - crate::check::generator_interior::resolve_interior( - self, def_id, body_id, interior, kind, - ); + crate::generator_interior::resolve_interior(self, def_id, body_id, interior, kind); } } #[instrument(skip(self), level = "debug")] pub(in super::super) fn select_all_obligations_or_error(&self) { - let errors = self.fulfillment_cx.borrow_mut().select_all_or_error(&self); + let mut errors = self.fulfillment_cx.borrow_mut().select_all_or_error(&self); if !errors.is_empty() { - self.report_fulfillment_errors(&errors, self.inh.body_id, false); + self.adjust_fulfillment_errors_for_expr_obligation(&mut errors); + self.err_ctxt().report_fulfillment_errors(&errors, self.inh.body_id, false); } } @@ -634,7 +625,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let mut result = self.fulfillment_cx.borrow_mut().select_where_possible(self); if !result.is_empty() { mutate_fulfillment_errors(&mut result); - self.report_fulfillment_errors(&result, self.inh.body_id, fallback_has_occurred); + self.adjust_fulfillment_errors_for_expr_obligation(&mut result); + self.err_ctxt().report_fulfillment_errors( + &result, + self.inh.body_id, + fallback_has_occurred, + ); } } @@ -831,23 +827,25 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let ty = item_ty.subst(self.tcx, substs); self.write_resolution(hir_id, Ok((def_kind, def_id))); - self.add_required_obligations_with_code( - span, - def_id, - &substs, - match lang_item { - hir::LangItem::IntoFutureIntoFuture => { - ObligationCauseCode::AwaitableExpr(expr_hir_id) - } - hir::LangItem::IteratorNext | hir::LangItem::IntoIterIntoIter => { - ObligationCauseCode::ForLoopIterator - } - hir::LangItem::TryTraitFromOutput - | hir::LangItem::TryTraitFromResidual - | hir::LangItem::TryTraitBranch => ObligationCauseCode::QuestionMark, - _ => traits::ItemObligation(def_id), - }, - ); + + let code = match lang_item { + hir::LangItem::IntoFutureIntoFuture => { + Some(ObligationCauseCode::AwaitableExpr(expr_hir_id)) + } + hir::LangItem::IteratorNext | hir::LangItem::IntoIterIntoIter => { + Some(ObligationCauseCode::ForLoopIterator) + } + hir::LangItem::TryTraitFromOutput + | hir::LangItem::TryTraitFromResidual + | hir::LangItem::TryTraitBranch => Some(ObligationCauseCode::QuestionMark), + _ => None, + }; + if let Some(code) = code { + self.add_required_obligations_with_code(span, def_id, substs, move |_, _| code.clone()); + } else { + self.add_required_obligations_for_hir(span, def_id, substs, hir_id); + } + (Res::Def(def_kind, def_id), ty) } @@ -986,7 +984,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { if found != self.tcx.types.unit { return; } - if let ExprKind::MethodCall(path_segment, [rcvr, ..], _) = expr.kind { + if let ExprKind::MethodCall(path_segment, rcvr, ..) = expr.kind { if self .typeck_results .borrow() @@ -1273,7 +1271,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { &mut self, param: &ty::GenericParamDef, arg: &GenericArg<'_>, - ) -> subst::GenericArg<'tcx> { + ) -> ty::GenericArg<'tcx> { match (¶m.kind, arg) { (GenericParamDefKind::Lifetime, GenericArg::Lifetime(lt)) => { <dyn AstConv<'_>>::ast_region_to_region(self.fcx, lt, Some(param)).into() @@ -1297,10 +1295,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { fn inferred_kind( &mut self, - substs: Option<&[subst::GenericArg<'tcx>]>, + substs: Option<&[ty::GenericArg<'tcx>]>, param: &ty::GenericParamDef, infer_args: bool, - ) -> subst::GenericArg<'tcx> { + ) -> ty::GenericArg<'tcx> { let tcx = self.fcx.tcx(); match param.kind { GenericParamDefKind::Lifetime => { @@ -1359,7 +1357,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // First, store the "user substs" for later. self.write_user_type_annotation_from_substs(hir_id, def_id, substs, user_self_ty); - self.add_required_obligations(span, def_id, &substs); + self.add_required_obligations_for_hir(span, def_id, &substs, hir_id); // Substitute the values for the type parameters into the type of // the referenced item. @@ -1396,35 +1394,66 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } /// Add all the obligations that are required, substituting and normalized appropriately. - pub(crate) fn add_required_obligations( + pub(crate) fn add_required_obligations_for_hir( &self, span: Span, def_id: DefId, - substs: &SubstsRef<'tcx>, + substs: SubstsRef<'tcx>, + hir_id: hir::HirId, ) { - self.add_required_obligations_with_code( - span, - def_id, - substs, - traits::ItemObligation(def_id), - ) + self.add_required_obligations_with_code(span, def_id, substs, |idx, span| { + if span.is_dummy() { + ObligationCauseCode::ExprItemObligation(def_id, hir_id, idx) + } else { + ObligationCauseCode::ExprBindingObligation(def_id, span, hir_id, idx) + } + }) } - #[tracing::instrument(level = "debug", skip(self, span, def_id, substs))] + #[instrument(level = "debug", skip(self, code, span, substs))] fn add_required_obligations_with_code( &self, span: Span, def_id: DefId, - substs: &SubstsRef<'tcx>, - code: ObligationCauseCode<'tcx>, + substs: SubstsRef<'tcx>, + code: impl Fn(usize, Span) -> ObligationCauseCode<'tcx>, ) { + let param_env = self.param_env; + + let remap = match self.tcx.def_kind(def_id) { + // Associated consts have `Self: ~const Trait` bounds that should be satisfiable when + // `Self: Trait` is satisfied because it does not matter whether the impl is `const`. + // Therefore we have to remap the param env here to be non-const. + hir::def::DefKind::AssocConst => true, + hir::def::DefKind::AssocFn + if self.tcx.def_kind(self.tcx.parent(def_id)) == hir::def::DefKind::Trait => + { + // N.B.: All callsites to this function involve checking a path expression. + // + // When instantiating a trait method as a function item, it does not actually matter whether + // the trait is `const` or not, or whether `where T: ~const Tr` needs to be satisfied as + // `const`. If we were to introduce instantiating trait methods as `const fn`s, we would + // check that after this, either via a bound `where F: ~const FnOnce` or when coercing to a + // `const fn` pointer. + // + // FIXME(fee1-dead) FIXME(const_trait_impl): update this doc when trait methods can satisfy + // `~const FnOnce` or can be coerced to `const fn` pointer. + true + } + _ => false, + }; let (bounds, _) = self.instantiate_bounds(span, def_id, &substs); - for obligation in traits::predicates_for_generics( - traits::ObligationCause::new(span, self.body_id, code), - self.param_env, + for mut obligation in traits::predicates_for_generics( + |idx, predicate_span| { + traits::ObligationCause::new(span, self.body_id, code(idx, predicate_span)) + }, + param_env, bounds, ) { + if remap { + obligation = obligation.without_const(self.tcx); + } self.register_predicate(obligation); } } @@ -1438,7 +1467,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ty } else { if !self.is_tainted_by_errors() { - self.emit_inference_failure_err((**self).body_id, sp, ty.into(), E0282, true) + self.err_ctxt() + .emit_inference_failure_err((**self).body_id, sp, ty.into(), E0282, true) .emit(); } let err = self.tcx.ty_error(); diff --git a/compiler/rustc_typeck/src/check/fn_ctxt/arg_matrix.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/arg_matrix.rs index 7602f2550..fc83994ca 100644 --- a/compiler/rustc_typeck/src/check/fn_ctxt/arg_matrix.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/arg_matrix.rs @@ -130,14 +130,17 @@ impl<'tcx> ArgMatrix<'tcx> { let ai = &self.expected_indices; let ii = &self.provided_indices; + // Issue: 100478, when we end the iteration, + // `next_unmatched_idx` will point to the index of the first unmatched + let mut next_unmatched_idx = 0; for i in 0..cmp::max(ai.len(), ii.len()) { - // If we eliminate the last row, any left-over inputs are considered missing + // If we eliminate the last row, any left-over arguments are considered missing if i >= mat.len() { - return Some(Issue::Missing(i)); + return Some(Issue::Missing(next_unmatched_idx)); } - // If we eliminate the last column, any left-over arguments are extra + // If we eliminate the last column, any left-over inputs are extra if mat[i].len() == 0 { - return Some(Issue::Extra(i)); + return Some(Issue::Extra(next_unmatched_idx)); } // Make sure we don't pass the bounds of our matrix @@ -145,6 +148,7 @@ impl<'tcx> ArgMatrix<'tcx> { let is_input = i < ii.len(); if is_arg && is_input && matches!(mat[i][i], Compatibility::Compatible) { // This is a satisfied input, so move along + next_unmatched_idx += 1; continue; } @@ -163,7 +167,7 @@ impl<'tcx> ArgMatrix<'tcx> { if is_input { for j in 0..ai.len() { // If we find at least one argument that could satisfy this input - // this argument isn't useless + // this input isn't useless if matches!(mat[i][j], Compatibility::Compatible) { useless = false; break; @@ -232,8 +236,8 @@ impl<'tcx> ArgMatrix<'tcx> { if matches!(c, Compatibility::Compatible) { Some(i) } else { None } }) .collect(); - if compat.len() != 1 { - // this could go into multiple slots, don't bother exploring both + if compat.len() < 1 { + // try to find a cycle even when this could go into multiple slots, see #101097 is_cycle = false; break; } @@ -309,7 +313,8 @@ impl<'tcx> ArgMatrix<'tcx> { } while !self.provided_indices.is_empty() || !self.expected_indices.is_empty() { - match self.find_issue() { + let res = self.find_issue(); + match res { Some(Issue::Invalid(idx)) => { let compatibility = self.compatibility_matrix[idx][idx].clone(); let input_idx = self.provided_indices[idx]; @@ -364,7 +369,9 @@ impl<'tcx> ArgMatrix<'tcx> { None => { // We didn't find any issues, so we need to push the algorithm forward // First, eliminate any arguments that currently satisfy their inputs - for (inp, arg) in self.eliminate_satisfied() { + let eliminated = self.eliminate_satisfied(); + assert!(!eliminated.is_empty(), "didn't eliminated any indice in this round"); + for (inp, arg) in eliminated { matched_inputs[arg] = Some(inp); } } diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs new file mode 100644 index 000000000..8e0fcb56c --- /dev/null +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs @@ -0,0 +1,2236 @@ +use crate::coercion::CoerceMany; +use crate::fn_ctxt::arg_matrix::{ArgMatrix, Compatibility, Error, ExpectedIdx, ProvidedIdx}; +use crate::gather_locals::Declaration; +use crate::method::MethodCallee; +use crate::Expectation::*; +use crate::TupleArgumentsFlag::*; +use crate::{ + struct_span_err, BreakableCtxt, Diverges, Expectation, FnCtxt, LocalTy, Needs, + TupleArgumentsFlag, +}; +use rustc_ast as ast; +use rustc_data_structures::fx::FxHashSet; +use rustc_errors::{pluralize, Applicability, Diagnostic, DiagnosticId, MultiSpan}; +use rustc_hir as hir; +use rustc_hir::def::{CtorOf, DefKind, Res}; +use rustc_hir::def_id::DefId; +use rustc_hir::{ExprKind, Node, QPath}; +use rustc_hir_analysis::astconv::AstConv; +use rustc_hir_analysis::check::intrinsicck::InlineAsmCtxt; +use rustc_hir_analysis::check::potentially_plural_count; +use rustc_hir_analysis::structured_errors::StructuredDiagnostic; +use rustc_index::vec::IndexVec; +use rustc_infer::infer::error_reporting::{FailureCode, ObligationCauseExt}; +use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind}; +use rustc_infer::infer::InferOk; +use rustc_infer::infer::TypeTrace; +use rustc_middle::ty::adjustment::AllowTwoPhase; +use rustc_middle::ty::visit::TypeVisitable; +use rustc_middle::ty::{self, DefIdTree, IsSuggestable, Ty, TypeSuperVisitable, TypeVisitor}; +use rustc_session::Session; +use rustc_span::symbol::Ident; +use rustc_span::{self, sym, Span}; +use rustc_trait_selection::traits::{self, ObligationCauseCode, SelectionContext}; + +use std::iter; +use std::mem; +use std::ops::ControlFlow; +use std::slice; + +impl<'a, 'tcx> FnCtxt<'a, 'tcx> { + pub(in super::super) fn check_casts(&mut self) { + // don't hold the borrow to deferred_cast_checks while checking to avoid borrow checker errors + // when writing to `self.param_env`. + let mut deferred_cast_checks = mem::take(&mut *self.deferred_cast_checks.borrow_mut()); + + debug!("FnCtxt::check_casts: {} deferred checks", deferred_cast_checks.len()); + for cast in deferred_cast_checks.drain(..) { + let prev_env = self.param_env; + self.param_env = self.param_env.with_constness(cast.constness); + + cast.check(self); + + self.param_env = prev_env; + } + + *self.deferred_cast_checks.borrow_mut() = deferred_cast_checks; + } + + pub(in super::super) fn check_transmutes(&self) { + let mut deferred_transmute_checks = self.deferred_transmute_checks.borrow_mut(); + debug!("FnCtxt::check_transmutes: {} deferred checks", deferred_transmute_checks.len()); + for (from, to, hir_id) in deferred_transmute_checks.drain(..) { + self.check_transmute(from, to, hir_id); + } + } + + pub(in super::super) fn check_asms(&self) { + let mut deferred_asm_checks = self.deferred_asm_checks.borrow_mut(); + debug!("FnCtxt::check_asm: {} deferred checks", deferred_asm_checks.len()); + for (asm, hir_id) in deferred_asm_checks.drain(..) { + let enclosing_id = self.tcx.hir().enclosing_body_owner(hir_id); + let get_operand_ty = |expr| { + let ty = self.typeck_results.borrow().expr_ty_adjusted(expr); + let ty = self.resolve_vars_if_possible(ty); + if ty.has_non_region_infer() { + assert!(self.is_tainted_by_errors()); + self.tcx.ty_error() + } else { + self.tcx.erase_regions(ty) + } + }; + InlineAsmCtxt::new_in_fn(self.tcx, self.param_env, get_operand_ty) + .check_asm(asm, self.tcx.hir().local_def_id_to_hir_id(enclosing_id)); + } + } + + pub(in super::super) fn check_method_argument_types( + &self, + sp: Span, + expr: &'tcx hir::Expr<'tcx>, + method: Result<MethodCallee<'tcx>, ()>, + args_no_rcvr: &'tcx [hir::Expr<'tcx>], + tuple_arguments: TupleArgumentsFlag, + expected: Expectation<'tcx>, + ) -> Ty<'tcx> { + let has_error = match method { + Ok(method) => method.substs.references_error() || method.sig.references_error(), + Err(_) => true, + }; + if has_error { + let err_inputs = self.err_args(args_no_rcvr.len()); + + let err_inputs = match tuple_arguments { + DontTupleArguments => err_inputs, + TupleArguments => vec![self.tcx.intern_tup(&err_inputs)], + }; + + self.check_argument_types( + sp, + expr, + &err_inputs, + None, + args_no_rcvr, + false, + tuple_arguments, + method.ok().map(|method| method.def_id), + ); + return self.tcx.ty_error(); + } + + let method = method.unwrap(); + // HACK(eddyb) ignore self in the definition (see above). + let expected_input_tys = self.expected_inputs_for_expected_output( + sp, + expected, + method.sig.output(), + &method.sig.inputs()[1..], + ); + self.check_argument_types( + sp, + expr, + &method.sig.inputs()[1..], + expected_input_tys, + args_no_rcvr, + method.sig.c_variadic, + tuple_arguments, + Some(method.def_id), + ); + method.sig.output() + } + + /// Generic function that factors out common logic from function calls, + /// method calls and overloaded operators. + pub(in super::super) fn check_argument_types( + &self, + // Span enclosing the call site + call_span: Span, + // Expression of the call site + call_expr: &'tcx hir::Expr<'tcx>, + // Types (as defined in the *signature* of the target function) + formal_input_tys: &[Ty<'tcx>], + // More specific expected types, after unifying with caller output types + expected_input_tys: Option<Vec<Ty<'tcx>>>, + // The expressions for each provided argument + provided_args: &'tcx [hir::Expr<'tcx>], + // Whether the function is variadic, for example when imported from C + c_variadic: bool, + // Whether the arguments have been bundled in a tuple (ex: closures) + tuple_arguments: TupleArgumentsFlag, + // The DefId for the function being called, for better error messages + fn_def_id: Option<DefId>, + ) { + let tcx = self.tcx; + + // Conceptually, we've got some number of expected inputs, and some number of provided arguments + // and we can form a grid of whether each argument could satisfy a given input: + // in1 | in2 | in3 | ... + // arg1 ? | | | + // arg2 | ? | | + // arg3 | | ? | + // ... + // Initially, we just check the diagonal, because in the case of correct code + // these are the only checks that matter + // However, in the unhappy path, we'll fill in this whole grid to attempt to provide + // better error messages about invalid method calls. + + // All the input types from the fn signature must outlive the call + // so as to validate implied bounds. + for (&fn_input_ty, arg_expr) in iter::zip(formal_input_tys, provided_args) { + self.register_wf_obligation(fn_input_ty.into(), arg_expr.span, traits::MiscObligation); + } + + let mut err_code = "E0061"; + + // If the arguments should be wrapped in a tuple (ex: closures), unwrap them here + let (formal_input_tys, expected_input_tys) = if tuple_arguments == TupleArguments { + let tuple_type = self.structurally_resolved_type(call_span, formal_input_tys[0]); + match tuple_type.kind() { + // We expected a tuple and got a tuple + ty::Tuple(arg_types) => { + // Argument length differs + if arg_types.len() != provided_args.len() { + err_code = "E0057"; + } + let expected_input_tys = match expected_input_tys { + Some(expected_input_tys) => match expected_input_tys.get(0) { + Some(ty) => match ty.kind() { + ty::Tuple(tys) => Some(tys.iter().collect()), + _ => None, + }, + None => None, + }, + None => None, + }; + (arg_types.iter().collect(), expected_input_tys) + } + _ => { + // Otherwise, there's a mismatch, so clear out what we're expecting, and set + // our input types to err_args so we don't blow up the error messages + struct_span_err!( + tcx.sess, + call_span, + E0059, + "cannot use call notation; the first type parameter \ + for the function trait is neither a tuple nor unit" + ) + .emit(); + (self.err_args(provided_args.len()), None) + } + } + } else { + (formal_input_tys.to_vec(), expected_input_tys) + }; + + // If there are no external expectations at the call site, just use the types from the function defn + let expected_input_tys = if let Some(expected_input_tys) = expected_input_tys { + assert_eq!(expected_input_tys.len(), formal_input_tys.len()); + expected_input_tys + } else { + formal_input_tys.clone() + }; + + let minimum_input_count = expected_input_tys.len(); + let provided_arg_count = provided_args.len(); + + let is_const_eval_select = matches!(fn_def_id, Some(def_id) if + self.tcx.def_kind(def_id) == hir::def::DefKind::Fn + && self.tcx.is_intrinsic(def_id) + && self.tcx.item_name(def_id) == sym::const_eval_select); + + // We introduce a helper function to demand that a given argument satisfy a given input + // This is more complicated than just checking type equality, as arguments could be coerced + // This version writes those types back so further type checking uses the narrowed types + let demand_compatible = |idx| { + let formal_input_ty: Ty<'tcx> = formal_input_tys[idx]; + let expected_input_ty: Ty<'tcx> = expected_input_tys[idx]; + let provided_arg = &provided_args[idx]; + + debug!("checking argument {}: {:?} = {:?}", idx, provided_arg, formal_input_ty); + + // We're on the happy path here, so we'll do a more involved check and write back types + // To check compatibility, we'll do 3 things: + // 1. Unify the provided argument with the expected type + let expectation = Expectation::rvalue_hint(self, expected_input_ty); + + let checked_ty = self.check_expr_with_expectation(provided_arg, expectation); + + // 2. Coerce to the most detailed type that could be coerced + // to, which is `expected_ty` if `rvalue_hint` returns an + // `ExpectHasType(expected_ty)`, or the `formal_ty` otherwise. + let coerced_ty = expectation.only_has_type(self).unwrap_or(formal_input_ty); + + // Cause selection errors caused by resolving a single argument to point at the + // argument and not the call. This lets us customize the span pointed to in the + // fulfillment error to be more accurate. + let coerced_ty = self.resolve_vars_with_obligations(coerced_ty); + + let coerce_error = self + .try_coerce(provided_arg, checked_ty, coerced_ty, AllowTwoPhase::Yes, None) + .err(); + + if coerce_error.is_some() { + return Compatibility::Incompatible(coerce_error); + } + + // Check that second and third argument of `const_eval_select` must be `FnDef`, and additionally that + // the second argument must be `const fn`. The first argument must be a tuple, but this is already expressed + // in the function signature (`F: FnOnce<ARG>`), so I did not bother to add another check here. + // + // This check is here because there is currently no way to express a trait bound for `FnDef` types only. + if is_const_eval_select && (1..=2).contains(&idx) { + if let ty::FnDef(def_id, _) = checked_ty.kind() { + if idx == 1 && !self.tcx.is_const_fn_raw(*def_id) { + self.tcx + .sess + .struct_span_err(provided_arg.span, "this argument must be a `const fn`") + .help("consult the documentation on `const_eval_select` for more information") + .emit(); + } + } else { + self.tcx + .sess + .struct_span_err(provided_arg.span, "this argument must be a function item") + .note(format!("expected a function item, found {checked_ty}")) + .help( + "consult the documentation on `const_eval_select` for more information", + ) + .emit(); + } + } + + // 3. Check if the formal type is a supertype of the checked one + // and register any such obligations for future type checks + let supertype_error = self + .at(&self.misc(provided_arg.span), self.param_env) + .sup(formal_input_ty, coerced_ty); + let subtyping_error = match supertype_error { + Ok(InferOk { obligations, value: () }) => { + self.register_predicates(obligations); + None + } + Err(err) => Some(err), + }; + + // If neither check failed, the types are compatible + match subtyping_error { + None => Compatibility::Compatible, + Some(_) => Compatibility::Incompatible(subtyping_error), + } + }; + + // To start, we only care "along the diagonal", where we expect every + // provided arg to be in the right spot + let mut compatibility_diagonal = + vec![Compatibility::Incompatible(None); provided_args.len()]; + + // Keep track of whether we *could possibly* be satisfied, i.e. whether we're on the happy path + // if the wrong number of arguments were supplied, we CAN'T be satisfied, + // and if we're c_variadic, the supplied arguments must be >= the minimum count from the function + // otherwise, they need to be identical, because rust doesn't currently support variadic functions + let mut call_appears_satisfied = if c_variadic { + provided_arg_count >= minimum_input_count + } else { + provided_arg_count == minimum_input_count + }; + + // Check the arguments. + // We do this in a pretty awful way: first we type-check any arguments + // that are not closures, then we type-check the closures. This is so + // that we have more information about the types of arguments when we + // type-check the functions. This isn't really the right way to do this. + for check_closures in [false, true] { + // More awful hacks: before we check argument types, try to do + // an "opportunistic" trait resolution of any trait bounds on + // the call. This helps coercions. + if check_closures { + self.select_obligations_where_possible(false, |_| {}) + } + + // Check each argument, to satisfy the input it was provided for + // Visually, we're traveling down the diagonal of the compatibility matrix + for (idx, arg) in provided_args.iter().enumerate() { + // Warn only for the first loop (the "no closures" one). + // Closure arguments themselves can't be diverging, but + // a previous argument can, e.g., `foo(panic!(), || {})`. + if !check_closures { + self.warn_if_unreachable(arg.hir_id, arg.span, "expression"); + } + + // For C-variadic functions, we don't have a declared type for all of + // the arguments hence we only do our usual type checking with + // the arguments who's types we do know. However, we *can* check + // for unreachable expressions (see above). + // FIXME: unreachable warning current isn't emitted + if idx >= minimum_input_count { + continue; + } + + let is_closure = matches!(arg.kind, ExprKind::Closure { .. }); + if is_closure != check_closures { + continue; + } + + let compatible = demand_compatible(idx); + let is_compatible = matches!(compatible, Compatibility::Compatible); + compatibility_diagonal[idx] = compatible; + + if !is_compatible { + call_appears_satisfied = false; + } + } + } + + if c_variadic && provided_arg_count < minimum_input_count { + err_code = "E0060"; + } + + for arg in provided_args.iter().skip(minimum_input_count) { + // Make sure we've checked this expr at least once. + let arg_ty = self.check_expr(&arg); + + // If the function is c-style variadic, we skipped a bunch of arguments + // so we need to check those, and write out the types + // Ideally this would be folded into the above, for uniform style + // but c-variadic is already a corner case + if c_variadic { + fn variadic_error<'tcx>( + sess: &'tcx Session, + span: Span, + ty: Ty<'tcx>, + cast_ty: &str, + ) { + use rustc_hir_analysis::structured_errors::MissingCastForVariadicArg; + + MissingCastForVariadicArg { sess, span, ty, cast_ty }.diagnostic().emit(); + } + + // There are a few types which get autopromoted when passed via varargs + // in C but we just error out instead and require explicit casts. + let arg_ty = self.structurally_resolved_type(arg.span, arg_ty); + match arg_ty.kind() { + ty::Float(ty::FloatTy::F32) => { + variadic_error(tcx.sess, arg.span, arg_ty, "c_double"); + } + ty::Int(ty::IntTy::I8 | ty::IntTy::I16) | ty::Bool => { + variadic_error(tcx.sess, arg.span, arg_ty, "c_int"); + } + ty::Uint(ty::UintTy::U8 | ty::UintTy::U16) => { + variadic_error(tcx.sess, arg.span, arg_ty, "c_uint"); + } + ty::FnDef(..) => { + let ptr_ty = self.tcx.mk_fn_ptr(arg_ty.fn_sig(self.tcx)); + let ptr_ty = self.resolve_vars_if_possible(ptr_ty); + variadic_error(tcx.sess, arg.span, arg_ty, &ptr_ty.to_string()); + } + _ => {} + } + } + } + + if !call_appears_satisfied { + let compatibility_diagonal = IndexVec::from_raw(compatibility_diagonal); + let provided_args = IndexVec::from_iter(provided_args.iter().take(if c_variadic { + minimum_input_count + } else { + provided_arg_count + })); + debug_assert_eq!( + formal_input_tys.len(), + expected_input_tys.len(), + "expected formal_input_tys to be the same size as expected_input_tys" + ); + let formal_and_expected_inputs = IndexVec::from_iter( + formal_input_tys + .iter() + .copied() + .zip(expected_input_tys.iter().copied()) + .map(|vars| self.resolve_vars_if_possible(vars)), + ); + + self.report_arg_errors( + compatibility_diagonal, + formal_and_expected_inputs, + provided_args, + c_variadic, + err_code, + fn_def_id, + call_span, + call_expr, + ); + } + } + + fn report_arg_errors( + &self, + compatibility_diagonal: IndexVec<ProvidedIdx, Compatibility<'tcx>>, + formal_and_expected_inputs: IndexVec<ExpectedIdx, (Ty<'tcx>, Ty<'tcx>)>, + provided_args: IndexVec<ProvidedIdx, &'tcx hir::Expr<'tcx>>, + c_variadic: bool, + err_code: &str, + fn_def_id: Option<DefId>, + call_span: Span, + call_expr: &hir::Expr<'tcx>, + ) { + // Next, let's construct the error + let (error_span, full_call_span, ctor_of, is_method) = match &call_expr.kind { + hir::ExprKind::Call( + hir::Expr { hir_id, span, kind: hir::ExprKind::Path(qpath), .. }, + _, + ) => { + if let Res::Def(DefKind::Ctor(of, _), _) = + self.typeck_results.borrow().qpath_res(qpath, *hir_id) + { + (call_span, *span, Some(of), false) + } else { + (call_span, *span, None, false) + } + } + hir::ExprKind::Call(hir::Expr { span, .. }, _) => (call_span, *span, None, false), + hir::ExprKind::MethodCall(path_segment, _, _, span) => { + let ident_span = path_segment.ident.span; + let ident_span = if let Some(args) = path_segment.args { + ident_span.with_hi(args.span_ext.hi()) + } else { + ident_span + }; + // methods are never ctors + (*span, ident_span, None, true) + } + k => span_bug!(call_span, "checking argument types on a non-call: `{:?}`", k), + }; + let args_span = error_span.trim_start(full_call_span).unwrap_or(error_span); + let call_name = match ctor_of { + Some(CtorOf::Struct) => "struct", + Some(CtorOf::Variant) => "enum variant", + None => "function", + }; + + // Don't print if it has error types or is just plain `_` + fn has_error_or_infer<'tcx>(tys: impl IntoIterator<Item = Ty<'tcx>>) -> bool { + tys.into_iter().any(|ty| ty.references_error() || ty.is_ty_var()) + } + + self.set_tainted_by_errors(); + let tcx = self.tcx; + + // Get the argument span in the context of the call span so that + // suggestions and labels are (more) correct when an arg is a + // macro invocation. + let normalize_span = |span: Span| -> Span { + let normalized_span = span.find_ancestor_inside(error_span).unwrap_or(span); + // Sometimes macros mess up the spans, so do not normalize the + // arg span to equal the error span, because that's less useful + // than pointing out the arg expr in the wrong context. + if normalized_span.source_equal(error_span) { span } else { normalized_span } + }; + + // Precompute the provided types and spans, since that's all we typically need for below + let provided_arg_tys: IndexVec<ProvidedIdx, (Ty<'tcx>, Span)> = provided_args + .iter() + .map(|expr| { + let ty = self + .typeck_results + .borrow() + .expr_ty_adjusted_opt(*expr) + .unwrap_or_else(|| tcx.ty_error()); + (self.resolve_vars_if_possible(ty), normalize_span(expr.span)) + }) + .collect(); + let callee_expr = match &call_expr.peel_blocks().kind { + hir::ExprKind::Call(callee, _) => Some(*callee), + hir::ExprKind::MethodCall(_, receiver, ..) => { + if let Some((DefKind::AssocFn, def_id)) = + self.typeck_results.borrow().type_dependent_def(call_expr.hir_id) + && let Some(assoc) = tcx.opt_associated_item(def_id) + && assoc.fn_has_self_parameter + { + Some(*receiver) + } else { + None + } + } + _ => None, + }; + let callee_ty = callee_expr + .and_then(|callee_expr| self.typeck_results.borrow().expr_ty_adjusted_opt(callee_expr)); + + // A "softer" version of the `demand_compatible`, which checks types without persisting them, + // and treats error types differently + // This will allow us to "probe" for other argument orders that would likely have been correct + let check_compatible = |provided_idx: ProvidedIdx, expected_idx: ExpectedIdx| { + if provided_idx.as_usize() == expected_idx.as_usize() { + return compatibility_diagonal[provided_idx].clone(); + } + + let (formal_input_ty, expected_input_ty) = formal_and_expected_inputs[expected_idx]; + // If either is an error type, we defy the usual convention and consider them to *not* be + // coercible. This prevents our error message heuristic from trying to pass errors into + // every argument. + if (formal_input_ty, expected_input_ty).references_error() { + return Compatibility::Incompatible(None); + } + + let (arg_ty, arg_span) = provided_arg_tys[provided_idx]; + + let expectation = Expectation::rvalue_hint(self, expected_input_ty); + let coerced_ty = expectation.only_has_type(self).unwrap_or(formal_input_ty); + let can_coerce = self.can_coerce(arg_ty, coerced_ty); + if !can_coerce { + return Compatibility::Incompatible(Some(ty::error::TypeError::Sorts( + ty::error::ExpectedFound::new(true, coerced_ty, arg_ty), + ))); + } + + // Using probe here, since we don't want this subtyping to affect inference. + let subtyping_error = self.probe(|_| { + self.at(&self.misc(arg_span), self.param_env).sup(formal_input_ty, coerced_ty).err() + }); + + // Same as above: if either the coerce type or the checked type is an error type, + // consider them *not* compatible. + let references_error = (coerced_ty, arg_ty).references_error(); + match (references_error, subtyping_error) { + (false, None) => Compatibility::Compatible, + (_, subtyping_error) => Compatibility::Incompatible(subtyping_error), + } + }; + + // The algorithm here is inspired by levenshtein distance and longest common subsequence. + // We'll try to detect 4 different types of mistakes: + // - An extra parameter has been provided that doesn't satisfy *any* of the other inputs + // - An input is missing, which isn't satisfied by *any* of the other arguments + // - Some number of arguments have been provided in the wrong order + // - A type is straight up invalid + + // First, let's find the errors + let (mut errors, matched_inputs) = + ArgMatrix::new(provided_args.len(), formal_and_expected_inputs.len(), check_compatible) + .find_errors(); + + // First, check if we just need to wrap some arguments in a tuple. + if let Some((mismatch_idx, terr)) = + compatibility_diagonal.iter().enumerate().find_map(|(i, c)| { + if let Compatibility::Incompatible(Some(terr)) = c { + Some((i, *terr)) + } else { + None + } + }) + { + // Is the first bad expected argument a tuple? + // Do we have as many extra provided arguments as the tuple's length? + // If so, we might have just forgotten to wrap some args in a tuple. + if let Some(ty::Tuple(tys)) = + formal_and_expected_inputs.get(mismatch_idx.into()).map(|tys| tys.1.kind()) + // If the tuple is unit, we're not actually wrapping any arguments. + && !tys.is_empty() + && provided_arg_tys.len() == formal_and_expected_inputs.len() - 1 + tys.len() + { + // Wrap up the N provided arguments starting at this position in a tuple. + let provided_as_tuple = tcx.mk_tup( + provided_arg_tys.iter().map(|(ty, _)| *ty).skip(mismatch_idx).take(tys.len()), + ); + + let mut satisfied = true; + // Check if the newly wrapped tuple + rest of the arguments are compatible. + for ((_, expected_ty), provided_ty) in std::iter::zip( + formal_and_expected_inputs.iter().skip(mismatch_idx), + [provided_as_tuple].into_iter().chain( + provided_arg_tys.iter().map(|(ty, _)| *ty).skip(mismatch_idx + tys.len()), + ), + ) { + if !self.can_coerce(provided_ty, *expected_ty) { + satisfied = false; + break; + } + } + + // If they're compatible, suggest wrapping in an arg, and we're done! + // Take some care with spans, so we don't suggest wrapping a macro's + // innards in parenthesis, for example. + if satisfied + && let Some((_, lo)) = + provided_arg_tys.get(ProvidedIdx::from_usize(mismatch_idx)) + && let Some((_, hi)) = + provided_arg_tys.get(ProvidedIdx::from_usize(mismatch_idx + tys.len() - 1)) + { + let mut err; + if tys.len() == 1 { + // A tuple wrap suggestion actually occurs within, + // so don't do anything special here. + err = self.err_ctxt().report_and_explain_type_error( + TypeTrace::types( + &self.misc(*lo), + true, + formal_and_expected_inputs[mismatch_idx.into()].1, + provided_arg_tys[mismatch_idx.into()].0, + ), + terr, + ); + err.span_label( + full_call_span, + format!("arguments to this {} are incorrect", call_name), + ); + } else { + err = tcx.sess.struct_span_err_with_code( + full_call_span, + &format!( + "this {} takes {}{} but {} {} supplied", + call_name, + if c_variadic { "at least " } else { "" }, + potentially_plural_count( + formal_and_expected_inputs.len(), + "argument" + ), + potentially_plural_count(provided_args.len(), "argument"), + pluralize!("was", provided_args.len()) + ), + DiagnosticId::Error(err_code.to_owned()), + ); + err.multipart_suggestion_verbose( + "wrap these arguments in parentheses to construct a tuple", + vec![ + (lo.shrink_to_lo(), "(".to_string()), + (hi.shrink_to_hi(), ")".to_string()), + ], + Applicability::MachineApplicable, + ); + }; + self.label_fn_like( + &mut err, + fn_def_id, + callee_ty, + Some(mismatch_idx), + is_method, + ); + err.emit(); + return; + } + } + } + + // Okay, so here's where it gets complicated in regards to what errors + // we emit and how. + // There are 3 different "types" of errors we might encounter. + // 1) Missing/extra/swapped arguments + // 2) Valid but incorrect arguments + // 3) Invalid arguments + // - Currently I think this only comes up with `CyclicTy` + // + // We first need to go through, remove those from (3) and emit those + // as their own error, particularly since they're error code and + // message is special. From what I can tell, we *must* emit these + // here (vs somewhere prior to this function) since the arguments + // become invalid *because* of how they get used in the function. + // It is what it is. + + if errors.is_empty() { + if cfg!(debug_assertions) { + span_bug!(error_span, "expected errors from argument matrix"); + } else { + tcx.sess + .struct_span_err( + error_span, + "argument type mismatch was detected, \ + but rustc had trouble determining where", + ) + .note( + "we would appreciate a bug report: \ + https://github.com/rust-lang/rust/issues/new", + ) + .emit(); + } + return; + } + + errors.drain_filter(|error| { + let Error::Invalid(provided_idx, expected_idx, Compatibility::Incompatible(Some(e))) = error else { return false }; + let (provided_ty, provided_span) = provided_arg_tys[*provided_idx]; + let (expected_ty, _) = formal_and_expected_inputs[*expected_idx]; + let cause = &self.misc(provided_span); + let trace = TypeTrace::types(cause, true, expected_ty, provided_ty); + if !matches!(trace.cause.as_failure_code(*e), FailureCode::Error0308(_)) { + self.err_ctxt().report_and_explain_type_error(trace, *e).emit(); + return true; + } + false + }); + + // We're done if we found errors, but we already emitted them. + if errors.is_empty() { + return; + } + + // Okay, now that we've emitted the special errors separately, we + // are only left missing/extra/swapped and mismatched arguments, both + // can be collated pretty easily if needed. + + // Next special case: if there is only one "Incompatible" error, just emit that + if let [ + Error::Invalid(provided_idx, expected_idx, Compatibility::Incompatible(Some(err))), + ] = &errors[..] + { + let (formal_ty, expected_ty) = formal_and_expected_inputs[*expected_idx]; + let (provided_ty, provided_arg_span) = provided_arg_tys[*provided_idx]; + let cause = &self.misc(provided_arg_span); + let trace = TypeTrace::types(cause, true, expected_ty, provided_ty); + let mut err = self.err_ctxt().report_and_explain_type_error(trace, *err); + self.emit_coerce_suggestions( + &mut err, + &provided_args[*provided_idx], + provided_ty, + Expectation::rvalue_hint(self, expected_ty) + .only_has_type(self) + .unwrap_or(formal_ty), + None, + None, + ); + err.span_label( + full_call_span, + format!("arguments to this {} are incorrect", call_name), + ); + // Call out where the function is defined + self.label_fn_like( + &mut err, + fn_def_id, + callee_ty, + Some(expected_idx.as_usize()), + is_method, + ); + err.emit(); + return; + } + + let mut err = if formal_and_expected_inputs.len() == provided_args.len() { + struct_span_err!( + tcx.sess, + full_call_span, + E0308, + "arguments to this {} are incorrect", + call_name, + ) + } else { + tcx.sess.struct_span_err_with_code( + full_call_span, + &format!( + "this {} takes {}{} but {} {} supplied", + call_name, + if c_variadic { "at least " } else { "" }, + potentially_plural_count(formal_and_expected_inputs.len(), "argument"), + potentially_plural_count(provided_args.len(), "argument"), + pluralize!("was", provided_args.len()) + ), + DiagnosticId::Error(err_code.to_owned()), + ) + }; + + // As we encounter issues, keep track of what we want to provide for the suggestion + let mut labels = vec![]; + // If there is a single error, we give a specific suggestion; otherwise, we change to + // "did you mean" with the suggested function call + enum SuggestionText { + None, + Provide(bool), + Remove(bool), + Swap, + Reorder, + DidYouMean, + } + let mut suggestion_text = SuggestionText::None; + + let mut errors = errors.into_iter().peekable(); + while let Some(error) = errors.next() { + match error { + Error::Invalid(provided_idx, expected_idx, compatibility) => { + let (formal_ty, expected_ty) = formal_and_expected_inputs[expected_idx]; + let (provided_ty, provided_span) = provided_arg_tys[provided_idx]; + if let Compatibility::Incompatible(error) = compatibility { + let cause = &self.misc(provided_span); + let trace = TypeTrace::types(cause, true, expected_ty, provided_ty); + if let Some(e) = error { + self.err_ctxt().note_type_err( + &mut err, + &trace.cause, + None, + Some(trace.values), + e, + false, + true, + ); + } + } + + self.emit_coerce_suggestions( + &mut err, + &provided_args[provided_idx], + provided_ty, + Expectation::rvalue_hint(self, expected_ty) + .only_has_type(self) + .unwrap_or(formal_ty), + None, + None, + ); + } + Error::Extra(arg_idx) => { + let (provided_ty, provided_span) = provided_arg_tys[arg_idx]; + let provided_ty_name = if !has_error_or_infer([provided_ty]) { + // FIXME: not suggestable, use something else + format!(" of type `{}`", provided_ty) + } else { + "".to_string() + }; + labels + .push((provided_span, format!("argument{} unexpected", provided_ty_name))); + suggestion_text = match suggestion_text { + SuggestionText::None => SuggestionText::Remove(false), + SuggestionText::Remove(_) => SuggestionText::Remove(true), + _ => SuggestionText::DidYouMean, + }; + } + Error::Missing(expected_idx) => { + // If there are multiple missing arguments adjacent to each other, + // then we can provide a single error. + + let mut missing_idxs = vec![expected_idx]; + while let Some(e) = errors.next_if(|e| { + matches!(e, Error::Missing(next_expected_idx) + if *next_expected_idx == *missing_idxs.last().unwrap() + 1) + }) { + match e { + Error::Missing(expected_idx) => missing_idxs.push(expected_idx), + _ => unreachable!(), + } + } + + // NOTE: Because we might be re-arranging arguments, might have extra + // arguments, etc. it's hard to *really* know where we should provide + // this error label, so as a heuristic, we point to the provided arg, or + // to the call if the missing inputs pass the provided args. + match &missing_idxs[..] { + &[expected_idx] => { + let (_, input_ty) = formal_and_expected_inputs[expected_idx]; + let span = if let Some((_, arg_span)) = + provided_arg_tys.get(expected_idx.to_provided_idx()) + { + *arg_span + } else { + args_span + }; + let rendered = if !has_error_or_infer([input_ty]) { + format!(" of type `{}`", input_ty) + } else { + "".to_string() + }; + labels.push((span, format!("an argument{} is missing", rendered))); + suggestion_text = match suggestion_text { + SuggestionText::None => SuggestionText::Provide(false), + SuggestionText::Provide(_) => SuggestionText::Provide(true), + _ => SuggestionText::DidYouMean, + }; + } + &[first_idx, second_idx] => { + let (_, first_expected_ty) = formal_and_expected_inputs[first_idx]; + let (_, second_expected_ty) = formal_and_expected_inputs[second_idx]; + let span = if let (Some((_, first_span)), Some((_, second_span))) = ( + provided_arg_tys.get(first_idx.to_provided_idx()), + provided_arg_tys.get(second_idx.to_provided_idx()), + ) { + first_span.to(*second_span) + } else { + args_span + }; + let rendered = + if !has_error_or_infer([first_expected_ty, second_expected_ty]) { + format!( + " of type `{}` and `{}`", + first_expected_ty, second_expected_ty + ) + } else { + "".to_string() + }; + labels.push((span, format!("two arguments{} are missing", rendered))); + suggestion_text = match suggestion_text { + SuggestionText::None | SuggestionText::Provide(_) => { + SuggestionText::Provide(true) + } + _ => SuggestionText::DidYouMean, + }; + } + &[first_idx, second_idx, third_idx] => { + let (_, first_expected_ty) = formal_and_expected_inputs[first_idx]; + let (_, second_expected_ty) = formal_and_expected_inputs[second_idx]; + let (_, third_expected_ty) = formal_and_expected_inputs[third_idx]; + let span = if let (Some((_, first_span)), Some((_, third_span))) = ( + provided_arg_tys.get(first_idx.to_provided_idx()), + provided_arg_tys.get(third_idx.to_provided_idx()), + ) { + first_span.to(*third_span) + } else { + args_span + }; + let rendered = if !has_error_or_infer([ + first_expected_ty, + second_expected_ty, + third_expected_ty, + ]) { + format!( + " of type `{}`, `{}`, and `{}`", + first_expected_ty, second_expected_ty, third_expected_ty + ) + } else { + "".to_string() + }; + labels.push((span, format!("three arguments{} are missing", rendered))); + suggestion_text = match suggestion_text { + SuggestionText::None | SuggestionText::Provide(_) => { + SuggestionText::Provide(true) + } + _ => SuggestionText::DidYouMean, + }; + } + missing_idxs => { + let first_idx = *missing_idxs.first().unwrap(); + let last_idx = *missing_idxs.last().unwrap(); + // NOTE: Because we might be re-arranging arguments, might have extra arguments, etc. + // It's hard to *really* know where we should provide this error label, so this is a + // decent heuristic + let span = if let (Some((_, first_span)), Some((_, last_span))) = ( + provided_arg_tys.get(first_idx.to_provided_idx()), + provided_arg_tys.get(last_idx.to_provided_idx()), + ) { + first_span.to(*last_span) + } else { + args_span + }; + labels.push((span, format!("multiple arguments are missing"))); + suggestion_text = match suggestion_text { + SuggestionText::None | SuggestionText::Provide(_) => { + SuggestionText::Provide(true) + } + _ => SuggestionText::DidYouMean, + }; + } + } + } + Error::Swap( + first_provided_idx, + second_provided_idx, + first_expected_idx, + second_expected_idx, + ) => { + let (first_provided_ty, first_span) = provided_arg_tys[first_provided_idx]; + let (_, first_expected_ty) = formal_and_expected_inputs[first_expected_idx]; + let first_provided_ty_name = if !has_error_or_infer([first_provided_ty]) { + format!(", found `{}`", first_provided_ty) + } else { + String::new() + }; + labels.push(( + first_span, + format!("expected `{}`{}", first_expected_ty, first_provided_ty_name), + )); + + let (second_provided_ty, second_span) = provided_arg_tys[second_provided_idx]; + let (_, second_expected_ty) = formal_and_expected_inputs[second_expected_idx]; + let second_provided_ty_name = if !has_error_or_infer([second_provided_ty]) { + format!(", found `{}`", second_provided_ty) + } else { + String::new() + }; + labels.push(( + second_span, + format!("expected `{}`{}", second_expected_ty, second_provided_ty_name), + )); + + suggestion_text = match suggestion_text { + SuggestionText::None => SuggestionText::Swap, + _ => SuggestionText::DidYouMean, + }; + } + Error::Permutation(args) => { + for (dst_arg, dest_input) in args { + let (_, expected_ty) = formal_and_expected_inputs[dst_arg]; + let (provided_ty, provided_span) = provided_arg_tys[dest_input]; + let provided_ty_name = if !has_error_or_infer([provided_ty]) { + format!(", found `{}`", provided_ty) + } else { + String::new() + }; + labels.push(( + provided_span, + format!("expected `{}`{}", expected_ty, provided_ty_name), + )); + } + + suggestion_text = match suggestion_text { + SuggestionText::None => SuggestionText::Reorder, + _ => SuggestionText::DidYouMean, + }; + } + } + } + + // If we have less than 5 things to say, it would be useful to call out exactly what's wrong + if labels.len() <= 5 { + for (span, label) in labels { + err.span_label(span, label); + } + } + + // Call out where the function is defined + self.label_fn_like(&mut err, fn_def_id, callee_ty, None, is_method); + + // And add a suggestion block for all of the parameters + let suggestion_text = match suggestion_text { + SuggestionText::None => None, + SuggestionText::Provide(plural) => { + Some(format!("provide the argument{}", if plural { "s" } else { "" })) + } + SuggestionText::Remove(plural) => { + Some(format!("remove the extra argument{}", if plural { "s" } else { "" })) + } + SuggestionText::Swap => Some("swap these arguments".to_string()), + SuggestionText::Reorder => Some("reorder these arguments".to_string()), + SuggestionText::DidYouMean => Some("did you mean".to_string()), + }; + if let Some(suggestion_text) = suggestion_text { + let source_map = self.sess().source_map(); + let (mut suggestion, suggestion_span) = + if let Some(call_span) = full_call_span.find_ancestor_inside(error_span) { + ("(".to_string(), call_span.shrink_to_hi().to(error_span.shrink_to_hi())) + } else { + ( + format!( + "{}(", + source_map.span_to_snippet(full_call_span).unwrap_or_else(|_| { + fn_def_id.map_or("".to_string(), |fn_def_id| { + tcx.item_name(fn_def_id).to_string() + }) + }) + ), + error_span, + ) + }; + let mut needs_comma = false; + for (expected_idx, provided_idx) in matched_inputs.iter_enumerated() { + if needs_comma { + suggestion += ", "; + } else { + needs_comma = true; + } + let suggestion_text = if let Some(provided_idx) = provided_idx + && let (_, provided_span) = provided_arg_tys[*provided_idx] + && let Ok(arg_text) = source_map.span_to_snippet(provided_span) + { + arg_text + } else { + // Propose a placeholder of the correct type + let (_, expected_ty) = formal_and_expected_inputs[expected_idx]; + if expected_ty.is_unit() { + "()".to_string() + } else if expected_ty.is_suggestable(tcx, false) { + format!("/* {} */", expected_ty) + } else { + "/* value */".to_string() + } + }; + suggestion += &suggestion_text; + } + suggestion += ")"; + err.span_suggestion_verbose( + suggestion_span, + &suggestion_text, + suggestion, + Applicability::HasPlaceholders, + ); + } + + err.emit(); + } + + // AST fragment checking + pub(in super::super) fn check_lit( + &self, + lit: &hir::Lit, + expected: Expectation<'tcx>, + ) -> Ty<'tcx> { + let tcx = self.tcx; + + match lit.node { + ast::LitKind::Str(..) => tcx.mk_static_str(), + ast::LitKind::ByteStr(ref v) => { + tcx.mk_imm_ref(tcx.lifetimes.re_static, tcx.mk_array(tcx.types.u8, v.len() as u64)) + } + ast::LitKind::Byte(_) => tcx.types.u8, + ast::LitKind::Char(_) => tcx.types.char, + ast::LitKind::Int(_, ast::LitIntType::Signed(t)) => tcx.mk_mach_int(ty::int_ty(t)), + ast::LitKind::Int(_, ast::LitIntType::Unsigned(t)) => tcx.mk_mach_uint(ty::uint_ty(t)), + ast::LitKind::Int(_, ast::LitIntType::Unsuffixed) => { + let opt_ty = expected.to_option(self).and_then(|ty| match ty.kind() { + ty::Int(_) | ty::Uint(_) => Some(ty), + ty::Char => Some(tcx.types.u8), + ty::RawPtr(..) => Some(tcx.types.usize), + ty::FnDef(..) | ty::FnPtr(_) => Some(tcx.types.usize), + _ => None, + }); + opt_ty.unwrap_or_else(|| self.next_int_var()) + } + ast::LitKind::Float(_, ast::LitFloatType::Suffixed(t)) => { + tcx.mk_mach_float(ty::float_ty(t)) + } + ast::LitKind::Float(_, ast::LitFloatType::Unsuffixed) => { + let opt_ty = expected.to_option(self).and_then(|ty| match ty.kind() { + ty::Float(_) => Some(ty), + _ => None, + }); + opt_ty.unwrap_or_else(|| self.next_float_var()) + } + ast::LitKind::Bool(_) => tcx.types.bool, + ast::LitKind::Err => tcx.ty_error(), + } + } + + pub fn check_struct_path( + &self, + qpath: &QPath<'_>, + hir_id: hir::HirId, + ) -> Option<(&'tcx ty::VariantDef, Ty<'tcx>)> { + let path_span = qpath.span(); + let (def, ty) = self.finish_resolving_struct_path(qpath, path_span, hir_id); + let variant = match def { + Res::Err => { + self.set_tainted_by_errors(); + return None; + } + Res::Def(DefKind::Variant, _) => match ty.kind() { + ty::Adt(adt, substs) => Some((adt.variant_of_res(def), adt.did(), substs)), + _ => bug!("unexpected type: {:?}", ty), + }, + Res::Def(DefKind::Struct | DefKind::Union | DefKind::TyAlias | DefKind::AssocTy, _) + | Res::SelfTyParam { .. } + | Res::SelfTyAlias { .. } => match ty.kind() { + ty::Adt(adt, substs) if !adt.is_enum() => { + Some((adt.non_enum_variant(), adt.did(), substs)) + } + _ => None, + }, + _ => bug!("unexpected definition: {:?}", def), + }; + + if let Some((variant, did, substs)) = variant { + debug!("check_struct_path: did={:?} substs={:?}", did, substs); + self.write_user_type_annotation_from_substs(hir_id, did, substs, None); + + // Check bounds on type arguments used in the path. + self.add_required_obligations_for_hir(path_span, did, substs, hir_id); + + Some((variant, ty)) + } else { + match ty.kind() { + ty::Error(_) => { + // E0071 might be caused by a spelling error, which will have + // already caused an error message and probably a suggestion + // elsewhere. Refrain from emitting more unhelpful errors here + // (issue #88844). + } + _ => { + struct_span_err!( + self.tcx.sess, + path_span, + E0071, + "expected struct, variant or union type, found {}", + ty.sort_string(self.tcx) + ) + .span_label(path_span, "not a struct") + .emit(); + } + } + None + } + } + + pub fn check_decl_initializer( + &self, + hir_id: hir::HirId, + pat: &'tcx hir::Pat<'tcx>, + init: &'tcx hir::Expr<'tcx>, + ) -> Ty<'tcx> { + // FIXME(tschottdorf): `contains_explicit_ref_binding()` must be removed + // for #42640 (default match binding modes). + // + // See #44848. + let ref_bindings = pat.contains_explicit_ref_binding(); + + let local_ty = self.local_ty(init.span, hir_id).revealed_ty; + if let Some(m) = ref_bindings { + // Somewhat subtle: if we have a `ref` binding in the pattern, + // we want to avoid introducing coercions for the RHS. This is + // both because it helps preserve sanity and, in the case of + // ref mut, for soundness (issue #23116). In particular, in + // the latter case, we need to be clear that the type of the + // referent for the reference that results is *equal to* the + // type of the place it is referencing, and not some + // supertype thereof. + let init_ty = self.check_expr_with_needs(init, Needs::maybe_mut_place(m)); + self.demand_eqtype(init.span, local_ty, init_ty); + init_ty + } else { + self.check_expr_coercable_to_type(init, local_ty, None) + } + } + + pub(in super::super) fn check_decl(&self, decl: Declaration<'tcx>) { + // Determine and write the type which we'll check the pattern against. + let decl_ty = self.local_ty(decl.span, decl.hir_id).decl_ty; + self.write_ty(decl.hir_id, decl_ty); + + // Type check the initializer. + if let Some(ref init) = decl.init { + let init_ty = self.check_decl_initializer(decl.hir_id, decl.pat, &init); + self.overwrite_local_ty_if_err(decl.hir_id, decl.pat, decl_ty, init_ty); + } + + // Does the expected pattern type originate from an expression and what is the span? + let (origin_expr, ty_span) = match (decl.ty, decl.init) { + (Some(ty), _) => (false, Some(ty.span)), // Bias towards the explicit user type. + (_, Some(init)) => { + (true, Some(init.span.find_ancestor_inside(decl.span).unwrap_or(init.span))) + } // No explicit type; so use the scrutinee. + _ => (false, None), // We have `let $pat;`, so the expected type is unconstrained. + }; + + // Type check the pattern. Override if necessary to avoid knock-on errors. + self.check_pat_top(&decl.pat, decl_ty, ty_span, origin_expr); + let pat_ty = self.node_ty(decl.pat.hir_id); + self.overwrite_local_ty_if_err(decl.hir_id, decl.pat, decl_ty, pat_ty); + + if let Some(blk) = decl.els { + let previous_diverges = self.diverges.get(); + let else_ty = self.check_block_with_expected(blk, NoExpectation); + let cause = self.cause(blk.span, ObligationCauseCode::LetElse); + if let Some(mut err) = + self.demand_eqtype_with_origin(&cause, self.tcx.types.never, else_ty) + { + err.emit(); + } + self.diverges.set(previous_diverges); + } + } + + /// Type check a `let` statement. + pub fn check_decl_local(&self, local: &'tcx hir::Local<'tcx>) { + self.check_decl(local.into()); + } + + pub fn check_stmt(&self, stmt: &'tcx hir::Stmt<'tcx>, is_last: bool) { + // Don't do all the complex logic below for `DeclItem`. + match stmt.kind { + hir::StmtKind::Item(..) => return, + hir::StmtKind::Local(..) | hir::StmtKind::Expr(..) | hir::StmtKind::Semi(..) => {} + } + + self.warn_if_unreachable(stmt.hir_id, stmt.span, "statement"); + + // Hide the outer diverging and `has_errors` flags. + let old_diverges = self.diverges.replace(Diverges::Maybe); + let old_has_errors = self.has_errors.replace(false); + + match stmt.kind { + hir::StmtKind::Local(l) => { + self.check_decl_local(l); + } + // Ignore for now. + hir::StmtKind::Item(_) => {} + hir::StmtKind::Expr(ref expr) => { + // Check with expected type of `()`. + self.check_expr_has_type_or_error(&expr, self.tcx.mk_unit(), |err| { + if expr.can_have_side_effects() { + self.suggest_semicolon_at_end(expr.span, err); + } + }); + } + hir::StmtKind::Semi(ref expr) => { + // All of this is equivalent to calling `check_expr`, but it is inlined out here + // in order to capture the fact that this `match` is the last statement in its + // function. This is done for better suggestions to remove the `;`. + let expectation = match expr.kind { + hir::ExprKind::Match(..) if is_last => IsLast(stmt.span), + _ => NoExpectation, + }; + self.check_expr_with_expectation(expr, expectation); + } + } + + // Combine the diverging and `has_error` flags. + self.diverges.set(self.diverges.get() | old_diverges); + self.has_errors.set(self.has_errors.get() | old_has_errors); + } + + pub fn check_block_no_value(&self, blk: &'tcx hir::Block<'tcx>) { + let unit = self.tcx.mk_unit(); + let ty = self.check_block_with_expected(blk, ExpectHasType(unit)); + + // if the block produces a `!` value, that can always be + // (effectively) coerced to unit. + if !ty.is_never() { + self.demand_suptype(blk.span, unit, ty); + } + } + + pub(in super::super) fn check_block_with_expected( + &self, + blk: &'tcx hir::Block<'tcx>, + expected: Expectation<'tcx>, + ) -> Ty<'tcx> { + let prev = self.ps.replace(self.ps.get().recurse(blk)); + + // In some cases, blocks have just one exit, but other blocks + // can be targeted by multiple breaks. This can happen both + // with labeled blocks as well as when we desugar + // a `try { ... }` expression. + // + // Example 1: + // + // 'a: { if true { break 'a Err(()); } Ok(()) } + // + // Here we would wind up with two coercions, one from + // `Err(())` and the other from the tail expression + // `Ok(())`. If the tail expression is omitted, that's a + // "forced unit" -- unless the block diverges, in which + // case we can ignore the tail expression (e.g., `'a: { + // break 'a 22; }` would not force the type of the block + // to be `()`). + let tail_expr = blk.expr.as_ref(); + let coerce_to_ty = expected.coercion_target_type(self, blk.span); + let coerce = if blk.targeted_by_break { + CoerceMany::new(coerce_to_ty) + } else { + let tail_expr: &[&hir::Expr<'_>] = match tail_expr { + Some(e) => slice::from_ref(e), + None => &[], + }; + CoerceMany::with_coercion_sites(coerce_to_ty, tail_expr) + }; + + let prev_diverges = self.diverges.get(); + let ctxt = BreakableCtxt { coerce: Some(coerce), may_break: false }; + + let (ctxt, ()) = self.with_breakable_ctxt(blk.hir_id, ctxt, || { + for (pos, s) in blk.stmts.iter().enumerate() { + self.check_stmt(s, blk.stmts.len() - 1 == pos); + } + + // check the tail expression **without** holding the + // `enclosing_breakables` lock below. + let tail_expr_ty = tail_expr.map(|t| self.check_expr_with_expectation(t, expected)); + + let mut enclosing_breakables = self.enclosing_breakables.borrow_mut(); + let ctxt = enclosing_breakables.find_breakable(blk.hir_id); + let coerce = ctxt.coerce.as_mut().unwrap(); + if let Some(tail_expr_ty) = tail_expr_ty { + let tail_expr = tail_expr.unwrap(); + let span = self.get_expr_coercion_span(tail_expr); + let cause = self.cause(span, ObligationCauseCode::BlockTailExpression(blk.hir_id)); + let ty_for_diagnostic = coerce.merged_ty(); + // We use coerce_inner here because we want to augment the error + // suggesting to wrap the block in square brackets if it might've + // been mistaken array syntax + coerce.coerce_inner( + self, + &cause, + Some(tail_expr), + tail_expr_ty, + Some(&mut |diag: &mut Diagnostic| { + self.suggest_block_to_brackets(diag, blk, tail_expr_ty, ty_for_diagnostic); + }), + false, + ); + } else { + // Subtle: if there is no explicit tail expression, + // that is typically equivalent to a tail expression + // of `()` -- except if the block diverges. In that + // case, there is no value supplied from the tail + // expression (assuming there are no other breaks, + // this implies that the type of the block will be + // `!`). + // + // #41425 -- label the implicit `()` as being the + // "found type" here, rather than the "expected type". + if !self.diverges.get().is_always() { + // #50009 -- Do not point at the entire fn block span, point at the return type + // span, as it is the cause of the requirement, and + // `consider_hint_about_removing_semicolon` will point at the last expression + // if it were a relevant part of the error. This improves usability in editors + // that highlight errors inline. + let mut sp = blk.span; + let mut fn_span = None; + if let Some((decl, ident)) = self.get_parent_fn_decl(blk.hir_id) { + let ret_sp = decl.output.span(); + if let Some(block_sp) = self.parent_item_span(blk.hir_id) { + // HACK: on some cases (`ui/liveness/liveness-issue-2163.rs`) the + // output would otherwise be incorrect and even misleading. Make sure + // the span we're aiming at correspond to a `fn` body. + if block_sp == blk.span { + sp = ret_sp; + fn_span = Some(ident.span); + } + } + } + coerce.coerce_forced_unit( + self, + &self.misc(sp), + &mut |err| { + if let Some(expected_ty) = expected.only_has_type(self) { + if !self.consider_removing_semicolon(blk, expected_ty, err) { + self.err_ctxt().consider_returning_binding( + blk, + expected_ty, + err, + ); + } + if expected_ty == self.tcx.types.bool { + // If this is caused by a missing `let` in a `while let`, + // silence this redundant error, as we already emit E0070. + + // Our block must be a `assign desugar local; assignment` + if let Some(hir::Node::Block(hir::Block { + stmts: + [ + hir::Stmt { + kind: + hir::StmtKind::Local(hir::Local { + source: + hir::LocalSource::AssignDesugar(_), + .. + }), + .. + }, + hir::Stmt { + kind: + hir::StmtKind::Expr(hir::Expr { + kind: hir::ExprKind::Assign(..), + .. + }), + .. + }, + ], + .. + })) = self.tcx.hir().find(blk.hir_id) + { + self.comes_from_while_condition(blk.hir_id, |_| { + err.downgrade_to_delayed_bug(); + }) + } + } + } + if let Some(fn_span) = fn_span { + err.span_label( + fn_span, + "implicitly returns `()` as its body has no tail or `return` \ + expression", + ); + } + }, + false, + ); + } + } + }); + + if ctxt.may_break { + // If we can break from the block, then the block's exit is always reachable + // (... as long as the entry is reachable) - regardless of the tail of the block. + self.diverges.set(prev_diverges); + } + + let mut ty = ctxt.coerce.unwrap().complete(self); + + if self.has_errors.get() || ty.references_error() { + ty = self.tcx.ty_error() + } + + self.write_ty(blk.hir_id, ty); + + self.ps.set(prev); + ty + } + + fn parent_item_span(&self, id: hir::HirId) -> Option<Span> { + let node = self.tcx.hir().get_by_def_id(self.tcx.hir().get_parent_item(id).def_id); + match node { + Node::Item(&hir::Item { kind: hir::ItemKind::Fn(_, _, body_id), .. }) + | Node::ImplItem(&hir::ImplItem { kind: hir::ImplItemKind::Fn(_, body_id), .. }) => { + let body = self.tcx.hir().body(body_id); + if let ExprKind::Block(block, _) = &body.value.kind { + return Some(block.span); + } + } + _ => {} + } + None + } + + /// Given a function block's `HirId`, returns its `FnDecl` if it exists, or `None` otherwise. + fn get_parent_fn_decl(&self, blk_id: hir::HirId) -> Option<(&'tcx hir::FnDecl<'tcx>, Ident)> { + let parent = self.tcx.hir().get_by_def_id(self.tcx.hir().get_parent_item(blk_id).def_id); + self.get_node_fn_decl(parent).map(|(fn_decl, ident, _)| (fn_decl, ident)) + } + + /// If `expr` is a `match` expression that has only one non-`!` arm, use that arm's tail + /// expression's `Span`, otherwise return `expr.span`. This is done to give better errors + /// when given code like the following: + /// ```text + /// if false { return 0i32; } else { 1u32 } + /// // ^^^^ point at this instead of the whole `if` expression + /// ``` + fn get_expr_coercion_span(&self, expr: &hir::Expr<'_>) -> rustc_span::Span { + let check_in_progress = |elem: &hir::Expr<'_>| { + self.typeck_results.borrow().node_type_opt(elem.hir_id).filter(|ty| !ty.is_never()).map( + |_| match elem.kind { + // Point at the tail expression when possible. + hir::ExprKind::Block(block, _) => block.expr.map_or(block.span, |e| e.span), + _ => elem.span, + }, + ) + }; + + if let hir::ExprKind::If(_, _, Some(el)) = expr.kind { + if let Some(rslt) = check_in_progress(el) { + return rslt; + } + } + + if let hir::ExprKind::Match(_, arms, _) = expr.kind { + let mut iter = arms.iter().filter_map(|arm| check_in_progress(arm.body)); + if let Some(span) = iter.next() { + if iter.next().is_none() { + return span; + } + } + } + + expr.span + } + + fn overwrite_local_ty_if_err( + &self, + hir_id: hir::HirId, + pat: &'tcx hir::Pat<'tcx>, + decl_ty: Ty<'tcx>, + ty: Ty<'tcx>, + ) { + if ty.references_error() { + // Override the types everywhere with `err()` to avoid knock on errors. + self.write_ty(hir_id, ty); + self.write_ty(pat.hir_id, ty); + let local_ty = LocalTy { decl_ty, revealed_ty: ty }; + self.locals.borrow_mut().insert(hir_id, local_ty); + self.locals.borrow_mut().insert(pat.hir_id, local_ty); + } + } + + // Finish resolving a path in a struct expression or pattern `S::A { .. }` if necessary. + // The newly resolved definition is written into `type_dependent_defs`. + fn finish_resolving_struct_path( + &self, + qpath: &QPath<'_>, + path_span: Span, + hir_id: hir::HirId, + ) -> (Res, Ty<'tcx>) { + match *qpath { + QPath::Resolved(ref maybe_qself, ref path) => { + let self_ty = maybe_qself.as_ref().map(|qself| self.to_ty(qself)); + let ty = <dyn AstConv<'_>>::res_to_ty(self, self_ty, path, true); + (path.res, ty) + } + QPath::TypeRelative(ref qself, ref segment) => { + let ty = self.to_ty(qself); + + let result = <dyn AstConv<'_>>::associated_path_to_ty( + self, hir_id, path_span, ty, qself, segment, true, + ); + let ty = result.map(|(ty, _, _)| ty).unwrap_or_else(|_| self.tcx().ty_error()); + let result = result.map(|(_, kind, def_id)| (kind, def_id)); + + // Write back the new resolution. + self.write_resolution(hir_id, result); + + (result.map_or(Res::Err, |(kind, def_id)| Res::Def(kind, def_id)), ty) + } + QPath::LangItem(lang_item, span, id) => { + self.resolve_lang_item_path(lang_item, span, hir_id, id) + } + } + } + + /// Given a vector of fulfillment errors, try to adjust the spans of the + /// errors to more accurately point at the cause of the failure. + /// + /// This applies to calls, methods, and struct expressions. This will also + /// try to deduplicate errors that are due to the same cause but might + /// have been created with different [`ObligationCause`][traits::ObligationCause]s. + pub(super) fn adjust_fulfillment_errors_for_expr_obligation( + &self, + errors: &mut Vec<traits::FulfillmentError<'tcx>>, + ) { + // Store a mapping from `(Span, Predicate) -> ObligationCause`, so that + // other errors that have the same span and predicate can also get fixed, + // even if their `ObligationCauseCode` isn't an `Expr*Obligation` kind. + // This is important since if we adjust one span but not the other, then + // we will have "duplicated" the error on the UI side. + let mut remap_cause = FxHashSet::default(); + let mut not_adjusted = vec![]; + + for error in errors { + let before_span = error.obligation.cause.span; + if self.adjust_fulfillment_error_for_expr_obligation(error) + || before_span != error.obligation.cause.span + { + // Store both the predicate and the predicate *without constness* + // since sometimes we instantiate and check both of these in a + // method call, for example. + remap_cause.insert(( + before_span, + error.obligation.predicate, + error.obligation.cause.clone(), + )); + remap_cause.insert(( + before_span, + error.obligation.predicate.without_const(self.tcx), + error.obligation.cause.clone(), + )); + } else { + // If it failed to be adjusted once around, it may be adjusted + // via the "remap cause" mapping the second time... + not_adjusted.push(error); + } + } + + for error in not_adjusted { + for (span, predicate, cause) in &remap_cause { + if *predicate == error.obligation.predicate + && span.contains(error.obligation.cause.span) + { + error.obligation.cause = cause.clone(); + continue; + } + } + } + } + + fn adjust_fulfillment_error_for_expr_obligation( + &self, + error: &mut traits::FulfillmentError<'tcx>, + ) -> bool { + let (traits::ExprItemObligation(def_id, hir_id, idx) | traits::ExprBindingObligation(def_id, _, hir_id, idx)) + = *error.obligation.cause.code().peel_derives() else { return false; }; + let hir = self.tcx.hir(); + let hir::Node::Expr(expr) = hir.get(hir_id) else { return false; }; + + // Skip over mentioning async lang item + if Some(def_id) == self.tcx.lang_items().from_generator_fn() + && error.obligation.cause.span.desugaring_kind() + == Some(rustc_span::DesugaringKind::Async) + { + return false; + } + + let Some(unsubstituted_pred) = + self.tcx.predicates_of(def_id).instantiate_identity(self.tcx).predicates.into_iter().nth(idx) + else { return false; }; + + let generics = self.tcx.generics_of(def_id); + let predicate_substs = match unsubstituted_pred.kind().skip_binder() { + ty::PredicateKind::Trait(pred) => pred.trait_ref.substs, + ty::PredicateKind::Projection(pred) => pred.projection_ty.substs, + _ => ty::List::empty(), + }; + + let find_param_matching = |matches: &dyn Fn(&ty::ParamTy) -> bool| { + predicate_substs.types().find_map(|ty| { + ty.walk().find_map(|arg| { + if let ty::GenericArgKind::Type(ty) = arg.unpack() + && let ty::Param(param_ty) = ty.kind() + && matches(param_ty) + { + Some(arg) + } else { + None + } + }) + }) + }; + + // Prefer generics that are local to the fn item, since these are likely + // to be the cause of the unsatisfied predicate. + let mut param_to_point_at = find_param_matching(&|param_ty| { + self.tcx.parent(generics.type_param(param_ty, self.tcx).def_id) == def_id + }); + // Fall back to generic that isn't local to the fn item. This will come + // from a trait or impl, for example. + let mut fallback_param_to_point_at = find_param_matching(&|param_ty| { + self.tcx.parent(generics.type_param(param_ty, self.tcx).def_id) != def_id + && param_ty.name != rustc_span::symbol::kw::SelfUpper + }); + // Finally, the `Self` parameter is possibly the reason that the predicate + // is unsatisfied. This is less likely to be true for methods, because + // method probe means that we already kinda check that the predicates due + // to the `Self` type are true. + let mut self_param_to_point_at = + find_param_matching(&|param_ty| param_ty.name == rustc_span::symbol::kw::SelfUpper); + + // Finally, for ambiguity-related errors, we actually want to look + // for a parameter that is the source of the inference type left + // over in this predicate. + if let traits::FulfillmentErrorCode::CodeAmbiguity = error.code { + fallback_param_to_point_at = None; + self_param_to_point_at = None; + param_to_point_at = + self.find_ambiguous_parameter_in(def_id, error.root_obligation.predicate); + } + + if self.closure_span_overlaps_error(error, expr.span) { + return false; + } + + match &expr.kind { + hir::ExprKind::Path(qpath) => { + if let hir::Node::Expr(hir::Expr { + kind: hir::ExprKind::Call(callee, args), + hir_id: call_hir_id, + span: call_span, + .. + }) = hir.get(hir.get_parent_node(expr.hir_id)) + && callee.hir_id == expr.hir_id + { + if self.closure_span_overlaps_error(error, *call_span) { + return false; + } + + for param in + [param_to_point_at, fallback_param_to_point_at, self_param_to_point_at] + .into_iter() + .flatten() + { + if self.point_at_arg_if_possible( + error, + def_id, + param, + *call_hir_id, + callee.span, + None, + args, + ) + { + return true; + } + } + } + // Notably, we only point to params that are local to the + // item we're checking, since those are the ones we are able + // to look in the final `hir::PathSegment` for. Everything else + // would require a deeper search into the `qpath` than I think + // is worthwhile. + if let Some(param_to_point_at) = param_to_point_at + && self.point_at_path_if_possible(error, def_id, param_to_point_at, qpath) + { + return true; + } + } + hir::ExprKind::MethodCall(segment, receiver, args, ..) => { + for param in [param_to_point_at, fallback_param_to_point_at, self_param_to_point_at] + .into_iter() + .flatten() + { + if self.point_at_arg_if_possible( + error, + def_id, + param, + hir_id, + segment.ident.span, + Some(receiver), + args, + ) { + return true; + } + } + if let Some(param_to_point_at) = param_to_point_at + && self.point_at_generic_if_possible(error, def_id, param_to_point_at, segment) + { + return true; + } + } + hir::ExprKind::Struct(qpath, fields, ..) => { + if let Res::Def(DefKind::Struct | DefKind::Variant, variant_def_id) = + self.typeck_results.borrow().qpath_res(qpath, hir_id) + { + for param in + [param_to_point_at, fallback_param_to_point_at, self_param_to_point_at] + { + if let Some(param) = param + && self.point_at_field_if_possible( + error, + def_id, + param, + variant_def_id, + fields, + ) + { + return true; + } + } + } + if let Some(param_to_point_at) = param_to_point_at + && self.point_at_path_if_possible(error, def_id, param_to_point_at, qpath) + { + return true; + } + } + _ => {} + } + + false + } + + fn closure_span_overlaps_error( + &self, + error: &traits::FulfillmentError<'tcx>, + span: Span, + ) -> bool { + if let traits::FulfillmentErrorCode::CodeSelectionError( + traits::SelectionError::OutputTypeParameterMismatch(_, expected, _), + ) = error.code + && let ty::Closure(def_id, _) | ty::Generator(def_id, ..) = expected.skip_binder().self_ty().kind() + && span.overlaps(self.tcx.def_span(*def_id)) + { + true + } else { + false + } + } + + fn point_at_arg_if_possible( + &self, + error: &mut traits::FulfillmentError<'tcx>, + def_id: DefId, + param_to_point_at: ty::GenericArg<'tcx>, + call_hir_id: hir::HirId, + callee_span: Span, + receiver: Option<&'tcx hir::Expr<'tcx>>, + args: &'tcx [hir::Expr<'tcx>], + ) -> bool { + let sig = self.tcx.fn_sig(def_id).skip_binder(); + let args_referencing_param: Vec<_> = sig + .inputs() + .iter() + .enumerate() + .filter(|(_, ty)| find_param_in_ty(**ty, param_to_point_at)) + .collect(); + // If there's one field that references the given generic, great! + if let [(idx, _)] = args_referencing_param.as_slice() + && let Some(arg) = receiver + .map_or(args.get(*idx), |rcvr| if *idx == 0 { Some(rcvr) } else { args.get(*idx - 1) }) { + error.obligation.cause.span = arg.span.find_ancestor_in_same_ctxt(error.obligation.cause.span).unwrap_or(arg.span); + error.obligation.cause.map_code(|parent_code| { + ObligationCauseCode::FunctionArgumentObligation { + arg_hir_id: arg.hir_id, + call_hir_id, + parent_code, + } + }); + return true; + } else if args_referencing_param.len() > 0 { + // If more than one argument applies, then point to the callee span at least... + // We have chance to fix this up further in `point_at_generics_if_possible` + error.obligation.cause.span = callee_span; + } + + false + } + + fn point_at_field_if_possible( + &self, + error: &mut traits::FulfillmentError<'tcx>, + def_id: DefId, + param_to_point_at: ty::GenericArg<'tcx>, + variant_def_id: DefId, + expr_fields: &[hir::ExprField<'tcx>], + ) -> bool { + let def = self.tcx.adt_def(def_id); + + let identity_substs = ty::InternalSubsts::identity_for_item(self.tcx, def_id); + let fields_referencing_param: Vec<_> = def + .variant_with_id(variant_def_id) + .fields + .iter() + .filter(|field| { + let field_ty = field.ty(self.tcx, identity_substs); + find_param_in_ty(field_ty, param_to_point_at) + }) + .collect(); + + if let [field] = fields_referencing_param.as_slice() { + for expr_field in expr_fields { + // Look for the ExprField that matches the field, using the + // same rules that check_expr_struct uses for macro hygiene. + if self.tcx.adjust_ident(expr_field.ident, variant_def_id) == field.ident(self.tcx) + { + error.obligation.cause.span = expr_field + .expr + .span + .find_ancestor_in_same_ctxt(error.obligation.cause.span) + .unwrap_or(expr_field.span); + return true; + } + } + } + + false + } + + fn point_at_path_if_possible( + &self, + error: &mut traits::FulfillmentError<'tcx>, + def_id: DefId, + param: ty::GenericArg<'tcx>, + qpath: &QPath<'tcx>, + ) -> bool { + match qpath { + hir::QPath::Resolved(_, path) => { + if let Some(segment) = path.segments.last() + && self.point_at_generic_if_possible(error, def_id, param, segment) + { + return true; + } + } + hir::QPath::TypeRelative(_, segment) => { + if self.point_at_generic_if_possible(error, def_id, param, segment) { + return true; + } + } + _ => {} + } + + false + } + + fn point_at_generic_if_possible( + &self, + error: &mut traits::FulfillmentError<'tcx>, + def_id: DefId, + param_to_point_at: ty::GenericArg<'tcx>, + segment: &hir::PathSegment<'tcx>, + ) -> bool { + let own_substs = self + .tcx + .generics_of(def_id) + .own_substs(ty::InternalSubsts::identity_for_item(self.tcx, def_id)); + let Some((index, _)) = own_substs + .iter() + .filter(|arg| matches!(arg.unpack(), ty::GenericArgKind::Type(_))) + .enumerate() + .find(|(_, arg)| **arg == param_to_point_at) else { return false }; + let Some(arg) = segment + .args() + .args + .iter() + .filter(|arg| matches!(arg, hir::GenericArg::Type(_))) + .nth(index) else { return false; }; + error.obligation.cause.span = arg + .span() + .find_ancestor_in_same_ctxt(error.obligation.cause.span) + .unwrap_or(arg.span()); + true + } + + fn find_ambiguous_parameter_in<T: TypeVisitable<'tcx>>( + &self, + item_def_id: DefId, + t: T, + ) -> Option<ty::GenericArg<'tcx>> { + struct FindAmbiguousParameter<'a, 'tcx>(&'a FnCtxt<'a, 'tcx>, DefId); + impl<'tcx> TypeVisitor<'tcx> for FindAmbiguousParameter<'_, 'tcx> { + type BreakTy = ty::GenericArg<'tcx>; + fn visit_ty(&mut self, ty: Ty<'tcx>) -> std::ops::ControlFlow<Self::BreakTy> { + if let Some(origin) = self.0.type_var_origin(ty) + && let TypeVariableOriginKind::TypeParameterDefinition(_, Some(def_id)) = + origin.kind + && let generics = self.0.tcx.generics_of(self.1) + && let Some(index) = generics.param_def_id_to_index(self.0.tcx, def_id) + && let Some(subst) = ty::InternalSubsts::identity_for_item(self.0.tcx, self.1) + .get(index as usize) + { + ControlFlow::Break(*subst) + } else { + ty.super_visit_with(self) + } + } + } + t.visit_with(&mut FindAmbiguousParameter(self, item_def_id)).break_value() + } + + fn label_fn_like( + &self, + err: &mut Diagnostic, + callable_def_id: Option<DefId>, + callee_ty: Option<Ty<'tcx>>, + // A specific argument should be labeled, instead of all of them + expected_idx: Option<usize>, + is_method: bool, + ) { + let Some(mut def_id) = callable_def_id else { + return; + }; + + if let Some(assoc_item) = self.tcx.opt_associated_item(def_id) + // Possibly points at either impl or trait item, so try to get it + // to point to trait item, then get the parent. + // This parent might be an impl in the case of an inherent function, + // but the next check will fail. + && let maybe_trait_item_def_id = assoc_item.trait_item_def_id.unwrap_or(def_id) + && let maybe_trait_def_id = self.tcx.parent(maybe_trait_item_def_id) + // Just an easy way to check "trait_def_id == Fn/FnMut/FnOnce" + && let Some(call_kind) = ty::ClosureKind::from_def_id(self.tcx, maybe_trait_def_id) + && let Some(callee_ty) = callee_ty + { + let callee_ty = callee_ty.peel_refs(); + match *callee_ty.kind() { + ty::Param(param) => { + let param = + self.tcx.generics_of(self.body_id.owner).type_param(¶m, self.tcx); + if param.kind.is_synthetic() { + // if it's `impl Fn() -> ..` then just fall down to the def-id based logic + def_id = param.def_id; + } else { + // Otherwise, find the predicate that makes this generic callable, + // and point at that. + let instantiated = self + .tcx + .explicit_predicates_of(self.body_id.owner) + .instantiate_identity(self.tcx); + // FIXME(compiler-errors): This could be problematic if something has two + // fn-like predicates with different args, but callable types really never + // do that, so it's OK. + for (predicate, span) in + std::iter::zip(instantiated.predicates, instantiated.spans) + { + if let ty::PredicateKind::Trait(pred) = predicate.kind().skip_binder() + && pred.self_ty().peel_refs() == callee_ty + && ty::ClosureKind::from_def_id(self.tcx, pred.def_id()).is_some() + { + err.span_note(span, "callable defined here"); + return; + } + } + } + } + ty::Opaque(new_def_id, _) + | ty::Closure(new_def_id, _) + | ty::FnDef(new_def_id, _) => { + def_id = new_def_id; + } + _ => { + // Look for a user-provided impl of a `Fn` trait, and point to it. + let new_def_id = self.probe(|_| { + let trait_ref = ty::TraitRef::new( + call_kind.to_def_id(self.tcx), + self.tcx.mk_substs( + [ + ty::GenericArg::from(callee_ty), + self.next_ty_var(TypeVariableOrigin { + kind: TypeVariableOriginKind::MiscVariable, + span: rustc_span::DUMMY_SP, + }) + .into(), + ] + .into_iter(), + ), + ); + let obligation = traits::Obligation::new( + traits::ObligationCause::dummy(), + self.param_env, + ty::Binder::dummy(ty::TraitPredicate { + trait_ref, + constness: ty::BoundConstness::NotConst, + polarity: ty::ImplPolarity::Positive, + }), + ); + match SelectionContext::new(&self).select(&obligation) { + Ok(Some(traits::ImplSource::UserDefined(impl_source))) => { + Some(impl_source.impl_def_id) + } + _ => None, + } + }); + if let Some(new_def_id) = new_def_id { + def_id = new_def_id; + } else { + return; + } + } + } + } + + if let Some(def_span) = self.tcx.def_ident_span(def_id) && !def_span.is_dummy() { + let mut spans: MultiSpan = def_span.into(); + + let params = self + .tcx + .hir() + .get_if_local(def_id) + .and_then(|node| node.body_id()) + .into_iter() + .flat_map(|id| self.tcx.hir().body(id).params) + .skip(if is_method { 1 } else { 0 }); + + for (_, param) in params + .into_iter() + .enumerate() + .filter(|(idx, _)| expected_idx.map_or(true, |expected_idx| expected_idx == *idx)) + { + spans.push_span_label(param.span, ""); + } + + let def_kind = self.tcx.def_kind(def_id); + err.span_note(spans, &format!("{} defined here", def_kind.descr(def_id))); + } else if let Some(hir::Node::Expr(e)) = self.tcx.hir().get_if_local(def_id) + && let hir::ExprKind::Closure(hir::Closure { body, .. }) = &e.kind + { + let param = expected_idx + .and_then(|expected_idx| self.tcx.hir().body(*body).params.get(expected_idx)); + let (kind, span) = if let Some(param) = param { + ("closure parameter", param.span) + } else { + ("closure", self.tcx.def_span(def_id)) + }; + err.span_note(span, &format!("{} defined here", kind)); + } else { + let def_kind = self.tcx.def_kind(def_id); + err.span_note( + self.tcx.def_span(def_id), + &format!("{} defined here", def_kind.descr(def_id)), + ); + } + } +} + +fn find_param_in_ty<'tcx>(ty: Ty<'tcx>, param_to_point_at: ty::GenericArg<'tcx>) -> bool { + let mut walk = ty.walk(); + while let Some(arg) = walk.next() { + if arg == param_to_point_at { + return true; + } else if let ty::GenericArgKind::Type(ty) = arg.unpack() + && let ty::Projection(..) = ty.kind() + { + // This logic may seem a bit strange, but typically when + // we have a projection type in a function signature, the + // argument that's being passed into that signature is + // not actually constraining that projection's substs in + // a meaningful way. So we skip it, and see improvements + // in some UI tests. + walk.skip_current_subtree(); + } + } + false +} diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs new file mode 100644 index 000000000..0c600daf4 --- /dev/null +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs @@ -0,0 +1,312 @@ +mod _impl; +mod arg_matrix; +mod checks; +mod suggestions; + +pub use _impl::*; +pub use suggestions::*; + +use crate::coercion::DynamicCoerceMany; +use crate::{Diverges, EnclosingBreakables, Inherited, UnsafetyState}; +use rustc_hir as hir; +use rustc_hir::def_id::DefId; +use rustc_hir_analysis::astconv::AstConv; +use rustc_infer::infer; +use rustc_infer::infer::error_reporting::TypeErrCtxt; +use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind}; +use rustc_middle::infer::unify_key::{ConstVariableOrigin, ConstVariableOriginKind}; +use rustc_middle::ty::subst::GenericArgKind; +use rustc_middle::ty::visit::TypeVisitable; +use rustc_middle::ty::{self, Const, Ty, TyCtxt}; +use rustc_session::Session; +use rustc_span::symbol::Ident; +use rustc_span::{self, Span}; +use rustc_trait_selection::traits::{ObligationCause, ObligationCauseCode}; + +use std::cell::{Cell, RefCell}; +use std::ops::Deref; + +/// The `FnCtxt` stores type-checking context needed to type-check bodies of +/// functions, closures, and `const`s, including performing type inference +/// with [`InferCtxt`]. +/// +/// This is in contrast to [`ItemCtxt`], which is used to type-check item *signatures* +/// and thus does not perform type inference. +/// +/// See [`ItemCtxt`]'s docs for more. +/// +/// [`ItemCtxt`]: rustc_hir_analysis::collect::ItemCtxt +/// [`InferCtxt`]: infer::InferCtxt +pub struct FnCtxt<'a, 'tcx> { + pub(super) body_id: hir::HirId, + + /// The parameter environment used for proving trait obligations + /// in this function. This can change when we descend into + /// closures (as they bring new things into scope), hence it is + /// not part of `Inherited` (as of the time of this writing, + /// closures do not yet change the environment, but they will + /// eventually). + pub(super) param_env: ty::ParamEnv<'tcx>, + + /// Number of errors that had been reported when we started + /// checking this function. On exit, if we find that *more* errors + /// have been reported, we will skip regionck and other work that + /// expects the types within the function to be consistent. + // FIXME(matthewjasper) This should not exist, and it's not correct + // if type checking is run in parallel. + err_count_on_creation: usize, + + /// If `Some`, this stores coercion information for returned + /// expressions. If `None`, this is in a context where return is + /// inappropriate, such as a const expression. + /// + /// This is a `RefCell<DynamicCoerceMany>`, which means that we + /// can track all the return expressions and then use them to + /// compute a useful coercion from the set, similar to a match + /// expression or other branching context. You can use methods + /// like `expected_ty` to access the declared return type (if + /// any). + pub(super) ret_coercion: Option<RefCell<DynamicCoerceMany<'tcx>>>, + + /// Used exclusively to reduce cost of advanced evaluation used for + /// more helpful diagnostics. + pub(super) in_tail_expr: bool, + + /// First span of a return site that we find. Used in error messages. + pub(super) ret_coercion_span: Cell<Option<Span>>, + + pub(super) resume_yield_tys: Option<(Ty<'tcx>, Ty<'tcx>)>, + + pub(super) ps: Cell<UnsafetyState>, + + /// Whether the last checked node generates a divergence (e.g., + /// `return` will set this to `Always`). In general, when entering + /// an expression or other node in the tree, the initial value + /// indicates whether prior parts of the containing expression may + /// have diverged. It is then typically set to `Maybe` (and the + /// old value remembered) for processing the subparts of the + /// current expression. As each subpart is processed, they may set + /// the flag to `Always`, etc. Finally, at the end, we take the + /// result and "union" it with the original value, so that when we + /// return the flag indicates if any subpart of the parent + /// expression (up to and including this part) has diverged. So, + /// if you read it after evaluating a subexpression `X`, the value + /// you get indicates whether any subexpression that was + /// evaluating up to and including `X` diverged. + /// + /// We currently use this flag only for diagnostic purposes: + /// + /// - To warn about unreachable code: if, after processing a + /// sub-expression but before we have applied the effects of the + /// current node, we see that the flag is set to `Always`, we + /// can issue a warning. This corresponds to something like + /// `foo(return)`; we warn on the `foo()` expression. (We then + /// update the flag to `WarnedAlways` to suppress duplicate + /// reports.) Similarly, if we traverse to a fresh statement (or + /// tail expression) from an `Always` setting, we will issue a + /// warning. This corresponds to something like `{return; + /// foo();}` or `{return; 22}`, where we would warn on the + /// `foo()` or `22`. + /// + /// An expression represents dead code if, after checking it, + /// the diverges flag is set to something other than `Maybe`. + pub(super) diverges: Cell<Diverges>, + + /// Whether any child nodes have any type errors. + pub(super) has_errors: Cell<bool>, + + pub(super) enclosing_breakables: RefCell<EnclosingBreakables<'tcx>>, + + pub(super) inh: &'a Inherited<'tcx>, + + /// True if the function or closure's return type is known before + /// entering the function/closure, i.e. if the return type is + /// either given explicitly or inferred from, say, an `Fn*` trait + /// bound. Used for diagnostic purposes only. + pub(super) return_type_pre_known: bool, + + /// True if the return type has an Opaque type + pub(super) return_type_has_opaque: bool, +} + +impl<'a, 'tcx> FnCtxt<'a, 'tcx> { + pub fn new( + inh: &'a Inherited<'tcx>, + param_env: ty::ParamEnv<'tcx>, + body_id: hir::HirId, + ) -> FnCtxt<'a, 'tcx> { + FnCtxt { + body_id, + param_env, + err_count_on_creation: inh.tcx.sess.err_count(), + ret_coercion: None, + in_tail_expr: false, + ret_coercion_span: Cell::new(None), + resume_yield_tys: None, + ps: Cell::new(UnsafetyState::function(hir::Unsafety::Normal, hir::CRATE_HIR_ID)), + diverges: Cell::new(Diverges::Maybe), + has_errors: Cell::new(false), + enclosing_breakables: RefCell::new(EnclosingBreakables { + stack: Vec::new(), + by_id: Default::default(), + }), + inh, + return_type_pre_known: true, + return_type_has_opaque: false, + } + } + + pub fn cause(&self, span: Span, code: ObligationCauseCode<'tcx>) -> ObligationCause<'tcx> { + ObligationCause::new(span, self.body_id, code) + } + + pub fn misc(&self, span: Span) -> ObligationCause<'tcx> { + self.cause(span, ObligationCauseCode::MiscObligation) + } + + pub fn sess(&self) -> &Session { + &self.tcx.sess + } + + /// Creates an `TypeErrCtxt` with a reference to the in-progress + /// `TypeckResults` which is used for diagnostics. + /// Use [`InferCtxt::err_ctxt`] to start one without a `TypeckResults`. + /// + /// [`InferCtxt::err_ctxt`]: infer::InferCtxt::err_ctxt + pub fn err_ctxt(&'a self) -> TypeErrCtxt<'a, 'tcx> { + TypeErrCtxt { infcx: &self.infcx, typeck_results: Some(self.typeck_results.borrow()) } + } + + pub fn errors_reported_since_creation(&self) -> bool { + self.tcx.sess.err_count() > self.err_count_on_creation + } +} + +impl<'a, 'tcx> Deref for FnCtxt<'a, 'tcx> { + type Target = Inherited<'tcx>; + fn deref(&self) -> &Self::Target { + &self.inh + } +} + +impl<'a, 'tcx> AstConv<'tcx> for FnCtxt<'a, 'tcx> { + fn tcx<'b>(&'b self) -> TyCtxt<'tcx> { + self.tcx + } + + fn item_def_id(&self) -> Option<DefId> { + None + } + + fn get_type_parameter_bounds( + &self, + _: Span, + def_id: DefId, + _: Ident, + ) -> ty::GenericPredicates<'tcx> { + let tcx = self.tcx; + let item_def_id = tcx.hir().ty_param_owner(def_id.expect_local()); + let generics = tcx.generics_of(item_def_id); + let index = generics.param_def_id_to_index[&def_id]; + ty::GenericPredicates { + parent: None, + predicates: tcx.arena.alloc_from_iter( + self.param_env.caller_bounds().iter().filter_map(|predicate| { + match predicate.kind().skip_binder() { + ty::PredicateKind::Trait(data) if data.self_ty().is_param(index) => { + // HACK(eddyb) should get the original `Span`. + let span = tcx.def_span(def_id); + Some((predicate, span)) + } + _ => None, + } + }), + ), + } + } + + fn re_infer(&self, def: Option<&ty::GenericParamDef>, span: Span) -> Option<ty::Region<'tcx>> { + let v = match def { + Some(def) => infer::EarlyBoundRegion(span, def.name), + None => infer::MiscVariable(span), + }; + Some(self.next_region_var(v)) + } + + fn allow_ty_infer(&self) -> bool { + true + } + + fn ty_infer(&self, param: Option<&ty::GenericParamDef>, span: Span) -> Ty<'tcx> { + if let Some(param) = param { + if let GenericArgKind::Type(ty) = self.var_for_def(span, param).unpack() { + return ty; + } + unreachable!() + } else { + self.next_ty_var(TypeVariableOrigin { + kind: TypeVariableOriginKind::TypeInference, + span, + }) + } + } + + fn ct_infer( + &self, + ty: Ty<'tcx>, + param: Option<&ty::GenericParamDef>, + span: Span, + ) -> Const<'tcx> { + if let Some(param) = param { + if let GenericArgKind::Const(ct) = self.var_for_def(span, param).unpack() { + return ct; + } + unreachable!() + } else { + self.next_const_var( + ty, + ConstVariableOrigin { kind: ConstVariableOriginKind::ConstInference, span }, + ) + } + } + + fn projected_ty_from_poly_trait_ref( + &self, + span: Span, + item_def_id: DefId, + item_segment: &hir::PathSegment<'_>, + poly_trait_ref: ty::PolyTraitRef<'tcx>, + ) -> Ty<'tcx> { + let trait_ref = self.replace_bound_vars_with_fresh_vars( + span, + infer::LateBoundRegionConversionTime::AssocTypeProjection(item_def_id), + poly_trait_ref, + ); + + let item_substs = <dyn AstConv<'tcx>>::create_substs_for_associated_item( + self, + span, + item_def_id, + item_segment, + trait_ref.substs, + ); + + self.tcx().mk_projection(item_def_id, item_substs) + } + + fn normalize_ty(&self, span: Span, ty: Ty<'tcx>) -> Ty<'tcx> { + if ty.has_escaping_bound_vars() { + ty // FIXME: normalization and escaping regions + } else { + self.normalize_associated_types_in(span, ty) + } + } + + fn set_tainted_by_errors(&self) { + self.infcx.set_tainted_by_errors() + } + + fn record_ty(&self, hir_id: hir::HirId, ty: Ty<'tcx>, _span: Span) { + self.write_ty(hir_id, ty) + } +} diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs new file mode 100644 index 000000000..4db9c56f9 --- /dev/null +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs @@ -0,0 +1,1250 @@ +use super::FnCtxt; + +use crate::errors::{AddReturnTypeSuggestion, ExpectedReturnTypeLabel}; +use rustc_ast::util::parser::{ExprPrecedence, PREC_POSTFIX}; +use rustc_errors::{Applicability, Diagnostic, MultiSpan}; +use rustc_hir as hir; +use rustc_hir::def::{CtorOf, DefKind}; +use rustc_hir::lang_items::LangItem; +use rustc_hir::{ + Expr, ExprKind, GenericBound, Node, Path, QPath, Stmt, StmtKind, TyKind, WherePredicate, +}; +use rustc_hir_analysis::astconv::AstConv; +use rustc_infer::infer::{self, TyCtxtInferExt}; +use rustc_infer::traits::{self, StatementAsExpression}; +use rustc_middle::lint::in_external_macro; +use rustc_middle::ty::{self, Binder, IsSuggestable, ToPredicate, Ty}; +use rustc_session::errors::ExprParenthesesNeeded; +use rustc_span::symbol::sym; +use rustc_span::Span; +use rustc_trait_selection::infer::InferCtxtExt; +use rustc_trait_selection::traits::error_reporting::DefIdOrName; +use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _; + +impl<'a, 'tcx> FnCtxt<'a, 'tcx> { + pub(in super::super) fn suggest_semicolon_at_end(&self, span: Span, err: &mut Diagnostic) { + err.span_suggestion_short( + span.shrink_to_hi(), + "consider using a semicolon here", + ";", + Applicability::MachineApplicable, + ); + } + + /// On implicit return expressions with mismatched types, provides the following suggestions: + /// + /// - Points out the method's return type as the reason for the expected type. + /// - Possible missing semicolon. + /// - Possible missing return type if the return type is the default, and not `fn main()`. + pub fn suggest_mismatched_types_on_tail( + &self, + err: &mut Diagnostic, + expr: &'tcx hir::Expr<'tcx>, + expected: Ty<'tcx>, + found: Ty<'tcx>, + blk_id: hir::HirId, + ) -> bool { + let expr = expr.peel_drop_temps(); + self.suggest_missing_semicolon(err, expr, expected, false); + let mut pointing_at_return_type = false; + if let Some((fn_decl, can_suggest)) = self.get_fn_decl(blk_id) { + let fn_id = self.tcx.hir().get_return_block(blk_id).unwrap(); + pointing_at_return_type = self.suggest_missing_return_type( + err, + &fn_decl, + expected, + found, + can_suggest, + fn_id, + ); + self.suggest_missing_break_or_return_expr( + err, expr, &fn_decl, expected, found, blk_id, fn_id, + ); + } + pointing_at_return_type + } + + /// When encountering an fn-like type, try accessing the output of the type + /// and suggesting calling it if it satisfies a predicate (i.e. if the + /// output has a method or a field): + /// ```compile_fail,E0308 + /// fn foo(x: usize) -> usize { x } + /// let x: usize = foo; // suggest calling the `foo` function: `foo(42)` + /// ``` + pub(crate) fn suggest_fn_call( + &self, + err: &mut Diagnostic, + expr: &hir::Expr<'_>, + found: Ty<'tcx>, + can_satisfy: impl FnOnce(Ty<'tcx>) -> bool, + ) -> bool { + let Some((def_id_or_name, output, inputs)) = self.extract_callable_info(expr, found) + else { return false; }; + if can_satisfy(output) { + let (sugg_call, mut applicability) = match inputs.len() { + 0 => ("".to_string(), Applicability::MachineApplicable), + 1..=4 => ( + inputs + .iter() + .map(|ty| { + if ty.is_suggestable(self.tcx, false) { + format!("/* {ty} */") + } else { + "/* value */".to_string() + } + }) + .collect::<Vec<_>>() + .join(", "), + Applicability::HasPlaceholders, + ), + _ => ("/* ... */".to_string(), Applicability::HasPlaceholders), + }; + + let msg = match def_id_or_name { + DefIdOrName::DefId(def_id) => match self.tcx.def_kind(def_id) { + DefKind::Ctor(CtorOf::Struct, _) => "construct this tuple struct".to_string(), + DefKind::Ctor(CtorOf::Variant, _) => "construct this tuple variant".to_string(), + kind => format!("call this {}", kind.descr(def_id)), + }, + DefIdOrName::Name(name) => format!("call this {name}"), + }; + + let sugg = match expr.kind { + hir::ExprKind::Call(..) + | hir::ExprKind::Path(..) + | hir::ExprKind::Index(..) + | hir::ExprKind::Lit(..) => { + vec![(expr.span.shrink_to_hi(), format!("({sugg_call})"))] + } + hir::ExprKind::Closure { .. } => { + // Might be `{ expr } || { bool }` + applicability = Applicability::MaybeIncorrect; + vec![ + (expr.span.shrink_to_lo(), "(".to_string()), + (expr.span.shrink_to_hi(), format!(")({sugg_call})")), + ] + } + _ => { + vec![ + (expr.span.shrink_to_lo(), "(".to_string()), + (expr.span.shrink_to_hi(), format!(")({sugg_call})")), + ] + } + }; + + err.multipart_suggestion_verbose( + format!("use parentheses to {msg}"), + sugg, + applicability, + ); + return true; + } + false + } + + /// Extracts information about a callable type for diagnostics. This is a + /// heuristic -- it doesn't necessarily mean that a type is always callable, + /// because the callable type must also be well-formed to be called. + pub(in super::super) fn extract_callable_info( + &self, + expr: &Expr<'_>, + found: Ty<'tcx>, + ) -> Option<(DefIdOrName, Ty<'tcx>, Vec<Ty<'tcx>>)> { + // Autoderef is useful here because sometimes we box callables, etc. + let Some((def_id_or_name, output, inputs)) = self.autoderef(expr.span, found).silence_errors().find_map(|(found, _)| { + match *found.kind() { + ty::FnPtr(fn_sig) => + Some((DefIdOrName::Name("function pointer"), fn_sig.output(), fn_sig.inputs())), + ty::FnDef(def_id, _) => { + let fn_sig = found.fn_sig(self.tcx); + Some((DefIdOrName::DefId(def_id), fn_sig.output(), fn_sig.inputs())) + } + ty::Closure(def_id, substs) => { + let fn_sig = substs.as_closure().sig(); + Some((DefIdOrName::DefId(def_id), fn_sig.output(), fn_sig.inputs().map_bound(|inputs| &inputs[1..]))) + } + ty::Opaque(def_id, substs) => { + self.tcx.bound_item_bounds(def_id).subst(self.tcx, substs).iter().find_map(|pred| { + if let ty::PredicateKind::Projection(proj) = pred.kind().skip_binder() + && Some(proj.projection_ty.item_def_id) == self.tcx.lang_items().fn_once_output() + // args tuple will always be substs[1] + && let ty::Tuple(args) = proj.projection_ty.substs.type_at(1).kind() + { + Some(( + DefIdOrName::DefId(def_id), + pred.kind().rebind(proj.term.ty().unwrap()), + pred.kind().rebind(args.as_slice()), + )) + } else { + None + } + }) + } + ty::Dynamic(data, _, ty::Dyn) => { + data.iter().find_map(|pred| { + if let ty::ExistentialPredicate::Projection(proj) = pred.skip_binder() + && Some(proj.item_def_id) == self.tcx.lang_items().fn_once_output() + // for existential projection, substs are shifted over by 1 + && let ty::Tuple(args) = proj.substs.type_at(0).kind() + { + Some(( + DefIdOrName::Name("trait object"), + pred.rebind(proj.term.ty().unwrap()), + pred.rebind(args.as_slice()), + )) + } else { + None + } + }) + } + ty::Param(param) => { + let def_id = self.tcx.generics_of(self.body_id.owner).type_param(¶m, self.tcx).def_id; + self.tcx.predicates_of(self.body_id.owner).predicates.iter().find_map(|(pred, _)| { + if let ty::PredicateKind::Projection(proj) = pred.kind().skip_binder() + && Some(proj.projection_ty.item_def_id) == self.tcx.lang_items().fn_once_output() + && proj.projection_ty.self_ty() == found + // args tuple will always be substs[1] + && let ty::Tuple(args) = proj.projection_ty.substs.type_at(1).kind() + { + Some(( + DefIdOrName::DefId(def_id), + pred.kind().rebind(proj.term.ty().unwrap()), + pred.kind().rebind(args.as_slice()), + )) + } else { + None + } + }) + } + _ => None, + } + }) else { return None; }; + + let output = self.replace_bound_vars_with_fresh_vars(expr.span, infer::FnCall, output); + let inputs = inputs + .skip_binder() + .iter() + .map(|ty| { + self.replace_bound_vars_with_fresh_vars( + expr.span, + infer::FnCall, + inputs.rebind(*ty), + ) + }) + .collect(); + + // We don't want to register any extra obligations, which should be + // implied by wf, but also because that would possibly result in + // erroneous errors later on. + let infer::InferOk { value: output, obligations: _ } = + self.normalize_associated_types_in_as_infer_ok(expr.span, output); + + if output.is_ty_var() { None } else { Some((def_id_or_name, output, inputs)) } + } + + pub fn suggest_two_fn_call( + &self, + err: &mut Diagnostic, + lhs_expr: &'tcx hir::Expr<'tcx>, + lhs_ty: Ty<'tcx>, + rhs_expr: &'tcx hir::Expr<'tcx>, + rhs_ty: Ty<'tcx>, + can_satisfy: impl FnOnce(Ty<'tcx>, Ty<'tcx>) -> bool, + ) -> bool { + let Some((_, lhs_output_ty, lhs_inputs)) = self.extract_callable_info(lhs_expr, lhs_ty) + else { return false; }; + let Some((_, rhs_output_ty, rhs_inputs)) = self.extract_callable_info(rhs_expr, rhs_ty) + else { return false; }; + + if can_satisfy(lhs_output_ty, rhs_output_ty) { + let mut sugg = vec![]; + let mut applicability = Applicability::MachineApplicable; + + for (expr, inputs) in [(lhs_expr, lhs_inputs), (rhs_expr, rhs_inputs)] { + let (sugg_call, this_applicability) = match inputs.len() { + 0 => ("".to_string(), Applicability::MachineApplicable), + 1..=4 => ( + inputs + .iter() + .map(|ty| { + if ty.is_suggestable(self.tcx, false) { + format!("/* {ty} */") + } else { + "/* value */".to_string() + } + }) + .collect::<Vec<_>>() + .join(", "), + Applicability::HasPlaceholders, + ), + _ => ("/* ... */".to_string(), Applicability::HasPlaceholders), + }; + + applicability = applicability.max(this_applicability); + + match expr.kind { + hir::ExprKind::Call(..) + | hir::ExprKind::Path(..) + | hir::ExprKind::Index(..) + | hir::ExprKind::Lit(..) => { + sugg.extend([(expr.span.shrink_to_hi(), format!("({sugg_call})"))]); + } + hir::ExprKind::Closure { .. } => { + // Might be `{ expr } || { bool }` + applicability = Applicability::MaybeIncorrect; + sugg.extend([ + (expr.span.shrink_to_lo(), "(".to_string()), + (expr.span.shrink_to_hi(), format!(")({sugg_call})")), + ]); + } + _ => { + sugg.extend([ + (expr.span.shrink_to_lo(), "(".to_string()), + (expr.span.shrink_to_hi(), format!(")({sugg_call})")), + ]); + } + } + } + + err.multipart_suggestion_verbose( + format!("use parentheses to call these"), + sugg, + applicability, + ); + + true + } else { + false + } + } + + pub fn suggest_deref_ref_or_into( + &self, + err: &mut Diagnostic, + expr: &hir::Expr<'tcx>, + expected: Ty<'tcx>, + found: Ty<'tcx>, + expected_ty_expr: Option<&'tcx hir::Expr<'tcx>>, + ) -> bool { + let expr = expr.peel_blocks(); + if let Some((sp, msg, suggestion, applicability, verbose, annotation)) = + self.check_ref(expr, found, expected) + { + if verbose { + err.span_suggestion_verbose(sp, &msg, suggestion, applicability); + } else { + err.span_suggestion(sp, &msg, suggestion, applicability); + } + if annotation { + let suggest_annotation = match expr.peel_drop_temps().kind { + hir::ExprKind::AddrOf(hir::BorrowKind::Ref, hir::Mutability::Not, _) => "&", + hir::ExprKind::AddrOf(hir::BorrowKind::Ref, hir::Mutability::Mut, _) => "&mut ", + _ => return true, + }; + let mut tuple_indexes = Vec::new(); + let mut expr_id = expr.hir_id; + for (parent_id, node) in self.tcx.hir().parent_iter(expr.hir_id) { + match node { + Node::Expr(&Expr { kind: ExprKind::Tup(subs), .. }) => { + tuple_indexes.push( + subs.iter() + .enumerate() + .find(|(_, sub_expr)| sub_expr.hir_id == expr_id) + .unwrap() + .0, + ); + expr_id = parent_id; + } + Node::Local(local) => { + if let Some(mut ty) = local.ty { + while let Some(index) = tuple_indexes.pop() { + match ty.kind { + TyKind::Tup(tys) => ty = &tys[index], + _ => return true, + } + } + let annotation_span = ty.span; + err.span_suggestion( + annotation_span.with_hi(annotation_span.lo()), + format!("alternatively, consider changing the type annotation"), + suggest_annotation, + Applicability::MaybeIncorrect, + ); + } + break; + } + _ => break, + } + } + } + return true; + } else if self.suggest_else_fn_with_closure(err, expr, found, expected) { + return true; + } else if self.suggest_fn_call(err, expr, found, |output| self.can_coerce(output, expected)) + && let ty::FnDef(def_id, ..) = &found.kind() + && let Some(sp) = self.tcx.hir().span_if_local(*def_id) + { + err.span_label(sp, format!("{found} defined here")); + return true; + } else if self.check_for_cast(err, expr, found, expected, expected_ty_expr) { + return true; + } else { + let methods = self.get_conversion_methods(expr.span, expected, found, expr.hir_id); + if !methods.is_empty() { + let mut suggestions = methods.iter() + .filter_map(|conversion_method| { + let receiver_method_ident = expr.method_ident(); + if let Some(method_ident) = receiver_method_ident + && method_ident.name == conversion_method.name + { + return None // do not suggest code that is already there (#53348) + } + + let method_call_list = [sym::to_vec, sym::to_string]; + let mut sugg = if let ExprKind::MethodCall(receiver_method, ..) = expr.kind + && receiver_method.ident.name == sym::clone + && method_call_list.contains(&conversion_method.name) + // If receiver is `.clone()` and found type has one of those methods, + // we guess that the user wants to convert from a slice type (`&[]` or `&str`) + // to an owned type (`Vec` or `String`). These conversions clone internally, + // so we remove the user's `clone` call. + { + vec![( + receiver_method.ident.span, + conversion_method.name.to_string() + )] + } else if expr.precedence().order() + < ExprPrecedence::MethodCall.order() + { + vec![ + (expr.span.shrink_to_lo(), "(".to_string()), + (expr.span.shrink_to_hi(), format!(").{}()", conversion_method.name)), + ] + } else { + vec![(expr.span.shrink_to_hi(), format!(".{}()", conversion_method.name))] + }; + let struct_pat_shorthand_field = self.maybe_get_struct_pattern_shorthand_field(expr); + if let Some(name) = struct_pat_shorthand_field { + sugg.insert( + 0, + (expr.span.shrink_to_lo(), format!("{}: ", name)), + ); + } + Some(sugg) + }) + .peekable(); + if suggestions.peek().is_some() { + err.multipart_suggestions( + "try using a conversion method", + suggestions, + Applicability::MaybeIncorrect, + ); + return true; + } + } else if let ty::Adt(found_adt, found_substs) = found.kind() + && self.tcx.is_diagnostic_item(sym::Option, found_adt.did()) + && let ty::Adt(expected_adt, expected_substs) = expected.kind() + && self.tcx.is_diagnostic_item(sym::Option, expected_adt.did()) + && let ty::Ref(_, inner_ty, _) = expected_substs.type_at(0).kind() + && inner_ty.is_str() + { + let ty = found_substs.type_at(0); + let mut peeled = ty; + let mut ref_cnt = 0; + while let ty::Ref(_, inner, _) = peeled.kind() { + peeled = *inner; + ref_cnt += 1; + } + if let ty::Adt(adt, _) = peeled.kind() + && self.tcx.is_diagnostic_item(sym::String, adt.did()) + { + err.span_suggestion_verbose( + expr.span.shrink_to_hi(), + "try converting the passed type into a `&str`", + format!(".map(|x| &*{}x)", "*".repeat(ref_cnt)), + Applicability::MaybeIncorrect, + ); + return true; + } + } + } + + false + } + + /// When encountering the expected boxed value allocated in the stack, suggest allocating it + /// in the heap by calling `Box::new()`. + pub(in super::super) fn suggest_boxing_when_appropriate( + &self, + err: &mut Diagnostic, + expr: &hir::Expr<'_>, + expected: Ty<'tcx>, + found: Ty<'tcx>, + ) -> bool { + if self.tcx.hir().is_inside_const_context(expr.hir_id) { + // Do not suggest `Box::new` in const context. + return false; + } + if !expected.is_box() || found.is_box() { + return false; + } + let boxed_found = self.tcx.mk_box(found); + if self.can_coerce(boxed_found, expected) { + err.multipart_suggestion( + "store this in the heap by calling `Box::new`", + vec![ + (expr.span.shrink_to_lo(), "Box::new(".to_string()), + (expr.span.shrink_to_hi(), ")".to_string()), + ], + Applicability::MachineApplicable, + ); + err.note( + "for more on the distinction between the stack and the heap, read \ + https://doc.rust-lang.org/book/ch15-01-box.html, \ + https://doc.rust-lang.org/rust-by-example/std/box.html, and \ + https://doc.rust-lang.org/std/boxed/index.html", + ); + true + } else { + false + } + } + + /// When encountering a closure that captures variables, where a FnPtr is expected, + /// suggest a non-capturing closure + pub(in super::super) fn suggest_no_capture_closure( + &self, + err: &mut Diagnostic, + expected: Ty<'tcx>, + found: Ty<'tcx>, + ) -> bool { + if let (ty::FnPtr(_), ty::Closure(def_id, _)) = (expected.kind(), found.kind()) { + if let Some(upvars) = self.tcx.upvars_mentioned(*def_id) { + // Report upto four upvars being captured to reduce the amount error messages + // reported back to the user. + let spans_and_labels = upvars + .iter() + .take(4) + .map(|(var_hir_id, upvar)| { + let var_name = self.tcx.hir().name(*var_hir_id).to_string(); + let msg = format!("`{}` captured here", var_name); + (upvar.span, msg) + }) + .collect::<Vec<_>>(); + + let mut multi_span: MultiSpan = + spans_and_labels.iter().map(|(sp, _)| *sp).collect::<Vec<_>>().into(); + for (sp, label) in spans_and_labels { + multi_span.push_span_label(sp, label); + } + err.span_note( + multi_span, + "closures can only be coerced to `fn` types if they do not capture any variables" + ); + return true; + } + } + false + } + + /// When encountering an `impl Future` where `BoxFuture` is expected, suggest `Box::pin`. + #[instrument(skip(self, err))] + pub(in super::super) fn suggest_calling_boxed_future_when_appropriate( + &self, + err: &mut Diagnostic, + expr: &hir::Expr<'_>, + expected: Ty<'tcx>, + found: Ty<'tcx>, + ) -> bool { + // Handle #68197. + + if self.tcx.hir().is_inside_const_context(expr.hir_id) { + // Do not suggest `Box::new` in const context. + return false; + } + let pin_did = self.tcx.lang_items().pin_type(); + // This guards the `unwrap` and `mk_box` below. + if pin_did.is_none() || self.tcx.lang_items().owned_box().is_none() { + return false; + } + let box_found = self.tcx.mk_box(found); + let pin_box_found = self.tcx.mk_lang_item(box_found, LangItem::Pin).unwrap(); + let pin_found = self.tcx.mk_lang_item(found, LangItem::Pin).unwrap(); + match expected.kind() { + ty::Adt(def, _) if Some(def.did()) == pin_did => { + if self.can_coerce(pin_box_found, expected) { + debug!("can coerce {:?} to {:?}, suggesting Box::pin", pin_box_found, expected); + match found.kind() { + ty::Adt(def, _) if def.is_box() => { + err.help("use `Box::pin`"); + } + _ => { + err.multipart_suggestion( + "you need to pin and box this expression", + vec![ + (expr.span.shrink_to_lo(), "Box::pin(".to_string()), + (expr.span.shrink_to_hi(), ")".to_string()), + ], + Applicability::MaybeIncorrect, + ); + } + } + true + } else if self.can_coerce(pin_found, expected) { + match found.kind() { + ty::Adt(def, _) if def.is_box() => { + err.help("use `Box::pin`"); + true + } + _ => false, + } + } else { + false + } + } + ty::Adt(def, _) if def.is_box() && self.can_coerce(box_found, expected) => { + // Check if the parent expression is a call to Pin::new. If it + // is and we were expecting a Box, ergo Pin<Box<expected>>, we + // can suggest Box::pin. + let parent = self.tcx.hir().get_parent_node(expr.hir_id); + let Some(Node::Expr(Expr { kind: ExprKind::Call(fn_name, _), .. })) = self.tcx.hir().find(parent) else { + return false; + }; + match fn_name.kind { + ExprKind::Path(QPath::TypeRelative( + hir::Ty { + kind: TyKind::Path(QPath::Resolved(_, Path { res: recv_ty, .. })), + .. + }, + method, + )) if recv_ty.opt_def_id() == pin_did && method.ident.name == sym::new => { + err.span_suggestion( + fn_name.span, + "use `Box::pin` to pin and box this expression", + "Box::pin", + Applicability::MachineApplicable, + ); + true + } + _ => false, + } + } + _ => false, + } + } + + /// A common error is to forget to add a semicolon at the end of a block, e.g., + /// + /// ```compile_fail,E0308 + /// # fn bar_that_returns_u32() -> u32 { 4 } + /// fn foo() { + /// bar_that_returns_u32() + /// } + /// ``` + /// + /// This routine checks if the return expression in a block would make sense on its own as a + /// statement and the return type has been left as default or has been specified as `()`. If so, + /// it suggests adding a semicolon. + /// + /// If the expression is the expression of a closure without block (`|| expr`), a + /// block is needed to be added too (`|| { expr; }`). This is denoted by `needs_block`. + pub fn suggest_missing_semicolon( + &self, + err: &mut Diagnostic, + expression: &'tcx hir::Expr<'tcx>, + expected: Ty<'tcx>, + needs_block: bool, + ) { + if expected.is_unit() { + // `BlockTailExpression` only relevant if the tail expr would be + // useful on its own. + match expression.kind { + ExprKind::Call(..) + | ExprKind::MethodCall(..) + | ExprKind::Loop(..) + | ExprKind::If(..) + | ExprKind::Match(..) + | ExprKind::Block(..) + if expression.can_have_side_effects() + // If the expression is from an external macro, then do not suggest + // adding a semicolon, because there's nowhere to put it. + // See issue #81943. + && !in_external_macro(self.tcx.sess, expression.span) => + { + if needs_block { + err.multipart_suggestion( + "consider using a semicolon here", + vec![ + (expression.span.shrink_to_lo(), "{ ".to_owned()), + (expression.span.shrink_to_hi(), "; }".to_owned()), + ], + Applicability::MachineApplicable, + ); + } else { + err.span_suggestion( + expression.span.shrink_to_hi(), + "consider using a semicolon here", + ";", + Applicability::MachineApplicable, + ); + } + } + _ => (), + } + } + } + + /// A possible error is to forget to add a return type that is needed: + /// + /// ```compile_fail,E0308 + /// # fn bar_that_returns_u32() -> u32 { 4 } + /// fn foo() { + /// bar_that_returns_u32() + /// } + /// ``` + /// + /// This routine checks if the return type is left as default, the method is not part of an + /// `impl` block and that it isn't the `main` method. If so, it suggests setting the return + /// type. + pub(in super::super) fn suggest_missing_return_type( + &self, + err: &mut Diagnostic, + fn_decl: &hir::FnDecl<'_>, + expected: Ty<'tcx>, + found: Ty<'tcx>, + can_suggest: bool, + fn_id: hir::HirId, + ) -> bool { + let found = + self.resolve_numeric_literals_with_default(self.resolve_vars_if_possible(found)); + // Only suggest changing the return type for methods that + // haven't set a return type at all (and aren't `fn main()` or an impl). + match &fn_decl.output { + &hir::FnRetTy::DefaultReturn(span) if expected.is_unit() && !can_suggest => { + // `fn main()` must return `()`, do not suggest changing return type + err.subdiagnostic(ExpectedReturnTypeLabel::Unit { span }); + return true; + } + &hir::FnRetTy::DefaultReturn(span) if expected.is_unit() => { + if found.is_suggestable(self.tcx, false) { + err.subdiagnostic(AddReturnTypeSuggestion::Add { span, found: found.to_string() }); + return true; + } else if let ty::Closure(_, substs) = found.kind() + // FIXME(compiler-errors): Get better at printing binders... + && let closure = substs.as_closure() + && closure.sig().is_suggestable(self.tcx, false) + { + err.subdiagnostic(AddReturnTypeSuggestion::Add { span, found: closure.print_as_impl_trait().to_string() }); + return true; + } else { + // FIXME: if `found` could be `impl Iterator` we should suggest that. + err.subdiagnostic(AddReturnTypeSuggestion::MissingHere { span }); + return true + } + } + &hir::FnRetTy::Return(ref ty) => { + // Only point to return type if the expected type is the return type, as if they + // are not, the expectation must have been caused by something else. + debug!("suggest_missing_return_type: return type {:?} node {:?}", ty, ty.kind); + let span = ty.span; + let ty = <dyn AstConv<'_>>::ast_ty_to_ty(self, ty); + debug!("suggest_missing_return_type: return type {:?}", ty); + debug!("suggest_missing_return_type: expected type {:?}", ty); + let bound_vars = self.tcx.late_bound_vars(fn_id); + let ty = Binder::bind_with_vars(ty, bound_vars); + let ty = self.normalize_associated_types_in(span, ty); + let ty = self.tcx.erase_late_bound_regions(ty); + if self.can_coerce(expected, ty) { + err.subdiagnostic(ExpectedReturnTypeLabel::Other { span, expected }); + self.try_suggest_return_impl_trait(err, expected, ty, fn_id); + return true; + } + } + _ => {} + } + false + } + + /// check whether the return type is a generic type with a trait bound + /// only suggest this if the generic param is not present in the arguments + /// if this is true, hint them towards changing the return type to `impl Trait` + /// ```compile_fail,E0308 + /// fn cant_name_it<T: Fn() -> u32>() -> T { + /// || 3 + /// } + /// ``` + fn try_suggest_return_impl_trait( + &self, + err: &mut Diagnostic, + expected: Ty<'tcx>, + found: Ty<'tcx>, + fn_id: hir::HirId, + ) { + // Only apply the suggestion if: + // - the return type is a generic parameter + // - the generic param is not used as a fn param + // - the generic param has at least one bound + // - the generic param doesn't appear in any other bounds where it's not the Self type + // Suggest: + // - Changing the return type to be `impl <all bounds>` + + debug!("try_suggest_return_impl_trait, expected = {:?}, found = {:?}", expected, found); + + let ty::Param(expected_ty_as_param) = expected.kind() else { return }; + + let fn_node = self.tcx.hir().find(fn_id); + + let Some(hir::Node::Item(hir::Item { + kind: + hir::ItemKind::Fn( + hir::FnSig { decl: hir::FnDecl { inputs: fn_parameters, output: fn_return, .. }, .. }, + hir::Generics { params, predicates, .. }, + _body_id, + ), + .. + })) = fn_node else { return }; + + if params.get(expected_ty_as_param.index as usize).is_none() { + return; + }; + + // get all where BoundPredicates here, because they are used in to cases below + let where_predicates = predicates + .iter() + .filter_map(|p| match p { + WherePredicate::BoundPredicate(hir::WhereBoundPredicate { + bounds, + bounded_ty, + .. + }) => { + // FIXME: Maybe these calls to `ast_ty_to_ty` can be removed (and the ones below) + let ty = <dyn AstConv<'_>>::ast_ty_to_ty(self, bounded_ty); + Some((ty, bounds)) + } + _ => None, + }) + .map(|(ty, bounds)| match ty.kind() { + ty::Param(param_ty) if param_ty == expected_ty_as_param => Ok(Some(bounds)), + // check whether there is any predicate that contains our `T`, like `Option<T>: Send` + _ => match ty.contains(expected) { + true => Err(()), + false => Ok(None), + }, + }) + .collect::<Result<Vec<_>, _>>(); + + let Ok(where_predicates) = where_predicates else { return }; + + // now get all predicates in the same types as the where bounds, so we can chain them + let predicates_from_where = + where_predicates.iter().flatten().flat_map(|bounds| bounds.iter()); + + // extract all bounds from the source code using their spans + let all_matching_bounds_strs = predicates_from_where + .filter_map(|bound| match bound { + GenericBound::Trait(_, _) => { + self.tcx.sess.source_map().span_to_snippet(bound.span()).ok() + } + _ => None, + }) + .collect::<Vec<String>>(); + + if all_matching_bounds_strs.len() == 0 { + return; + } + + let all_bounds_str = all_matching_bounds_strs.join(" + "); + + let ty_param_used_in_fn_params = fn_parameters.iter().any(|param| { + let ty = <dyn AstConv<'_>>::ast_ty_to_ty(self, param); + matches!(ty.kind(), ty::Param(fn_param_ty_param) if expected_ty_as_param == fn_param_ty_param) + }); + + if ty_param_used_in_fn_params { + return; + } + + err.span_suggestion( + fn_return.span(), + "consider using an impl return type", + format!("impl {}", all_bounds_str), + Applicability::MaybeIncorrect, + ); + } + + pub(in super::super) fn suggest_missing_break_or_return_expr( + &self, + err: &mut Diagnostic, + expr: &'tcx hir::Expr<'tcx>, + fn_decl: &hir::FnDecl<'_>, + expected: Ty<'tcx>, + found: Ty<'tcx>, + id: hir::HirId, + fn_id: hir::HirId, + ) { + if !expected.is_unit() { + return; + } + let found = self.resolve_vars_with_obligations(found); + + let in_loop = self.is_loop(id) + || self.tcx.hir().parent_iter(id).any(|(parent_id, _)| self.is_loop(parent_id)); + + let in_local_statement = self.is_local_statement(id) + || self + .tcx + .hir() + .parent_iter(id) + .any(|(parent_id, _)| self.is_local_statement(parent_id)); + + if in_loop && in_local_statement { + err.multipart_suggestion( + "you might have meant to break the loop with this value", + vec![ + (expr.span.shrink_to_lo(), "break ".to_string()), + (expr.span.shrink_to_hi(), ";".to_string()), + ], + Applicability::MaybeIncorrect, + ); + return; + } + + if let hir::FnRetTy::Return(ty) = fn_decl.output { + let ty = <dyn AstConv<'_>>::ast_ty_to_ty(self, ty); + let bound_vars = self.tcx.late_bound_vars(fn_id); + let ty = self.tcx.erase_late_bound_regions(Binder::bind_with_vars(ty, bound_vars)); + let ty = self.normalize_associated_types_in(expr.span, ty); + let ty = match self.tcx.asyncness(fn_id.owner) { + hir::IsAsync::Async => { + let infcx = self.tcx.infer_ctxt().build(); + infcx + .get_impl_future_output_ty(ty) + .unwrap_or_else(|| { + span_bug!( + fn_decl.output.span(), + "failed to get output type of async function" + ) + }) + .skip_binder() + } + hir::IsAsync::NotAsync => ty, + }; + if self.can_coerce(found, ty) { + err.multipart_suggestion( + "you might have meant to return this value", + vec![ + (expr.span.shrink_to_lo(), "return ".to_string()), + (expr.span.shrink_to_hi(), ";".to_string()), + ], + Applicability::MaybeIncorrect, + ); + } + } + } + + pub(in super::super) fn suggest_missing_parentheses( + &self, + err: &mut Diagnostic, + expr: &hir::Expr<'_>, + ) -> bool { + let sp = self.tcx.sess.source_map().start_point(expr.span); + if let Some(sp) = self.tcx.sess.parse_sess.ambiguous_block_expr_parse.borrow().get(&sp) { + // `{ 42 } &&x` (#61475) or `{ 42 } && if x { 1 } else { 0 }` + err.subdiagnostic(ExprParenthesesNeeded::surrounding(*sp)); + true + } else { + false + } + } + + /// Given an expression type mismatch, peel any `&` expressions until we get to + /// a block expression, and then suggest replacing the braces with square braces + /// if it was possibly mistaken array syntax. + pub(crate) fn suggest_block_to_brackets_peeling_refs( + &self, + diag: &mut Diagnostic, + mut expr: &hir::Expr<'_>, + mut expr_ty: Ty<'tcx>, + mut expected_ty: Ty<'tcx>, + ) -> bool { + loop { + match (&expr.kind, expr_ty.kind(), expected_ty.kind()) { + ( + hir::ExprKind::AddrOf(_, _, inner_expr), + ty::Ref(_, inner_expr_ty, _), + ty::Ref(_, inner_expected_ty, _), + ) => { + expr = *inner_expr; + expr_ty = *inner_expr_ty; + expected_ty = *inner_expected_ty; + } + (hir::ExprKind::Block(blk, _), _, _) => { + self.suggest_block_to_brackets(diag, *blk, expr_ty, expected_ty); + break true; + } + _ => break false, + } + } + } + + pub(crate) fn suggest_copied_or_cloned( + &self, + diag: &mut Diagnostic, + expr: &hir::Expr<'_>, + expr_ty: Ty<'tcx>, + expected_ty: Ty<'tcx>, + ) -> bool { + let ty::Adt(adt_def, substs) = expr_ty.kind() else { return false; }; + let ty::Adt(expected_adt_def, expected_substs) = expected_ty.kind() else { return false; }; + if adt_def != expected_adt_def { + return false; + } + + let mut suggest_copied_or_cloned = || { + let expr_inner_ty = substs.type_at(0); + let expected_inner_ty = expected_substs.type_at(0); + if let ty::Ref(_, ty, hir::Mutability::Not) = expr_inner_ty.kind() + && self.can_eq(self.param_env, *ty, expected_inner_ty).is_ok() + { + let def_path = self.tcx.def_path_str(adt_def.did()); + if self.type_is_copy_modulo_regions(self.param_env, *ty, expr.span) { + diag.span_suggestion_verbose( + expr.span.shrink_to_hi(), + format!( + "use `{def_path}::copied` to copy the value inside the `{def_path}`" + ), + ".copied()", + Applicability::MachineApplicable, + ); + return true; + } else if let Some(clone_did) = self.tcx.lang_items().clone_trait() + && rustc_trait_selection::traits::type_known_to_meet_bound_modulo_regions( + self, + self.param_env, + *ty, + clone_did, + expr.span + ) + { + diag.span_suggestion_verbose( + expr.span.shrink_to_hi(), + format!( + "use `{def_path}::cloned` to clone the value inside the `{def_path}`" + ), + ".cloned()", + Applicability::MachineApplicable, + ); + return true; + } + } + false + }; + + if let Some(result_did) = self.tcx.get_diagnostic_item(sym::Result) + && adt_def.did() == result_did + // Check that the error types are equal + && self.can_eq(self.param_env, substs.type_at(1), expected_substs.type_at(1)).is_ok() + { + return suggest_copied_or_cloned(); + } else if let Some(option_did) = self.tcx.get_diagnostic_item(sym::Option) + && adt_def.did() == option_did + { + return suggest_copied_or_cloned(); + } + + false + } + + pub(crate) fn suggest_into( + &self, + diag: &mut Diagnostic, + expr: &hir::Expr<'_>, + expr_ty: Ty<'tcx>, + expected_ty: Ty<'tcx>, + ) -> bool { + let expr = expr.peel_blocks(); + + // We have better suggestions for scalar interconversions... + if expr_ty.is_scalar() && expected_ty.is_scalar() { + return false; + } + + // Don't suggest turning a block into another type (e.g. `{}.into()`) + if matches!(expr.kind, hir::ExprKind::Block(..)) { + return false; + } + + // We'll later suggest `.as_ref` when noting the type error, + // so skip if we will suggest that instead. + if self.err_ctxt().should_suggest_as_ref(expected_ty, expr_ty).is_some() { + return false; + } + + if let Some(into_def_id) = self.tcx.get_diagnostic_item(sym::Into) + && self.predicate_must_hold_modulo_regions(&traits::Obligation::new( + self.misc(expr.span), + self.param_env, + ty::Binder::dummy(ty::TraitRef { + def_id: into_def_id, + substs: self.tcx.mk_substs_trait(expr_ty, &[expected_ty.into()]), + }) + .to_poly_trait_predicate() + .to_predicate(self.tcx), + )) + { + let sugg = if expr.precedence().order() >= PREC_POSTFIX { + vec![(expr.span.shrink_to_hi(), ".into()".to_owned())] + } else { + vec![(expr.span.shrink_to_lo(), "(".to_owned()), (expr.span.shrink_to_hi(), ").into()".to_owned())] + }; + diag.multipart_suggestion( + format!("call `Into::into` on this expression to convert `{expr_ty}` into `{expected_ty}`"), + sugg, + Applicability::MaybeIncorrect + ); + return true; + } + + false + } + + /// Suggest wrapping the block in square brackets instead of curly braces + /// in case the block was mistaken array syntax, e.g. `{ 1 }` -> `[ 1 ]`. + pub(crate) fn suggest_block_to_brackets( + &self, + diag: &mut Diagnostic, + blk: &hir::Block<'_>, + blk_ty: Ty<'tcx>, + expected_ty: Ty<'tcx>, + ) { + if let ty::Slice(elem_ty) | ty::Array(elem_ty, _) = expected_ty.kind() { + if self.can_coerce(blk_ty, *elem_ty) + && blk.stmts.is_empty() + && blk.rules == hir::BlockCheckMode::DefaultBlock + { + let source_map = self.tcx.sess.source_map(); + if let Ok(snippet) = source_map.span_to_snippet(blk.span) { + if snippet.starts_with('{') && snippet.ends_with('}') { + diag.multipart_suggestion_verbose( + "to create an array, use square brackets instead of curly braces", + vec![ + ( + blk.span + .shrink_to_lo() + .with_hi(rustc_span::BytePos(blk.span.lo().0 + 1)), + "[".to_string(), + ), + ( + blk.span + .shrink_to_hi() + .with_lo(rustc_span::BytePos(blk.span.hi().0 - 1)), + "]".to_string(), + ), + ], + Applicability::MachineApplicable, + ); + } + } + } + } + } + + fn is_loop(&self, id: hir::HirId) -> bool { + let node = self.tcx.hir().get(id); + matches!(node, Node::Expr(Expr { kind: ExprKind::Loop(..), .. })) + } + + fn is_local_statement(&self, id: hir::HirId) -> bool { + let node = self.tcx.hir().get(id); + matches!(node, Node::Stmt(Stmt { kind: StmtKind::Local(..), .. })) + } + + /// Suggest that `&T` was cloned instead of `T` because `T` does not implement `Clone`, + /// which is a side-effect of autoref. + pub(crate) fn note_type_is_not_clone( + &self, + diag: &mut Diagnostic, + expected_ty: Ty<'tcx>, + found_ty: Ty<'tcx>, + expr: &hir::Expr<'_>, + ) { + let hir::ExprKind::MethodCall(segment, callee_expr, &[], _) = expr.kind else { return; }; + let Some(clone_trait_did) = self.tcx.lang_items().clone_trait() else { return; }; + let ty::Ref(_, pointee_ty, _) = found_ty.kind() else { return }; + let results = self.typeck_results.borrow(); + // First, look for a `Clone::clone` call + if segment.ident.name == sym::clone + && results.type_dependent_def_id(expr.hir_id).map_or( + false, + |did| { + let assoc_item = self.tcx.associated_item(did); + assoc_item.container == ty::AssocItemContainer::TraitContainer + && assoc_item.container_id(self.tcx) == clone_trait_did + }, + ) + // If that clone call hasn't already dereferenced the self type (i.e. don't give this + // diagnostic in cases where we have `(&&T).clone()` and we expect `T`). + && !results.expr_adjustments(callee_expr).iter().any(|adj| matches!(adj.kind, ty::adjustment::Adjust::Deref(..))) + // Check that we're in fact trying to clone into the expected type + && self.can_coerce(*pointee_ty, expected_ty) + // And the expected type doesn't implement `Clone` + && !self.predicate_must_hold_considering_regions(&traits::Obligation { + cause: traits::ObligationCause::dummy(), + param_env: self.param_env, + recursion_depth: 0, + predicate: ty::Binder::dummy(ty::TraitRef { + def_id: clone_trait_did, + substs: self.tcx.mk_substs([expected_ty.into()].iter()), + }) + .without_const() + .to_predicate(self.tcx), + }) + { + diag.span_note( + callee_expr.span, + &format!( + "`{expected_ty}` does not implement `Clone`, so `{found_ty}` was cloned instead" + ), + ); + } + } + + /// A common error is to add an extra semicolon: + /// + /// ```compile_fail,E0308 + /// fn foo() -> usize { + /// 22; + /// } + /// ``` + /// + /// This routine checks if the final statement in a block is an + /// expression with an explicit semicolon whose type is compatible + /// with `expected_ty`. If so, it suggests removing the semicolon. + pub(crate) fn consider_removing_semicolon( + &self, + blk: &'tcx hir::Block<'tcx>, + expected_ty: Ty<'tcx>, + err: &mut Diagnostic, + ) -> bool { + if let Some((span_semi, boxed)) = self.err_ctxt().could_remove_semicolon(blk, expected_ty) { + if let StatementAsExpression::NeedsBoxing = boxed { + err.span_suggestion_verbose( + span_semi, + "consider removing this semicolon and boxing the expression", + "", + Applicability::HasPlaceholders, + ); + } else { + err.span_suggestion_short( + span_semi, + "remove this semicolon to return this value", + "", + Applicability::MachineApplicable, + ); + } + true + } else { + false + } + } +} diff --git a/compiler/rustc_typeck/src/check/gather_locals.rs b/compiler/rustc_hir_typeck/src/gather_locals.rs index 8f34a970f..9a096f24f 100644 --- a/compiler/rustc_typeck/src/check/gather_locals.rs +++ b/compiler/rustc_hir_typeck/src/gather_locals.rs @@ -1,9 +1,10 @@ -use crate::check::{FnCtxt, LocalTy, UserType}; +use crate::{FnCtxt, LocalTy}; use rustc_hir as hir; use rustc_hir::intravisit::{self, Visitor}; use rustc_hir::PatKind; use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind}; use rustc_middle::ty::Ty; +use rustc_middle::ty::UserType; use rustc_span::Span; use rustc_trait_selection::traits; diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs b/compiler/rustc_hir_typeck/src/generator_interior/drop_ranges/cfg_build.rs index a2c23db16..122ad7009 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs +++ b/compiler/rustc_hir_typeck/src/generator_interior/drop_ranges/cfg_build.rs @@ -210,7 +210,7 @@ impl<'a, 'tcx> DropRangeVisitor<'a, 'tcx> { } /// For an expression with an uninhabited return type (e.g. a function that returns !), - /// this adds a self edge to to the CFG to model the fact that the function does not + /// this adds a self edge to the CFG to model the fact that the function does not /// return. fn handle_uninhabited_return(&mut self, expr: &Expr<'tcx>) { let ty = self.typeck_results.expr_ty(expr); @@ -256,6 +256,8 @@ impl<'a, 'tcx> DropRangeVisitor<'a, 'tcx> { | hir::Node::TypeBinding(..) | hir::Node::TraitRef(..) | hir::Node::Pat(..) + | hir::Node::PatField(..) + | hir::Node::ExprField(..) | hir::Node::Arm(..) | hir::Node::Local(..) | hir::Node::Ctor(..) @@ -432,7 +434,8 @@ impl<'a, 'tcx> Visitor<'tcx> for DropRangeVisitor<'a, 'tcx> { self.handle_uninhabited_return(expr); } - ExprKind::MethodCall(_, exprs, _) => { + ExprKind::MethodCall(_, receiver, exprs, _) => { + self.visit_expr(receiver); for expr in exprs { self.visit_expr(expr); } diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_propagate.rs b/compiler/rustc_hir_typeck/src/generator_interior/drop_ranges/cfg_propagate.rs index 139d17d2e..139d17d2e 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_propagate.rs +++ b/compiler/rustc_hir_typeck/src/generator_interior/drop_ranges/cfg_propagate.rs diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_visualize.rs b/compiler/rustc_hir_typeck/src/generator_interior/drop_ranges/cfg_visualize.rs index c0a0bfe8e..c0a0bfe8e 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_visualize.rs +++ b/compiler/rustc_hir_typeck/src/generator_interior/drop_ranges/cfg_visualize.rs diff --git a/compiler/rustc_hir_typeck/src/generator_interior/drop_ranges/mod.rs b/compiler/rustc_hir_typeck/src/generator_interior/drop_ranges/mod.rs new file mode 100644 index 000000000..4f3bdfbe7 --- /dev/null +++ b/compiler/rustc_hir_typeck/src/generator_interior/drop_ranges/mod.rs @@ -0,0 +1,309 @@ +//! Drop range analysis finds the portions of the tree where a value is guaranteed to be dropped +//! (i.e. moved, uninitialized, etc.). This is used to exclude the types of those values from the +//! generator type. See `InteriorVisitor::record` for where the results of this analysis are used. +//! +//! There are three phases to this analysis: +//! 1. Use `ExprUseVisitor` to identify the interesting values that are consumed and borrowed. +//! 2. Use `DropRangeVisitor` to find where the interesting values are dropped or reinitialized, +//! and also build a control flow graph. +//! 3. Use `DropRanges::propagate_to_fixpoint` to flow the dropped/reinitialized information through +//! the CFG and find the exact points where we know a value is definitely dropped. +//! +//! The end result is a data structure that maps the post-order index of each node in the HIR tree +//! to a set of values that are known to be dropped at that location. + +use self::cfg_build::build_control_flow_graph; +use self::record_consumed_borrow::find_consumed_and_borrowed; +use crate::FnCtxt; +use hir::def_id::DefId; +use hir::{Body, HirId, HirIdMap, Node}; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_hir as hir; +use rustc_index::bit_set::BitSet; +use rustc_index::vec::IndexVec; +use rustc_middle::hir::map::Map; +use rustc_middle::hir::place::{PlaceBase, PlaceWithHirId}; +use rustc_middle::ty; +use std::collections::BTreeMap; +use std::fmt::Debug; + +mod cfg_build; +mod cfg_propagate; +mod cfg_visualize; +mod record_consumed_borrow; + +pub fn compute_drop_ranges<'a, 'tcx>( + fcx: &'a FnCtxt<'a, 'tcx>, + def_id: DefId, + body: &'tcx Body<'tcx>, +) -> DropRanges { + if fcx.sess().opts.unstable_opts.drop_tracking { + let consumed_borrowed_places = find_consumed_and_borrowed(fcx, def_id, body); + + let typeck_results = &fcx.typeck_results.borrow(); + let num_exprs = fcx.tcx.region_scope_tree(def_id).body_expr_count(body.id()).unwrap_or(0); + let (mut drop_ranges, borrowed_temporaries) = build_control_flow_graph( + fcx.tcx.hir(), + fcx.tcx, + typeck_results, + consumed_borrowed_places, + body, + num_exprs, + ); + + drop_ranges.propagate_to_fixpoint(); + + debug!("borrowed_temporaries = {borrowed_temporaries:?}"); + DropRanges { + tracked_value_map: drop_ranges.tracked_value_map, + nodes: drop_ranges.nodes, + borrowed_temporaries: Some(borrowed_temporaries), + } + } else { + // If drop range tracking is not enabled, skip all the analysis and produce an + // empty set of DropRanges. + DropRanges { + tracked_value_map: FxHashMap::default(), + nodes: IndexVec::new(), + borrowed_temporaries: None, + } + } +} + +/// Applies `f` to consumable node in the HIR subtree pointed to by `place`. +/// +/// This includes the place itself, and if the place is a reference to a local +/// variable then `f` is also called on the HIR node for that variable as well. +/// +/// For example, if `place` points to `foo()`, then `f` is called once for the +/// result of `foo`. On the other hand, if `place` points to `x` then `f` will +/// be called both on the `ExprKind::Path` node that represents the expression +/// as well as the HirId of the local `x` itself. +fn for_each_consumable<'tcx>(hir: Map<'tcx>, place: TrackedValue, mut f: impl FnMut(TrackedValue)) { + f(place); + let node = hir.find(place.hir_id()); + if let Some(Node::Expr(expr)) = node { + match expr.kind { + hir::ExprKind::Path(hir::QPath::Resolved( + _, + hir::Path { res: hir::def::Res::Local(hir_id), .. }, + )) => { + f(TrackedValue::Variable(*hir_id)); + } + _ => (), + } + } +} + +rustc_index::newtype_index! { + pub struct PostOrderId { + DEBUG_FORMAT = "id({})", + } +} + +rustc_index::newtype_index! { + pub struct TrackedValueIndex { + DEBUG_FORMAT = "hidx({})", + } +} + +/// Identifies a value whose drop state we need to track. +#[derive(PartialEq, Eq, Hash, Clone, Copy)] +enum TrackedValue { + /// Represents a named variable, such as a let binding, parameter, or upvar. + /// + /// The HirId points to the variable's definition site. + Variable(HirId), + /// A value produced as a result of an expression. + /// + /// The HirId points to the expression that returns this value. + Temporary(HirId), +} + +impl Debug for TrackedValue { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + ty::tls::with_opt(|opt_tcx| { + if let Some(tcx) = opt_tcx { + write!(f, "{}", tcx.hir().node_to_string(self.hir_id())) + } else { + match self { + Self::Variable(hir_id) => write!(f, "Variable({:?})", hir_id), + Self::Temporary(hir_id) => write!(f, "Temporary({:?})", hir_id), + } + } + }) + } +} + +impl TrackedValue { + fn hir_id(&self) -> HirId { + match self { + TrackedValue::Variable(hir_id) | TrackedValue::Temporary(hir_id) => *hir_id, + } + } + + fn from_place_with_projections_allowed(place_with_id: &PlaceWithHirId<'_>) -> Self { + match place_with_id.place.base { + PlaceBase::Rvalue | PlaceBase::StaticItem => { + TrackedValue::Temporary(place_with_id.hir_id) + } + PlaceBase::Local(hir_id) + | PlaceBase::Upvar(ty::UpvarId { var_path: ty::UpvarPath { hir_id }, .. }) => { + TrackedValue::Variable(hir_id) + } + } + } +} + +/// Represents a reason why we might not be able to convert a HirId or Place +/// into a tracked value. +#[derive(Debug)] +enum TrackedValueConversionError { + /// Place projects are not currently supported. + /// + /// The reasoning around these is kind of subtle, so we choose to be more + /// conservative around these for now. There is no reason in theory we + /// cannot support these, we just have not implemented it yet. + PlaceProjectionsNotSupported, +} + +impl TryFrom<&PlaceWithHirId<'_>> for TrackedValue { + type Error = TrackedValueConversionError; + + fn try_from(place_with_id: &PlaceWithHirId<'_>) -> Result<Self, Self::Error> { + if !place_with_id.place.projections.is_empty() { + debug!( + "TrackedValue from PlaceWithHirId: {:?} has projections, which are not supported.", + place_with_id + ); + return Err(TrackedValueConversionError::PlaceProjectionsNotSupported); + } + + Ok(TrackedValue::from_place_with_projections_allowed(place_with_id)) + } +} + +pub struct DropRanges { + tracked_value_map: FxHashMap<TrackedValue, TrackedValueIndex>, + nodes: IndexVec<PostOrderId, NodeInfo>, + borrowed_temporaries: Option<FxHashSet<HirId>>, +} + +impl DropRanges { + pub fn is_dropped_at(&self, hir_id: HirId, location: usize) -> bool { + self.tracked_value_map + .get(&TrackedValue::Temporary(hir_id)) + .or(self.tracked_value_map.get(&TrackedValue::Variable(hir_id))) + .cloned() + .map_or(false, |tracked_value_id| { + self.expect_node(location.into()).drop_state.contains(tracked_value_id) + }) + } + + pub fn is_borrowed_temporary(&self, expr: &hir::Expr<'_>) -> bool { + if let Some(b) = &self.borrowed_temporaries { b.contains(&expr.hir_id) } else { true } + } + + /// Returns a reference to the NodeInfo for a node, panicking if it does not exist + fn expect_node(&self, id: PostOrderId) -> &NodeInfo { + &self.nodes[id] + } +} + +/// Tracks information needed to compute drop ranges. +struct DropRangesBuilder { + /// The core of DropRangesBuilder is a set of nodes, which each represent + /// one expression. We primarily refer to them by their index in a + /// post-order traversal of the HIR tree, since this is what + /// generator_interior uses to talk about yield positions. + /// + /// This IndexVec keeps the relevant details for each node. See the + /// NodeInfo struct for more details, but this information includes things + /// such as the set of control-flow successors, which variables are dropped + /// or reinitialized, and whether each variable has been inferred to be + /// known-dropped or potentially reinitialized at each point. + nodes: IndexVec<PostOrderId, NodeInfo>, + /// We refer to values whose drop state we are tracking by the HirId of + /// where they are defined. Within a NodeInfo, however, we store the + /// drop-state in a bit vector indexed by a HirIdIndex + /// (see NodeInfo::drop_state). The hir_id_map field stores the mapping + /// from HirIds to the HirIdIndex that is used to represent that value in + /// bitvector. + tracked_value_map: FxHashMap<TrackedValue, TrackedValueIndex>, + + /// When building the control flow graph, we don't always know the + /// post-order index of the target node at the point we encounter it. + /// For example, this happens with break and continue. In those cases, + /// we store a pair of the PostOrderId of the source and the HirId + /// of the target. Once we have gathered all of these edges, we make a + /// pass over the set of deferred edges (see process_deferred_edges in + /// cfg_build.rs), look up the PostOrderId for the target (since now the + /// post-order index for all nodes is known), and add missing control flow + /// edges. + deferred_edges: Vec<(PostOrderId, HirId)>, + /// This maps HirIds of expressions to their post-order index. It is + /// used in process_deferred_edges to correctly add back-edges. + post_order_map: HirIdMap<PostOrderId>, +} + +impl Debug for DropRangesBuilder { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("DropRanges") + .field("hir_id_map", &self.tracked_value_map) + .field("post_order_maps", &self.post_order_map) + .field("nodes", &self.nodes.iter_enumerated().collect::<BTreeMap<_, _>>()) + .finish() + } +} + +/// DropRanges keeps track of what values are definitely dropped at each point in the code. +/// +/// Values of interest are defined by the hir_id of their place. Locations in code are identified +/// by their index in the post-order traversal. At its core, DropRanges maps +/// (hir_id, post_order_id) -> bool, where a true value indicates that the value is definitely +/// dropped at the point of the node identified by post_order_id. +impl DropRangesBuilder { + /// Returns the number of values (hir_ids) that are tracked + fn num_values(&self) -> usize { + self.tracked_value_map.len() + } + + fn node_mut(&mut self, id: PostOrderId) -> &mut NodeInfo { + let size = self.num_values(); + self.nodes.ensure_contains_elem(id, || NodeInfo::new(size)); + &mut self.nodes[id] + } + + fn add_control_edge(&mut self, from: PostOrderId, to: PostOrderId) { + trace!("adding control edge from {:?} to {:?}", from, to); + self.node_mut(from).successors.push(to); + } +} + +#[derive(Debug)] +struct NodeInfo { + /// IDs of nodes that can follow this one in the control flow + /// + /// If the vec is empty, then control proceeds to the next node. + successors: Vec<PostOrderId>, + + /// List of hir_ids that are dropped by this node. + drops: Vec<TrackedValueIndex>, + + /// List of hir_ids that are reinitialized by this node. + reinits: Vec<TrackedValueIndex>, + + /// Set of values that are definitely dropped at this point. + drop_state: BitSet<TrackedValueIndex>, +} + +impl NodeInfo { + fn new(num_values: usize) -> Self { + Self { + successors: vec![], + drops: vec![], + reinits: vec![], + drop_state: BitSet::new_filled(num_values), + } + } +} diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/record_consumed_borrow.rs b/compiler/rustc_hir_typeck/src/generator_interior/drop_ranges/record_consumed_borrow.rs index ded0888c3..bfe95852a 100644 --- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/record_consumed_borrow.rs +++ b/compiler/rustc_hir_typeck/src/generator_interior/drop_ranges/record_consumed_borrow.rs @@ -1,13 +1,16 @@ use super::TrackedValue; use crate::{ - check::FnCtxt, expr_use_visitor::{self, ExprUseVisitor}, + FnCtxt, }; use hir::{def_id::DefId, Body, HirId, HirIdMap}; use rustc_data_structures::fx::FxHashSet; use rustc_hir as hir; -use rustc_middle::hir::place::{PlaceBase, Projection, ProjectionKind}; use rustc_middle::ty::{ParamEnv, TyCtxt}; +use rustc_middle::{ + hir::place::{PlaceBase, Projection, ProjectionKind}, + ty::TypeVisitable, +}; pub(super) fn find_consumed_and_borrowed<'a, 'tcx>( fcx: &'a FnCtxt<'a, 'tcx>, @@ -159,8 +162,8 @@ impl<'tcx> expr_use_visitor::Delegate<'tcx> for ExprUseDelegate<'tcx> { bk: rustc_middle::ty::BorrowKind, ) { debug!( - "borrow: place_with_id = {place_with_id:?}, diag_expr_id={diag_expr_id:?}, \ - borrow_kind={bk:?}" + "borrow: place_with_id = {place_with_id:#?}, diag_expr_id={diag_expr_id:#?}, \ + borrow_kind={bk:#?}" ); self.borrow_place(place_with_id); @@ -198,7 +201,13 @@ impl<'tcx> expr_use_visitor::Delegate<'tcx> for ExprUseDelegate<'tcx> { // If the type being assigned needs dropped, then the mutation counts as a borrow // since it is essentially doing `Drop::drop(&mut x); x = new_value;`. - if assignee_place.place.base_ty.needs_drop(self.tcx, self.param_env) { + let ty = self.tcx.erase_regions(assignee_place.place.base_ty); + if ty.needs_infer() { + self.tcx.sess.delay_span_bug( + self.tcx.hir().span(assignee_place.hir_id), + &format!("inference variables in {ty}"), + ); + } else if ty.needs_drop(self.tcx, self.param_env) { self.places .borrowed .insert(TrackedValue::from_place_with_projections_allowed(assignee_place)); diff --git a/compiler/rustc_hir_typeck/src/generator_interior/mod.rs b/compiler/rustc_hir_typeck/src/generator_interior/mod.rs new file mode 100644 index 000000000..b7dd599cd --- /dev/null +++ b/compiler/rustc_hir_typeck/src/generator_interior/mod.rs @@ -0,0 +1,647 @@ +//! This calculates the types which has storage which lives across a suspension point in a +//! generator from the perspective of typeck. The actual types used at runtime +//! is calculated in `rustc_mir_transform::generator` and may be a subset of the +//! types computed here. + +use self::drop_ranges::DropRanges; +use super::FnCtxt; +use rustc_data_structures::fx::{FxHashSet, FxIndexSet}; +use rustc_errors::{pluralize, DelayDm}; +use rustc_hir as hir; +use rustc_hir::def::{CtorKind, DefKind, Res}; +use rustc_hir::def_id::DefId; +use rustc_hir::hir_id::HirIdSet; +use rustc_hir::intravisit::{self, Visitor}; +use rustc_hir::{Arm, Expr, ExprKind, Guard, HirId, Pat, PatKind}; +use rustc_middle::middle::region::{self, Scope, ScopeData, YieldData}; +use rustc_middle::ty::{self, RvalueScopes, Ty, TyCtxt, TypeVisitable}; +use rustc_span::symbol::sym; +use rustc_span::Span; + +mod drop_ranges; + +struct InteriorVisitor<'a, 'tcx> { + fcx: &'a FnCtxt<'a, 'tcx>, + region_scope_tree: &'a region::ScopeTree, + types: FxIndexSet<ty::GeneratorInteriorTypeCause<'tcx>>, + rvalue_scopes: &'a RvalueScopes, + expr_count: usize, + kind: hir::GeneratorKind, + prev_unresolved_span: Option<Span>, + linted_values: HirIdSet, + drop_ranges: DropRanges, +} + +impl<'a, 'tcx> InteriorVisitor<'a, 'tcx> { + fn record( + &mut self, + ty: Ty<'tcx>, + hir_id: HirId, + scope: Option<region::Scope>, + expr: Option<&'tcx Expr<'tcx>>, + source_span: Span, + ) { + use rustc_span::DUMMY_SP; + + let ty = self.fcx.resolve_vars_if_possible(ty); + + debug!( + "attempting to record type ty={:?}; hir_id={:?}; scope={:?}; expr={:?}; source_span={:?}; expr_count={:?}", + ty, hir_id, scope, expr, source_span, self.expr_count, + ); + + let live_across_yield = scope + .map(|s| { + self.region_scope_tree.yield_in_scope(s).and_then(|yield_data| { + // If we are recording an expression that is the last yield + // in the scope, or that has a postorder CFG index larger + // than the one of all of the yields, then its value can't + // be storage-live (and therefore live) at any of the yields. + // + // See the mega-comment at `yield_in_scope` for a proof. + + yield_data + .iter() + .find(|yield_data| { + debug!( + "comparing counts yield: {} self: {}, source_span = {:?}", + yield_data.expr_and_pat_count, self.expr_count, source_span + ); + + if self.fcx.sess().opts.unstable_opts.drop_tracking + && self + .drop_ranges + .is_dropped_at(hir_id, yield_data.expr_and_pat_count) + { + debug!("value is dropped at yield point; not recording"); + return false; + } + + // If it is a borrowing happening in the guard, + // it needs to be recorded regardless because they + // do live across this yield point. + yield_data.expr_and_pat_count >= self.expr_count + }) + .cloned() + }) + }) + .unwrap_or_else(|| { + Some(YieldData { span: DUMMY_SP, expr_and_pat_count: 0, source: self.kind.into() }) + }); + + if let Some(yield_data) = live_across_yield { + debug!( + "type in expr = {:?}, scope = {:?}, type = {:?}, count = {}, yield_span = {:?}", + expr, scope, ty, self.expr_count, yield_data.span + ); + + if let Some((unresolved_type, unresolved_type_span)) = + self.fcx.unresolved_type_vars(&ty) + { + // If unresolved type isn't a ty_var then unresolved_type_span is None + let span = self + .prev_unresolved_span + .unwrap_or_else(|| unresolved_type_span.unwrap_or(source_span)); + + // If we encounter an int/float variable, then inference fallback didn't + // finish due to some other error. Don't emit spurious additional errors. + if let ty::Infer(ty::InferTy::IntVar(_) | ty::InferTy::FloatVar(_)) = + unresolved_type.kind() + { + self.fcx + .tcx + .sess + .delay_span_bug(span, &format!("Encountered var {:?}", unresolved_type)); + } else { + let note = format!( + "the type is part of the {} because of this {}", + self.kind, yield_data.source + ); + + self.fcx + .need_type_info_err_in_generator(self.kind, span, unresolved_type) + .span_note(yield_data.span, &*note) + .emit(); + } + } else { + // Insert the type into the ordered set. + let scope_span = scope.map(|s| s.span(self.fcx.tcx, self.region_scope_tree)); + + if !self.linted_values.contains(&hir_id) { + check_must_not_suspend_ty( + self.fcx, + ty, + hir_id, + SuspendCheckData { + expr, + source_span, + yield_span: yield_data.span, + plural_len: 1, + ..Default::default() + }, + ); + self.linted_values.insert(hir_id); + } + + self.types.insert(ty::GeneratorInteriorTypeCause { + span: source_span, + ty, + scope_span, + yield_span: yield_data.span, + expr: expr.map(|e| e.hir_id), + }); + } + } else { + debug!( + "no type in expr = {:?}, count = {:?}, span = {:?}", + expr, + self.expr_count, + expr.map(|e| e.span) + ); + if let Some((unresolved_type, unresolved_type_span)) = + self.fcx.unresolved_type_vars(&ty) + { + debug!( + "remained unresolved_type = {:?}, unresolved_type_span: {:?}", + unresolved_type, unresolved_type_span + ); + self.prev_unresolved_span = unresolved_type_span; + } + } + } +} + +pub fn resolve_interior<'a, 'tcx>( + fcx: &'a FnCtxt<'a, 'tcx>, + def_id: DefId, + body_id: hir::BodyId, + interior: Ty<'tcx>, + kind: hir::GeneratorKind, +) { + let body = fcx.tcx.hir().body(body_id); + let typeck_results = fcx.inh.typeck_results.borrow(); + let mut visitor = InteriorVisitor { + fcx, + types: FxIndexSet::default(), + region_scope_tree: fcx.tcx.region_scope_tree(def_id), + rvalue_scopes: &typeck_results.rvalue_scopes, + expr_count: 0, + kind, + prev_unresolved_span: None, + linted_values: <_>::default(), + drop_ranges: drop_ranges::compute_drop_ranges(fcx, def_id, body), + }; + intravisit::walk_body(&mut visitor, body); + + // Check that we visited the same amount of expressions as the RegionResolutionVisitor + let region_expr_count = fcx.tcx.region_scope_tree(def_id).body_expr_count(body_id).unwrap(); + assert_eq!(region_expr_count, visitor.expr_count); + + // The types are already kept in insertion order. + let types = visitor.types; + + // The types in the generator interior contain lifetimes local to the generator itself, + // which should not be exposed outside of the generator. Therefore, we replace these + // lifetimes with existentially-bound lifetimes, which reflect the exact value of the + // lifetimes not being known by users. + // + // These lifetimes are used in auto trait impl checking (for example, + // if a Sync generator contains an &'α T, we need to check whether &'α T: Sync), + // so knowledge of the exact relationships between them isn't particularly important. + + debug!("types in generator {:?}, span = {:?}", types, body.value.span); + + let mut counter = 0; + let mut captured_tys = FxHashSet::default(); + let type_causes: Vec<_> = types + .into_iter() + .filter_map(|mut cause| { + // Erase regions and canonicalize late-bound regions to deduplicate as many types as we + // can. + let ty = fcx.normalize_associated_types_in(cause.span, cause.ty); + let erased = fcx.tcx.erase_regions(ty); + if captured_tys.insert(erased) { + // Replace all regions inside the generator interior with late bound regions. + // Note that each region slot in the types gets a new fresh late bound region, + // which means that none of the regions inside relate to any other, even if + // typeck had previously found constraints that would cause them to be related. + let folded = fcx.tcx.fold_regions(erased, |_, current_depth| { + let br = ty::BoundRegion { + var: ty::BoundVar::from_u32(counter), + kind: ty::BrAnon(counter), + }; + let r = fcx.tcx.mk_region(ty::ReLateBound(current_depth, br)); + counter += 1; + r + }); + + cause.ty = folded; + Some(cause) + } else { + None + } + }) + .collect(); + + // Extract type components to build the witness type. + let type_list = fcx.tcx.mk_type_list(type_causes.iter().map(|cause| cause.ty)); + let bound_vars = fcx.tcx.mk_bound_variable_kinds( + (0..counter).map(|i| ty::BoundVariableKind::Region(ty::BrAnon(i))), + ); + let witness = + fcx.tcx.mk_generator_witness(ty::Binder::bind_with_vars(type_list, bound_vars.clone())); + + drop(typeck_results); + // Store the generator types and spans into the typeck results for this generator. + fcx.inh.typeck_results.borrow_mut().generator_interior_types = + ty::Binder::bind_with_vars(type_causes, bound_vars); + + debug!( + "types in generator after region replacement {:?}, span = {:?}", + witness, body.value.span + ); + + // Unify the type variable inside the generator with the new witness + match fcx.at(&fcx.misc(body.value.span), fcx.param_env).eq(interior, witness) { + Ok(ok) => fcx.register_infer_ok_obligations(ok), + _ => bug!("failed to relate {interior} and {witness}"), + } +} + +// This visitor has to have the same visit_expr calls as RegionResolutionVisitor in +// librustc_middle/middle/region.rs since `expr_count` is compared against the results +// there. +impl<'a, 'tcx> Visitor<'tcx> for InteriorVisitor<'a, 'tcx> { + fn visit_arm(&mut self, arm: &'tcx Arm<'tcx>) { + let Arm { guard, pat, body, .. } = arm; + self.visit_pat(pat); + if let Some(ref g) = guard { + { + // If there is a guard, we need to count all variables bound in the pattern as + // borrowed for the entire guard body, regardless of whether they are accessed. + // We do this by walking the pattern bindings and recording `&T` for any `x: T` + // that is bound. + + struct ArmPatCollector<'a, 'b, 'tcx> { + interior_visitor: &'a mut InteriorVisitor<'b, 'tcx>, + scope: Scope, + } + + impl<'a, 'b, 'tcx> Visitor<'tcx> for ArmPatCollector<'a, 'b, 'tcx> { + fn visit_pat(&mut self, pat: &'tcx Pat<'tcx>) { + intravisit::walk_pat(self, pat); + if let PatKind::Binding(_, id, ident, ..) = pat.kind { + let ty = + self.interior_visitor.fcx.typeck_results.borrow().node_type(id); + let tcx = self.interior_visitor.fcx.tcx; + let ty = tcx.mk_ref( + // Use `ReErased` as `resolve_interior` is going to replace all the + // regions anyway. + tcx.mk_region(ty::ReErased), + ty::TypeAndMut { ty, mutbl: hir::Mutability::Not }, + ); + self.interior_visitor.record( + ty, + id, + Some(self.scope), + None, + ident.span, + ); + } + } + } + + ArmPatCollector { + interior_visitor: self, + scope: Scope { id: g.body().hir_id.local_id, data: ScopeData::Node }, + } + .visit_pat(pat); + } + + match g { + Guard::If(ref e) => { + self.visit_expr(e); + } + Guard::IfLet(ref l) => { + self.visit_let_expr(l); + } + } + } + self.visit_expr(body); + } + + fn visit_pat(&mut self, pat: &'tcx Pat<'tcx>) { + intravisit::walk_pat(self, pat); + + self.expr_count += 1; + + if let PatKind::Binding(..) = pat.kind { + let scope = self.region_scope_tree.var_scope(pat.hir_id.local_id).unwrap(); + let ty = self.fcx.typeck_results.borrow().pat_ty(pat); + self.record(ty, pat.hir_id, Some(scope), None, pat.span); + } + } + + fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { + match &expr.kind { + ExprKind::Call(callee, args) => match &callee.kind { + ExprKind::Path(qpath) => { + let res = self.fcx.typeck_results.borrow().qpath_res(qpath, callee.hir_id); + match res { + // Direct calls never need to keep the callee `ty::FnDef` + // ZST in a temporary, so skip its type, just in case it + // can significantly complicate the generator type. + Res::Def( + DefKind::Fn | DefKind::AssocFn | DefKind::Ctor(_, CtorKind::Fn), + _, + ) => { + // NOTE(eddyb) this assumes a path expression has + // no nested expressions to keep track of. + self.expr_count += 1; + + // Record the rest of the call expression normally. + for arg in *args { + self.visit_expr(arg); + } + } + _ => intravisit::walk_expr(self, expr), + } + } + _ => intravisit::walk_expr(self, expr), + }, + _ => intravisit::walk_expr(self, expr), + } + + self.expr_count += 1; + + debug!("is_borrowed_temporary: {:?}", self.drop_ranges.is_borrowed_temporary(expr)); + + let ty = self.fcx.typeck_results.borrow().expr_ty_adjusted_opt(expr); + + // Typically, the value produced by an expression is consumed by its parent in some way, + // so we only have to check if the parent contains a yield (note that the parent may, for + // example, store the value into a local variable, but then we already consider local + // variables to be live across their scope). + // + // However, in the case of temporary values, we are going to store the value into a + // temporary on the stack that is live for the current temporary scope and then return a + // reference to it. That value may be live across the entire temporary scope. + // + // There's another subtlety: if the type has an observable drop, it must be dropped after + // the yield, even if it's not borrowed or referenced after the yield. Ideally this would + // *only* happen for types with observable drop, not all types which wrap them, but that + // doesn't match the behavior of MIR borrowck and causes ICEs. See the FIXME comment in + // src/test/ui/generator/drop-tracking-parent-expression.rs. + let scope = if self.drop_ranges.is_borrowed_temporary(expr) + || ty.map_or(true, |ty| { + // Avoid ICEs in needs_drop. + let ty = self.fcx.resolve_vars_if_possible(ty); + let ty = self.fcx.tcx.erase_regions(ty); + if ty.needs_infer() { + self.fcx + .tcx + .sess + .delay_span_bug(expr.span, &format!("inference variables in {ty}")); + true + } else { + ty.needs_drop(self.fcx.tcx, self.fcx.param_env) + } + }) { + self.rvalue_scopes.temporary_scope(self.region_scope_tree, expr.hir_id.local_id) + } else { + let parent_expr = self + .fcx + .tcx + .hir() + .parent_iter(expr.hir_id) + .find(|(_, node)| matches!(node, hir::Node::Expr(_))) + .map(|(id, _)| id); + debug!("parent_expr: {:?}", parent_expr); + match parent_expr { + Some(parent) => Some(Scope { id: parent.local_id, data: ScopeData::Node }), + None => { + self.rvalue_scopes.temporary_scope(self.region_scope_tree, expr.hir_id.local_id) + } + } + }; + + // If there are adjustments, then record the final type -- + // this is the actual value that is being produced. + if let Some(adjusted_ty) = ty { + self.record(adjusted_ty, expr.hir_id, scope, Some(expr), expr.span); + } + + // Also record the unadjusted type (which is the only type if + // there are no adjustments). The reason for this is that the + // unadjusted value is sometimes a "temporary" that would wind + // up in a MIR temporary. + // + // As an example, consider an expression like `vec![].push(x)`. + // Here, the `vec![]` would wind up MIR stored into a + // temporary variable `t` which we can borrow to invoke + // `<Vec<_>>::push(&mut t, x)`. + // + // Note that an expression can have many adjustments, and we + // are just ignoring those intermediate types. This is because + // those intermediate values are always linearly "consumed" by + // the other adjustments, and hence would never be directly + // captured in the MIR. + // + // (Note that this partly relies on the fact that the `Deref` + // traits always return references, which means their content + // can be reborrowed without needing to spill to a temporary. + // If this were not the case, then we could conceivably have + // to create intermediate temporaries.) + // + // The type table might not have information for this expression + // if it is in a malformed scope. (#66387) + if let Some(ty) = self.fcx.typeck_results.borrow().expr_ty_opt(expr) { + self.record(ty, expr.hir_id, scope, Some(expr), expr.span); + } else { + self.fcx.tcx.sess.delay_span_bug(expr.span, "no type for node"); + } + } +} + +#[derive(Default)] +struct SuspendCheckData<'a, 'tcx> { + expr: Option<&'tcx Expr<'tcx>>, + source_span: Span, + yield_span: Span, + descr_pre: &'a str, + descr_post: &'a str, + plural_len: usize, +} + +// Returns whether it emitted a diagnostic or not +// Note that this fn and the proceeding one are based on the code +// for creating must_use diagnostics +// +// Note that this technique was chosen over things like a `Suspend` marker trait +// as it is simpler and has precedent in the compiler +fn check_must_not_suspend_ty<'tcx>( + fcx: &FnCtxt<'_, 'tcx>, + ty: Ty<'tcx>, + hir_id: HirId, + data: SuspendCheckData<'_, 'tcx>, +) -> bool { + if ty.is_unit() + // FIXME: should this check `is_ty_uninhabited_from`. This query is not available in this stage + // of typeck (before ReVar and RePlaceholder are removed), but may remove noise, like in + // `must_use` + // || fcx.tcx.is_ty_uninhabited_from(fcx.tcx.parent_module(hir_id).to_def_id(), ty, fcx.param_env) + { + return false; + } + + let plural_suffix = pluralize!(data.plural_len); + + debug!("Checking must_not_suspend for {}", ty); + + match *ty.kind() { + ty::Adt(..) if ty.is_box() => { + let boxed_ty = ty.boxed_ty(); + let descr_pre = &format!("{}boxed ", data.descr_pre); + check_must_not_suspend_ty(fcx, boxed_ty, hir_id, SuspendCheckData { descr_pre, ..data }) + } + ty::Adt(def, _) => check_must_not_suspend_def(fcx.tcx, def.did(), hir_id, data), + // FIXME: support adding the attribute to TAITs + ty::Opaque(def, _) => { + let mut has_emitted = false; + for &(predicate, _) in fcx.tcx.explicit_item_bounds(def) { + // We only look at the `DefId`, so it is safe to skip the binder here. + if let ty::PredicateKind::Trait(ref poly_trait_predicate) = + predicate.kind().skip_binder() + { + let def_id = poly_trait_predicate.trait_ref.def_id; + let descr_pre = &format!("{}implementer{} of ", data.descr_pre, plural_suffix); + if check_must_not_suspend_def( + fcx.tcx, + def_id, + hir_id, + SuspendCheckData { descr_pre, ..data }, + ) { + has_emitted = true; + break; + } + } + } + has_emitted + } + ty::Dynamic(binder, _, _) => { + let mut has_emitted = false; + for predicate in binder.iter() { + if let ty::ExistentialPredicate::Trait(ref trait_ref) = predicate.skip_binder() { + let def_id = trait_ref.def_id; + let descr_post = &format!(" trait object{}{}", plural_suffix, data.descr_post); + if check_must_not_suspend_def( + fcx.tcx, + def_id, + hir_id, + SuspendCheckData { descr_post, ..data }, + ) { + has_emitted = true; + break; + } + } + } + has_emitted + } + ty::Tuple(fields) => { + let mut has_emitted = false; + let comps = match data.expr.map(|e| &e.kind) { + Some(hir::ExprKind::Tup(comps)) => { + debug_assert_eq!(comps.len(), fields.len()); + Some(comps) + } + _ => None, + }; + for (i, ty) in fields.iter().enumerate() { + let descr_post = &format!(" in tuple element {i}"); + let span = comps.and_then(|c| c.get(i)).map(|e| e.span).unwrap_or(data.source_span); + if check_must_not_suspend_ty( + fcx, + ty, + hir_id, + SuspendCheckData { + descr_post, + expr: comps.and_then(|comps| comps.get(i)), + source_span: span, + ..data + }, + ) { + has_emitted = true; + } + } + has_emitted + } + ty::Array(ty, len) => { + let descr_pre = &format!("{}array{} of ", data.descr_pre, plural_suffix); + check_must_not_suspend_ty( + fcx, + ty, + hir_id, + SuspendCheckData { + descr_pre, + plural_len: len.try_eval_usize(fcx.tcx, fcx.param_env).unwrap_or(0) as usize + + 1, + ..data + }, + ) + } + // If drop tracking is enabled, we want to look through references, since the referrent + // may not be considered live across the await point. + ty::Ref(_region, ty, _mutability) if fcx.sess().opts.unstable_opts.drop_tracking => { + let descr_pre = &format!("{}reference{} to ", data.descr_pre, plural_suffix); + check_must_not_suspend_ty(fcx, ty, hir_id, SuspendCheckData { descr_pre, ..data }) + } + _ => false, + } +} + +fn check_must_not_suspend_def( + tcx: TyCtxt<'_>, + def_id: DefId, + hir_id: HirId, + data: SuspendCheckData<'_, '_>, +) -> bool { + if let Some(attr) = tcx.get_attr(def_id, sym::must_not_suspend) { + tcx.struct_span_lint_hir( + rustc_session::lint::builtin::MUST_NOT_SUSPEND, + hir_id, + data.source_span, + DelayDm(|| { + format!( + "{}`{}`{} held across a suspend point, but should not be", + data.descr_pre, + tcx.def_path_str(def_id), + data.descr_post, + ) + }), + |lint| { + // add span pointing to the offending yield/await + lint.span_label(data.yield_span, "the value is held across this suspend point"); + + // Add optional reason note + if let Some(note) = attr.value_str() { + // FIXME(guswynn): consider formatting this better + lint.span_note(data.source_span, note.as_str()); + } + + // Add some quick suggestions on what to do + // FIXME: can `drop` work as a suggestion here as well? + lint.span_help( + data.source_span, + "consider using a block (`{ ... }`) \ + to shrink the value's scope, ending before the suspend point", + ); + + lint + }, + ); + + true + } else { + false + } +} diff --git a/compiler/rustc_hir_typeck/src/inherited.rs b/compiler/rustc_hir_typeck/src/inherited.rs new file mode 100644 index 000000000..0fb7651b3 --- /dev/null +++ b/compiler/rustc_hir_typeck/src/inherited.rs @@ -0,0 +1,213 @@ +use super::callee::DeferredCallResolution; + +use rustc_data_structures::fx::FxHashSet; +use rustc_data_structures::sync::Lrc; +use rustc_hir as hir; +use rustc_hir::def_id::LocalDefId; +use rustc_hir::HirIdMap; +use rustc_infer::infer; +use rustc_infer::infer::{DefiningAnchor, InferCtxt, InferOk, TyCtxtInferExt}; +use rustc_middle::ty::fold::TypeFoldable; +use rustc_middle::ty::visit::TypeVisitable; +use rustc_middle::ty::{self, Ty, TyCtxt}; +use rustc_span::def_id::LocalDefIdMap; +use rustc_span::{self, Span}; +use rustc_trait_selection::infer::InferCtxtExt as _; +use rustc_trait_selection::traits::{ + self, ObligationCause, ObligationCtxt, TraitEngine, TraitEngineExt as _, +}; + +use std::cell::RefCell; +use std::ops::Deref; + +/// Closures defined within the function. For example: +/// ```ignore (illustrative) +/// fn foo() { +/// bar(move|| { ... }) +/// } +/// ``` +/// Here, the function `foo()` and the closure passed to +/// `bar()` will each have their own `FnCtxt`, but they will +/// share the inherited fields. +pub struct Inherited<'tcx> { + pub(super) infcx: InferCtxt<'tcx>, + + pub(super) typeck_results: RefCell<ty::TypeckResults<'tcx>>, + + pub(super) locals: RefCell<HirIdMap<super::LocalTy<'tcx>>>, + + pub(super) fulfillment_cx: RefCell<Box<dyn TraitEngine<'tcx>>>, + + // Some additional `Sized` obligations badly affect type inference. + // These obligations are added in a later stage of typeck. + // Removing these may also cause additional complications, see #101066. + pub(super) deferred_sized_obligations: + RefCell<Vec<(Ty<'tcx>, Span, traits::ObligationCauseCode<'tcx>)>>, + + // When we process a call like `c()` where `c` is a closure type, + // we may not have decided yet whether `c` is a `Fn`, `FnMut`, or + // `FnOnce` closure. In that case, we defer full resolution of the + // call until upvar inference can kick in and make the + // decision. We keep these deferred resolutions grouped by the + // def-id of the closure, so that once we decide, we can easily go + // back and process them. + pub(super) deferred_call_resolutions: RefCell<LocalDefIdMap<Vec<DeferredCallResolution<'tcx>>>>, + + pub(super) deferred_cast_checks: RefCell<Vec<super::cast::CastCheck<'tcx>>>, + + pub(super) deferred_transmute_checks: RefCell<Vec<(Ty<'tcx>, Ty<'tcx>, hir::HirId)>>, + + pub(super) deferred_asm_checks: RefCell<Vec<(&'tcx hir::InlineAsm<'tcx>, hir::HirId)>>, + + pub(super) deferred_generator_interiors: + RefCell<Vec<(hir::BodyId, Ty<'tcx>, hir::GeneratorKind)>>, + + pub(super) body_id: Option<hir::BodyId>, + + /// Whenever we introduce an adjustment from `!` into a type variable, + /// we record that type variable here. This is later used to inform + /// fallback. See the `fallback` module for details. + pub(super) diverging_type_vars: RefCell<FxHashSet<Ty<'tcx>>>, +} + +impl<'tcx> Deref for Inherited<'tcx> { + type Target = InferCtxt<'tcx>; + fn deref(&self) -> &Self::Target { + &self.infcx + } +} + +/// A temporary returned by `Inherited::build(...)`. This is necessary +/// for multiple `InferCtxt` to share the same `typeck_results` +/// without using `Rc` or something similar. +pub struct InheritedBuilder<'tcx> { + infcx: infer::InferCtxtBuilder<'tcx>, + def_id: LocalDefId, + typeck_results: RefCell<ty::TypeckResults<'tcx>>, +} + +impl<'tcx> Inherited<'tcx> { + pub fn build(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> InheritedBuilder<'tcx> { + let hir_owner = tcx.hir().local_def_id_to_hir_id(def_id).owner; + + InheritedBuilder { + infcx: tcx + .infer_ctxt() + .ignoring_regions() + .with_opaque_type_inference(DefiningAnchor::Bind(hir_owner.def_id)) + .with_normalize_fn_sig_for_diagnostic(Lrc::new(move |infcx, fn_sig| { + if fn_sig.has_escaping_bound_vars() { + return fn_sig; + } + infcx.probe(|_| { + let ocx = ObligationCtxt::new_in_snapshot(infcx); + let normalized_fn_sig = ocx.normalize( + ObligationCause::dummy(), + // FIXME(compiler-errors): This is probably not the right param-env... + infcx.tcx.param_env(def_id), + fn_sig, + ); + if ocx.select_all_or_error().is_empty() { + let normalized_fn_sig = + infcx.resolve_vars_if_possible(normalized_fn_sig); + if !normalized_fn_sig.needs_infer() { + return normalized_fn_sig; + } + } + fn_sig + }) + })), + def_id, + typeck_results: RefCell::new(ty::TypeckResults::new(hir_owner)), + } + } +} + +impl<'tcx> InheritedBuilder<'tcx> { + pub fn enter<F, R>(mut self, f: F) -> R + where + F: FnOnce(&Inherited<'tcx>) -> R, + { + let def_id = self.def_id; + f(&Inherited::new(self.infcx.build(), def_id, self.typeck_results)) + } +} + +impl<'tcx> Inherited<'tcx> { + fn new( + infcx: InferCtxt<'tcx>, + def_id: LocalDefId, + typeck_results: RefCell<ty::TypeckResults<'tcx>>, + ) -> Self { + let tcx = infcx.tcx; + let body_id = tcx.hir().maybe_body_owned_by(def_id); + + Inherited { + typeck_results, + infcx, + fulfillment_cx: RefCell::new(<dyn TraitEngine<'_>>::new(tcx)), + locals: RefCell::new(Default::default()), + deferred_sized_obligations: RefCell::new(Vec::new()), + deferred_call_resolutions: RefCell::new(Default::default()), + deferred_cast_checks: RefCell::new(Vec::new()), + deferred_transmute_checks: RefCell::new(Vec::new()), + deferred_asm_checks: RefCell::new(Vec::new()), + deferred_generator_interiors: RefCell::new(Vec::new()), + diverging_type_vars: RefCell::new(Default::default()), + body_id, + } + } + + #[instrument(level = "debug", skip(self))] + pub(super) fn register_predicate(&self, obligation: traits::PredicateObligation<'tcx>) { + if obligation.has_escaping_bound_vars() { + span_bug!(obligation.cause.span, "escaping bound vars in predicate {:?}", obligation); + } + self.fulfillment_cx.borrow_mut().register_predicate_obligation(self, obligation); + } + + pub(super) fn register_predicates<I>(&self, obligations: I) + where + I: IntoIterator<Item = traits::PredicateObligation<'tcx>>, + { + for obligation in obligations { + self.register_predicate(obligation); + } + } + + pub(super) fn register_infer_ok_obligations<T>(&self, infer_ok: InferOk<'tcx, T>) -> T { + self.register_predicates(infer_ok.obligations); + infer_ok.value + } + + pub(super) fn normalize_associated_types_in<T>( + &self, + span: Span, + body_id: hir::HirId, + param_env: ty::ParamEnv<'tcx>, + value: T, + ) -> T + where + T: TypeFoldable<'tcx>, + { + self.normalize_associated_types_in_with_cause( + ObligationCause::misc(span, body_id), + param_env, + value, + ) + } + + pub(super) fn normalize_associated_types_in_with_cause<T>( + &self, + cause: ObligationCause<'tcx>, + param_env: ty::ParamEnv<'tcx>, + value: T, + ) -> T + where + T: TypeFoldable<'tcx>, + { + let ok = self.partially_normalize_associated_types_in(cause, param_env, value); + debug!(?ok); + self.register_infer_ok_obligations(ok) + } +} diff --git a/compiler/rustc_hir_typeck/src/intrinsicck.rs b/compiler/rustc_hir_typeck/src/intrinsicck.rs new file mode 100644 index 000000000..9812d96fc --- /dev/null +++ b/compiler/rustc_hir_typeck/src/intrinsicck.rs @@ -0,0 +1,108 @@ +use hir::HirId; +use rustc_errors::struct_span_err; +use rustc_hir as hir; +use rustc_index::vec::Idx; +use rustc_middle::ty::layout::{LayoutError, SizeSkeleton}; +use rustc_middle::ty::{self, Ty, TyCtxt}; +use rustc_target::abi::{Pointer, VariantIdx}; + +use super::FnCtxt; + +/// If the type is `Option<T>`, it will return `T`, otherwise +/// the type itself. Works on most `Option`-like types. +fn unpack_option_like<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Ty<'tcx> { + let ty::Adt(def, substs) = *ty.kind() else { return ty }; + + if def.variants().len() == 2 && !def.repr().c() && def.repr().int.is_none() { + let data_idx; + + let one = VariantIdx::new(1); + let zero = VariantIdx::new(0); + + if def.variant(zero).fields.is_empty() { + data_idx = one; + } else if def.variant(one).fields.is_empty() { + data_idx = zero; + } else { + return ty; + } + + if def.variant(data_idx).fields.len() == 1 { + return def.variant(data_idx).fields[0].ty(tcx, substs); + } + } + + ty +} + +impl<'a, 'tcx> FnCtxt<'a, 'tcx> { + pub fn check_transmute(&self, from: Ty<'tcx>, to: Ty<'tcx>, hir_id: HirId) { + let tcx = self.tcx; + let span = tcx.hir().span(hir_id); + let normalize = |ty| { + let ty = self.resolve_vars_if_possible(ty); + self.tcx.normalize_erasing_regions(self.param_env, ty) + }; + let from = normalize(from); + let to = normalize(to); + trace!(?from, ?to); + + // Transmutes that are only changing lifetimes are always ok. + if from == to { + return; + } + + let skel = |ty| SizeSkeleton::compute(ty, tcx, self.param_env); + let sk_from = skel(from); + let sk_to = skel(to); + trace!(?sk_from, ?sk_to); + + // Check for same size using the skeletons. + if let (Ok(sk_from), Ok(sk_to)) = (sk_from, sk_to) { + if sk_from.same_size(sk_to) { + return; + } + + // Special-case transmuting from `typeof(function)` and + // `Option<typeof(function)>` to present a clearer error. + let from = unpack_option_like(tcx, from); + if let (&ty::FnDef(..), SizeSkeleton::Known(size_to)) = (from.kind(), sk_to) && size_to == Pointer.size(&tcx) { + struct_span_err!(tcx.sess, span, E0591, "can't transmute zero-sized type") + .note(&format!("source type: {from}")) + .note(&format!("target type: {to}")) + .help("cast with `as` to a pointer instead") + .emit(); + return; + } + } + + // Try to display a sensible error with as much information as possible. + let skeleton_string = |ty: Ty<'tcx>, sk| match sk { + Ok(SizeSkeleton::Known(size)) => format!("{} bits", size.bits()), + Ok(SizeSkeleton::Pointer { tail, .. }) => format!("pointer to `{tail}`"), + Err(LayoutError::Unknown(bad)) => { + if bad == ty { + "this type does not have a fixed size".to_owned() + } else { + format!("size can vary because of {bad}") + } + } + Err(err) => err.to_string(), + }; + + let mut err = struct_span_err!( + tcx.sess, + span, + E0512, + "cannot transmute between types of different sizes, \ + or dependently-sized types" + ); + if from == to { + err.note(&format!("`{from}` does not have a fixed size")); + } else { + err.note(&format!("source type: `{}` ({})", from, skeleton_string(from, sk_from))) + .note(&format!("target type: `{}` ({})", to, skeleton_string(to, sk_to))); + } + err.emit(); + } +} diff --git a/compiler/rustc_hir_typeck/src/lib.rs b/compiler/rustc_hir_typeck/src/lib.rs new file mode 100644 index 000000000..959c54866 --- /dev/null +++ b/compiler/rustc_hir_typeck/src/lib.rs @@ -0,0 +1,507 @@ +#![feature(if_let_guard)] +#![feature(let_chains)] +#![feature(try_blocks)] +#![feature(never_type)] +#![feature(min_specialization)] +#![feature(control_flow_enum)] +#![feature(drain_filter)] +#![allow(rustc::potential_query_instability)] +#![recursion_limit = "256"] + +#[macro_use] +extern crate tracing; + +#[macro_use] +extern crate rustc_middle; + +mod _match; +mod autoderef; +mod callee; +// Used by clippy; +pub mod cast; +mod check; +mod closure; +mod coercion; +mod demand; +mod diverges; +mod errors; +mod expectation; +mod expr; +// Used by clippy; +pub mod expr_use_visitor; +mod fallback; +mod fn_ctxt; +mod gather_locals; +mod generator_interior; +mod inherited; +mod intrinsicck; +mod mem_categorization; +mod method; +mod op; +mod pat; +mod place_op; +mod rvalue_scopes; +mod upvar; +mod writeback; + +pub use diverges::Diverges; +pub use expectation::Expectation; +pub use fn_ctxt::*; +pub use inherited::{Inherited, InheritedBuilder}; + +use crate::check::check_fn; +use crate::coercion::DynamicCoerceMany; +use crate::gather_locals::GatherLocalsVisitor; +use rustc_data_structures::unord::UnordSet; +use rustc_errors::{struct_span_err, MultiSpan}; +use rustc_hir as hir; +use rustc_hir::def::Res; +use rustc_hir::intravisit::Visitor; +use rustc_hir::{HirIdMap, Node}; +use rustc_hir_analysis::astconv::AstConv; +use rustc_hir_analysis::check::check_abi; +use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind}; +use rustc_middle::traits; +use rustc_middle::ty::query::Providers; +use rustc_middle::ty::{self, Ty, TyCtxt}; +use rustc_session::config; +use rustc_session::Session; +use rustc_span::def_id::{DefId, LocalDefId}; +use rustc_span::Span; + +#[macro_export] +macro_rules! type_error_struct { + ($session:expr, $span:expr, $typ:expr, $code:ident, $($message:tt)*) => ({ + let mut err = rustc_errors::struct_span_err!($session, $span, $code, $($message)*); + + if $typ.references_error() { + err.downgrade_to_delayed_bug(); + } + + err + }) +} + +/// The type of a local binding, including the revealed type for anon types. +#[derive(Copy, Clone, Debug)] +pub struct LocalTy<'tcx> { + decl_ty: Ty<'tcx>, + revealed_ty: Ty<'tcx>, +} + +#[derive(Copy, Clone)] +pub struct UnsafetyState { + pub def: hir::HirId, + pub unsafety: hir::Unsafety, + from_fn: bool, +} + +impl UnsafetyState { + pub fn function(unsafety: hir::Unsafety, def: hir::HirId) -> UnsafetyState { + UnsafetyState { def, unsafety, from_fn: true } + } + + pub fn recurse(self, blk: &hir::Block<'_>) -> UnsafetyState { + use hir::BlockCheckMode; + match self.unsafety { + // If this unsafe, then if the outer function was already marked as + // unsafe we shouldn't attribute the unsafe'ness to the block. This + // way the block can be warned about instead of ignoring this + // extraneous block (functions are never warned about). + hir::Unsafety::Unsafe if self.from_fn => self, + + unsafety => { + let (unsafety, def) = match blk.rules { + BlockCheckMode::UnsafeBlock(..) => (hir::Unsafety::Unsafe, blk.hir_id), + BlockCheckMode::DefaultBlock => (unsafety, self.def), + }; + UnsafetyState { def, unsafety, from_fn: false } + } + } + } +} + +/// If this `DefId` is a "primary tables entry", returns +/// `Some((body_id, body_ty, fn_sig))`. Otherwise, returns `None`. +/// +/// If this function returns `Some`, then `typeck_results(def_id)` will +/// succeed; if it returns `None`, then `typeck_results(def_id)` may or +/// may not succeed. In some cases where this function returns `None` +/// (notably closures), `typeck_results(def_id)` would wind up +/// redirecting to the owning function. +fn primary_body_of( + tcx: TyCtxt<'_>, + id: hir::HirId, +) -> Option<(hir::BodyId, Option<&hir::Ty<'_>>, Option<&hir::FnSig<'_>>)> { + match tcx.hir().get(id) { + Node::Item(item) => match item.kind { + hir::ItemKind::Const(ty, body) | hir::ItemKind::Static(ty, _, body) => { + Some((body, Some(ty), None)) + } + hir::ItemKind::Fn(ref sig, .., body) => Some((body, None, Some(sig))), + _ => None, + }, + Node::TraitItem(item) => match item.kind { + hir::TraitItemKind::Const(ty, Some(body)) => Some((body, Some(ty), None)), + hir::TraitItemKind::Fn(ref sig, hir::TraitFn::Provided(body)) => { + Some((body, None, Some(sig))) + } + _ => None, + }, + Node::ImplItem(item) => match item.kind { + hir::ImplItemKind::Const(ty, body) => Some((body, Some(ty), None)), + hir::ImplItemKind::Fn(ref sig, body) => Some((body, None, Some(sig))), + _ => None, + }, + Node::AnonConst(constant) => Some((constant.body, None, None)), + _ => None, + } +} + +fn has_typeck_results(tcx: TyCtxt<'_>, def_id: DefId) -> bool { + // Closures' typeck results come from their outermost function, + // as they are part of the same "inference environment". + let typeck_root_def_id = tcx.typeck_root_def_id(def_id); + if typeck_root_def_id != def_id { + return tcx.has_typeck_results(typeck_root_def_id); + } + + if let Some(def_id) = def_id.as_local() { + let id = tcx.hir().local_def_id_to_hir_id(def_id); + primary_body_of(tcx, id).is_some() + } else { + false + } +} + +fn used_trait_imports(tcx: TyCtxt<'_>, def_id: LocalDefId) -> &UnordSet<LocalDefId> { + &*tcx.typeck(def_id).used_trait_imports +} + +fn typeck_item_bodies(tcx: TyCtxt<'_>, (): ()) { + tcx.hir().par_body_owners(|body_owner_def_id| tcx.ensure().typeck(body_owner_def_id)); +} + +fn typeck_const_arg<'tcx>( + tcx: TyCtxt<'tcx>, + (did, param_did): (LocalDefId, DefId), +) -> &ty::TypeckResults<'tcx> { + let fallback = move || tcx.type_of(param_did); + typeck_with_fallback(tcx, did, fallback) +} + +fn typeck<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> &ty::TypeckResults<'tcx> { + if let Some(param_did) = tcx.opt_const_param_of(def_id) { + tcx.typeck_const_arg((def_id, param_did)) + } else { + let fallback = move || tcx.type_of(def_id.to_def_id()); + typeck_with_fallback(tcx, def_id, fallback) + } +} + +/// Used only to get `TypeckResults` for type inference during error recovery. +/// Currently only used for type inference of `static`s and `const`s to avoid type cycle errors. +fn diagnostic_only_typeck<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> &ty::TypeckResults<'tcx> { + let fallback = move || { + let span = tcx.hir().span(tcx.hir().local_def_id_to_hir_id(def_id)); + tcx.ty_error_with_message(span, "diagnostic only typeck table used") + }; + typeck_with_fallback(tcx, def_id, fallback) +} + +fn typeck_with_fallback<'tcx>( + tcx: TyCtxt<'tcx>, + def_id: LocalDefId, + fallback: impl Fn() -> Ty<'tcx> + 'tcx, +) -> &'tcx ty::TypeckResults<'tcx> { + // Closures' typeck results come from their outermost function, + // as they are part of the same "inference environment". + let typeck_root_def_id = tcx.typeck_root_def_id(def_id.to_def_id()).expect_local(); + if typeck_root_def_id != def_id { + return tcx.typeck(typeck_root_def_id); + } + + let id = tcx.hir().local_def_id_to_hir_id(def_id); + let span = tcx.hir().span(id); + + // Figure out what primary body this item has. + let (body_id, body_ty, fn_sig) = primary_body_of(tcx, id).unwrap_or_else(|| { + span_bug!(span, "can't type-check body of {:?}", def_id); + }); + let body = tcx.hir().body(body_id); + + let typeck_results = Inherited::build(tcx, def_id).enter(|inh| { + let param_env = tcx.param_env(def_id); + let mut fcx = if let Some(hir::FnSig { header, decl, .. }) = fn_sig { + let fn_sig = if rustc_hir_analysis::collect::get_infer_ret_ty(&decl.output).is_some() { + let fcx = FnCtxt::new(&inh, param_env, body.value.hir_id); + <dyn AstConv<'_>>::ty_of_fn(&fcx, id, header.unsafety, header.abi, decl, None, None) + } else { + tcx.fn_sig(def_id) + }; + + check_abi(tcx, id, span, fn_sig.abi()); + + // Compute the function signature from point of view of inside the fn. + let fn_sig = tcx.liberate_late_bound_regions(def_id.to_def_id(), fn_sig); + let fn_sig = inh.normalize_associated_types_in( + body.value.span, + body_id.hir_id, + param_env, + fn_sig, + ); + check_fn(&inh, param_env, fn_sig, decl, id, body, None, true).0 + } else { + let fcx = FnCtxt::new(&inh, param_env, body.value.hir_id); + let expected_type = body_ty + .and_then(|ty| match ty.kind { + hir::TyKind::Infer => Some(<dyn AstConv<'_>>::ast_ty_to_ty(&fcx, ty)), + _ => None, + }) + .unwrap_or_else(|| match tcx.hir().get(id) { + Node::AnonConst(_) => match tcx.hir().get(tcx.hir().get_parent_node(id)) { + Node::Expr(&hir::Expr { + kind: hir::ExprKind::ConstBlock(ref anon_const), + .. + }) if anon_const.hir_id == id => fcx.next_ty_var(TypeVariableOrigin { + kind: TypeVariableOriginKind::TypeInference, + span, + }), + Node::Ty(&hir::Ty { + kind: hir::TyKind::Typeof(ref anon_const), .. + }) if anon_const.hir_id == id => fcx.next_ty_var(TypeVariableOrigin { + kind: TypeVariableOriginKind::TypeInference, + span, + }), + Node::Expr(&hir::Expr { kind: hir::ExprKind::InlineAsm(asm), .. }) + | Node::Item(&hir::Item { kind: hir::ItemKind::GlobalAsm(asm), .. }) => { + let operand_ty = asm + .operands + .iter() + .filter_map(|(op, _op_sp)| match op { + hir::InlineAsmOperand::Const { anon_const } + if anon_const.hir_id == id => + { + // Inline assembly constants must be integers. + Some(fcx.next_int_var()) + } + hir::InlineAsmOperand::SymFn { anon_const } + if anon_const.hir_id == id => + { + Some(fcx.next_ty_var(TypeVariableOrigin { + kind: TypeVariableOriginKind::MiscVariable, + span, + })) + } + _ => None, + }) + .next(); + operand_ty.unwrap_or_else(fallback) + } + _ => fallback(), + }, + _ => fallback(), + }); + + let expected_type = fcx.normalize_associated_types_in(body.value.span, expected_type); + fcx.require_type_is_sized(expected_type, body.value.span, traits::ConstSized); + + // Gather locals in statics (because of block expressions). + GatherLocalsVisitor::new(&fcx).visit_body(body); + + fcx.check_expr_coercable_to_type(&body.value, expected_type, None); + + fcx.write_ty(id, expected_type); + + fcx + }; + + let fallback_has_occurred = fcx.type_inference_fallback(); + + // Even though coercion casts provide type hints, we check casts after fallback for + // backwards compatibility. This makes fallback a stronger type hint than a cast coercion. + fcx.check_casts(); + fcx.select_obligations_where_possible(fallback_has_occurred, |_| {}); + + // Closure and generator analysis may run after fallback + // because they don't constrain other type variables. + // Closure analysis only runs on closures. Therefore they only need to fulfill non-const predicates (as of now) + let prev_constness = fcx.param_env.constness(); + fcx.param_env = fcx.param_env.without_const(); + fcx.closure_analyze(body); + fcx.param_env = fcx.param_env.with_constness(prev_constness); + assert!(fcx.deferred_call_resolutions.borrow().is_empty()); + // Before the generator analysis, temporary scopes shall be marked to provide more + // precise information on types to be captured. + fcx.resolve_rvalue_scopes(def_id.to_def_id()); + fcx.resolve_generator_interiors(def_id.to_def_id()); + + for (ty, span, code) in fcx.deferred_sized_obligations.borrow_mut().drain(..) { + let ty = fcx.normalize_ty(span, ty); + fcx.require_type_is_sized(ty, span, code); + } + + fcx.select_all_obligations_or_error(); + + if !fcx.infcx.is_tainted_by_errors() { + fcx.check_transmutes(); + } + + fcx.check_asms(); + + fcx.infcx.skip_region_resolution(); + + fcx.resolve_type_vars_in_body(body) + }); + + // Consistency check our TypeckResults instance can hold all ItemLocalIds + // it will need to hold. + assert_eq!(typeck_results.hir_owner, id.owner); + + typeck_results +} + +/// When `check_fn` is invoked on a generator (i.e., a body that +/// includes yield), it returns back some information about the yield +/// points. +struct GeneratorTypes<'tcx> { + /// Type of generator argument / values returned by `yield`. + resume_ty: Ty<'tcx>, + + /// Type of value that is yielded. + yield_ty: Ty<'tcx>, + + /// Types that are captured (see `GeneratorInterior` for more). + interior: Ty<'tcx>, + + /// Indicates if the generator is movable or static (immovable). + movability: hir::Movability, +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum Needs { + MutPlace, + None, +} + +impl Needs { + fn maybe_mut_place(m: hir::Mutability) -> Self { + match m { + hir::Mutability::Mut => Needs::MutPlace, + hir::Mutability::Not => Needs::None, + } + } +} + +#[derive(Debug, Copy, Clone)] +pub enum PlaceOp { + Deref, + Index, +} + +pub struct BreakableCtxt<'tcx> { + may_break: bool, + + // this is `null` for loops where break with a value is illegal, + // such as `while`, `for`, and `while let` + coerce: Option<DynamicCoerceMany<'tcx>>, +} + +pub struct EnclosingBreakables<'tcx> { + stack: Vec<BreakableCtxt<'tcx>>, + by_id: HirIdMap<usize>, +} + +impl<'tcx> EnclosingBreakables<'tcx> { + fn find_breakable(&mut self, target_id: hir::HirId) -> &mut BreakableCtxt<'tcx> { + self.opt_find_breakable(target_id).unwrap_or_else(|| { + bug!("could not find enclosing breakable with id {}", target_id); + }) + } + + fn opt_find_breakable(&mut self, target_id: hir::HirId) -> Option<&mut BreakableCtxt<'tcx>> { + match self.by_id.get(&target_id) { + Some(ix) => Some(&mut self.stack[*ix]), + None => None, + } + } +} + +fn report_unexpected_variant_res(tcx: TyCtxt<'_>, res: Res, qpath: &hir::QPath<'_>, span: Span) { + struct_span_err!( + tcx.sess, + span, + E0533, + "expected unit struct, unit variant or constant, found {} `{}`", + res.descr(), + rustc_hir_pretty::qpath_to_string(qpath), + ) + .emit(); +} + +/// Controls whether the arguments are tupled. This is used for the call +/// operator. +/// +/// Tupling means that all call-side arguments are packed into a tuple and +/// passed as a single parameter. For example, if tupling is enabled, this +/// function: +/// ``` +/// fn f(x: (isize, isize)) {} +/// ``` +/// Can be called as: +/// ```ignore UNSOLVED (can this be done in user code?) +/// # fn f(x: (isize, isize)) {} +/// f(1, 2); +/// ``` +/// Instead of: +/// ``` +/// # fn f(x: (isize, isize)) {} +/// f((1, 2)); +/// ``` +#[derive(Clone, Eq, PartialEq)] +enum TupleArgumentsFlag { + DontTupleArguments, + TupleArguments, +} + +fn fatally_break_rust(sess: &Session) { + let handler = sess.diagnostic(); + handler.span_bug_no_panic( + MultiSpan::new(), + "It looks like you're trying to break rust; would you like some ICE?", + ); + handler.note_without_error("the compiler expectedly panicked. this is a feature."); + handler.note_without_error( + "we would appreciate a joke overview: \ + https://github.com/rust-lang/rust/issues/43162#issuecomment-320764675", + ); + handler.note_without_error(&format!( + "rustc {} running on {}", + option_env!("CFG_VERSION").unwrap_or("unknown_version"), + config::host_triple(), + )); +} + +fn has_expected_num_generic_args<'tcx>( + tcx: TyCtxt<'tcx>, + trait_did: Option<DefId>, + expected: usize, +) -> bool { + trait_did.map_or(true, |trait_did| { + let generics = tcx.generics_of(trait_did); + generics.count() == expected + if generics.has_self { 1 } else { 0 } + }) +} + +pub fn provide(providers: &mut Providers) { + method::provide(providers); + *providers = Providers { + typeck_item_bodies, + typeck_const_arg, + typeck, + diagnostic_only_typeck, + has_typeck_results, + used_trait_imports, + ..*providers + }; +} diff --git a/compiler/rustc_typeck/src/mem_categorization.rs b/compiler/rustc_hir_typeck/src/mem_categorization.rs index ced919f66..362f1c343 100644 --- a/compiler/rustc_typeck/src/mem_categorization.rs +++ b/compiler/rustc_hir_typeck/src/mem_categorization.rs @@ -92,7 +92,7 @@ impl HirNode for hir::Pat<'_> { #[derive(Clone)] pub(crate) struct MemCategorizationContext<'a, 'tcx> { pub(crate) typeck_results: &'a ty::TypeckResults<'tcx>, - infcx: &'a InferCtxt<'a, 'tcx>, + infcx: &'a InferCtxt<'tcx>, param_env: ty::ParamEnv<'tcx>, body_owner: LocalDefId, upvars: Option<&'tcx FxIndexMap<hir::HirId, hir::Upvar>>, @@ -103,7 +103,7 @@ pub(crate) type McResult<T> = Result<T, ()>; impl<'a, 'tcx> MemCategorizationContext<'a, 'tcx> { /// Creates a `MemCategorizationContext`. pub(crate) fn new( - infcx: &'a InferCtxt<'a, 'tcx>, + infcx: &'a InferCtxt<'tcx>, param_env: ty::ParamEnv<'tcx>, body_owner: LocalDefId, typeck_results: &'a ty::TypeckResults<'tcx>, @@ -184,7 +184,7 @@ impl<'a, 'tcx> MemCategorizationContext<'a, 'tcx> { /// modes #42640) may look like `Some(x)` but in fact have /// implicit deref patterns attached (e.g., it is really /// `&Some(x)`). In that case, we return the "outermost" type - /// (e.g., `&Option<T>). + /// (e.g., `&Option<T>`). pub(crate) fn pat_ty_adjusted(&self, pat: &hir::Pat<'_>) -> McResult<Ty<'tcx>> { // Check for implicit `&` types wrapping the pattern; note // that these are never attached to binding patterns, so @@ -265,6 +265,7 @@ impl<'a, 'tcx> MemCategorizationContext<'a, 'tcx> { self.cat_expr_adjusted_with(expr, || Ok(previous), adjustment) } + #[instrument(level = "debug", skip(self, previous))] fn cat_expr_adjusted_with<F>( &self, expr: &hir::Expr<'_>, @@ -274,7 +275,6 @@ impl<'a, 'tcx> MemCategorizationContext<'a, 'tcx> { where F: FnOnce() -> McResult<PlaceWithHirId<'tcx>>, { - debug!("cat_expr_adjusted_with({:?}): {:?}", adjustment, expr); let target = self.resolve_vars_if_possible(adjustment.target); match adjustment.kind { adjustment::Adjust::Deref(overloaded) => { @@ -292,13 +292,15 @@ impl<'a, 'tcx> MemCategorizationContext<'a, 'tcx> { adjustment::Adjust::NeverToAny | adjustment::Adjust::Pointer(_) - | adjustment::Adjust::Borrow(_) => { + | adjustment::Adjust::Borrow(_) + | adjustment::Adjust::DynStar => { // Result is an rvalue. Ok(self.cat_rvalue(expr.hir_id, expr.span, target)) } } } + #[instrument(level = "debug", skip(self))] pub(crate) fn cat_expr_unadjusted( &self, expr: &hir::Expr<'_>, @@ -387,6 +389,7 @@ impl<'a, 'tcx> MemCategorizationContext<'a, 'tcx> { } } + #[instrument(level = "debug", skip(self, span))] pub(crate) fn cat_res( &self, hir_id: hir::HirId, @@ -394,8 +397,6 @@ impl<'a, 'tcx> MemCategorizationContext<'a, 'tcx> { expr_ty: Ty<'tcx>, res: Res, ) -> McResult<PlaceWithHirId<'tcx>> { - debug!("cat_res: id={:?} expr={:?} def={:?}", hir_id, expr_ty, res); - match res { Res::Def( DefKind::Ctor(..) @@ -475,13 +476,12 @@ impl<'a, 'tcx> MemCategorizationContext<'a, 'tcx> { ret } + #[instrument(level = "debug", skip(self))] fn cat_overloaded_place( &self, expr: &hir::Expr<'_>, base: &hir::Expr<'_>, ) -> McResult<PlaceWithHirId<'tcx>> { - debug!("cat_overloaded_place(expr={:?}, base={:?})", expr, base); - // Reconstruct the output assuming it's a reference with the // same region and mutability as the receiver. This holds for // `Deref(Mut)::Deref(_mut)` and `Index(Mut)::index(_mut)`. @@ -497,13 +497,12 @@ impl<'a, 'tcx> MemCategorizationContext<'a, 'tcx> { self.cat_deref(expr, base) } + #[instrument(level = "debug", skip(self, node))] fn cat_deref( &self, node: &impl HirNode, base_place: PlaceWithHirId<'tcx>, ) -> McResult<PlaceWithHirId<'tcx>> { - debug!("cat_deref: base_place={:?}", base_place); - let base_curr_ty = base_place.place.ty(); let deref_ty = match base_curr_ty.builtin_deref(true) { Some(mt) => mt.ty, @@ -562,7 +561,8 @@ impl<'a, 'tcx> MemCategorizationContext<'a, 'tcx> { Res::Def(DefKind::Ctor(CtorOf::Struct, ..), _) | Res::Def(DefKind::Struct | DefKind::Union | DefKind::TyAlias | DefKind::AssocTy, _) | Res::SelfCtor(..) - | Res::SelfTy { .. } => { + | Res::SelfTyParam { .. } + | Res::SelfTyAlias { .. } => { // Structs and Unions have only have one variant. Ok(VariantIdx::new(0)) } diff --git a/compiler/rustc_typeck/src/check/method/confirm.rs b/compiler/rustc_hir_typeck/src/method/confirm.rs index 2c89b63ae..be4ea9986 100644 --- a/compiler/rustc_typeck/src/check/method/confirm.rs +++ b/compiler/rustc_hir_typeck/src/method/confirm.rs @@ -1,16 +1,16 @@ use super::{probe, MethodCallee}; -use crate::astconv::{AstConv, CreateSubstsForGenericArgsCtxt, IsMethodCall}; -use crate::check::{callee, FnCtxt}; +use crate::{callee, FnCtxt}; use rustc_hir as hir; use rustc_hir::def_id::DefId; use rustc_hir::GenericArg; +use rustc_hir_analysis::astconv::{AstConv, CreateSubstsForGenericArgsCtxt, IsMethodCall}; use rustc_infer::infer::{self, InferOk}; use rustc_middle::traits::{ObligationCauseCode, UnifyReceiverContext}; use rustc_middle::ty::adjustment::{Adjust, Adjustment, PointerCast}; use rustc_middle::ty::adjustment::{AllowTwoPhase, AutoBorrow, AutoBorrowMutability}; use rustc_middle::ty::fold::TypeFoldable; -use rustc_middle::ty::subst::{self, Subst, SubstsRef}; +use rustc_middle::ty::subst::{self, SubstsRef}; use rustc_middle::ty::{self, GenericParamDefKind, Ty}; use rustc_span::Span; use rustc_trait_selection::traits; @@ -491,7 +491,19 @@ impl<'a, 'tcx> ConfirmContext<'a, 'tcx> { // so we just call `predicates_for_generics` directly to avoid redoing work. // `self.add_required_obligations(self.span, def_id, &all_substs);` for obligation in traits::predicates_for_generics( - traits::ObligationCause::new(self.span, self.body_id, traits::ItemObligation(def_id)), + |idx, span| { + let code = if span.is_dummy() { + ObligationCauseCode::ExprItemObligation(def_id, self.call_expr.hir_id, idx) + } else { + ObligationCauseCode::ExprBindingObligation( + def_id, + span, + self.call_expr.hir_id, + idx, + ) + }; + traits::ObligationCause::new(self.span, self.body_id, code) + }, self.param_env, method_predicates, ) { diff --git a/compiler/rustc_hir_typeck/src/method/mod.rs b/compiler/rustc_hir_typeck/src/method/mod.rs new file mode 100644 index 000000000..a1278edef --- /dev/null +++ b/compiler/rustc_hir_typeck/src/method/mod.rs @@ -0,0 +1,625 @@ +//! Method lookup: the secret sauce of Rust. See the [rustc dev guide] for more information. +//! +//! [rustc dev guide]: https://rustc-dev-guide.rust-lang.org/method-lookup.html + +mod confirm; +mod prelude2021; +pub mod probe; +mod suggest; + +pub use self::suggest::SelfSource; +pub use self::MethodError::*; + +use crate::{Expectation, FnCtxt}; +use rustc_data_structures::sync::Lrc; +use rustc_errors::{Applicability, Diagnostic}; +use rustc_hir as hir; +use rustc_hir::def::{CtorOf, DefKind, Namespace}; +use rustc_hir::def_id::DefId; +use rustc_infer::infer::{self, InferOk}; +use rustc_middle::traits::ObligationCause; +use rustc_middle::ty::subst::{InternalSubsts, SubstsRef}; +use rustc_middle::ty::{self, DefIdTree, GenericParamDefKind, ToPredicate, Ty, TypeVisitable}; +use rustc_span::symbol::Ident; +use rustc_span::Span; +use rustc_trait_selection::traits; +use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt; + +use self::probe::{IsSuggestion, ProbeScope}; + +pub fn provide(providers: &mut ty::query::Providers) { + probe::provide(providers); +} + +#[derive(Clone, Copy, Debug)] +pub struct MethodCallee<'tcx> { + /// Impl method ID, for inherent methods, or trait method ID, otherwise. + pub def_id: DefId, + pub substs: SubstsRef<'tcx>, + + /// Instantiated method signature, i.e., it has been + /// substituted, normalized, and has had late-bound + /// lifetimes replaced with inference variables. + pub sig: ty::FnSig<'tcx>, +} + +#[derive(Debug)] +pub enum MethodError<'tcx> { + // Did not find an applicable method, but we did find various near-misses that may work. + NoMatch(NoMatchData<'tcx>), + + // Multiple methods might apply. + Ambiguity(Vec<CandidateSource>), + + // Found an applicable method, but it is not visible. The third argument contains a list of + // not-in-scope traits which may work. + PrivateMatch(DefKind, DefId, Vec<DefId>), + + // Found a `Self: Sized` bound where `Self` is a trait object, also the caller may have + // forgotten to import a trait. + IllegalSizedBound(Vec<DefId>, bool, Span), + + // Found a match, but the return type is wrong + BadReturnType, +} + +// Contains a list of static methods that may apply, a list of unsatisfied trait predicates which +// could lead to matches if satisfied, and a list of not-in-scope traits which may work. +#[derive(Debug)] +pub struct NoMatchData<'tcx> { + pub static_candidates: Vec<CandidateSource>, + pub unsatisfied_predicates: + Vec<(ty::Predicate<'tcx>, Option<ty::Predicate<'tcx>>, Option<ObligationCause<'tcx>>)>, + pub out_of_scope_traits: Vec<DefId>, + pub lev_candidate: Option<ty::AssocItem>, + pub mode: probe::Mode, +} + +// A pared down enum describing just the places from which a method +// candidate can arise. Used for error reporting only. +#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] +pub enum CandidateSource { + Impl(DefId), + Trait(DefId /* trait id */), +} + +impl<'a, 'tcx> FnCtxt<'a, 'tcx> { + /// Determines whether the type `self_ty` supports a method name `method_name` or not. + #[instrument(level = "debug", skip(self))] + pub fn method_exists( + &self, + method_name: Ident, + self_ty: Ty<'tcx>, + call_expr_id: hir::HirId, + allow_private: bool, + ) -> bool { + let mode = probe::Mode::MethodCall; + match self.probe_for_name( + method_name.span, + mode, + method_name, + IsSuggestion(false), + self_ty, + call_expr_id, + ProbeScope::TraitsInScope, + ) { + Ok(..) => true, + Err(NoMatch(..)) => false, + Err(Ambiguity(..)) => true, + Err(PrivateMatch(..)) => allow_private, + Err(IllegalSizedBound(..)) => true, + Err(BadReturnType) => bug!("no return type expectations but got BadReturnType"), + } + } + + /// Adds a suggestion to call the given method to the provided diagnostic. + #[instrument(level = "debug", skip(self, err, call_expr))] + pub(crate) fn suggest_method_call( + &self, + err: &mut Diagnostic, + msg: &str, + method_name: Ident, + self_ty: Ty<'tcx>, + call_expr: &hir::Expr<'_>, + span: Option<Span>, + ) { + let params = self + .probe_for_name( + method_name.span, + probe::Mode::MethodCall, + method_name, + IsSuggestion(false), + self_ty, + call_expr.hir_id, + ProbeScope::TraitsInScope, + ) + .map(|pick| { + let sig = self.tcx.fn_sig(pick.item.def_id); + sig.inputs().skip_binder().len().saturating_sub(1) + }) + .unwrap_or(0); + + // Account for `foo.bar<T>`; + let sugg_span = span.unwrap_or(call_expr.span).shrink_to_hi(); + let (suggestion, applicability) = ( + format!("({})", (0..params).map(|_| "_").collect::<Vec<_>>().join(", ")), + if params > 0 { Applicability::HasPlaceholders } else { Applicability::MaybeIncorrect }, + ); + + err.span_suggestion_verbose(sugg_span, msg, suggestion, applicability); + } + + /// Performs method lookup. If lookup is successful, it will return the callee + /// and store an appropriate adjustment for the self-expr. In some cases it may + /// report an error (e.g., invoking the `drop` method). + /// + /// # Arguments + /// + /// Given a method call like `foo.bar::<T1,...Tn>(a, b + 1, ...)`: + /// + /// * `self`: the surrounding `FnCtxt` (!) + /// * `self_ty`: the (unadjusted) type of the self expression (`foo`) + /// * `segment`: the name and generic arguments of the method (`bar::<T1, ...Tn>`) + /// * `span`: the span for the method call + /// * `call_expr`: the complete method call: (`foo.bar::<T1,...Tn>(...)`) + /// * `self_expr`: the self expression (`foo`) + /// * `args`: the expressions of the arguments (`a, b + 1, ...`) + #[instrument(level = "debug", skip(self))] + pub fn lookup_method( + &self, + self_ty: Ty<'tcx>, + segment: &hir::PathSegment<'_>, + span: Span, + call_expr: &'tcx hir::Expr<'tcx>, + self_expr: &'tcx hir::Expr<'tcx>, + args: &'tcx [hir::Expr<'tcx>], + ) -> Result<MethodCallee<'tcx>, MethodError<'tcx>> { + let pick = + self.lookup_probe(span, segment.ident, self_ty, call_expr, ProbeScope::TraitsInScope)?; + + self.lint_dot_call_from_2018(self_ty, segment, span, call_expr, self_expr, &pick, args); + + for import_id in &pick.import_ids { + debug!("used_trait_import: {:?}", import_id); + Lrc::get_mut(&mut self.typeck_results.borrow_mut().used_trait_imports) + .unwrap() + .insert(*import_id); + } + + self.tcx.check_stability(pick.item.def_id, Some(call_expr.hir_id), span, None); + + let result = + self.confirm_method(span, self_expr, call_expr, self_ty, pick.clone(), segment); + debug!("result = {:?}", result); + + if let Some(span) = result.illegal_sized_bound { + let mut needs_mut = false; + if let ty::Ref(region, t_type, mutability) = self_ty.kind() { + let trait_type = self + .tcx + .mk_ref(*region, ty::TypeAndMut { ty: *t_type, mutbl: mutability.invert() }); + // We probe again to see if there might be a borrow mutability discrepancy. + match self.lookup_probe( + span, + segment.ident, + trait_type, + call_expr, + ProbeScope::TraitsInScope, + ) { + Ok(ref new_pick) if *new_pick != pick => { + needs_mut = true; + } + _ => {} + } + } + + // We probe again, taking all traits into account (not only those in scope). + let mut candidates = match self.lookup_probe( + span, + segment.ident, + self_ty, + call_expr, + ProbeScope::AllTraits, + ) { + // If we find a different result the caller probably forgot to import a trait. + Ok(ref new_pick) if *new_pick != pick => vec![new_pick.item.container_id(self.tcx)], + Err(Ambiguity(ref sources)) => sources + .iter() + .filter_map(|source| { + match *source { + // Note: this cannot come from an inherent impl, + // because the first probing succeeded. + CandidateSource::Impl(def) => self.tcx.trait_id_of_impl(def), + CandidateSource::Trait(_) => None, + } + }) + .collect(), + _ => Vec::new(), + }; + candidates.retain(|candidate| *candidate != self.tcx.parent(result.callee.def_id)); + + return Err(IllegalSizedBound(candidates, needs_mut, span)); + } + + Ok(result.callee) + } + + #[instrument(level = "debug", skip(self, call_expr))] + pub fn lookup_probe( + &self, + span: Span, + method_name: Ident, + self_ty: Ty<'tcx>, + call_expr: &'tcx hir::Expr<'tcx>, + scope: ProbeScope, + ) -> probe::PickResult<'tcx> { + let mode = probe::Mode::MethodCall; + let self_ty = self.resolve_vars_if_possible(self_ty); + self.probe_for_name( + span, + mode, + method_name, + IsSuggestion(false), + self_ty, + call_expr.hir_id, + scope, + ) + } + + pub(super) fn obligation_for_method( + &self, + span: Span, + trait_def_id: DefId, + self_ty: Ty<'tcx>, + opt_input_types: Option<&[Ty<'tcx>]>, + ) -> (traits::Obligation<'tcx, ty::Predicate<'tcx>>, &'tcx ty::List<ty::subst::GenericArg<'tcx>>) + { + // Construct a trait-reference `self_ty : Trait<input_tys>` + let substs = InternalSubsts::for_item(self.tcx, trait_def_id, |param, _| { + match param.kind { + GenericParamDefKind::Lifetime | GenericParamDefKind::Const { .. } => {} + GenericParamDefKind::Type { .. } => { + if param.index == 0 { + return self_ty.into(); + } else if let Some(input_types) = opt_input_types { + return input_types[param.index as usize - 1].into(); + } + } + } + self.var_for_def(span, param) + }); + + let trait_ref = ty::TraitRef::new(trait_def_id, substs); + + // Construct an obligation + let poly_trait_ref = ty::Binder::dummy(trait_ref); + ( + traits::Obligation::misc( + span, + self.body_id, + self.param_env, + poly_trait_ref.without_const().to_predicate(self.tcx), + ), + substs, + ) + } + + pub(super) fn obligation_for_op_method( + &self, + span: Span, + trait_def_id: DefId, + self_ty: Ty<'tcx>, + opt_input_type: Option<Ty<'tcx>>, + opt_input_expr: Option<&'tcx hir::Expr<'tcx>>, + expected: Expectation<'tcx>, + ) -> (traits::Obligation<'tcx, ty::Predicate<'tcx>>, &'tcx ty::List<ty::subst::GenericArg<'tcx>>) + { + // Construct a trait-reference `self_ty : Trait<input_tys>` + let substs = InternalSubsts::for_item(self.tcx, trait_def_id, |param, _| { + match param.kind { + GenericParamDefKind::Lifetime | GenericParamDefKind::Const { .. } => {} + GenericParamDefKind::Type { .. } => { + if param.index == 0 { + return self_ty.into(); + } else if let Some(input_type) = opt_input_type { + return input_type.into(); + } + } + } + self.var_for_def(span, param) + }); + + let trait_ref = ty::TraitRef::new(trait_def_id, substs); + + // Construct an obligation + let poly_trait_ref = ty::Binder::dummy(trait_ref); + let output_ty = expected.only_has_type(self).and_then(|ty| (!ty.needs_infer()).then(|| ty)); + + ( + traits::Obligation::new( + traits::ObligationCause::new( + span, + self.body_id, + traits::BinOp { + rhs_span: opt_input_expr.map(|expr| expr.span), + is_lit: opt_input_expr + .map_or(false, |expr| matches!(expr.kind, hir::ExprKind::Lit(_))), + output_ty, + }, + ), + self.param_env, + poly_trait_ref.without_const().to_predicate(self.tcx), + ), + substs, + ) + } + + /// `lookup_method_in_trait` is used for overloaded operators. + /// It does a very narrow slice of what the normal probe/confirm path does. + /// In particular, it doesn't really do any probing: it simply constructs + /// an obligation for a particular trait with the given self type and checks + /// whether that trait is implemented. + #[instrument(level = "debug", skip(self, span))] + pub(super) fn lookup_method_in_trait( + &self, + span: Span, + m_name: Ident, + trait_def_id: DefId, + self_ty: Ty<'tcx>, + opt_input_types: Option<&[Ty<'tcx>]>, + ) -> Option<InferOk<'tcx, MethodCallee<'tcx>>> { + let (obligation, substs) = + self.obligation_for_method(span, trait_def_id, self_ty, opt_input_types); + self.construct_obligation_for_trait( + span, + m_name, + trait_def_id, + obligation, + substs, + None, + false, + ) + } + + pub(super) fn lookup_op_method_in_trait( + &self, + span: Span, + m_name: Ident, + trait_def_id: DefId, + self_ty: Ty<'tcx>, + opt_input_type: Option<Ty<'tcx>>, + opt_input_expr: Option<&'tcx hir::Expr<'tcx>>, + expected: Expectation<'tcx>, + ) -> Option<InferOk<'tcx, MethodCallee<'tcx>>> { + let (obligation, substs) = self.obligation_for_op_method( + span, + trait_def_id, + self_ty, + opt_input_type, + opt_input_expr, + expected, + ); + self.construct_obligation_for_trait( + span, + m_name, + trait_def_id, + obligation, + substs, + opt_input_expr, + true, + ) + } + + // FIXME(#18741): it seems likely that we can consolidate some of this + // code with the other method-lookup code. In particular, the second half + // of this method is basically the same as confirmation. + fn construct_obligation_for_trait( + &self, + span: Span, + m_name: Ident, + trait_def_id: DefId, + obligation: traits::PredicateObligation<'tcx>, + substs: &'tcx ty::List<ty::subst::GenericArg<'tcx>>, + opt_input_expr: Option<&'tcx hir::Expr<'tcx>>, + is_op: bool, + ) -> Option<InferOk<'tcx, MethodCallee<'tcx>>> { + debug!(?obligation); + + // Now we want to know if this can be matched + if !self.predicate_may_hold(&obligation) { + debug!("--> Cannot match obligation"); + // Cannot be matched, no such method resolution is possible. + return None; + } + + // Trait must have a method named `m_name` and it should not have + // type parameters or early-bound regions. + let tcx = self.tcx; + let Some(method_item) = self.associated_value(trait_def_id, m_name) else { + tcx.sess.delay_span_bug( + span, + "operator trait does not have corresponding operator method", + ); + return None; + }; + let def_id = method_item.def_id; + let generics = tcx.generics_of(def_id); + assert_eq!(generics.params.len(), 0); + + debug!("lookup_in_trait_adjusted: method_item={:?}", method_item); + let mut obligations = vec![]; + + // Instantiate late-bound regions and substitute the trait + // parameters into the method type to get the actual method type. + // + // N.B., instantiate late-bound regions first so that + // `instantiate_type_scheme` can normalize associated types that + // may reference those regions. + let fn_sig = tcx.bound_fn_sig(def_id); + let fn_sig = fn_sig.subst(self.tcx, substs); + let fn_sig = self.replace_bound_vars_with_fresh_vars(span, infer::FnCall, fn_sig); + + let InferOk { value, obligations: o } = if is_op { + self.normalize_op_associated_types_in_as_infer_ok(span, fn_sig, opt_input_expr) + } else { + self.normalize_associated_types_in_as_infer_ok(span, fn_sig) + }; + let fn_sig = { + obligations.extend(o); + value + }; + + // Register obligations for the parameters. This will include the + // `Self` parameter, which in turn has a bound of the main trait, + // so this also effectively registers `obligation` as well. (We + // used to register `obligation` explicitly, but that resulted in + // double error messages being reported.) + // + // Note that as the method comes from a trait, it should not have + // any late-bound regions appearing in its bounds. + let bounds = self.tcx.predicates_of(def_id).instantiate(self.tcx, substs); + + let InferOk { value, obligations: o } = if is_op { + self.normalize_op_associated_types_in_as_infer_ok(span, bounds, opt_input_expr) + } else { + self.normalize_associated_types_in_as_infer_ok(span, bounds) + }; + let bounds = { + obligations.extend(o); + value + }; + + assert!(!bounds.has_escaping_bound_vars()); + + let cause = if is_op { + ObligationCause::new( + span, + self.body_id, + traits::BinOp { + rhs_span: opt_input_expr.map(|expr| expr.span), + is_lit: opt_input_expr + .map_or(false, |expr| matches!(expr.kind, hir::ExprKind::Lit(_))), + output_ty: None, + }, + ) + } else { + traits::ObligationCause::misc(span, self.body_id) + }; + let predicates_cause = cause.clone(); + obligations.extend(traits::predicates_for_generics( + move |_, _| predicates_cause.clone(), + self.param_env, + bounds, + )); + + // Also add an obligation for the method type being well-formed. + let method_ty = tcx.mk_fn_ptr(ty::Binder::dummy(fn_sig)); + debug!( + "lookup_in_trait_adjusted: matched method method_ty={:?} obligation={:?}", + method_ty, obligation + ); + obligations.push(traits::Obligation::new( + cause, + self.param_env, + ty::Binder::dummy(ty::PredicateKind::WellFormed(method_ty.into())).to_predicate(tcx), + )); + + let callee = MethodCallee { def_id, substs, sig: fn_sig }; + + debug!("callee = {:?}", callee); + + Some(InferOk { obligations, value: callee }) + } + + /// Performs a [full-qualified function call] (formerly "universal function call") lookup. If + /// lookup is successful, it will return the type of definition and the [`DefId`] of the found + /// function definition. + /// + /// [full-qualified function call]: https://doc.rust-lang.org/reference/expressions/call-expr.html#disambiguating-function-calls + /// + /// # Arguments + /// + /// Given a function call like `Foo::bar::<T1,...Tn>(...)`: + /// + /// * `self`: the surrounding `FnCtxt` (!) + /// * `span`: the span of the call, excluding arguments (`Foo::bar::<T1, ...Tn>`) + /// * `method_name`: the identifier of the function within the container type (`bar`) + /// * `self_ty`: the type to search within (`Foo`) + /// * `self_ty_span` the span for the type being searched within (span of `Foo`) + /// * `expr_id`: the [`hir::HirId`] of the expression composing the entire call + #[instrument(level = "debug", skip(self), ret)] + pub fn resolve_fully_qualified_call( + &self, + span: Span, + method_name: Ident, + self_ty: Ty<'tcx>, + self_ty_span: Span, + expr_id: hir::HirId, + ) -> Result<(DefKind, DefId), MethodError<'tcx>> { + let tcx = self.tcx; + + // Check if we have an enum variant. + if let ty::Adt(adt_def, _) = self_ty.kind() { + if adt_def.is_enum() { + let variant_def = adt_def + .variants() + .iter() + .find(|vd| tcx.hygienic_eq(method_name, vd.ident(tcx), adt_def.did())); + if let Some(variant_def) = variant_def { + // Braced variants generate unusable names in value namespace (reserved for + // possible future use), so variants resolved as associated items may refer to + // them as well. It's ok to use the variant's id as a ctor id since an + // error will be reported on any use of such resolution anyway. + let ctor_def_id = variant_def.ctor_def_id.unwrap_or(variant_def.def_id); + tcx.check_stability(ctor_def_id, Some(expr_id), span, Some(method_name.span)); + return Ok(( + DefKind::Ctor(CtorOf::Variant, variant_def.ctor_kind), + ctor_def_id, + )); + } + } + } + + let pick = self.probe_for_name( + span, + probe::Mode::Path, + method_name, + IsSuggestion(false), + self_ty, + expr_id, + ProbeScope::TraitsInScope, + )?; + + self.lint_fully_qualified_call_from_2018( + span, + method_name, + self_ty, + self_ty_span, + expr_id, + &pick, + ); + + debug!(?pick); + { + let mut typeck_results = self.typeck_results.borrow_mut(); + let used_trait_imports = Lrc::get_mut(&mut typeck_results.used_trait_imports).unwrap(); + for import_id in pick.import_ids { + debug!(used_trait_import=?import_id); + used_trait_imports.insert(import_id); + } + } + + let def_kind = pick.item.kind.as_def_kind(); + tcx.check_stability(pick.item.def_id, Some(expr_id), span, Some(method_name.span)); + Ok((def_kind, pick.item.def_id)) + } + + /// Finds item with name `item_name` defined in impl/trait `def_id` + /// and return it, or `None`, if no such item was defined there. + pub fn associated_value(&self, def_id: DefId, item_name: Ident) -> Option<ty::AssocItem> { + self.tcx + .associated_items(def_id) + .find_by_name_and_namespace(self.tcx, item_name, Namespace::ValueNS, def_id) + .copied() + } +} diff --git a/compiler/rustc_typeck/src/check/method/prelude2021.rs b/compiler/rustc_hir_typeck/src/method/prelude2021.rs index 7c68d9304..3c98a2aa3 100644 --- a/compiler/rustc_typeck/src/check/method/prelude2021.rs +++ b/compiler/rustc_hir_typeck/src/method/prelude2021.rs @@ -1,3 +1,7 @@ +use crate::{ + method::probe::{self, Pick}, + FnCtxt, +}; use hir::def_id::DefId; use hir::HirId; use hir::ItemKind; @@ -12,11 +16,6 @@ use rustc_span::symbol::{sym, Ident}; use rustc_span::Span; use rustc_trait_selection::infer::InferCtxtExt; -use crate::check::{ - method::probe::{self, Pick}, - FnCtxt, -}; - impl<'a, 'tcx> FnCtxt<'a, 'tcx> { pub(super) fn lint_dot_call_from_2018( &self, @@ -82,14 +81,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { prelude_or_array_lint, self_expr.hir_id, self_expr.span, + format!("trait method `{}` will become ambiguous in Rust 2021", segment.ident.name), |lint| { let sp = self_expr.span; - let mut lint = lint.build(&format!( - "trait method `{}` will become ambiguous in Rust 2021", - segment.ident.name - )); - let derefs = "*".repeat(pick.autoderefs); let autoref = match pick.autoref_or_ptr_adjustment { @@ -133,7 +128,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ); } - lint.emit(); + lint }, ); } else { @@ -143,6 +138,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { prelude_or_array_lint, call_expr.hir_id, call_expr.span, + format!("trait method `{}` will become ambiguous in Rust 2021", segment.ident.name), |lint| { let sp = call_expr.span; let trait_name = self.trait_path_or_bare_name( @@ -151,16 +147,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { pick.item.container_id(self.tcx), ); - let mut lint = lint.build(&format!( - "trait method `{}` will become ambiguous in Rust 2021", - segment.ident.name - )); - let (self_adjusted, precise) = self.adjust_expr(pick, self_expr, sp); if precise { let args = args .iter() - .skip(1) .map(|arg| { let span = arg.span.find_ancestor_inside(sp).unwrap_or_default(); format!( @@ -203,7 +193,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ); } - lint.emit(); + lint }, ); } @@ -258,15 +248,23 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { return; } - self.tcx.struct_span_lint_hir(RUST_2021_PRELUDE_COLLISIONS, expr_id, span, |lint| { - // "type" refers to either a type or, more likely, a trait from which - // the associated function or method is from. - let container_id = pick.item.container_id(self.tcx); - let trait_path = self.trait_path_or_bare_name(span, expr_id, container_id); - let trait_generics = self.tcx.generics_of(container_id); - - let trait_name = - if trait_generics.params.len() <= trait_generics.has_self as usize { + self.tcx.struct_span_lint_hir( + RUST_2021_PRELUDE_COLLISIONS, + expr_id, + span, + format!( + "trait-associated function `{}` will become ambiguous in Rust 2021", + method_name.name + ), + |lint| { + // "type" refers to either a type or, more likely, a trait from which + // the associated function or method is from. + let container_id = pick.item.container_id(self.tcx); + let trait_path = self.trait_path_or_bare_name(span, expr_id, container_id); + let trait_generics = self.tcx.generics_of(container_id); + + let trait_name = if trait_generics.params.len() <= trait_generics.has_self as usize + { trait_path } else { let counts = trait_generics.own_counts(); @@ -283,44 +281,42 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ) }; - let mut lint = lint.build(&format!( - "trait-associated function `{}` will become ambiguous in Rust 2021", - method_name.name - )); - - let mut self_ty_name = self_ty_span - .find_ancestor_inside(span) - .and_then(|span| self.sess().source_map().span_to_snippet(span).ok()) - .unwrap_or_else(|| self_ty.to_string()); - - // Get the number of generics the self type has (if an Adt) unless we can determine that - // the user has written the self type with generics already which we (naively) do by looking - // for a "<" in `self_ty_name`. - if !self_ty_name.contains('<') { - if let Adt(def, _) = self_ty.kind() { - let generics = self.tcx.generics_of(def.did()); - if !generics.params.is_empty() { - let counts = generics.own_counts(); - self_ty_name += &format!( - "<{}>", - std::iter::repeat("'_") - .take(counts.lifetimes) - .chain(std::iter::repeat("_").take(counts.types + counts.consts)) - .collect::<Vec<_>>() - .join(", ") - ); + let mut self_ty_name = self_ty_span + .find_ancestor_inside(span) + .and_then(|span| self.sess().source_map().span_to_snippet(span).ok()) + .unwrap_or_else(|| self_ty.to_string()); + + // Get the number of generics the self type has (if an Adt) unless we can determine that + // the user has written the self type with generics already which we (naively) do by looking + // for a "<" in `self_ty_name`. + if !self_ty_name.contains('<') { + if let Adt(def, _) = self_ty.kind() { + let generics = self.tcx.generics_of(def.did()); + if !generics.params.is_empty() { + let counts = generics.own_counts(); + self_ty_name += &format!( + "<{}>", + std::iter::repeat("'_") + .take(counts.lifetimes) + .chain( + std::iter::repeat("_").take(counts.types + counts.consts) + ) + .collect::<Vec<_>>() + .join(", ") + ); + } } } - } - lint.span_suggestion( - span, - "disambiguate the associated function", - format!("<{} as {}>::{}", self_ty_name, trait_name, method_name.name,), - Applicability::MachineApplicable, - ); - - lint.emit(); - }); + lint.span_suggestion( + span, + "disambiguate the associated function", + format!("<{} as {}>::{}", self_ty_name, trait_name, method_name.name,), + Applicability::MachineApplicable, + ); + + lint + }, + ); } fn trait_path_or_bare_name( diff --git a/compiler/rustc_typeck/src/check/method/probe.rs b/compiler/rustc_hir_typeck/src/method/probe.rs index efe15fec7..28aa2302f 100644 --- a/compiler/rustc_typeck/src/check/method/probe.rs +++ b/compiler/rustc_hir_typeck/src/method/probe.rs @@ -3,14 +3,12 @@ use super::CandidateSource; use super::MethodError; use super::NoMatchData; -use crate::check::FnCtxt; use crate::errors::MethodCallOnUnknownType; -use crate::hir::def::DefKind; -use crate::hir::def_id::DefId; - +use crate::FnCtxt; use rustc_data_structures::fx::FxHashSet; use rustc_errors::Applicability; use rustc_hir as hir; +use rustc_hir::def::DefKind; use rustc_hir::def::Namespace; use rustc_infer::infer::canonical::OriginalQueryValues; use rustc_infer::infer::canonical::{Canonical, QueryResponse}; @@ -19,10 +17,11 @@ use rustc_infer::infer::{self, InferOk, TyCtxtInferExt}; use rustc_middle::infer::unify_key::{ConstVariableOrigin, ConstVariableOriginKind}; use rustc_middle::middle::stability; use rustc_middle::ty::fast_reject::{simplify_type, TreatParams}; -use rustc_middle::ty::subst::{InternalSubsts, Subst, SubstsRef}; use rustc_middle::ty::GenericParamDefKind; use rustc_middle::ty::{self, ParamEnvAnd, ToPredicate, Ty, TyCtxt, TypeFoldable, TypeVisitable}; +use rustc_middle::ty::{InternalSubsts, SubstsRef}; use rustc_session::lint; +use rustc_span::def_id::DefId; use rustc_span::def_id::LocalDefId; use rustc_span::lev_distance::{ find_best_match_for_name_with_substrings, lev_distance_with_substrings, @@ -253,7 +252,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { /// would result in an error (basically, the same criteria we /// would use to decide if a method is a plausible fit for /// ambiguity purposes). - #[instrument(level = "debug", skip(self, scope_expr_id))] + #[instrument(level = "debug", skip(self, candidate_filter))] pub fn probe_for_return_type( &self, span: Span, @@ -261,11 +260,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { return_type: Ty<'tcx>, self_ty: Ty<'tcx>, scope_expr_id: hir::HirId, + candidate_filter: impl Fn(&ty::AssocItem) -> bool, ) -> Vec<ty::AssocItem> { - debug!( - "probe(self_ty={:?}, return_type={}, scope_expr_id={})", - self_ty, return_type, scope_expr_id - ); let method_names = self .probe_op( span, @@ -276,7 +272,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self_ty, scope_expr_id, ProbeScope::AllTraits, - |probe_cx| Ok(probe_cx.candidate_method_names()), + |probe_cx| Ok(probe_cx.candidate_method_names(candidate_filter)), ) .unwrap_or_default(); method_names @@ -299,7 +295,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { .collect() } - #[instrument(level = "debug", skip(self, scope_expr_id))] + #[instrument(level = "debug", skip(self))] pub fn probe_for_name( &self, span: Span, @@ -310,10 +306,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { scope_expr_id: hir::HirId, scope: ProbeScope, ) -> PickResult<'tcx> { - debug!( - "probe(self_ty={:?}, item_name={}, scope_expr_id={})", - self_ty, item_name, scope_expr_id - ); self.probe_op( span, mode, @@ -417,9 +409,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { lint::builtin::TYVAR_BEHIND_RAW_POINTER, scope_expr_id, span, - |lint| { - lint.build("type annotations needed").emit(); - }, + "type annotations needed", + |lint| lint, ); } } else { @@ -481,69 +472,65 @@ fn method_autoderef_steps<'tcx>( ) -> MethodAutoderefStepsResult<'tcx> { debug!("method_autoderef_steps({:?})", goal); - tcx.infer_ctxt().enter_with_canonical(DUMMY_SP, &goal, |ref infcx, goal, inference_vars| { - let ParamEnvAnd { param_env, value: self_ty } = goal; - - let mut autoderef = - Autoderef::new(infcx, param_env, hir::CRATE_HIR_ID, DUMMY_SP, self_ty, DUMMY_SP) - .include_raw_pointers() - .silence_errors(); - let mut reached_raw_pointer = false; - let mut steps: Vec<_> = autoderef - .by_ref() - .map(|(ty, d)| { - let step = CandidateStep { - self_ty: infcx.make_query_response_ignoring_pending_obligations( - inference_vars.clone(), - ty, - ), - autoderefs: d, - from_unsafe_deref: reached_raw_pointer, - unsize: false, - }; - if let ty::RawPtr(_) = ty.kind() { - // all the subsequent steps will be from_unsafe_deref - reached_raw_pointer = true; - } - step - }) - .collect(); - - let final_ty = autoderef.final_ty(true); - let opt_bad_ty = match final_ty.kind() { - ty::Infer(ty::TyVar(_)) | ty::Error(_) => Some(MethodAutoderefBadTy { - reached_raw_pointer, - ty: infcx - .make_query_response_ignoring_pending_obligations(inference_vars, final_ty), - }), - ty::Array(elem_ty, _) => { - let dereferences = steps.len() - 1; - - steps.push(CandidateStep { - self_ty: infcx.make_query_response_ignoring_pending_obligations( - inference_vars, - infcx.tcx.mk_slice(*elem_ty), - ), - autoderefs: dereferences, - // this could be from an unsafe deref if we had - // a *mut/const [T; N] - from_unsafe_deref: reached_raw_pointer, - unsize: true, - }); - - None + let (ref infcx, goal, inference_vars) = tcx.infer_ctxt().build_with_canonical(DUMMY_SP, &goal); + let ParamEnvAnd { param_env, value: self_ty } = goal; + + let mut autoderef = + Autoderef::new(infcx, param_env, hir::CRATE_HIR_ID, DUMMY_SP, self_ty, DUMMY_SP) + .include_raw_pointers() + .silence_errors(); + let mut reached_raw_pointer = false; + let mut steps: Vec<_> = autoderef + .by_ref() + .map(|(ty, d)| { + let step = CandidateStep { + self_ty: infcx + .make_query_response_ignoring_pending_obligations(inference_vars.clone(), ty), + autoderefs: d, + from_unsafe_deref: reached_raw_pointer, + unsize: false, + }; + if let ty::RawPtr(_) = ty.kind() { + // all the subsequent steps will be from_unsafe_deref + reached_raw_pointer = true; } - _ => None, - }; - - debug!("method_autoderef_steps: steps={:?} opt_bad_ty={:?}", steps, opt_bad_ty); + step + }) + .collect(); + + let final_ty = autoderef.final_ty(true); + let opt_bad_ty = match final_ty.kind() { + ty::Infer(ty::TyVar(_)) | ty::Error(_) => Some(MethodAutoderefBadTy { + reached_raw_pointer, + ty: infcx.make_query_response_ignoring_pending_obligations(inference_vars, final_ty), + }), + ty::Array(elem_ty, _) => { + let dereferences = steps.len() - 1; + + steps.push(CandidateStep { + self_ty: infcx.make_query_response_ignoring_pending_obligations( + inference_vars, + infcx.tcx.mk_slice(*elem_ty), + ), + autoderefs: dereferences, + // this could be from an unsafe deref if we had + // a *mut/const [T; N] + from_unsafe_deref: reached_raw_pointer, + unsize: true, + }); - MethodAutoderefStepsResult { - steps: tcx.arena.alloc_from_iter(steps), - opt_bad_ty: opt_bad_ty.map(|ty| &*tcx.arena.alloc(ty)), - reached_recursion_limit: autoderef.reached_recursion_limit(), + None } - }) + _ => None, + }; + + debug!("method_autoderef_steps: steps={:?} opt_bad_ty={:?}", steps, opt_bad_ty); + + MethodAutoderefStepsResult { + steps: tcx.arena.alloc_from_iter(steps), + opt_bad_ty: opt_bad_ty.map(|ty| &*tcx.arena.alloc(ty)), + reached_recursion_limit: autoderef.reached_recursion_limit(), + } } impl<'a, 'tcx> ProbeContext<'a, 'tcx> { @@ -980,12 +967,16 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { } } - fn candidate_method_names(&self) -> Vec<Ident> { + fn candidate_method_names( + &self, + candidate_filter: impl Fn(&ty::AssocItem) -> bool, + ) -> Vec<Ident> { let mut set = FxHashSet::default(); let mut names: Vec<_> = self .inherent_candidates .iter() .chain(&self.extension_candidates) + .filter(|candidate| candidate_filter(&candidate.item)) .filter(|candidate| { if let Some(return_ty) = self.return_type { self.matches_return_type(&candidate.item, None, return_ty) @@ -1366,24 +1357,24 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { stable_pick: &Pick<'_>, unstable_candidates: &[(Candidate<'tcx>, Symbol)], ) { + let def_kind = stable_pick.item.kind.as_def_kind(); self.tcx.struct_span_lint_hir( lint::builtin::UNSTABLE_NAME_COLLISIONS, self.scope_expr_id, self.span, + format!( + "{} {} with this name may be added to the standard library in the future", + def_kind.article(), + def_kind.descr(stable_pick.item.def_id), + ), |lint| { - let def_kind = stable_pick.item.kind.as_def_kind(); - let mut diag = lint.build(&format!( - "{} {} with this name may be added to the standard library in the future", - def_kind.article(), - def_kind.descr(stable_pick.item.def_id), - )); match (stable_pick.item.kind, stable_pick.item.container) { (ty::AssocKind::Fn, _) => { // FIXME: This should be a `span_suggestion` instead of `help` // However `self.span` only // highlights the method name, so we can't use it. Also consider reusing // the code from `report_method_error()`. - diag.help(&format!( + lint.help(&format!( "call with fully qualified syntax `{}(...)` to keep using the current \ method", self.tcx.def_path_str(stable_pick.item.def_id), @@ -1391,7 +1382,7 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { } (ty::AssocKind::Const, ty::AssocItemContainer::TraitContainer) => { let def_id = stable_pick.item.container_id(self.tcx); - diag.span_suggestion( + lint.span_suggestion( self.span, "use the fully qualified path to the associated const", format!( @@ -1407,7 +1398,7 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { } if self.tcx.sess.is_nightly_build() { for (candidate, feature) in unstable_candidates { - diag.help(&format!( + lint.help(&format!( "add `#![feature({})]` to the crate attributes to enable `{}`", feature, self.tcx.def_path_str(candidate.item.def_id), @@ -1415,7 +1406,7 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { } } - diag.emit(); + lint }, ); } @@ -1514,8 +1505,11 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { traits::normalize(selcx, self.param_env, cause.clone(), impl_bounds); // Convert the bounds into obligations. - let impl_obligations = - traits::predicates_for_generics(cause, self.param_env, impl_bounds); + let impl_obligations = traits::predicates_for_generics( + move |_, _| cause.clone(), + self.param_env, + impl_bounds, + ); let candidate_obligations = impl_obligations .chain(norm_obligations.into_iter()) @@ -1700,7 +1694,7 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { pcx.allow_similar_names = true; pcx.assemble_inherent_candidates(); - let method_names = pcx.candidate_method_names(); + let method_names = pcx.candidate_method_names(|_| true); pcx.allow_similar_names = false; let applicable_close_candidates: Vec<ty::AssocItem> = method_names .iter() diff --git a/compiler/rustc_typeck/src/check/method/suggest.rs b/compiler/rustc_hir_typeck/src/method/suggest.rs index c92b93cbc..6c21ed902 100644 --- a/compiler/rustc_typeck/src/check/method/suggest.rs +++ b/compiler/rustc_hir_typeck/src/method/suggest.rs @@ -1,7 +1,9 @@ //! Give useful errors and suggestions to users when an item can't be //! found or is otherwise invalid. -use crate::check::FnCtxt; +use crate::errors; +use crate::FnCtxt; +use rustc_ast::ast::Mutability; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_errors::{ pluralize, struct_span_err, Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed, @@ -16,12 +18,12 @@ use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKi use rustc_middle::traits::util::supertraits; use rustc_middle::ty::fast_reject::{simplify_type, TreatParams}; use rustc_middle::ty::print::with_crate_prefix; -use rustc_middle::ty::ToPolyTraitRef; use rustc_middle::ty::{self, DefIdTree, ToPredicate, Ty, TyCtxt, TypeVisitable}; +use rustc_middle::ty::{IsSuggestable, ToPolyTraitRef}; use rustc_span::symbol::{kw, sym, Ident}; use rustc_span::Symbol; use rustc_span::{lev_distance, source_map, ExpnKind, FileName, MacroKind, Span}; -use rustc_trait_selection::traits::error_reporting::on_unimplemented::InferCtxtExt as _; +use rustc_trait_selection::traits::error_reporting::on_unimplemented::TypeErrCtxtExt as _; use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _; use rustc_trait_selection::traits::{ FulfillmentError, Obligation, ObligationCause, ObligationCauseCode, OnUnimplementedNote, @@ -30,8 +32,8 @@ use rustc_trait_selection::traits::{ use std::cmp::Ordering; use std::iter; -use super::probe::{Mode, ProbeScope}; -use super::{super::suggest_call_constructor, CandidateSource, MethodError, NoMatchData}; +use super::probe::{AutorefOrPtrAdjustment, IsSuggestion, Mode, ProbeScope}; +use super::{CandidateSource, MethodError, NoMatchData}; impl<'a, 'tcx> FnCtxt<'a, 'tcx> { fn is_fn_ty(&self, ty: Ty<'tcx>, span: Span) -> bool { @@ -95,7 +97,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { item_name: Ident, source: SelfSource<'tcx>, error: MethodError<'tcx>, - args: Option<&'tcx [hir::Expr<'tcx>]>, + args: Option<(&'tcx hir::Expr<'tcx>, &'tcx [hir::Expr<'tcx>])>, ) -> Option<DiagnosticBuilder<'_, ErrorGuaranteed>> { // Avoid suggestions when we don't know what's going on. if rcvr_ty.references_error() { @@ -104,7 +106,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let report_candidates = |span: Span, err: &mut Diagnostic, - mut sources: Vec<CandidateSource>, + sources: &mut Vec<CandidateSource>, sugg_span: Span| { sources.sort(); sources.dedup(); @@ -246,7 +248,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { match error { MethodError::NoMatch(NoMatchData { - static_candidates: static_sources, + static_candidates: mut static_sources, unsatisfied_predicates, out_of_scope_traits, lev_candidate, @@ -270,7 +272,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } }; - if self.suggest_constraining_numerical_ty( + if self.suggest_wrapping_range_with_parens( + tcx, actual, source, span, item_name, &ty_str, + ) || self.suggest_constraining_numerical_ty( tcx, actual, source, span, item_kind, item_name, &ty_str, ) { return None; @@ -363,44 +367,21 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ); } - if self.is_fn_ty(rcvr_ty, span) { - if let SelfSource::MethodCall(expr) = source { - let suggest = if let ty::FnDef(def_id, _) = rcvr_ty.kind() { - if let Some(local_id) = def_id.as_local() { - let hir_id = tcx.hir().local_def_id_to_hir_id(local_id); - let node = tcx.hir().get(hir_id); - let fields = node.tuple_fields(); - if let Some(fields) = fields - && let Some(DefKind::Ctor(of, _)) = self.tcx.opt_def_kind(local_id) { - Some((fields.len(), of)) - } else { - None - } - } else { - // The logic here isn't smart but `associated_item_def_ids` - // doesn't work nicely on local. - if let DefKind::Ctor(of, _) = tcx.def_kind(def_id) { - let parent_def_id = tcx.parent(*def_id); - Some((tcx.associated_item_def_ids(parent_def_id).len(), of)) - } else { - None - } - } - } else { - None - }; - - // If the function is a tuple constructor, we recommend that they call it - if let Some((fields, kind)) = suggest { - suggest_call_constructor(expr.span, kind, fields, &mut err); - } else { - // General case - err.span_label( - expr.span, - "this is a function, perhaps you wish to call it", - ); - } - } + if let SelfSource::MethodCall(rcvr_expr) = source { + self.suggest_fn_call(&mut err, rcvr_expr, rcvr_ty, |output_ty| { + let call_expr = self + .tcx + .hir() + .expect_expr(self.tcx.hir().get_parent_node(rcvr_expr.hir_id)); + let probe = self.lookup_probe( + span, + item_name, + output_ty, + call_expr, + ProbeScope::AllTraits, + ); + probe.is_ok() + }); } let mut custom_span_label = false; @@ -441,9 +422,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { err.help(&format!("try with `{}::{}`", ty_str, item_name,)); } - report_candidates(span, &mut err, static_sources, sugg_span); + report_candidates(span, &mut err, &mut static_sources, sugg_span); } else if static_sources.len() > 1 { - report_candidates(span, &mut err, static_sources, sugg_span); + report_candidates(span, &mut err, &mut static_sources, sugg_span); } let mut bound_spans = vec![]; @@ -560,7 +541,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { bound_spans.push((self.tcx.def_span(def.did()), msg)) } // Point at the trait object that couldn't satisfy the bound. - ty::Dynamic(preds, _) => { + ty::Dynamic(preds, _, _) => { for pred in preds.iter() { match pred.skip_binder() { ty::ExistentialPredicate::Trait(tr) => bound_spans @@ -877,8 +858,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // Avoid crashing. return (None, None); } - let OnUnimplementedNote { message, label, .. } = - self.on_unimplemented_note(trait_ref, &obligation); + let OnUnimplementedNote { message, label, .. } = self + .err_ctxt() + .on_unimplemented_note(trait_ref, &obligation); (message, label) }) .unwrap_or((None, None)) @@ -904,7 +886,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } - let label_span_not_found = |err: &mut DiagnosticBuilder<'_, _>| { + let label_span_not_found = |err: &mut Diagnostic| { if unsatisfied_predicates.is_empty() { err.span_label(span, format!("{item_kind} not found in `{ty_str}`")); let is_string_or_ref_str = match actual.kind() { @@ -1000,9 +982,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { label_span_not_found(&mut err); } - self.check_for_field_method(&mut err, source, span, actual, item_name); + // Don't suggest (for example) `expr.field.method()` if `expr.method()` + // doesn't exist due to unsatisfied predicates. + if unsatisfied_predicates.is_empty() { + self.check_for_field_method(&mut err, source, span, actual, item_name); + } - self.check_for_unwrap_self(&mut err, source, span, actual, item_name); + self.check_for_inner_self(&mut err, source, span, actual, item_name); bound_spans.sort(); bound_spans.dedup(); @@ -1017,10 +1003,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { span, rcvr_ty, item_name, - args.map(|args| args.len()), + args.map(|(_, args)| args.len() + 1), source, out_of_scope_traits, &unsatisfied_predicates, + &static_sources, unsatisfied_bounds, ); } @@ -1062,23 +1049,38 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // that had unsatisfied trait bounds if unsatisfied_predicates.is_empty() { let def_kind = lev_candidate.kind.as_def_kind(); - err.span_suggestion( - span, - &format!( - "there is {} {} with a similar name", - def_kind.article(), - def_kind.descr(lev_candidate.def_id), - ), - lev_candidate.name, - Applicability::MaybeIncorrect, - ); + // Methods are defined within the context of a struct and their first parameter is always self, + // which represents the instance of the struct the method is being called on + // Associated functions don’t take self as a parameter and + // they are not methods because they don’t have an instance of the struct to work with. + if def_kind == DefKind::AssocFn && lev_candidate.fn_has_self_parameter { + err.span_suggestion( + span, + &format!("there is a method with a similar name",), + lev_candidate.name, + Applicability::MaybeIncorrect, + ); + } else { + err.span_suggestion( + span, + &format!( + "there is {} {} with a similar name", + def_kind.article(), + def_kind.descr(lev_candidate.def_id), + ), + lev_candidate.name, + Applicability::MaybeIncorrect, + ); + } } } + self.check_for_deref_method(&mut err, source, rcvr_ty, item_name); + return Some(err); } - MethodError::Ambiguity(sources) => { + MethodError::Ambiguity(mut sources) => { let mut err = struct_span_err!( self.sess(), item_name.span, @@ -1087,7 +1089,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ); err.span_label(item_name.span, format!("multiple `{}` found", item_name)); - report_candidates(span, &mut err, sources, sugg_span); + report_candidates(span, &mut err, &mut sources, sugg_span); err.emit(); } @@ -1150,7 +1152,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { rcvr_ty: Ty<'tcx>, expr: &hir::Expr<'_>, item_name: Ident, - err: &mut DiagnosticBuilder<'tcx, ErrorGuaranteed>, + err: &mut Diagnostic, ) -> bool { let tcx = self.tcx; let field_receiver = self.autoderef(span, rcvr_ty).find_map(|(ty, _)| match ty.kind() { @@ -1165,7 +1167,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { _ => None, }); if let Some((field, field_ty)) = field_receiver { - let scope = tcx.parent_module(self.body_id).to_def_id(); + let scope = tcx.parent_module(self.body_id); let is_accessible = field.vis.is_accessible_from(scope, tcx); if is_accessible { @@ -1204,6 +1206,89 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { false } + /// Suggest possible range with adding parentheses, for example: + /// when encountering `0..1.map(|i| i + 1)` suggest `(0..1).map(|i| i + 1)`. + fn suggest_wrapping_range_with_parens( + &self, + tcx: TyCtxt<'tcx>, + actual: Ty<'tcx>, + source: SelfSource<'tcx>, + span: Span, + item_name: Ident, + ty_str: &str, + ) -> bool { + if let SelfSource::MethodCall(expr) = source { + for (_, parent) in tcx.hir().parent_iter(expr.hir_id).take(5) { + if let Node::Expr(parent_expr) = parent { + let lang_item = match parent_expr.kind { + ExprKind::Struct(ref qpath, _, _) => match **qpath { + QPath::LangItem(LangItem::Range, ..) => Some(LangItem::Range), + QPath::LangItem(LangItem::RangeTo, ..) => Some(LangItem::RangeTo), + QPath::LangItem(LangItem::RangeToInclusive, ..) => { + Some(LangItem::RangeToInclusive) + } + _ => None, + }, + ExprKind::Call(ref func, _) => match func.kind { + // `..=` desugars into `::std::ops::RangeInclusive::new(...)`. + ExprKind::Path(QPath::LangItem(LangItem::RangeInclusiveNew, ..)) => { + Some(LangItem::RangeInclusiveStruct) + } + _ => None, + }, + _ => None, + }; + + if lang_item.is_none() { + continue; + } + + let span_included = match parent_expr.kind { + hir::ExprKind::Struct(_, eps, _) => { + eps.len() > 0 && eps.last().map_or(false, |ep| ep.span.contains(span)) + } + // `..=` desugars into `::std::ops::RangeInclusive::new(...)`. + hir::ExprKind::Call(ref func, ..) => func.span.contains(span), + _ => false, + }; + + if !span_included { + continue; + } + + let range_def_id = self.tcx.require_lang_item(lang_item.unwrap(), None); + let range_ty = + self.tcx.bound_type_of(range_def_id).subst(self.tcx, &[actual.into()]); + + let pick = self.probe_for_name( + span, + Mode::MethodCall, + item_name, + IsSuggestion(true), + range_ty, + expr.hir_id, + ProbeScope::AllTraits, + ); + if pick.is_ok() { + let range_span = parent_expr.span.with_hi(expr.span.hi()); + tcx.sess.emit_err(errors::MissingParentheseInRange { + span, + ty_str: ty_str.to_string(), + method_name: item_name.as_str().to_string(), + add_missing_parentheses: Some(errors::AddMissingParenthesesInRange { + func_name: item_name.name.as_str().to_string(), + left: range_span.shrink_to_lo(), + right: range_span.shrink_to_hi(), + }), + }); + return true; + } + } + } + } + false + } + fn suggest_constraining_numerical_ty( &self, tcx: TyCtxt<'tcx>, @@ -1266,7 +1351,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // If this is a floating point literal that ends with '.', // get rid of it to stop this from becoming a member access. let snippet = snippet.strip_suffix('.').unwrap_or(&snippet); - err.span_suggestion( lit.span, &format!( @@ -1282,7 +1366,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // local binding if let hir::def::Res::Local(hir_id) = path.res { let span = tcx.hir().span(hir_id); - let snippet = tcx.sess.source_map().span_to_snippet(span); let filename = tcx.sess.source_map().span_to_filename(span); let parent_node = @@ -1292,7 +1375,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { concrete_type, ); - match (filename, parent_node, snippet) { + match (filename, parent_node) { ( FileName::Real(_), Node::Local(hir::Local { @@ -1300,14 +1383,14 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ty, .. }), - Ok(ref snippet), ) => { + let type_span = ty.map(|ty| ty.span.with_lo(span.hi())).unwrap_or(span.shrink_to_hi()); err.span_suggestion( // account for `let x: _ = 42;` - // ^^^^ - span.to(ty.as_ref().map(|ty| ty.span).unwrap_or(span)), + // ^^^ + type_span, &msg, - format!("{}: {}", snippet, concrete_type), + format!(": {concrete_type}"), Applicability::MaybeIncorrect, ); } @@ -1327,55 +1410,82 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { fn check_for_field_method( &self, - err: &mut DiagnosticBuilder<'tcx, ErrorGuaranteed>, + err: &mut Diagnostic, source: SelfSource<'tcx>, span: Span, actual: Ty<'tcx>, item_name: Ident, ) { if let SelfSource::MethodCall(expr) = source - && let Some((fields, substs)) = self.get_field_candidates(span, actual) + && let mod_id = self.tcx.parent_module(expr.hir_id).to_def_id() + && let Some((fields, substs)) = + self.get_field_candidates_considering_privacy(span, actual, mod_id) { let call_expr = self.tcx.hir().expect_expr(self.tcx.hir().get_parent_node(expr.hir_id)); - for candidate_field in fields.iter() { - if let Some(field_path) = self.check_for_nested_field_satisfying( - span, - &|_, field_ty| { - self.lookup_probe( - span, - item_name, - field_ty, - call_expr, - ProbeScope::AllTraits, - ) - .is_ok() - }, - candidate_field, - substs, - vec![], - self.tcx.parent_module(expr.hir_id).to_def_id(), - ) { - let field_path_str = field_path + + let lang_items = self.tcx.lang_items(); + let never_mention_traits = [ + lang_items.clone_trait(), + lang_items.deref_trait(), + lang_items.deref_mut_trait(), + self.tcx.get_diagnostic_item(sym::AsRef), + self.tcx.get_diagnostic_item(sym::AsMut), + self.tcx.get_diagnostic_item(sym::Borrow), + self.tcx.get_diagnostic_item(sym::BorrowMut), + ]; + let candidate_fields: Vec<_> = fields + .filter_map(|candidate_field| { + self.check_for_nested_field_satisfying( + span, + &|_, field_ty| { + self.lookup_probe( + span, + item_name, + field_ty, + call_expr, + ProbeScope::TraitsInScope, + ) + .map_or(false, |pick| { + !never_mention_traits + .iter() + .flatten() + .any(|def_id| self.tcx.parent(pick.item.def_id) == *def_id) + }) + }, + candidate_field, + substs, + vec![], + mod_id, + ) + }) + .map(|field_path| { + field_path .iter() .map(|id| id.name.to_ident_string()) .collect::<Vec<String>>() - .join("."); - debug!("field_path_str: {:?}", field_path_str); - - err.span_suggestion_verbose( - item_name.span.shrink_to_lo(), - "one of the expressions' fields has a method of the same name", - format!("{field_path_str}."), - Applicability::MaybeIncorrect, - ); - } + .join(".") + }) + .collect(); + + let len = candidate_fields.len(); + if len > 0 { + err.span_suggestions( + item_name.span.shrink_to_lo(), + format!( + "{} of the expressions' fields {} a method of the same name", + if len > 1 { "some" } else { "one" }, + if len > 1 { "have" } else { "has" }, + ), + candidate_fields.iter().map(|path| format!("{path}.")), + Applicability::MaybeIncorrect, + ); } } } - fn check_for_unwrap_self( + fn check_for_inner_self( &self, - err: &mut DiagnosticBuilder<'tcx, ErrorGuaranteed>, + err: &mut Diagnostic, source: SelfSource<'tcx>, span: Span, actual: Ty<'tcx>, @@ -1386,81 +1496,168 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let call_expr = tcx.hir().expect_expr(tcx.hir().get_parent_node(expr.hir_id)); let ty::Adt(kind, substs) = actual.kind() else { return; }; - if !kind.is_enum() { - return; - } + match kind.adt_kind() { + ty::AdtKind::Enum => { + let matching_variants: Vec<_> = kind + .variants() + .iter() + .flat_map(|variant| { + let [field] = &variant.fields[..] else { return None; }; + let field_ty = field.ty(tcx, substs); + + // Skip `_`, since that'll just lead to ambiguity. + if self.resolve_vars_if_possible(field_ty).is_ty_var() { + return None; + } - let matching_variants: Vec<_> = kind - .variants() - .iter() - .flat_map(|variant| { - let [field] = &variant.fields[..] else { return None; }; - let field_ty = field.ty(tcx, substs); + self.lookup_probe( + span, + item_name, + field_ty, + call_expr, + ProbeScope::TraitsInScope, + ) + .ok() + .map(|pick| (variant, field, pick)) + }) + .collect(); + + let ret_ty_matches = |diagnostic_item| { + if let Some(ret_ty) = self + .ret_coercion + .as_ref() + .map(|c| self.resolve_vars_if_possible(c.borrow().expected_ty())) + && let ty::Adt(kind, _) = ret_ty.kind() + && tcx.get_diagnostic_item(diagnostic_item) == Some(kind.did()) + { + true + } else { + false + } + }; - // Skip `_`, since that'll just lead to ambiguity. - if self.resolve_vars_if_possible(field_ty).is_ty_var() { - return None; + match &matching_variants[..] { + [(_, field, pick)] => { + let self_ty = field.ty(tcx, substs); + err.span_note( + tcx.def_span(pick.item.def_id), + &format!("the method `{item_name}` exists on the type `{self_ty}`"), + ); + let (article, kind, variant, question) = + if tcx.is_diagnostic_item(sym::Result, kind.did()) { + ("a", "Result", "Err", ret_ty_matches(sym::Result)) + } else if tcx.is_diagnostic_item(sym::Option, kind.did()) { + ("an", "Option", "None", ret_ty_matches(sym::Option)) + } else { + return; + }; + if question { + err.span_suggestion_verbose( + expr.span.shrink_to_hi(), + format!( + "use the `?` operator to extract the `{self_ty}` value, propagating \ + {article} `{kind}::{variant}` value to the caller" + ), + "?", + Applicability::MachineApplicable, + ); + } else { + err.span_suggestion_verbose( + expr.span.shrink_to_hi(), + format!( + "consider using `{kind}::expect` to unwrap the `{self_ty}` value, \ + panicking if the value is {article} `{kind}::{variant}`" + ), + ".expect(\"REASON\")", + Applicability::HasPlaceholders, + ); + } + } + // FIXME(compiler-errors): Support suggestions for other matching enum variants + _ => {} } - - self.lookup_probe(span, item_name, field_ty, call_expr, ProbeScope::AllTraits) - .ok() - .map(|pick| (variant, field, pick)) - }) - .collect(); - - let ret_ty_matches = |diagnostic_item| { - if let Some(ret_ty) = self - .ret_coercion - .as_ref() - .map(|c| self.resolve_vars_if_possible(c.borrow().expected_ty())) - && let ty::Adt(kind, _) = ret_ty.kind() - && tcx.get_diagnostic_item(diagnostic_item) == Some(kind.did()) - { - true - } else { - false } - }; + // Target wrapper types - types that wrap or pretend to wrap another type, + // perhaps this inner type is meant to be called? + ty::AdtKind::Struct | ty::AdtKind::Union => { + let [first] = ***substs else { return; }; + let ty::GenericArgKind::Type(ty) = first.unpack() else { return; }; + let Ok(pick) = self.lookup_probe( + span, + item_name, + ty, + call_expr, + ProbeScope::TraitsInScope, + ) else { return; }; - match &matching_variants[..] { - [(_, field, pick)] => { - let self_ty = field.ty(tcx, substs); - err.span_note( - tcx.def_span(pick.item.def_id), - &format!("the method `{item_name}` exists on the type `{self_ty}`"), - ); - let (article, kind, variant, question) = - if Some(kind.did()) == tcx.get_diagnostic_item(sym::Result) { - ("a", "Result", "Err", ret_ty_matches(sym::Result)) - } else if Some(kind.did()) == tcx.get_diagnostic_item(sym::Option) { - ("an", "Option", "None", ret_ty_matches(sym::Option)) - } else { - return; + let name = self.ty_to_value_string(actual); + let inner_id = kind.did(); + let mutable = if let Some(AutorefOrPtrAdjustment::Autoref { mutbl, .. }) = + pick.autoref_or_ptr_adjustment + { + Some(mutbl) + } else { + None + }; + + if tcx.is_diagnostic_item(sym::LocalKey, inner_id) { + err.help("use `with` or `try_with` to access thread local storage"); + } else if Some(kind.did()) == tcx.lang_items().maybe_uninit() { + err.help(format!( + "if this `{name}` has been initialized, \ + use one of the `assume_init` methods to access the inner value" + )); + } else if tcx.is_diagnostic_item(sym::RefCell, inner_id) { + let (suggestion, borrow_kind, panic_if) = match mutable { + Some(Mutability::Not) => (".borrow()", "borrow", "a mutable borrow exists"), + Some(Mutability::Mut) => { + (".borrow_mut()", "mutably borrow", "any borrows exist") + } + None => return, }; - if question { err.span_suggestion_verbose( expr.span.shrink_to_hi(), format!( - "use the `?` operator to extract the `{self_ty}` value, propagating \ - {article} `{kind}::{variant}` value to the caller" + "use `{suggestion}` to {borrow_kind} the `{ty}`, \ + panicking if {panic_if}" ), - "?", - Applicability::MachineApplicable, + suggestion, + Applicability::MaybeIncorrect, ); - } else { + } else if tcx.is_diagnostic_item(sym::Mutex, inner_id) { err.span_suggestion_verbose( expr.span.shrink_to_hi(), format!( - "consider using `{kind}::expect` to unwrap the `{self_ty}` value, \ - panicking if the value is {article} `{kind}::{variant}`" + "use `.lock().unwrap()` to borrow the `{ty}`, \ + blocking the current thread until it can be acquired" ), - ".expect(\"REASON\")", - Applicability::HasPlaceholders, + ".lock().unwrap()", + Applicability::MaybeIncorrect, ); - } + } else if tcx.is_diagnostic_item(sym::RwLock, inner_id) { + let (suggestion, borrow_kind) = match mutable { + Some(Mutability::Not) => (".read().unwrap()", "borrow"), + Some(Mutability::Mut) => (".write().unwrap()", "mutably borrow"), + None => return, + }; + err.span_suggestion_verbose( + expr.span.shrink_to_hi(), + format!( + "use `{suggestion}` to {borrow_kind} the `{ty}`, \ + blocking the current thread until it can be acquired" + ), + suggestion, + Applicability::MaybeIncorrect, + ); + } else { + return; + }; + + err.span_note( + tcx.def_span(pick.item.def_id), + &format!("the method `{item_name}` exists on the type `{ty}`"), + ); } - // FIXME(compiler-errors): Support suggestions for other matching enum variants - _ => {} } } @@ -1631,6 +1828,62 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } + fn check_for_deref_method( + &self, + err: &mut Diagnostic, + self_source: SelfSource<'tcx>, + rcvr_ty: Ty<'tcx>, + item_name: Ident, + ) { + let SelfSource::QPath(ty) = self_source else { return; }; + for (deref_ty, _) in self.autoderef(rustc_span::DUMMY_SP, rcvr_ty).skip(1) { + if let Ok(pick) = self.probe_for_name( + ty.span, + Mode::Path, + item_name, + IsSuggestion(true), + deref_ty, + ty.hir_id, + ProbeScope::TraitsInScope, + ) { + if deref_ty.is_suggestable(self.tcx, true) + // If this method receives `&self`, then the provided + // argument _should_ coerce, so it's valid to suggest + // just changing the path. + && pick.item.fn_has_self_parameter + && let Some(self_ty) = + self.tcx.fn_sig(pick.item.def_id).inputs().skip_binder().get(0) + && self_ty.is_ref() + { + let suggested_path = match deref_ty.kind() { + ty::Bool + | ty::Char + | ty::Int(_) + | ty::Uint(_) + | ty::Float(_) + | ty::Adt(_, _) + | ty::Str + | ty::Projection(_) + | ty::Param(_) => format!("{deref_ty}"), + _ => format!("<{deref_ty}>"), + }; + err.span_suggestion_verbose( + ty.span, + format!("the function `{item_name}` is implemented on `{deref_ty}`"), + suggested_path, + Applicability::MaybeIncorrect, + ); + } else { + err.span_note( + ty.span, + format!("the function `{item_name}` is implemented on `{deref_ty}`"), + ); + } + return; + } + } + } + /// Print out the type for use in value namespace. fn ty_to_value_string(&self, ty: Ty<'tcx>) -> String { match ty.kind() { @@ -1763,6 +2016,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { Option<ty::Predicate<'tcx>>, Option<ObligationCause<'tcx>>, )], + static_candidates: &[CandidateSource], unsatisfied_bounds: bool, ) { let mut alt_rcvr_sugg = false; @@ -1877,6 +2131,16 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { None => true, }) .filter(|info| { + // Static candidates are already implemented, and known not to work + // Do not suggest them again + static_candidates.iter().all(|sc| match *sc { + CandidateSource::Trait(def_id) => def_id != info.def_id, + CandidateSource::Impl(def_id) => { + self.tcx.trait_id_of_impl(def_id) != Some(info.def_id) + } + }) + }) + .filter(|info| { // We approximate the coherence rules to only suggest // traits that are legal to implement by requiring that // either the type or trait is local. Multi-dispatch means @@ -1999,7 +2263,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { Colon, Nothing, } - let ast_generics = hir.get_generics(id.owner).unwrap(); + let ast_generics = hir.get_generics(id.owner.def_id).unwrap(); let (sp, mut introducer) = if let Some(span) = ast_generics.bounds_span_for_suggestions(def_id) { @@ -2158,6 +2422,60 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } + /// issue #102320, for `unwrap_or` with closure as argument, suggest `unwrap_or_else` + /// FIXME: currently not working for suggesting `map_or_else`, see #102408 + pub(crate) fn suggest_else_fn_with_closure( + &self, + err: &mut Diagnostic, + expr: &hir::Expr<'_>, + found: Ty<'tcx>, + expected: Ty<'tcx>, + ) -> bool { + let Some((_def_id_or_name, output, _inputs)) = self.extract_callable_info(expr, found) + else { return false; }; + + if !self.can_coerce(output, expected) { + return false; + } + + let parent = self.tcx.hir().get_parent_node(expr.hir_id); + if let Some(Node::Expr(call_expr)) = self.tcx.hir().find(parent) && + let hir::ExprKind::MethodCall( + hir::PathSegment { ident: method_name, .. }, + self_expr, + args, + .., + ) = call_expr.kind && + let Some(self_ty) = self.typeck_results.borrow().expr_ty_opt(self_expr) { + let new_name = Ident { + name: Symbol::intern(&format!("{}_else", method_name.as_str())), + span: method_name.span, + }; + let probe = self.lookup_probe( + expr.span, + new_name, + self_ty, + self_expr, + ProbeScope::TraitsInScope, + ); + + // check the method arguments number + if let Ok(pick) = probe && + let fn_sig = self.tcx.fn_sig(pick.item.def_id) && + let fn_args = fn_sig.skip_binder().inputs() && + fn_args.len() == args.len() + 1 { + err.span_suggestion_verbose( + method_name.span.shrink_to_hi(), + &format!("try calling `{}` instead", new_name.name.as_str()), + "_else", + Applicability::MaybeIncorrect, + ); + return true; + } + } + false + } + /// Checks whether there is a local type somewhere in the chain of /// autoderefs of `rcvr_ty`. fn type_derefs_to_local( @@ -2232,7 +2550,7 @@ pub fn all_traits(tcx: TyCtxt<'_>) -> Vec<TraitInfo> { fn print_disambiguation_help<'tcx>( item_name: Ident, - args: Option<&'tcx [hir::Expr<'tcx>]>, + args: Option<(&'tcx hir::Expr<'tcx>, &'tcx [hir::Expr<'tcx>])>, err: &mut Diagnostic, trait_name: String, rcvr_ty: Ty<'_>, @@ -2244,7 +2562,7 @@ fn print_disambiguation_help<'tcx>( fn_has_self_parameter: bool, ) { let mut applicability = Applicability::MachineApplicable; - let (span, sugg) = if let (ty::AssocKind::Fn, Some(args)) = (kind, args) { + let (span, sugg) = if let (ty::AssocKind::Fn, Some((receiver, args))) = (kind, args) { let args = format!( "({}{})", if rcvr_ty.is_region_ptr() { @@ -2252,7 +2570,8 @@ fn print_disambiguation_help<'tcx>( } else { "" }, - args.iter() + std::iter::once(receiver) + .chain(args.iter()) .map(|arg| source_map.span_to_snippet(arg.span).unwrap_or_else(|_| { applicability = Applicability::HasPlaceholders; "_".to_owned() diff --git a/compiler/rustc_hir_typeck/src/op.rs b/compiler/rustc_hir_typeck/src/op.rs new file mode 100644 index 000000000..895739976 --- /dev/null +++ b/compiler/rustc_hir_typeck/src/op.rs @@ -0,0 +1,994 @@ +//! Code related to processing overloaded binary and unary operators. + +use super::method::MethodCallee; +use super::{has_expected_num_generic_args, FnCtxt}; +use crate::Expectation; +use rustc_ast as ast; +use rustc_errors::{self, struct_span_err, Applicability, Diagnostic}; +use rustc_hir as hir; +use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind}; +use rustc_infer::traits::ObligationCauseCode; +use rustc_middle::ty::adjustment::{ + Adjust, Adjustment, AllowTwoPhase, AutoBorrow, AutoBorrowMutability, +}; +use rustc_middle::ty::print::with_no_trimmed_paths; +use rustc_middle::ty::{self, DefIdTree, Ty, TyCtxt, TypeFolder, TypeSuperFoldable, TypeVisitable}; +use rustc_session::errors::ExprParenthesesNeeded; +use rustc_span::source_map::Spanned; +use rustc_span::symbol::{sym, Ident}; +use rustc_span::Span; +use rustc_trait_selection::infer::InferCtxtExt; +use rustc_trait_selection::traits::error_reporting::suggestions::TypeErrCtxtExt as _; +use rustc_trait_selection::traits::{FulfillmentError, TraitEngine, TraitEngineExt}; +use rustc_type_ir::sty::TyKind::*; + +impl<'a, 'tcx> FnCtxt<'a, 'tcx> { + /// Checks a `a <op>= b` + pub fn check_binop_assign( + &self, + expr: &'tcx hir::Expr<'tcx>, + op: hir::BinOp, + lhs: &'tcx hir::Expr<'tcx>, + rhs: &'tcx hir::Expr<'tcx>, + expected: Expectation<'tcx>, + ) -> Ty<'tcx> { + let (lhs_ty, rhs_ty, return_ty) = + self.check_overloaded_binop(expr, lhs, rhs, op, IsAssign::Yes, expected); + + let ty = + if !lhs_ty.is_ty_var() && !rhs_ty.is_ty_var() && is_builtin_binop(lhs_ty, rhs_ty, op) { + self.enforce_builtin_binop_types(lhs.span, lhs_ty, rhs.span, rhs_ty, op); + self.tcx.mk_unit() + } else { + return_ty + }; + + self.check_lhs_assignable(lhs, "E0067", op.span, |err| { + if let Some(lhs_deref_ty) = self.deref_once_mutably_for_diagnostic(lhs_ty) { + if self + .lookup_op_method( + lhs_deref_ty, + Some(rhs_ty), + Some(rhs), + Op::Binary(op, IsAssign::Yes), + expected, + ) + .is_ok() + { + // If LHS += RHS is an error, but *LHS += RHS is successful, then we will have + // emitted a better suggestion during error handling in check_overloaded_binop. + if self + .lookup_op_method( + lhs_ty, + Some(rhs_ty), + Some(rhs), + Op::Binary(op, IsAssign::Yes), + expected, + ) + .is_err() + { + err.downgrade_to_delayed_bug(); + } else { + // Otherwise, it's valid to suggest dereferencing the LHS here. + err.span_suggestion_verbose( + lhs.span.shrink_to_lo(), + "consider dereferencing the left-hand side of this operation", + "*", + Applicability::MaybeIncorrect, + ); + } + } + } + }); + + ty + } + + /// Checks a potentially overloaded binary operator. + pub fn check_binop( + &self, + expr: &'tcx hir::Expr<'tcx>, + op: hir::BinOp, + lhs_expr: &'tcx hir::Expr<'tcx>, + rhs_expr: &'tcx hir::Expr<'tcx>, + expected: Expectation<'tcx>, + ) -> Ty<'tcx> { + let tcx = self.tcx; + + debug!( + "check_binop(expr.hir_id={}, expr={:?}, op={:?}, lhs_expr={:?}, rhs_expr={:?})", + expr.hir_id, expr, op, lhs_expr, rhs_expr + ); + + match BinOpCategory::from(op) { + BinOpCategory::Shortcircuit => { + // && and || are a simple case. + self.check_expr_coercable_to_type(lhs_expr, tcx.types.bool, None); + let lhs_diverges = self.diverges.get(); + self.check_expr_coercable_to_type(rhs_expr, tcx.types.bool, None); + + // Depending on the LHS' value, the RHS can never execute. + self.diverges.set(lhs_diverges); + + tcx.types.bool + } + _ => { + // Otherwise, we always treat operators as if they are + // overloaded. This is the way to be most flexible w/r/t + // types that get inferred. + let (lhs_ty, rhs_ty, return_ty) = self.check_overloaded_binop( + expr, + lhs_expr, + rhs_expr, + op, + IsAssign::No, + expected, + ); + + // Supply type inference hints if relevant. Probably these + // hints should be enforced during select as part of the + // `consider_unification_despite_ambiguity` routine, but this + // more convenient for now. + // + // The basic idea is to help type inference by taking + // advantage of things we know about how the impls for + // scalar types are arranged. This is important in a + // scenario like `1_u32 << 2`, because it lets us quickly + // deduce that the result type should be `u32`, even + // though we don't know yet what type 2 has and hence + // can't pin this down to a specific impl. + if !lhs_ty.is_ty_var() + && !rhs_ty.is_ty_var() + && is_builtin_binop(lhs_ty, rhs_ty, op) + { + let builtin_return_ty = self.enforce_builtin_binop_types( + lhs_expr.span, + lhs_ty, + rhs_expr.span, + rhs_ty, + op, + ); + self.demand_suptype(expr.span, builtin_return_ty, return_ty); + } + + return_ty + } + } + } + + fn enforce_builtin_binop_types( + &self, + lhs_span: Span, + lhs_ty: Ty<'tcx>, + rhs_span: Span, + rhs_ty: Ty<'tcx>, + op: hir::BinOp, + ) -> Ty<'tcx> { + debug_assert!(is_builtin_binop(lhs_ty, rhs_ty, op)); + + // Special-case a single layer of referencing, so that things like `5.0 + &6.0f32` work. + // (See https://github.com/rust-lang/rust/issues/57447.) + let (lhs_ty, rhs_ty) = (deref_ty_if_possible(lhs_ty), deref_ty_if_possible(rhs_ty)); + + let tcx = self.tcx; + match BinOpCategory::from(op) { + BinOpCategory::Shortcircuit => { + self.demand_suptype(lhs_span, tcx.types.bool, lhs_ty); + self.demand_suptype(rhs_span, tcx.types.bool, rhs_ty); + tcx.types.bool + } + + BinOpCategory::Shift => { + // result type is same as LHS always + lhs_ty + } + + BinOpCategory::Math | BinOpCategory::Bitwise => { + // both LHS and RHS and result will have the same type + self.demand_suptype(rhs_span, lhs_ty, rhs_ty); + lhs_ty + } + + BinOpCategory::Comparison => { + // both LHS and RHS and result will have the same type + self.demand_suptype(rhs_span, lhs_ty, rhs_ty); + tcx.types.bool + } + } + } + + fn check_overloaded_binop( + &self, + expr: &'tcx hir::Expr<'tcx>, + lhs_expr: &'tcx hir::Expr<'tcx>, + rhs_expr: &'tcx hir::Expr<'tcx>, + op: hir::BinOp, + is_assign: IsAssign, + expected: Expectation<'tcx>, + ) -> (Ty<'tcx>, Ty<'tcx>, Ty<'tcx>) { + debug!( + "check_overloaded_binop(expr.hir_id={}, op={:?}, is_assign={:?})", + expr.hir_id, op, is_assign + ); + + let lhs_ty = match is_assign { + IsAssign::No => { + // Find a suitable supertype of the LHS expression's type, by coercing to + // a type variable, to pass as the `Self` to the trait, avoiding invariant + // trait matching creating lifetime constraints that are too strict. + // e.g., adding `&'a T` and `&'b T`, given `&'x T: Add<&'x T>`, will result + // in `&'a T <: &'x T` and `&'b T <: &'x T`, instead of `'a = 'b = 'x`. + let lhs_ty = self.check_expr(lhs_expr); + let fresh_var = self.next_ty_var(TypeVariableOrigin { + kind: TypeVariableOriginKind::MiscVariable, + span: lhs_expr.span, + }); + self.demand_coerce(lhs_expr, lhs_ty, fresh_var, Some(rhs_expr), AllowTwoPhase::No) + } + IsAssign::Yes => { + // rust-lang/rust#52126: We have to use strict + // equivalence on the LHS of an assign-op like `+=`; + // overwritten or mutably-borrowed places cannot be + // coerced to a supertype. + self.check_expr(lhs_expr) + } + }; + let lhs_ty = self.resolve_vars_with_obligations(lhs_ty); + + // N.B., as we have not yet type-checked the RHS, we don't have the + // type at hand. Make a variable to represent it. The whole reason + // for this indirection is so that, below, we can check the expr + // using this variable as the expected type, which sometimes lets + // us do better coercions than we would be able to do otherwise, + // particularly for things like `String + &String`. + let rhs_ty_var = self.next_ty_var(TypeVariableOrigin { + kind: TypeVariableOriginKind::MiscVariable, + span: rhs_expr.span, + }); + + let result = self.lookup_op_method( + lhs_ty, + Some(rhs_ty_var), + Some(rhs_expr), + Op::Binary(op, is_assign), + expected, + ); + + // see `NB` above + let rhs_ty = self.check_expr_coercable_to_type(rhs_expr, rhs_ty_var, Some(lhs_expr)); + let rhs_ty = self.resolve_vars_with_obligations(rhs_ty); + + let return_ty = match result { + Ok(method) => { + let by_ref_binop = !op.node.is_by_value(); + if is_assign == IsAssign::Yes || by_ref_binop { + if let ty::Ref(region, _, mutbl) = method.sig.inputs()[0].kind() { + let mutbl = match mutbl { + hir::Mutability::Not => AutoBorrowMutability::Not, + hir::Mutability::Mut => AutoBorrowMutability::Mut { + // Allow two-phase borrows for binops in initial deployment + // since they desugar to methods + allow_two_phase_borrow: AllowTwoPhase::Yes, + }, + }; + let autoref = Adjustment { + kind: Adjust::Borrow(AutoBorrow::Ref(*region, mutbl)), + target: method.sig.inputs()[0], + }; + self.apply_adjustments(lhs_expr, vec![autoref]); + } + } + if by_ref_binop { + if let ty::Ref(region, _, mutbl) = method.sig.inputs()[1].kind() { + let mutbl = match mutbl { + hir::Mutability::Not => AutoBorrowMutability::Not, + hir::Mutability::Mut => AutoBorrowMutability::Mut { + // Allow two-phase borrows for binops in initial deployment + // since they desugar to methods + allow_two_phase_borrow: AllowTwoPhase::Yes, + }, + }; + let autoref = Adjustment { + kind: Adjust::Borrow(AutoBorrow::Ref(*region, mutbl)), + target: method.sig.inputs()[1], + }; + // HACK(eddyb) Bypass checks due to reborrows being in + // some cases applied on the RHS, on top of which we need + // to autoref, which is not allowed by apply_adjustments. + // self.apply_adjustments(rhs_expr, vec![autoref]); + self.typeck_results + .borrow_mut() + .adjustments_mut() + .entry(rhs_expr.hir_id) + .or_default() + .push(autoref); + } + } + self.write_method_call(expr.hir_id, method); + + method.sig.output() + } + // error types are considered "builtin" + Err(_) if lhs_ty.references_error() || rhs_ty.references_error() => self.tcx.ty_error(), + Err(errors) => { + let (_, trait_def_id) = + lang_item_for_op(self.tcx, Op::Binary(op, is_assign), op.span); + let missing_trait = trait_def_id + .map(|def_id| with_no_trimmed_paths!(self.tcx.def_path_str(def_id))); + let (mut err, output_def_id) = match is_assign { + IsAssign::Yes => { + let mut err = struct_span_err!( + self.tcx.sess, + expr.span, + E0368, + "binary assignment operation `{}=` cannot be applied to type `{}`", + op.node.as_str(), + lhs_ty, + ); + err.span_label( + lhs_expr.span, + format!("cannot use `{}=` on type `{}`", op.node.as_str(), lhs_ty), + ); + self.note_unmet_impls_on_type(&mut err, errors); + (err, None) + } + IsAssign::No => { + let message = match op.node { + hir::BinOpKind::Add => { + format!("cannot add `{rhs_ty}` to `{lhs_ty}`") + } + hir::BinOpKind::Sub => { + format!("cannot subtract `{rhs_ty}` from `{lhs_ty}`") + } + hir::BinOpKind::Mul => { + format!("cannot multiply `{lhs_ty}` by `{rhs_ty}`") + } + hir::BinOpKind::Div => { + format!("cannot divide `{lhs_ty}` by `{rhs_ty}`") + } + hir::BinOpKind::Rem => { + format!("cannot mod `{lhs_ty}` by `{rhs_ty}`") + } + hir::BinOpKind::BitAnd => { + format!("no implementation for `{lhs_ty} & {rhs_ty}`") + } + hir::BinOpKind::BitXor => { + format!("no implementation for `{lhs_ty} ^ {rhs_ty}`") + } + hir::BinOpKind::BitOr => { + format!("no implementation for `{lhs_ty} | {rhs_ty}`") + } + hir::BinOpKind::Shl => { + format!("no implementation for `{lhs_ty} << {rhs_ty}`") + } + hir::BinOpKind::Shr => { + format!("no implementation for `{lhs_ty} >> {rhs_ty}`") + } + _ => format!( + "binary operation `{}` cannot be applied to type `{}`", + op.node.as_str(), + lhs_ty + ), + }; + let output_def_id = trait_def_id.and_then(|def_id| { + self.tcx + .associated_item_def_ids(def_id) + .iter() + .find(|item_def_id| { + self.tcx.associated_item(*item_def_id).name == sym::Output + }) + .cloned() + }); + let mut err = struct_span_err!(self.tcx.sess, op.span, E0369, "{message}"); + if !lhs_expr.span.eq(&rhs_expr.span) { + err.span_label(lhs_expr.span, lhs_ty.to_string()); + err.span_label(rhs_expr.span, rhs_ty.to_string()); + } + self.note_unmet_impls_on_type(&mut err, errors); + (err, output_def_id) + } + }; + + let mut suggest_deref_binop = |lhs_deref_ty: Ty<'tcx>| { + if self + .lookup_op_method( + lhs_deref_ty, + Some(rhs_ty), + Some(rhs_expr), + Op::Binary(op, is_assign), + expected, + ) + .is_ok() + { + let msg = &format!( + "`{}{}` can be used on `{}` if you dereference the left-hand side", + op.node.as_str(), + match is_assign { + IsAssign::Yes => "=", + IsAssign::No => "", + }, + lhs_deref_ty, + ); + err.span_suggestion_verbose( + lhs_expr.span.shrink_to_lo(), + msg, + "*", + rustc_errors::Applicability::MachineApplicable, + ); + } + }; + + let is_compatible = |lhs_ty, rhs_ty| { + self.lookup_op_method( + lhs_ty, + Some(rhs_ty), + Some(rhs_expr), + Op::Binary(op, is_assign), + expected, + ) + .is_ok() + }; + + // We should suggest `a + b` => `*a + b` if `a` is copy, and suggest + // `a += b` => `*a += b` if a is a mut ref. + if !op.span.can_be_used_for_suggestions() { + // Suppress suggestions when lhs and rhs are not in the same span as the error + } else if is_assign == IsAssign::Yes + && let Some(lhs_deref_ty) = self.deref_once_mutably_for_diagnostic(lhs_ty) + { + suggest_deref_binop(lhs_deref_ty); + } else if is_assign == IsAssign::No + && let Ref(_, lhs_deref_ty, _) = lhs_ty.kind() + { + if self.type_is_copy_modulo_regions( + self.param_env, + *lhs_deref_ty, + lhs_expr.span, + ) { + suggest_deref_binop(*lhs_deref_ty); + } + } else if self.suggest_fn_call(&mut err, lhs_expr, lhs_ty, |lhs_ty| { + is_compatible(lhs_ty, rhs_ty) + }) || self.suggest_fn_call(&mut err, rhs_expr, rhs_ty, |rhs_ty| { + is_compatible(lhs_ty, rhs_ty) + }) || self.suggest_two_fn_call( + &mut err, + rhs_expr, + rhs_ty, + lhs_expr, + lhs_ty, + |lhs_ty, rhs_ty| is_compatible(lhs_ty, rhs_ty), + ) { + // Cool + } + + if let Some(missing_trait) = missing_trait { + if op.node == hir::BinOpKind::Add + && self.check_str_addition( + lhs_expr, rhs_expr, lhs_ty, rhs_ty, &mut err, is_assign, op, + ) + { + // This has nothing here because it means we did string + // concatenation (e.g., "Hello " + "World!"). This means + // we don't want the note in the else clause to be emitted + } else if lhs_ty.has_non_region_param() { + // Look for a TraitPredicate in the Fulfillment errors, + // and use it to generate a suggestion. + // + // Note that lookup_op_method must be called again but + // with a specific rhs_ty instead of a placeholder so + // the resulting predicate generates a more specific + // suggestion for the user. + let errors = self + .lookup_op_method( + lhs_ty, + Some(rhs_ty), + Some(rhs_expr), + Op::Binary(op, is_assign), + expected, + ) + .unwrap_err(); + if !errors.is_empty() { + for error in errors { + if let Some(trait_pred) = + error.obligation.predicate.to_opt_poly_trait_pred() + { + let output_associated_item = match error.obligation.cause.code() + { + ObligationCauseCode::BinOp { + output_ty: Some(output_ty), + .. + } => { + // Make sure that we're attaching `Output = ..` to the right trait predicate + if let Some(output_def_id) = output_def_id + && let Some(trait_def_id) = trait_def_id + && self.tcx.parent(output_def_id) == trait_def_id + { + Some(("Output", *output_ty)) + } else { + None + } + } + _ => None, + }; + + self.err_ctxt().suggest_restricting_param_bound( + &mut err, + trait_pred, + output_associated_item, + self.body_id, + ); + } + } + } else { + // When we know that a missing bound is responsible, we don't show + // this note as it is redundant. + err.note(&format!( + "the trait `{missing_trait}` is not implemented for `{lhs_ty}`" + )); + } + } + } + err.emit(); + self.tcx.ty_error() + } + }; + + (lhs_ty, rhs_ty, return_ty) + } + + /// Provide actionable suggestions when trying to add two strings with incorrect types, + /// like `&str + &str`, `String + String` and `&str + &String`. + /// + /// If this function returns `true` it means a note was printed, so we don't need + /// to print the normal "implementation of `std::ops::Add` might be missing" note + fn check_str_addition( + &self, + lhs_expr: &'tcx hir::Expr<'tcx>, + rhs_expr: &'tcx hir::Expr<'tcx>, + lhs_ty: Ty<'tcx>, + rhs_ty: Ty<'tcx>, + err: &mut Diagnostic, + is_assign: IsAssign, + op: hir::BinOp, + ) -> bool { + let str_concat_note = "string concatenation requires an owned `String` on the left"; + let rm_borrow_msg = "remove the borrow to obtain an owned `String`"; + let to_owned_msg = "create an owned `String` from a string reference"; + + let is_std_string = |ty: Ty<'tcx>| { + ty.ty_adt_def() + .map_or(false, |ty_def| self.tcx.is_diagnostic_item(sym::String, ty_def.did())) + }; + + match (lhs_ty.kind(), rhs_ty.kind()) { + (&Ref(_, l_ty, _), &Ref(_, r_ty, _)) // &str or &String + &str, &String or &&str + if (*l_ty.kind() == Str || is_std_string(l_ty)) + && (*r_ty.kind() == Str + || is_std_string(r_ty) + || matches!( + r_ty.kind(), Ref(_, inner_ty, _) if *inner_ty.kind() == Str + )) => + { + if let IsAssign::No = is_assign { // Do not supply this message if `&str += &str` + err.span_label(op.span, "`+` cannot be used to concatenate two `&str` strings"); + err.note(str_concat_note); + if let hir::ExprKind::AddrOf(_, _, lhs_inner_expr) = lhs_expr.kind { + err.span_suggestion_verbose( + lhs_expr.span.until(lhs_inner_expr.span), + rm_borrow_msg, + "", + Applicability::MachineApplicable + ); + } else { + err.span_suggestion_verbose( + lhs_expr.span.shrink_to_hi(), + to_owned_msg, + ".to_owned()", + Applicability::MachineApplicable + ); + } + } + true + } + (&Ref(_, l_ty, _), &Adt(..)) // Handle `&str` & `&String` + `String` + if (*l_ty.kind() == Str || is_std_string(l_ty)) && is_std_string(rhs_ty) => + { + err.span_label( + op.span, + "`+` cannot be used to concatenate a `&str` with a `String`", + ); + match is_assign { + IsAssign::No => { + let sugg_msg; + let lhs_sugg = if let hir::ExprKind::AddrOf(_, _, lhs_inner_expr) = lhs_expr.kind { + sugg_msg = "remove the borrow on the left and add one on the right"; + (lhs_expr.span.until(lhs_inner_expr.span), "".to_owned()) + } else { + sugg_msg = "create an owned `String` on the left and add a borrow on the right"; + (lhs_expr.span.shrink_to_hi(), ".to_owned()".to_owned()) + }; + let suggestions = vec![ + lhs_sugg, + (rhs_expr.span.shrink_to_lo(), "&".to_owned()), + ]; + err.multipart_suggestion_verbose( + sugg_msg, + suggestions, + Applicability::MachineApplicable, + ); + } + IsAssign::Yes => { + err.note(str_concat_note); + } + } + true + } + _ => false, + } + } + + pub fn check_user_unop( + &self, + ex: &'tcx hir::Expr<'tcx>, + operand_ty: Ty<'tcx>, + op: hir::UnOp, + expected: Expectation<'tcx>, + ) -> Ty<'tcx> { + assert!(op.is_by_value()); + match self.lookup_op_method(operand_ty, None, None, Op::Unary(op, ex.span), expected) { + Ok(method) => { + self.write_method_call(ex.hir_id, method); + method.sig.output() + } + Err(errors) => { + let actual = self.resolve_vars_if_possible(operand_ty); + if !actual.references_error() { + let mut err = struct_span_err!( + self.tcx.sess, + ex.span, + E0600, + "cannot apply unary operator `{}` to type `{}`", + op.as_str(), + actual + ); + err.span_label( + ex.span, + format!("cannot apply unary operator `{}`", op.as_str()), + ); + + if operand_ty.has_non_region_param() { + let predicates = errors.iter().filter_map(|error| { + error.obligation.predicate.to_opt_poly_trait_pred() + }); + for pred in predicates { + self.err_ctxt().suggest_restricting_param_bound( + &mut err, + pred, + None, + self.body_id, + ); + } + } + + let sp = self.tcx.sess.source_map().start_point(ex.span); + if let Some(sp) = + self.tcx.sess.parse_sess.ambiguous_block_expr_parse.borrow().get(&sp) + { + // If the previous expression was a block expression, suggest parentheses + // (turning this into a binary subtraction operation instead.) + // for example, `{2} - 2` -> `({2}) - 2` (see src\test\ui\parser\expr-as-stmt.rs) + err.subdiagnostic(ExprParenthesesNeeded::surrounding(*sp)); + } else { + match actual.kind() { + Uint(_) if op == hir::UnOp::Neg => { + err.note("unsigned values cannot be negated"); + + if let hir::ExprKind::Unary( + _, + hir::Expr { + kind: + hir::ExprKind::Lit(Spanned { + node: ast::LitKind::Int(1, _), + .. + }), + .. + }, + ) = ex.kind + { + err.span_suggestion( + ex.span, + &format!( + "you may have meant the maximum value of `{actual}`", + ), + format!("{actual}::MAX"), + Applicability::MaybeIncorrect, + ); + } + } + Str | Never | Char | Tuple(_) | Array(_, _) => {} + Ref(_, lty, _) if *lty.kind() == Str => {} + _ => { + self.note_unmet_impls_on_type(&mut err, errors); + } + } + } + err.emit(); + } + self.tcx.ty_error() + } + } + } + + fn lookup_op_method( + &self, + lhs_ty: Ty<'tcx>, + other_ty: Option<Ty<'tcx>>, + other_ty_expr: Option<&'tcx hir::Expr<'tcx>>, + op: Op, + expected: Expectation<'tcx>, + ) -> Result<MethodCallee<'tcx>, Vec<FulfillmentError<'tcx>>> { + let span = match op { + Op::Binary(op, _) => op.span, + Op::Unary(_, span) => span, + }; + let (opname, trait_did) = lang_item_for_op(self.tcx, op, span); + + debug!( + "lookup_op_method(lhs_ty={:?}, op={:?}, opname={:?}, trait_did={:?})", + lhs_ty, op, opname, trait_did + ); + + // Catches cases like #83893, where a lang item is declared with the + // wrong number of generic arguments. Should have yielded an error + // elsewhere by now, but we have to catch it here so that we do not + // index `other_tys` out of bounds (if the lang item has too many + // generic arguments, `other_tys` is too short). + if !has_expected_num_generic_args( + self.tcx, + trait_did, + match op { + // Binary ops have a generic right-hand side, unary ops don't + Op::Binary(..) => 1, + Op::Unary(..) => 0, + }, + ) { + return Err(vec![]); + } + + let opname = Ident::with_dummy_span(opname); + let method = trait_did.and_then(|trait_did| { + self.lookup_op_method_in_trait( + span, + opname, + trait_did, + lhs_ty, + other_ty, + other_ty_expr, + expected, + ) + }); + + match (method, trait_did) { + (Some(ok), _) => { + let method = self.register_infer_ok_obligations(ok); + self.select_obligations_where_possible(false, |_| {}); + Ok(method) + } + (None, None) => Err(vec![]), + (None, Some(trait_did)) => { + let (obligation, _) = self.obligation_for_op_method( + span, + trait_did, + lhs_ty, + other_ty, + other_ty_expr, + expected, + ); + let mut fulfill = <dyn TraitEngine<'_>>::new(self.tcx); + fulfill.register_predicate_obligation(self, obligation); + Err(fulfill.select_where_possible(&self.infcx)) + } + } + } +} + +fn lang_item_for_op( + tcx: TyCtxt<'_>, + op: Op, + span: Span, +) -> (rustc_span::Symbol, Option<hir::def_id::DefId>) { + let lang = tcx.lang_items(); + if let Op::Binary(op, IsAssign::Yes) = op { + match op.node { + hir::BinOpKind::Add => (sym::add_assign, lang.add_assign_trait()), + hir::BinOpKind::Sub => (sym::sub_assign, lang.sub_assign_trait()), + hir::BinOpKind::Mul => (sym::mul_assign, lang.mul_assign_trait()), + hir::BinOpKind::Div => (sym::div_assign, lang.div_assign_trait()), + hir::BinOpKind::Rem => (sym::rem_assign, lang.rem_assign_trait()), + hir::BinOpKind::BitXor => (sym::bitxor_assign, lang.bitxor_assign_trait()), + hir::BinOpKind::BitAnd => (sym::bitand_assign, lang.bitand_assign_trait()), + hir::BinOpKind::BitOr => (sym::bitor_assign, lang.bitor_assign_trait()), + hir::BinOpKind::Shl => (sym::shl_assign, lang.shl_assign_trait()), + hir::BinOpKind::Shr => (sym::shr_assign, lang.shr_assign_trait()), + hir::BinOpKind::Lt + | hir::BinOpKind::Le + | hir::BinOpKind::Ge + | hir::BinOpKind::Gt + | hir::BinOpKind::Eq + | hir::BinOpKind::Ne + | hir::BinOpKind::And + | hir::BinOpKind::Or => { + span_bug!(span, "impossible assignment operation: {}=", op.node.as_str()) + } + } + } else if let Op::Binary(op, IsAssign::No) = op { + match op.node { + hir::BinOpKind::Add => (sym::add, lang.add_trait()), + hir::BinOpKind::Sub => (sym::sub, lang.sub_trait()), + hir::BinOpKind::Mul => (sym::mul, lang.mul_trait()), + hir::BinOpKind::Div => (sym::div, lang.div_trait()), + hir::BinOpKind::Rem => (sym::rem, lang.rem_trait()), + hir::BinOpKind::BitXor => (sym::bitxor, lang.bitxor_trait()), + hir::BinOpKind::BitAnd => (sym::bitand, lang.bitand_trait()), + hir::BinOpKind::BitOr => (sym::bitor, lang.bitor_trait()), + hir::BinOpKind::Shl => (sym::shl, lang.shl_trait()), + hir::BinOpKind::Shr => (sym::shr, lang.shr_trait()), + hir::BinOpKind::Lt => (sym::lt, lang.partial_ord_trait()), + hir::BinOpKind::Le => (sym::le, lang.partial_ord_trait()), + hir::BinOpKind::Ge => (sym::ge, lang.partial_ord_trait()), + hir::BinOpKind::Gt => (sym::gt, lang.partial_ord_trait()), + hir::BinOpKind::Eq => (sym::eq, lang.eq_trait()), + hir::BinOpKind::Ne => (sym::ne, lang.eq_trait()), + hir::BinOpKind::And | hir::BinOpKind::Or => { + span_bug!(span, "&& and || are not overloadable") + } + } + } else if let Op::Unary(hir::UnOp::Not, _) = op { + (sym::not, lang.not_trait()) + } else if let Op::Unary(hir::UnOp::Neg, _) = op { + (sym::neg, lang.neg_trait()) + } else { + bug!("lookup_op_method: op not supported: {:?}", op) + } +} + +// Binary operator categories. These categories summarize the behavior +// with respect to the builtin operations supported. +enum BinOpCategory { + /// &&, || -- cannot be overridden + Shortcircuit, + + /// <<, >> -- when shifting a single integer, rhs can be any + /// integer type. For simd, types must match. + Shift, + + /// +, -, etc -- takes equal types, produces same type as input, + /// applicable to ints/floats/simd + Math, + + /// &, |, ^ -- takes equal types, produces same type as input, + /// applicable to ints/floats/simd/bool + Bitwise, + + /// ==, !=, etc -- takes equal types, produces bools, except for simd, + /// which produce the input type + Comparison, +} + +impl BinOpCategory { + fn from(op: hir::BinOp) -> BinOpCategory { + match op.node { + hir::BinOpKind::Shl | hir::BinOpKind::Shr => BinOpCategory::Shift, + + hir::BinOpKind::Add + | hir::BinOpKind::Sub + | hir::BinOpKind::Mul + | hir::BinOpKind::Div + | hir::BinOpKind::Rem => BinOpCategory::Math, + + hir::BinOpKind::BitXor | hir::BinOpKind::BitAnd | hir::BinOpKind::BitOr => { + BinOpCategory::Bitwise + } + + hir::BinOpKind::Eq + | hir::BinOpKind::Ne + | hir::BinOpKind::Lt + | hir::BinOpKind::Le + | hir::BinOpKind::Ge + | hir::BinOpKind::Gt => BinOpCategory::Comparison, + + hir::BinOpKind::And | hir::BinOpKind::Or => BinOpCategory::Shortcircuit, + } + } +} + +/// Whether the binary operation is an assignment (`a += b`), or not (`a + b`) +#[derive(Clone, Copy, Debug, PartialEq)] +enum IsAssign { + No, + Yes, +} + +#[derive(Clone, Copy, Debug)] +enum Op { + Binary(hir::BinOp, IsAssign), + Unary(hir::UnOp, Span), +} + +/// Dereferences a single level of immutable referencing. +fn deref_ty_if_possible<'tcx>(ty: Ty<'tcx>) -> Ty<'tcx> { + match ty.kind() { + ty::Ref(_, ty, hir::Mutability::Not) => *ty, + _ => ty, + } +} + +/// Returns `true` if this is a built-in arithmetic operation (e.g., u32 +/// + u32, i16x4 == i16x4) and false if these types would have to be +/// overloaded to be legal. There are two reasons that we distinguish +/// builtin operations from overloaded ones (vs trying to drive +/// everything uniformly through the trait system and intrinsics or +/// something like that): +/// +/// 1. Builtin operations can trivially be evaluated in constants. +/// 2. For comparison operators applied to SIMD types the result is +/// not of type `bool`. For example, `i16x4 == i16x4` yields a +/// type like `i16x4`. This means that the overloaded trait +/// `PartialEq` is not applicable. +/// +/// Reason #2 is the killer. I tried for a while to always use +/// overloaded logic and just check the types in constants/codegen after +/// the fact, and it worked fine, except for SIMD types. -nmatsakis +fn is_builtin_binop<'tcx>(lhs: Ty<'tcx>, rhs: Ty<'tcx>, op: hir::BinOp) -> bool { + // Special-case a single layer of referencing, so that things like `5.0 + &6.0f32` work. + // (See https://github.com/rust-lang/rust/issues/57447.) + let (lhs, rhs) = (deref_ty_if_possible(lhs), deref_ty_if_possible(rhs)); + + match BinOpCategory::from(op) { + BinOpCategory::Shortcircuit => true, + + BinOpCategory::Shift => { + lhs.references_error() + || rhs.references_error() + || lhs.is_integral() && rhs.is_integral() + } + + BinOpCategory::Math => { + lhs.references_error() + || rhs.references_error() + || lhs.is_integral() && rhs.is_integral() + || lhs.is_floating_point() && rhs.is_floating_point() + } + + BinOpCategory::Bitwise => { + lhs.references_error() + || rhs.references_error() + || lhs.is_integral() && rhs.is_integral() + || lhs.is_floating_point() && rhs.is_floating_point() + || lhs.is_bool() && rhs.is_bool() + } + + BinOpCategory::Comparison => { + lhs.references_error() || rhs.references_error() || lhs.is_scalar() && rhs.is_scalar() + } + } +} + +struct TypeParamEraser<'a, 'tcx>(&'a FnCtxt<'a, 'tcx>, Span); + +impl<'tcx> TypeFolder<'tcx> for TypeParamEraser<'_, 'tcx> { + fn tcx(&self) -> TyCtxt<'tcx> { + self.0.tcx + } + + fn fold_ty(&mut self, ty: Ty<'tcx>) -> Ty<'tcx> { + match ty.kind() { + ty::Param(_) => self.0.next_ty_var(TypeVariableOrigin { + kind: TypeVariableOriginKind::MiscVariable, + span: self.1, + }), + _ => ty.super_fold_with(self), + } + } +} diff --git a/compiler/rustc_typeck/src/check/pat.rs b/compiler/rustc_hir_typeck/src/pat.rs index 837c32355..ea90da4a6 100644 --- a/compiler/rustc_typeck/src/check/pat.rs +++ b/compiler/rustc_hir_typeck/src/pat.rs @@ -1,6 +1,5 @@ -use crate::check::FnCtxt; +use crate::FnCtxt; use rustc_ast as ast; - use rustc_data_structures::fx::FxHashMap; use rustc_errors::{ pluralize, struct_span_err, Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed, @@ -569,7 +568,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ) -> Ty<'tcx> { // Determine the binding mode... let bm = match ba { - hir::BindingAnnotation::Unannotated => def_bm, + hir::BindingAnnotation::NONE => def_bm, _ => BindingMode::convert(ba), }; // ...and store it in a side table: @@ -600,7 +599,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // If there are multiple arms, make sure they all agree on // what the type of the binding `x` ought to be. if var_id != pat.hir_id { - self.check_binding_alt_eq_ty(pat.span, var_id, local_ty, ti); + self.check_binding_alt_eq_ty(ba, pat.span, var_id, local_ty, ti); } if let Some(p) = sub { @@ -610,7 +609,14 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { local_ty } - fn check_binding_alt_eq_ty(&self, span: Span, var_id: HirId, ty: Ty<'tcx>, ti: TopInfo<'tcx>) { + fn check_binding_alt_eq_ty( + &self, + ba: hir::BindingAnnotation, + span: Span, + var_id: HirId, + ty: Ty<'tcx>, + ti: TopInfo<'tcx>, + ) { let var_ty = self.local_ty(span, var_id).decl_ty; if let Some(mut err) = self.demand_eqtype_pat_diag(span, var_ty, ty, ti) { let hir = self.tcx.hir(); @@ -628,12 +634,50 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { }); let pre = if in_match { "in the same arm, " } else { "" }; err.note(&format!("{}a binding must have the same type in all alternatives", pre)); - // FIXME: check if `var_ty` and `ty` can be made the same type by adding or removing - // `ref` or `&` to the pattern. + self.suggest_adding_missing_ref_or_removing_ref( + &mut err, + span, + var_ty, + self.resolve_vars_with_obligations(ty), + ba, + ); err.emit(); } } + fn suggest_adding_missing_ref_or_removing_ref( + &self, + err: &mut Diagnostic, + span: Span, + expected: Ty<'tcx>, + actual: Ty<'tcx>, + ba: hir::BindingAnnotation, + ) { + match (expected.kind(), actual.kind(), ba) { + (ty::Ref(_, inner_ty, _), _, hir::BindingAnnotation::NONE) + if self.can_eq(self.param_env, *inner_ty, actual).is_ok() => + { + err.span_suggestion_verbose( + span.shrink_to_lo(), + "consider adding `ref`", + "ref ", + Applicability::MaybeIncorrect, + ); + } + (_, ty::Ref(_, inner_ty, _), hir::BindingAnnotation::REF) + if self.can_eq(self.param_env, expected, *inner_ty).is_ok() => + { + err.span_suggestion_verbose( + span.with_hi(span.lo() + BytePos(4)), + "consider removing `ref`", + "", + Applicability::MaybeIncorrect, + ); + } + _ => (), + } + } + // Precondition: pat is a Ref(_) pattern fn borrow_pat_suggestion(&self, err: &mut Diagnostic, pat: &Pat<'_>) { let tcx = self.tcx; @@ -882,7 +926,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ), ); match self.tcx.hir().get(self.tcx.hir().get_parent_node(pat.hir_id)) { - hir::Node::Pat(Pat { kind: hir::PatKind::Struct(..), .. }) => { + hir::Node::PatField(..) => { e.span_suggestion_verbose( ident.span.shrink_to_hi(), "bind the struct field to a different name instead", @@ -936,7 +980,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { pat: &'tcx Pat<'tcx>, qpath: &'tcx hir::QPath<'tcx>, subpats: &'tcx [Pat<'tcx>], - ddpos: Option<usize>, + ddpos: hir::DotDotPos, expected: Ty<'tcx>, def_bm: BindingMode, ti: TopInfo<'tcx>, @@ -1021,7 +1065,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // Type-check subpatterns. if subpats.len() == variant.fields.len() - || subpats.len() < variant.fields.len() && ddpos.is_some() + || subpats.len() < variant.fields.len() && ddpos.as_opt_usize().is_some() { let ty::Adt(_, substs) = pat_ty.kind() else { bug!("unexpected pattern type {:?}", pat_ty); @@ -1209,14 +1253,14 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { &self, span: Span, elements: &'tcx [Pat<'tcx>], - ddpos: Option<usize>, + ddpos: hir::DotDotPos, expected: Ty<'tcx>, def_bm: BindingMode, ti: TopInfo<'tcx>, ) -> Ty<'tcx> { let tcx = self.tcx; let mut expected_len = elements.len(); - if ddpos.is_some() { + if ddpos.as_opt_usize().is_some() { // Require known type only when `..` is present. if let ty::Tuple(tys) = self.structurally_resolved_type(span, expected).kind() { expected_len = tys.len(); @@ -1352,7 +1396,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { .iter() .copied() .filter(|(field, _)| { - field.vis.is_accessible_from(tcx.parent_module(pat.hir_id).to_def_id(), tcx) + field.vis.is_accessible_from(tcx.parent_module(pat.hir_id), tcx) && !matches!( tcx.eval_stability(field.did, None, DUMMY_SP, None), EvalResult::Deny { .. } @@ -1745,10 +1789,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { &unmentioned_fields.iter().map(|(_, i)| i).collect::<Vec<_>>(), ); - self.tcx.struct_span_lint_hir(NON_EXHAUSTIVE_OMITTED_PATTERNS, pat.hir_id, pat.span, |build| { - let mut lint = build.build("some fields are not explicitly listed"); + self.tcx.struct_span_lint_hir(NON_EXHAUSTIVE_OMITTED_PATTERNS, pat.hir_id, pat.span, "some fields are not explicitly listed", |lint| { lint.span_label(pat.span, format!("field{} {} not listed", rustc_errors::pluralize!(unmentioned_fields.len()), joined_patterns)); - lint.help( "ensure that all fields are mentioned explicitly by adding the suggested fields", ); @@ -1756,7 +1798,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { "the pattern is of type `{}` and the `non_exhaustive_omitted_patterns` attribute was found", ty, )); - lint.emit(); + + lint }); } diff --git a/compiler/rustc_typeck/src/check/place_op.rs b/compiler/rustc_hir_typeck/src/place_op.rs index 2e0f37eba..ba8cf6926 100644 --- a/compiler/rustc_typeck/src/check/place_op.rs +++ b/compiler/rustc_hir_typeck/src/place_op.rs @@ -1,5 +1,5 @@ -use crate::check::method::MethodCallee; -use crate::check::{has_expected_num_generic_args, FnCtxt, PlaceOp}; +use crate::method::MethodCallee; +use crate::{has_expected_num_generic_args, FnCtxt, PlaceOp}; use rustc_ast as ast; use rustc_errors::Applicability; use rustc_hir as hir; diff --git a/compiler/rustc_typeck/src/check/rvalue_scopes.rs b/compiler/rustc_hir_typeck/src/rvalue_scopes.rs index 22c9e7961..22c9e7961 100644 --- a/compiler/rustc_typeck/src/check/rvalue_scopes.rs +++ b/compiler/rustc_hir_typeck/src/rvalue_scopes.rs diff --git a/compiler/rustc_typeck/src/check/upvar.rs b/compiler/rustc_hir_typeck/src/upvar.rs index dd8f943b9..4dea40829 100644 --- a/compiler/rustc_typeck/src/check/upvar.rs +++ b/compiler/rustc_hir_typeck/src/upvar.rs @@ -352,7 +352,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { /// and that the path can be captured with required capture kind (depending on use in closure, /// move closure etc.) /// - /// Returns the set of of adjusted information along with the inferred closure kind and span + /// Returns the set of adjusted information along with the inferred closure kind and span /// associated with the closure kind inference. /// /// Note that we *always* infer a minimal kind, even if @@ -749,10 +749,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { lint::builtin::RUST_2021_INCOMPATIBLE_CLOSURE_CAPTURES, closure_hir_id, closure_head_span, + reasons.migration_message(), |lint| { - let mut diagnostics_builder = lint.build( - &reasons.migration_message(), - ); for NeededMigration { var_hir_id, diagnostics_info } in &need_migrations { // Labels all the usage of the captured variable and why they are responsible // for migration being needed @@ -760,13 +758,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { match &lint_note.captures_info { UpvarMigrationInfo::CapturingPrecise { source_expr: Some(capture_expr_id), var_name: captured_name } => { let cause_span = self.tcx.hir().span(*capture_expr_id); - diagnostics_builder.span_label(cause_span, format!("in Rust 2018, this closure captures all of `{}`, but in Rust 2021, it will only capture `{}`", + lint.span_label(cause_span, format!("in Rust 2018, this closure captures all of `{}`, but in Rust 2021, it will only capture `{}`", self.tcx.hir().name(*var_hir_id), captured_name, )); } UpvarMigrationInfo::CapturingNothing { use_span } => { - diagnostics_builder.span_label(*use_span, format!("in Rust 2018, this causes the closure to capture `{}`, but in Rust 2021, it has no effect", + lint.span_label(*use_span, format!("in Rust 2018, this causes the closure to capture `{}`, but in Rust 2021, it has no effect", self.tcx.hir().name(*var_hir_id), )); } @@ -781,13 +779,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { match &lint_note.captures_info { UpvarMigrationInfo::CapturingPrecise { var_name: captured_name, .. } => { - diagnostics_builder.span_label(drop_location_span, format!("in Rust 2018, `{}` is dropped here, but in Rust 2021, only `{}` will be dropped here as part of the closure", + lint.span_label(drop_location_span, format!("in Rust 2018, `{}` is dropped here, but in Rust 2021, only `{}` will be dropped here as part of the closure", self.tcx.hir().name(*var_hir_id), captured_name, )); } UpvarMigrationInfo::CapturingNothing { use_span: _ } => { - diagnostics_builder.span_label(drop_location_span, format!("in Rust 2018, `{v}` is dropped here along with the closure, but in Rust 2021 `{v}` is not part of the closure", + lint.span_label(drop_location_span, format!("in Rust 2018, `{v}` is dropped here along with the closure, but in Rust 2021 `{v}` is not part of the closure", v = self.tcx.hir().name(*var_hir_id), )); } @@ -800,7 +798,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { match &lint_note.captures_info { UpvarMigrationInfo::CapturingPrecise { var_name: captured_name, .. } => { let var_name = self.tcx.hir().name(*var_hir_id); - diagnostics_builder.span_label(closure_head_span, format!("\ + lint.span_label(closure_head_span, format!("\ in Rust 2018, this closure implements {missing_trait} \ as `{var_name}` implements {missing_trait}, but in Rust 2021, \ this closure will no longer implement {missing_trait} \ @@ -814,7 +812,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } } - diagnostics_builder.note("for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2021/disjoint-capture-in-closures.html>"); + lint.note("for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2021/disjoint-capture-in-closures.html>"); let diagnostic_msg = format!( "add a dummy let to cause {} to be fully captured", @@ -857,7 +855,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // We take the indentation from the next non-empty line. let line2 = lines.find(|line| !line.is_empty()).unwrap_or_default(); let indent = line2.split_once(|c: char| !c.is_whitespace()).unwrap_or_default().0; - diagnostics_builder.span_suggestion( + lint.span_suggestion( closure_body_span.with_lo(closure_body_span.lo() + BytePos::from_usize(line1.len())).shrink_to_lo(), &diagnostic_msg, format!("\n{indent}{migration_string};"), @@ -868,7 +866,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // braces, but with more than just the opening // brace on the first line. We put the `let` // directly after the `{`. - diagnostics_builder.span_suggestion( + lint.span_suggestion( closure_body_span.with_lo(closure_body_span.lo() + BytePos(1)).shrink_to_lo(), &diagnostic_msg, format!(" {migration_string};"), @@ -877,7 +875,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } else { // This is a closure without braces around the body. // We add braces to add the `let` before the body. - diagnostics_builder.multipart_suggestion( + lint.multipart_suggestion( &diagnostic_msg, vec![ (closure_body_span.shrink_to_lo(), format!("{{ {migration_string}; ")), @@ -887,7 +885,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ); } } else { - diagnostics_builder.span_suggestion( + lint.span_suggestion( closure_span, &diagnostic_msg, migration_string, @@ -895,7 +893,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ); } - diagnostics_builder.emit(); + lint }, ); } @@ -1217,7 +1215,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // Combine all the reasons of why the root variable should be captured as a result of // auto trait implementation issues - auto_trait_migration_reasons.extend(capture_trait_reasons.clone()); + auto_trait_migration_reasons.extend(capture_trait_reasons.iter().copied()); diagnostics_info.push(MigrationLintNote { captures_info, @@ -2024,6 +2022,10 @@ fn should_do_rust_2021_incompatible_closure_captures_analysis( tcx: TyCtxt<'_>, closure_id: hir::HirId, ) -> bool { + if tcx.sess.rust_2021() { + return false; + } + let (level, _) = tcx.lint_level_at_node(lint::builtin::RUST_2021_INCOMPATIBLE_CLOSURE_CAPTURES, closure_id); diff --git a/compiler/rustc_typeck/src/check/writeback.rs b/compiler/rustc_hir_typeck/src/writeback.rs index f549807c3..1e26daa9c 100644 --- a/compiler/rustc_typeck/src/check/writeback.rs +++ b/compiler/rustc_hir_typeck/src/writeback.rs @@ -2,8 +2,7 @@ // unresolved type variables and replaces "ty_var" types with their // substitutions. -use crate::check::FnCtxt; - +use crate::FnCtxt; use hir::def_id::LocalDefId; use rustc_data_structures::fx::FxHashMap; use rustc_errors::ErrorGuaranteed; @@ -16,6 +15,7 @@ use rustc_middle::mir::FakeReadCause; use rustc_middle::ty::adjustment::{Adjust, Adjustment, PointerCast}; use rustc_middle::ty::fold::{TypeFoldable, TypeFolder, TypeSuperFoldable}; use rustc_middle::ty::visit::{TypeSuperVisitable, TypeVisitable}; +use rustc_middle::ty::TypeckResults; use rustc_middle::ty::{self, ClosureSizeProfileData, Ty, TyCtxt}; use rustc_span::symbol::sym; use rustc_span::Span; @@ -192,6 +192,27 @@ impl<'cx, 'tcx> WritebackCx<'cx, 'tcx> { } } + // (ouz-a 1005988): Normally `[T] : std::ops::Index<usize>` should be normalized + // into [T] but currently `Where` clause stops the normalization process for it, + // here we compare types of expr and base in a code without `Where` clause they would be equal + // if they are not we don't modify the expr, hence we bypass the ICE + fn is_builtin_index( + &mut self, + typeck_results: &TypeckResults<'tcx>, + e: &hir::Expr<'_>, + base_ty: Ty<'tcx>, + index_ty: Ty<'tcx>, + ) -> bool { + if let Some(elem_ty) = base_ty.builtin_index() { + let Some(exp_ty) = typeck_results.expr_ty_opt(e) else {return false;}; + let resolved_exp_ty = self.resolve(exp_ty, &e.span); + + elem_ty == resolved_exp_ty && index_ty == self.fcx.tcx.types.usize + } else { + false + } + } + // Similar to operators, indexing is always assumed to be overloaded // Here, correct cases where an indexing expression can be simplified // to use builtin indexing because the index type is known to be @@ -222,8 +243,9 @@ impl<'cx, 'tcx> WritebackCx<'cx, 'tcx> { ) }); let index_ty = self.fcx.resolve_vars_if_possible(index_ty); + let resolved_base_ty = self.resolve(*base_ty, &base.span); - if base_ty.builtin_index().is_some() && index_ty == self.fcx.tcx.types.usize { + if self.is_builtin_index(&typeck_results, e, resolved_base_ty, index_ty) { // Remove the method call record typeck_results.type_dependent_defs_mut().remove(e.hir_id); typeck_results.node_substs_mut().remove(e.hir_id); @@ -292,6 +314,17 @@ impl<'cx, 'tcx> Visitor<'tcx> for WritebackCx<'cx, 'tcx> { intravisit::walk_expr(self, e); } + fn visit_generic_param(&mut self, p: &'tcx hir::GenericParam<'tcx>) { + match &p.kind { + hir::GenericParamKind::Lifetime { .. } => { + // Nothing to write back here + } + hir::GenericParamKind::Type { .. } | hir::GenericParamKind::Const { .. } => { + self.tcx().sess.delay_span_bug(p.span, format!("unexpected generic param: {p:?}")); + } + } + } + fn visit_block(&mut self, b: &'tcx hir::Block<'tcx>) { self.visit_node_id(b.span, b.hir_id); intravisit::walk_block(self, b); @@ -468,7 +501,7 @@ impl<'cx, 'tcx> WritebackCx<'cx, 'tcx> { if !errors_buffer.is_empty() { errors_buffer.sort_by_key(|diag| diag.span.primary_span()); - for mut diag in errors_buffer.drain(..) { + for mut diag in errors_buffer { self.tcx().sess.diagnostic().emit_diagnostic(&mut diag); } } @@ -503,33 +536,37 @@ impl<'cx, 'tcx> WritebackCx<'cx, 'tcx> { let opaque_types = self.fcx.infcx.inner.borrow_mut().opaque_type_storage.take_opaque_types(); for (opaque_type_key, decl) in opaque_types { - let hidden_type = match decl.origin { - hir::OpaqueTyOrigin::FnReturn(_) | hir::OpaqueTyOrigin::AsyncFn(_) => { - let ty = self.resolve(decl.hidden_type.ty, &decl.hidden_type.span); - struct RecursionChecker { - def_id: LocalDefId, - } - impl<'tcx> ty::TypeVisitor<'tcx> for RecursionChecker { - type BreakTy = (); - fn visit_ty(&mut self, t: Ty<'tcx>) -> ControlFlow<Self::BreakTy> { - if let ty::Opaque(def_id, _) = *t.kind() { - if def_id == self.def_id.to_def_id() { - return ControlFlow::Break(()); - } - } - t.super_visit_with(self) + let hidden_type = self.resolve(decl.hidden_type, &decl.hidden_type.span); + let opaque_type_key = self.resolve(opaque_type_key, &decl.hidden_type.span); + + struct RecursionChecker { + def_id: LocalDefId, + } + impl<'tcx> ty::TypeVisitor<'tcx> for RecursionChecker { + type BreakTy = (); + fn visit_ty(&mut self, t: Ty<'tcx>) -> ControlFlow<Self::BreakTy> { + if let ty::Opaque(def_id, _) = *t.kind() { + if def_id == self.def_id.to_def_id() { + return ControlFlow::Break(()); } } - if ty - .visit_with(&mut RecursionChecker { def_id: opaque_type_key.def_id }) - .is_break() - { - return; - } - Some(ty) + t.super_visit_with(self) } - hir::OpaqueTyOrigin::TyAlias => None, - }; + } + if hidden_type + .visit_with(&mut RecursionChecker { def_id: opaque_type_key.def_id }) + .is_break() + { + continue; + } + + let hidden_type = hidden_type.remap_generic_params_to_declaration_params( + opaque_type_key, + self.fcx.infcx.tcx, + true, + decl.origin, + ); + self.typeck_results.concrete_opaque_types.insert(opaque_type_key.def_id, hidden_type); } } @@ -667,7 +704,7 @@ impl Locatable for hir::HirId { /// unresolved types and so forth. struct Resolver<'cx, 'tcx> { tcx: TyCtxt<'tcx>, - infcx: &'cx InferCtxt<'cx, 'tcx>, + infcx: &'cx InferCtxt<'tcx>, span: &'cx dyn Locatable, body: &'tcx hir::Body<'tcx>, @@ -684,27 +721,14 @@ impl<'cx, 'tcx> Resolver<'cx, 'tcx> { Resolver { tcx: fcx.tcx, infcx: fcx, span, body, replaced_with_error: false } } - fn report_type_error(&self, t: Ty<'tcx>) { + fn report_error(&self, p: impl Into<ty::GenericArg<'tcx>>) { if !self.tcx.sess.has_errors().is_some() { self.infcx + .err_ctxt() .emit_inference_failure_err( Some(self.body.id()), self.span.to_span(self.tcx), - t.into(), - E0282, - false, - ) - .emit(); - } - } - - fn report_const_error(&self, c: ty::Const<'tcx>) { - if self.tcx.sess.has_errors().is_none() { - self.infcx - .emit_inference_failure_err( - Some(self.body.id()), - self.span.to_span(self.tcx), - c.into(), + p.into(), E0282, false, ) @@ -749,7 +773,7 @@ impl<'cx, 'tcx> TypeFolder<'tcx> for Resolver<'cx, 'tcx> { } Err(_) => { debug!("Resolver::fold_ty: input type `{:?}` not fully resolvable", t); - self.report_type_error(t); + self.report_error(t); self.replaced_with_error = true; self.tcx().ty_error() } @@ -766,7 +790,7 @@ impl<'cx, 'tcx> TypeFolder<'tcx> for Resolver<'cx, 'tcx> { Ok(ct) => self.tcx.erase_regions(ct), Err(_) => { debug!("Resolver::fold_const: input const `{:?}` not fully resolvable", ct); - self.report_const_error(ct); + self.report_error(ct); self.replaced_with_error = true; self.tcx().const_error(ct.ty()) } |