diff --git a/src/librustc_trait_selection/autoderef.rs b/src/librustc_trait_selection/autoderef.rs new file mode 100644 index 0000000000000..d542e16d83f10 --- /dev/null +++ b/src/librustc_trait_selection/autoderef.rs @@ -0,0 +1,229 @@ +use crate::traits::query::evaluate_obligation::InferCtxtExt; +use crate::traits::{self, TraitEngine}; +use rustc_errors::struct_span_err; +use rustc_hir as hir; +use rustc_infer::infer::InferCtxt; +use rustc_middle::ty::{self, TraitRef, Ty, TyCtxt, WithConstness}; +use rustc_middle::ty::{ToPredicate, TypeFoldable}; +use rustc_session::DiagnosticMessageId; +use rustc_span::symbol::Ident; +use rustc_span::Span; + +#[derive(Copy, Clone, Debug)] +pub enum AutoderefKind { + Builtin, + Overloaded, +} + +struct AutoderefSnapshot<'tcx> { + at_start: bool, + reached_recursion_limit: bool, + steps: Vec<(Ty<'tcx>, AutoderefKind)>, + cur_ty: Ty<'tcx>, + obligations: Vec>, +} + +pub struct Autoderef<'a, 'tcx> { + // Meta infos: + infcx: &'a InferCtxt<'a, 'tcx>, + span: Span, + body_id: hir::HirId, + param_env: ty::ParamEnv<'tcx>, + + // Current state: + state: AutoderefSnapshot<'tcx>, + + // Configurations: + include_raw_pointers: bool, + silence_errors: bool, +} + +impl<'a, 'tcx> Iterator for Autoderef<'a, 'tcx> { + type Item = (Ty<'tcx>, usize); + + fn next(&mut self) -> Option { + let tcx = self.infcx.tcx; + + debug!("autoderef: steps={:?}, cur_ty={:?}", self.state.steps, self.state.cur_ty); + if self.state.at_start { + self.state.at_start = false; + debug!("autoderef stage #0 is {:?}", self.state.cur_ty); + return Some((self.state.cur_ty, 0)); + } + + // If we have reached the recursion limit, error gracefully. + if !tcx.sess.recursion_limit().value_within_limit(self.state.steps.len()) { + if !self.silence_errors { + report_autoderef_recursion_limit_error(tcx, self.span, self.state.cur_ty); + } + self.state.reached_recursion_limit = true; + return None; + } + + if self.state.cur_ty.is_ty_var() { + return None; + } + + // Otherwise, deref if type is derefable: + let (kind, new_ty) = + if let Some(mt) = self.state.cur_ty.builtin_deref(self.include_raw_pointers) { + (AutoderefKind::Builtin, mt.ty) + } else if let Some(ty) = self.overloaded_deref_ty(self.state.cur_ty) { + (AutoderefKind::Overloaded, ty) + } else { + return None; + }; + + if new_ty.references_error() { + return None; + } + + self.state.steps.push((self.state.cur_ty, kind)); + debug!( + "autoderef stage #{:?} is {:?} from {:?}", + self.step_count(), + new_ty, + (self.state.cur_ty, kind) + ); + self.state.cur_ty = new_ty; + + Some((self.state.cur_ty, self.step_count())) + } +} + +impl<'a, 'tcx> Autoderef<'a, 'tcx> { + pub fn new( + infcx: &'a InferCtxt<'a, 'tcx>, + param_env: ty::ParamEnv<'tcx>, + body_id: hir::HirId, + span: Span, + base_ty: Ty<'tcx>, + ) -> Autoderef<'a, 'tcx> { + Autoderef { + infcx, + span, + body_id, + param_env, + state: AutoderefSnapshot { + steps: vec![], + cur_ty: infcx.resolve_vars_if_possible(&base_ty), + obligations: vec![], + at_start: true, + reached_recursion_limit: false, + }, + include_raw_pointers: false, + silence_errors: false, + } + } + + fn overloaded_deref_ty(&mut self, ty: Ty<'tcx>) -> Option> { + debug!("overloaded_deref_ty({:?})", ty); + + let tcx = self.infcx.tcx; + + // + let trait_ref = TraitRef { + def_id: tcx.lang_items().deref_trait()?, + substs: tcx.mk_substs_trait(ty, &[]), + }; + + let cause = traits::ObligationCause::misc(self.span, self.body_id); + + let obligation = traits::Obligation::new( + cause.clone(), + self.param_env, + trait_ref.without_const().to_predicate(tcx), + ); + if !self.infcx.predicate_may_hold(&obligation) { + debug!("overloaded_deref_ty: cannot match obligation"); + return None; + } + + let mut fulfillcx = traits::FulfillmentContext::new_in_snapshot(); + let normalized_ty = fulfillcx.normalize_projection_type( + &self.infcx, + self.param_env, + ty::ProjectionTy::from_ref_and_name(tcx, trait_ref, Ident::from_str("Target")), + cause, + ); + if let Err(e) = fulfillcx.select_where_possible(&self.infcx) { + // This shouldn't happen, except for evaluate/fulfill mismatches, + // but that's not a reason for an ICE (`predicate_may_hold` is conservative + // by design). + debug!("overloaded_deref_ty: encountered errors {:?} while fulfilling", e); + return None; + } + let obligations = fulfillcx.pending_obligations(); + debug!("overloaded_deref_ty({:?}) = ({:?}, {:?})", ty, normalized_ty, obligations); + self.state.obligations.extend(obligations); + + Some(self.infcx.resolve_vars_if_possible(&normalized_ty)) + } + + /// Returns the final type we ended up with, which may be an inference + /// variable (we will resolve it first, if we want). + pub fn final_ty(&self, resolve: bool) -> Ty<'tcx> { + if resolve { + self.infcx.resolve_vars_if_possible(&self.state.cur_ty) + } else { + self.state.cur_ty + } + } + + pub fn step_count(&self) -> usize { + self.state.steps.len() + } + + pub fn into_obligations(self) -> Vec> { + self.state.obligations + } + + pub fn steps(&self) -> &[(Ty<'tcx>, AutoderefKind)] { + &self.state.steps + } + + pub fn span(&self) -> Span { + self.span.clone() + } + + pub fn reached_recursion_limit(&self) -> bool { + self.state.reached_recursion_limit + } + + /// also dereference through raw pointer types + /// e.g., assuming ptr_to_Foo is the type `*const Foo` + /// fcx.autoderef(span, ptr_to_Foo) => [*const Foo] + /// fcx.autoderef(span, ptr_to_Foo).include_raw_ptrs() => [*const Foo, Foo] + pub fn include_raw_pointers(mut self) -> Self { + self.include_raw_pointers = true; + self + } + + pub fn silence_errors(mut self) -> Self { + self.silence_errors = true; + self + } +} + +pub fn report_autoderef_recursion_limit_error<'tcx>(tcx: TyCtxt<'tcx>, span: Span, ty: Ty<'tcx>) { + // We've reached the recursion limit, error gracefully. + let suggested_limit = tcx.sess.recursion_limit() * 2; + let msg = format!("reached the recursion limit while auto-dereferencing `{:?}`", ty); + let error_id = (DiagnosticMessageId::ErrorId(55), Some(span), msg); + let fresh = tcx.sess.one_time_diagnostics.borrow_mut().insert(error_id); + if fresh { + struct_span_err!( + tcx.sess, + span, + E0055, + "reached the recursion limit while auto-dereferencing `{:?}`", + ty + ) + .span_label(span, "deref recursion limit reached") + .help(&format!( + "consider adding a `#![recursion_limit=\"{}\"]` attribute to your crate (`{}`)", + suggested_limit, tcx.crate_name, + )) + .emit(); + } +} diff --git a/src/librustc_trait_selection/lib.rs b/src/librustc_trait_selection/lib.rs index ea886cd1f9e9b..4692fa04ed587 100644 --- a/src/librustc_trait_selection/lib.rs +++ b/src/librustc_trait_selection/lib.rs @@ -28,6 +28,7 @@ extern crate log; #[macro_use] extern crate rustc_middle; +pub mod autoderef; pub mod infer; pub mod opaque_types; pub mod traits; diff --git a/src/librustc_trait_selection/traits/error_reporting/mod.rs b/src/librustc_trait_selection/traits/error_reporting/mod.rs index 060877f80adef..fd0c1a54d27ad 100644 --- a/src/librustc_trait_selection/traits/error_reporting/mod.rs +++ b/src/librustc_trait_selection/traits/error_reporting/mod.rs @@ -402,6 +402,7 @@ impl<'a, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'a, 'tcx> { err.span_label(enclosing_scope_span, s.as_str()); } + self.suggest_dereferences(&obligation, &mut err, &trait_ref, points_at_arg); self.suggest_borrow_on_unsized_slice(&obligation.cause.code, &mut err); self.suggest_fn_call(&obligation, &mut err, &trait_ref, points_at_arg); self.suggest_remove_reference(&obligation, &mut err, &trait_ref); diff --git a/src/librustc_trait_selection/traits/error_reporting/suggestions.rs b/src/librustc_trait_selection/traits/error_reporting/suggestions.rs index dfd7dac72d8e1..176bd90303ddd 100644 --- a/src/librustc_trait_selection/traits/error_reporting/suggestions.rs +++ b/src/librustc_trait_selection/traits/error_reporting/suggestions.rs @@ -3,6 +3,7 @@ use super::{ SelectionContext, }; +use crate::autoderef::Autoderef; use crate::infer::InferCtxt; use crate::traits::normalize_projection_type; @@ -13,11 +14,11 @@ use rustc_hir::def_id::DefId; use rustc_hir::intravisit::Visitor; use rustc_hir::lang_items; use rustc_hir::{AsyncGeneratorKind, GeneratorKind, Node}; -use rustc_middle::ty::TypeckTables; use rustc_middle::ty::{ self, suggest_constraining_type_param, AdtKind, DefIdTree, Infer, InferTy, ToPredicate, Ty, TyCtxt, TypeFoldable, WithConstness, }; +use rustc_middle::ty::{TypeAndMut, TypeckTables}; use rustc_span::symbol::{kw, sym, Ident, Symbol}; use rustc_span::{MultiSpan, Span, DUMMY_SP}; use std::fmt; @@ -48,6 +49,14 @@ pub trait InferCtxtExt<'tcx> { err: &mut DiagnosticBuilder<'_>, ); + fn suggest_dereferences( + &self, + obligation: &PredicateObligation<'tcx>, + err: &mut DiagnosticBuilder<'tcx>, + trait_ref: &ty::PolyTraitRef<'tcx>, + points_at_arg: bool, + ); + fn get_closure_name( &self, def_id: DefId, @@ -450,6 +459,62 @@ impl<'a, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'a, 'tcx> { } } + /// When after several dereferencing, the reference satisfies the trait + /// binding. This function provides dereference suggestion for this + /// specific situation. + fn suggest_dereferences( + &self, + obligation: &PredicateObligation<'tcx>, + err: &mut DiagnosticBuilder<'tcx>, + trait_ref: &ty::PolyTraitRef<'tcx>, + points_at_arg: bool, + ) { + // It only make sense when suggesting dereferences for arguments + if !points_at_arg { + return; + } + let param_env = obligation.param_env; + let body_id = obligation.cause.body_id; + let span = obligation.cause.span; + let real_trait_ref = match &obligation.cause.code { + ObligationCauseCode::ImplDerivedObligation(cause) + | ObligationCauseCode::DerivedObligation(cause) + | ObligationCauseCode::BuiltinDerivedObligation(cause) => &cause.parent_trait_ref, + _ => trait_ref, + }; + let real_ty = match real_trait_ref.self_ty().no_bound_vars() { + Some(ty) => ty, + None => return, + }; + + if let ty::Ref(region, base_ty, mutbl) = real_ty.kind { + let mut autoderef = Autoderef::new(self, param_env, body_id, span, base_ty); + if let Some(steps) = autoderef.find_map(|(ty, steps)| { + // Re-add the `&` + let ty = self.tcx.mk_ref(region, TypeAndMut { ty, mutbl }); + let obligation = + self.mk_trait_obligation_with_new_self_ty(param_env, real_trait_ref, ty); + Some(steps).filter(|_| self.predicate_may_hold(&obligation)) + }) { + if steps > 0 { + if let Ok(src) = self.tcx.sess.source_map().span_to_snippet(span) { + // Don't care about `&mut` because `DerefMut` is used less + // often and user will not expect autoderef happens. + if src.starts_with("&") && !src.starts_with("&mut ") { + let derefs = "*".repeat(steps); + err.span_suggestion( + span, + "consider adding dereference here", + format!("&{}{}", derefs, &src[1..]), + Applicability::MachineApplicable, + ); + } + } + } + } + } + } + /// When encountering an assignment of an unsized trait, like `let x = ""[..];`, provide a /// suggestion to borrow the initializer in order to use have a slice instead. fn suggest_borrow_on_unsized_slice( diff --git a/src/librustc_typeck/check/autoderef.rs b/src/librustc_typeck/check/autoderef.rs index 2570025959cb4..97d2b3e5a8e45 100644 --- a/src/librustc_typeck/check/autoderef.rs +++ b/src/librustc_typeck/check/autoderef.rs @@ -1,191 +1,46 @@ +//! Some helper functions for `AutoDeref` use super::method::MethodCallee; use super::{FnCtxt, PlaceOp}; -use rustc_errors::struct_span_err; -use rustc_hir as hir; -use rustc_infer::infer::{InferCtxt, InferOk}; +use rustc_infer::infer::InferOk; use rustc_middle::ty::adjustment::{Adjust, Adjustment, OverloadedDeref}; -use rustc_middle::ty::{self, TraitRef, Ty, TyCtxt, WithConstness}; -use rustc_middle::ty::{ToPredicate, TypeFoldable}; -use rustc_session::DiagnosticMessageId; -use rustc_span::symbol::Ident; +use rustc_middle::ty::{self, Ty}; use rustc_span::Span; -use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt; -use rustc_trait_selection::traits::{self, TraitEngine}; +use rustc_trait_selection::autoderef::{Autoderef, AutoderefKind}; use std::iter; -#[derive(Copy, Clone, Debug)] -enum AutoderefKind { - Builtin, - Overloaded, -} - -pub struct Autoderef<'a, 'tcx> { - infcx: &'a InferCtxt<'a, 'tcx>, - body_id: hir::HirId, - param_env: ty::ParamEnv<'tcx>, - steps: Vec<(Ty<'tcx>, AutoderefKind)>, - cur_ty: Ty<'tcx>, - obligations: Vec>, - at_start: bool, - include_raw_pointers: bool, - span: Span, - silence_errors: bool, - reached_recursion_limit: bool, -} - -impl<'a, 'tcx> Iterator for Autoderef<'a, 'tcx> { - type Item = (Ty<'tcx>, usize); - - fn next(&mut self) -> Option { - let tcx = self.infcx.tcx; - - debug!("autoderef: steps={:?}, cur_ty={:?}", self.steps, self.cur_ty); - if self.at_start { - self.at_start = false; - debug!("autoderef stage #0 is {:?}", self.cur_ty); - return Some((self.cur_ty, 0)); - } - - if !tcx.sess.recursion_limit().value_within_limit(self.steps.len()) { - if !self.silence_errors { - report_autoderef_recursion_limit_error(tcx, self.span, self.cur_ty); - } - self.reached_recursion_limit = true; - return None; - } - - if self.cur_ty.is_ty_var() { - return None; - } - - // Otherwise, deref if type is derefable: - let (kind, new_ty) = if let Some(mt) = self.cur_ty.builtin_deref(self.include_raw_pointers) - { - (AutoderefKind::Builtin, mt.ty) - } else { - let ty = self.overloaded_deref_ty(self.cur_ty)?; - (AutoderefKind::Overloaded, ty) - }; - - if new_ty.references_error() { - return None; - } - - self.steps.push((self.cur_ty, kind)); - debug!( - "autoderef stage #{:?} is {:?} from {:?}", - self.steps.len(), - new_ty, - (self.cur_ty, kind) - ); - self.cur_ty = new_ty; - - Some((self.cur_ty, self.steps.len())) +impl<'a, 'tcx> FnCtxt<'a, 'tcx> { + pub fn autoderef(&'a self, span: Span, base_ty: Ty<'tcx>) -> Autoderef<'a, 'tcx> { + Autoderef::new(self, self.param_env, self.body_id, span, base_ty) } -} -impl<'a, 'tcx> Autoderef<'a, 'tcx> { - pub fn new( - infcx: &'a InferCtxt<'a, 'tcx>, - param_env: ty::ParamEnv<'tcx>, - body_id: hir::HirId, + pub fn try_overloaded_deref( + &self, span: Span, base_ty: Ty<'tcx>, - ) -> Autoderef<'a, 'tcx> { - Autoderef { - infcx, - body_id, - param_env, - steps: vec![], - cur_ty: infcx.resolve_vars_if_possible(&base_ty), - obligations: vec![], - at_start: true, - include_raw_pointers: false, - silence_errors: false, - reached_recursion_limit: false, - span, - } - } - - fn overloaded_deref_ty(&mut self, ty: Ty<'tcx>) -> Option> { - debug!("overloaded_deref_ty({:?})", ty); - - let tcx = self.infcx.tcx; - - // - let trait_ref = TraitRef { - def_id: tcx.lang_items().deref_trait()?, - substs: tcx.mk_substs_trait(ty, &[]), - }; - - let cause = traits::ObligationCause::misc(self.span, self.body_id); - - let obligation = traits::Obligation::new( - cause.clone(), - self.param_env, - trait_ref.without_const().to_predicate(tcx), - ); - if !self.infcx.predicate_may_hold(&obligation) { - debug!("overloaded_deref_ty: cannot match obligation"); - return None; - } - - let mut fulfillcx = traits::FulfillmentContext::new_in_snapshot(); - let normalized_ty = fulfillcx.normalize_projection_type( - &self.infcx, - self.param_env, - ty::ProjectionTy::from_ref_and_name(tcx, trait_ref, Ident::from_str("Target")), - cause, - ); - if let Err(e) = fulfillcx.select_where_possible(&self.infcx) { - // This shouldn't happen, except for evaluate/fulfill mismatches, - // but that's not a reason for an ICE (`predicate_may_hold` is conservative - // by design). - debug!("overloaded_deref_ty: encountered errors {:?} while fulfilling", e); - return None; - } - let obligations = fulfillcx.pending_obligations(); - debug!("overloaded_deref_ty({:?}) = ({:?}, {:?})", ty, normalized_ty, obligations); - self.obligations.extend(obligations); - - Some(self.infcx.resolve_vars_if_possible(&normalized_ty)) - } - - /// Returns the final type, generating an error if it is an - /// unresolved inference variable. - pub fn unambiguous_final_ty(&self, fcx: &FnCtxt<'a, 'tcx>) -> Ty<'tcx> { - fcx.structurally_resolved_type(self.span, self.cur_ty) - } - - /// Returns the final type we ended up with, which may well be an - /// inference variable (we will resolve it first, if possible). - pub fn maybe_ambiguous_final_ty(&self) -> Ty<'tcx> { - self.infcx.resolve_vars_if_possible(&self.cur_ty) - } - - pub fn step_count(&self) -> usize { - self.steps.len() + ) -> Option>> { + self.try_overloaded_place_op(span, base_ty, &[], PlaceOp::Deref) } /// Returns the adjustment steps. - pub fn adjust_steps(&self, fcx: &FnCtxt<'a, 'tcx>) -> Vec> { - fcx.register_infer_ok_obligations(self.adjust_steps_as_infer_ok(fcx)) + pub fn adjust_steps(&self, autoderef: &Autoderef<'a, 'tcx>) -> Vec> { + self.register_infer_ok_obligations(self.adjust_steps_as_infer_ok(autoderef)) } pub fn adjust_steps_as_infer_ok( &self, - fcx: &FnCtxt<'a, 'tcx>, + autoderef: &Autoderef<'a, 'tcx>, ) -> InferOk<'tcx, Vec>> { let mut obligations = vec![]; - let targets = self.steps.iter().skip(1).map(|&(ty, _)| ty).chain(iter::once(self.cur_ty)); - let steps: Vec<_> = self - .steps + let steps = autoderef.steps(); + let targets = + steps.iter().skip(1).map(|&(ty, _)| ty).chain(iter::once(autoderef.final_ty(false))); + let steps: Vec<_> = steps .iter() .map(|&(source, kind)| { if let AutoderefKind::Overloaded = kind { - fcx.try_overloaded_deref(self.span, source).and_then( + self.try_overloaded_deref(autoderef.span(), source).and_then( |InferOk { value: method, obligations: o }| { obligations.extend(o); if let ty::Ref(region, _, mutbl) = method.sig.output().kind { @@ -205,67 +60,4 @@ impl<'a, 'tcx> Autoderef<'a, 'tcx> { InferOk { obligations, value: steps } } - - /// also dereference through raw pointer types - /// e.g., assuming ptr_to_Foo is the type `*const Foo` - /// fcx.autoderef(span, ptr_to_Foo) => [*const Foo] - /// fcx.autoderef(span, ptr_to_Foo).include_raw_ptrs() => [*const Foo, Foo] - pub fn include_raw_pointers(mut self) -> Self { - self.include_raw_pointers = true; - self - } - - pub fn silence_errors(mut self) -> Self { - self.silence_errors = true; - self - } - - pub fn reached_recursion_limit(&self) -> bool { - self.reached_recursion_limit - } - - pub fn finalize(self, fcx: &FnCtxt<'a, 'tcx>) { - fcx.register_predicates(self.into_obligations()); - } - - pub fn into_obligations(self) -> Vec> { - self.obligations - } -} - -pub fn report_autoderef_recursion_limit_error<'tcx>(tcx: TyCtxt<'tcx>, span: Span, ty: Ty<'tcx>) { - // We've reached the recursion limit, error gracefully. - let suggested_limit = tcx.sess.recursion_limit() * 2; - let msg = format!("reached the recursion limit while auto-dereferencing `{:?}`", ty); - let error_id = (DiagnosticMessageId::ErrorId(55), Some(span), msg); - let fresh = tcx.sess.one_time_diagnostics.borrow_mut().insert(error_id); - if fresh { - struct_span_err!( - tcx.sess, - span, - E0055, - "reached the recursion limit while auto-dereferencing `{:?}`", - ty - ) - .span_label(span, "deref recursion limit reached") - .help(&format!( - "consider adding a `#![recursion_limit=\"{}\"]` attribute to your crate (`{}`)", - suggested_limit, tcx.crate_name, - )) - .emit(); - } -} - -impl<'a, 'tcx> FnCtxt<'a, 'tcx> { - pub fn autoderef(&'a self, span: Span, base_ty: Ty<'tcx>) -> Autoderef<'a, 'tcx> { - Autoderef::new(self, self.param_env, self.body_id, span, base_ty) - } - - pub fn try_overloaded_deref( - &self, - span: Span, - base_ty: Ty<'tcx>, - ) -> Option>> { - self.try_overloaded_place_op(span, base_ty, &[], PlaceOp::Deref) - } } diff --git a/src/librustc_typeck/check/callee.rs b/src/librustc_typeck/check/callee.rs index fe200a0ad2a1c..308ed5d840202 100644 --- a/src/librustc_typeck/check/callee.rs +++ b/src/librustc_typeck/check/callee.rs @@ -1,4 +1,3 @@ -use super::autoderef::Autoderef; use super::method::MethodCallee; use super::{Expectation, FnCtxt, TupleArgumentsFlag}; use crate::type_error_struct; @@ -17,6 +16,7 @@ use rustc_middle::ty::{self, Ty, TyCtxt, TypeFoldable}; use rustc_span::symbol::Ident; use rustc_span::Span; use rustc_target::spec::abi; +use rustc_trait_selection::autoderef::Autoderef; /// Checks that it is legal to call methods of the trait corresponding /// to `trait_id` (this only cares about the trait, not the specific @@ -72,7 +72,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { while result.is_none() && autoderef.next().is_some() { result = self.try_overloaded_call_step(call_expr, callee_expr, arg_exprs, &autoderef); } - autoderef.finalize(self); + self.register_predicates(autoderef.into_obligations()); let output = match result { None => { @@ -106,7 +106,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { arg_exprs: &'tcx [hir::Expr<'tcx>], autoderef: &Autoderef<'a, 'tcx>, ) -> Option> { - let adjusted_ty = autoderef.unambiguous_final_ty(self); + 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 @@ -115,7 +116,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // 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 = autoderef.adjust_steps(self); + let adjustments = self.adjust_steps(autoderef); self.apply_adjustments(callee_expr, adjustments); return Some(CallStep::Builtin(adjusted_ty)); } @@ -135,7 +136,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { &closure_sig, ) .0; - let adjustments = autoderef.adjust_steps(self); + let adjustments = self.adjust_steps(autoderef); self.record_deferred_call_resolution( def_id, DeferredCallResolution { @@ -176,7 +177,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { 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 = autoderef.adjust_steps(self); + let mut adjustments = self.adjust_steps(autoderef); adjustments.extend(autoref); self.apply_adjustments(callee_expr, adjustments); CallStep::Overloaded(method) diff --git a/src/librustc_typeck/check/coercion.rs b/src/librustc_typeck/check/coercion.rs index 15d210b89b64d..dec53c369bb18 100644 --- a/src/librustc_typeck/check/coercion.rs +++ b/src/librustc_typeck/check/coercion.rs @@ -409,7 +409,7 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> { } let InferOk { value: mut adjustments, obligations: o } = - autoderef.adjust_steps_as_infer_ok(self); + self.adjust_steps_as_infer_ok(&autoderef); obligations.extend(o); obligations.extend(autoderef.into_obligations()); diff --git a/src/librustc_typeck/check/expr.rs b/src/librustc_typeck/check/expr.rs index 6a00667637155..1eaa5a6c31e20 100644 --- a/src/librustc_typeck/check/expr.rs +++ b/src/librustc_typeck/check/expr.rs @@ -1447,9 +1447,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // of error recovery. self.write_field_index(expr.hir_id, index); if field.vis.is_accessible_from(def_scope, self.tcx) { - let adjustments = autoderef.adjust_steps(self); + let adjustments = self.adjust_steps(&autoderef); self.apply_adjustments(base, adjustments); - autoderef.finalize(self); + self.register_predicates(autoderef.into_obligations()); self.tcx.check_stability(field.did, Some(expr.hir_id), expr.span); return field_ty; @@ -1462,9 +1462,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { if let Ok(index) = fstr.parse::() { if fstr == index.to_string() { if let Some(field_ty) = tys.get(index) { - let adjustments = autoderef.adjust_steps(self); + let adjustments = self.adjust_steps(&autoderef); self.apply_adjustments(base, adjustments); - autoderef.finalize(self); + self.register_predicates(autoderef.into_obligations()); self.write_field_index(expr.hir_id, index); return field_ty.expect_ty(); @@ -1475,7 +1475,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { _ => {} } } - autoderef.unambiguous_final_ty(self); + self.structurally_resolved_type(autoderef.span(), autoderef.final_ty(false)); if let Some((did, field_ty)) = private_candidate { self.ban_private_field_access(expr, expr_t, field, did); diff --git a/src/librustc_typeck/check/method/confirm.rs b/src/librustc_typeck/check/method/confirm.rs index 6844c9416af8a..1c3d23a3a241f 100644 --- a/src/librustc_typeck/check/method/confirm.rs +++ b/src/librustc_typeck/check/method/confirm.rs @@ -144,9 +144,10 @@ impl<'a, 'tcx> ConfirmContext<'a, 'tcx> { }; assert_eq!(n, pick.autoderefs); - let mut adjustments = autoderef.adjust_steps(self); + let mut adjustments = self.adjust_steps(&autoderef); - let mut target = autoderef.unambiguous_final_ty(self); + let mut target = + self.structurally_resolved_type(autoderef.span(), autoderef.final_ty(false)); if let Some(mutbl) = pick.autoref { let region = self.next_region_var(infer::Autoref(self.span)); @@ -176,7 +177,7 @@ impl<'a, 'tcx> ConfirmContext<'a, 'tcx> { assert!(pick.unsize.is_none()); } - autoderef.finalize(self); + self.register_predicates(autoderef.into_obligations()); // Write out the final adjustments. self.apply_adjustments(self.self_expr, adjustments); diff --git a/src/librustc_typeck/check/method/probe.rs b/src/librustc_typeck/check/method/probe.rs index eb8f76687174e..8c4ef24b9455c 100644 --- a/src/librustc_typeck/check/method/probe.rs +++ b/src/librustc_typeck/check/method/probe.rs @@ -3,7 +3,6 @@ use super::MethodError; use super::NoMatchData; use super::{CandidateSource, ImplSource, TraitSource}; -use crate::check::autoderef::{self, Autoderef}; use crate::check::FnCtxt; use crate::hir::def::DefKind; use crate::hir::def_id::DefId; @@ -30,6 +29,7 @@ use rustc_session::config::nightly_options; use rustc_session::lint; use rustc_span::def_id::LocalDefId; use rustc_span::{symbol::Ident, Span, Symbol, DUMMY_SP}; +use rustc_trait_selection::autoderef::{self, Autoderef}; use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt; use rustc_trait_selection::traits::query::method_autoderef::MethodAutoderefBadTy; use rustc_trait_selection::traits::query::method_autoderef::{ @@ -477,7 +477,7 @@ fn method_autoderef_steps<'tcx>( }) .collect(); - let final_ty = autoderef.maybe_ambiguous_final_ty(); + 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, diff --git a/src/librustc_typeck/check/place_op.rs b/src/librustc_typeck/check/place_op.rs index d1c22cd1ac03e..b7c8f310a1414 100644 --- a/src/librustc_typeck/check/place_op.rs +++ b/src/librustc_typeck/check/place_op.rs @@ -1,4 +1,3 @@ -use crate::check::autoderef::Autoderef; use crate::check::method::MethodCallee; use crate::check::{FnCtxt, PlaceOp}; use rustc_hir as hir; @@ -9,6 +8,7 @@ use rustc_middle::ty::adjustment::{AllowTwoPhase, AutoBorrow, AutoBorrowMutabili use rustc_middle::ty::{self, Ty}; use rustc_span::symbol::{sym, Ident}; use rustc_span::Span; +use rustc_trait_selection::autoderef::Autoderef; impl<'a, 'tcx> FnCtxt<'a, 'tcx> { /// Type-check `*oprnd_expr` with `oprnd_expr` type-checked already. @@ -57,7 +57,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { while result.is_none() && autoderef.next().is_some() { result = self.try_index_step(expr, base_expr, &autoderef, idx_ty); } - autoderef.finalize(self); + self.register_predicates(autoderef.into_obligations()); result } @@ -73,7 +73,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { autoderef: &Autoderef<'a, 'tcx>, index_ty: Ty<'tcx>, ) -> Option<(/*index type*/ Ty<'tcx>, /*element type*/ Ty<'tcx>)> { - let adjusted_ty = autoderef.unambiguous_final_ty(self); + let adjusted_ty = + self.structurally_resolved_type(autoderef.span(), autoderef.final_ty(false)); debug!( "try_index_step(expr={:?}, base_expr={:?}, adjusted_ty={:?}, \ index_ty={:?})", @@ -105,7 +106,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { debug!("try_index_step: success, using overloaded indexing"); let method = self.register_infer_ok_obligations(ok); - let mut adjustments = autoderef.adjust_steps(self); + let mut adjustments = self.adjust_steps(autoderef); if let ty::Ref(region, _, hir::Mutability::Not) = method.sig.inputs()[0].kind { adjustments.push(Adjustment { kind: Adjust::Borrow(AutoBorrow::Ref(region, AutoBorrowMutability::Not)), diff --git a/src/librustc_typeck/check/wfcheck.rs b/src/librustc_typeck/check/wfcheck.rs index f3297ed674347..d1a86a7ee89a8 100644 --- a/src/librustc_typeck/check/wfcheck.rs +++ b/src/librustc_typeck/check/wfcheck.rs @@ -1118,7 +1118,7 @@ fn receiver_is_valid<'fcx, 'tcx>( ); if can_eq_self(potential_self_ty) { - autoderef.finalize(fcx); + fcx.register_predicates(autoderef.into_obligations()); if let Some(mut err) = fcx.demand_eqtype_with_origin(&cause, self_ty, potential_self_ty) diff --git a/src/test/ui/traits/trait-suggest-deferences-issue-39029.fixed b/src/test/ui/traits/trait-suggest-deferences-issue-39029.fixed new file mode 100644 index 0000000000000..2bb34b0ebee6f --- /dev/null +++ b/src/test/ui/traits/trait-suggest-deferences-issue-39029.fixed @@ -0,0 +1,18 @@ +// run-rustfix +use std::net::TcpListener; + +struct NoToSocketAddrs(String); + +impl std::ops::Deref for NoToSocketAddrs { + type Target = String; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +fn main() { + let _works = TcpListener::bind("some string"); + let bad = NoToSocketAddrs("bad".to_owned()); + let _errors = TcpListener::bind(&*bad); + //~^ ERROR the trait bound `NoToSocketAddrs: std::net::ToSocketAddrs` is not satisfied +} diff --git a/src/test/ui/traits/trait-suggest-deferences-issue-39029.rs b/src/test/ui/traits/trait-suggest-deferences-issue-39029.rs new file mode 100644 index 0000000000000..33d524608a058 --- /dev/null +++ b/src/test/ui/traits/trait-suggest-deferences-issue-39029.rs @@ -0,0 +1,18 @@ +// run-rustfix +use std::net::TcpListener; + +struct NoToSocketAddrs(String); + +impl std::ops::Deref for NoToSocketAddrs { + type Target = String; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +fn main() { + let _works = TcpListener::bind("some string"); + let bad = NoToSocketAddrs("bad".to_owned()); + let _errors = TcpListener::bind(&bad); + //~^ ERROR the trait bound `NoToSocketAddrs: std::net::ToSocketAddrs` is not satisfied +} diff --git a/src/test/ui/traits/trait-suggest-deferences-issue-39029.stderr b/src/test/ui/traits/trait-suggest-deferences-issue-39029.stderr new file mode 100644 index 0000000000000..0bf9794a744c9 --- /dev/null +++ b/src/test/ui/traits/trait-suggest-deferences-issue-39029.stderr @@ -0,0 +1,19 @@ +error[E0277]: the trait bound `NoToSocketAddrs: std::net::ToSocketAddrs` is not satisfied + --> $DIR/trait-suggest-deferences-issue-39029.rs:16:37 + | +LL | let _errors = TcpListener::bind(&bad); + | ^^^^ + | | + | the trait `std::net::ToSocketAddrs` is not implemented for `NoToSocketAddrs` + | help: consider adding dereference here: `&*bad` + | + ::: $SRC_DIR/libstd/net/tcp.rs:LL:COL + | +LL | pub fn bind(addr: A) -> io::Result { + | ------------- required by this bound in `std::net::TcpListener::bind` + | + = note: required because of the requirements on the impl of `std::net::ToSocketAddrs` for `&NoToSocketAddrs` + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0277`. diff --git a/src/test/ui/traits/trait-suggest-deferences-issue-62530.fixed b/src/test/ui/traits/trait-suggest-deferences-issue-62530.fixed new file mode 100644 index 0000000000000..fa7b9167d8d7f --- /dev/null +++ b/src/test/ui/traits/trait-suggest-deferences-issue-62530.fixed @@ -0,0 +1,15 @@ +// run-rustfix +fn takes_str(_x: &str) {} + +fn takes_type_parameter(_x: T) where T: SomeTrait {} + +trait SomeTrait {} +impl SomeTrait for &'_ str {} +impl SomeTrait for char {} + +fn main() { + let string = String::new(); + takes_str(&string); // Ok + takes_type_parameter(&*string); // Error + //~^ ERROR the trait bound `&std::string::String: SomeTrait` is not satisfied +} diff --git a/src/test/ui/traits/trait-suggest-deferences-issue-62530.rs b/src/test/ui/traits/trait-suggest-deferences-issue-62530.rs new file mode 100644 index 0000000000000..e785f01217735 --- /dev/null +++ b/src/test/ui/traits/trait-suggest-deferences-issue-62530.rs @@ -0,0 +1,15 @@ +// run-rustfix +fn takes_str(_x: &str) {} + +fn takes_type_parameter(_x: T) where T: SomeTrait {} + +trait SomeTrait {} +impl SomeTrait for &'_ str {} +impl SomeTrait for char {} + +fn main() { + let string = String::new(); + takes_str(&string); // Ok + takes_type_parameter(&string); // Error + //~^ ERROR the trait bound `&std::string::String: SomeTrait` is not satisfied +} diff --git a/src/test/ui/traits/trait-suggest-deferences-issue-62530.stderr b/src/test/ui/traits/trait-suggest-deferences-issue-62530.stderr new file mode 100644 index 0000000000000..9c2a582638ecb --- /dev/null +++ b/src/test/ui/traits/trait-suggest-deferences-issue-62530.stderr @@ -0,0 +1,15 @@ +error[E0277]: the trait bound `&std::string::String: SomeTrait` is not satisfied + --> $DIR/trait-suggest-deferences-issue-62530.rs:13:26 + | +LL | fn takes_type_parameter(_x: T) where T: SomeTrait {} + | --------- required by this bound in `takes_type_parameter` +... +LL | takes_type_parameter(&string); // Error + | ^^^^^^^ + | | + | the trait `SomeTrait` is not implemented for `&std::string::String` + | help: consider adding dereference here: `&*string` + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0277`. diff --git a/src/test/ui/traits/trait-suggest-deferences-multiple-0.fixed b/src/test/ui/traits/trait-suggest-deferences-multiple-0.fixed new file mode 100644 index 0000000000000..b7160b75c605e --- /dev/null +++ b/src/test/ui/traits/trait-suggest-deferences-multiple-0.fixed @@ -0,0 +1,36 @@ +// run-rustfix +use std::ops::Deref; + +trait Happy {} +struct LDM; +impl Happy for &LDM {} + +struct Foo(LDM); +struct Bar(Foo); +struct Baz(Bar); +impl Deref for Foo { + type Target = LDM; + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl Deref for Bar { + type Target = Foo; + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl Deref for Baz { + type Target = Bar; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +fn foo(_: T) where T: Happy {} + +fn main() { + let baz = Baz(Bar(Foo(LDM))); + foo(&***baz); + //~^ ERROR the trait bound `&Baz: Happy` is not satisfied +} diff --git a/src/test/ui/traits/trait-suggest-deferences-multiple-0.rs b/src/test/ui/traits/trait-suggest-deferences-multiple-0.rs new file mode 100644 index 0000000000000..9ac55177ffadd --- /dev/null +++ b/src/test/ui/traits/trait-suggest-deferences-multiple-0.rs @@ -0,0 +1,36 @@ +// run-rustfix +use std::ops::Deref; + +trait Happy {} +struct LDM; +impl Happy for &LDM {} + +struct Foo(LDM); +struct Bar(Foo); +struct Baz(Bar); +impl Deref for Foo { + type Target = LDM; + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl Deref for Bar { + type Target = Foo; + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl Deref for Baz { + type Target = Bar; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +fn foo(_: T) where T: Happy {} + +fn main() { + let baz = Baz(Bar(Foo(LDM))); + foo(&baz); + //~^ ERROR the trait bound `&Baz: Happy` is not satisfied +} diff --git a/src/test/ui/traits/trait-suggest-deferences-multiple-0.stderr b/src/test/ui/traits/trait-suggest-deferences-multiple-0.stderr new file mode 100644 index 0000000000000..add34a553bc9f --- /dev/null +++ b/src/test/ui/traits/trait-suggest-deferences-multiple-0.stderr @@ -0,0 +1,15 @@ +error[E0277]: the trait bound `&Baz: Happy` is not satisfied + --> $DIR/trait-suggest-deferences-multiple-0.rs:34:9 + | +LL | fn foo(_: T) where T: Happy {} + | ----- required by this bound in `foo` +... +LL | foo(&baz); + | ^^^^ + | | + | the trait `Happy` is not implemented for `&Baz` + | help: consider adding dereference here: `&***baz` + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0277`. diff --git a/src/test/ui/traits/trait-suggest-deferences-multiple-1.rs b/src/test/ui/traits/trait-suggest-deferences-multiple-1.rs new file mode 100644 index 0000000000000..91c6c7924a408 --- /dev/null +++ b/src/test/ui/traits/trait-suggest-deferences-multiple-1.rs @@ -0,0 +1,54 @@ +use std::ops::{Deref, DerefMut}; + +trait Happy {} +struct LDM; +impl Happy for &mut LDM {} + +struct Foo(LDM); +struct Bar(Foo); +struct Baz(Bar); +impl Deref for Foo { + type Target = LDM; + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl Deref for Bar { + type Target = Foo; + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl Deref for Baz { + type Target = Bar; + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl DerefMut for Foo { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} +impl DerefMut for Bar { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} +impl DerefMut for Baz { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + + +fn foo(_: T) where T: Happy {} + +fn main() { + // Currently the compiler doesn't try to suggest dereferences for situations + // where DerefMut involves. So this test is meant to ensure compiler doesn't + // generate incorrect help message. + let mut baz = Baz(Bar(Foo(LDM))); + foo(&mut baz); + //~^ ERROR the trait bound `&mut Baz: Happy` is not satisfied +} diff --git a/src/test/ui/traits/trait-suggest-deferences-multiple-1.stderr b/src/test/ui/traits/trait-suggest-deferences-multiple-1.stderr new file mode 100644 index 0000000000000..e90278fa16f0e --- /dev/null +++ b/src/test/ui/traits/trait-suggest-deferences-multiple-1.stderr @@ -0,0 +1,12 @@ +error[E0277]: the trait bound `&mut Baz: Happy` is not satisfied + --> $DIR/trait-suggest-deferences-multiple-1.rs:52:9 + | +LL | fn foo(_: T) where T: Happy {} + | ----- required by this bound in `foo` +... +LL | foo(&mut baz); + | ^^^^^^^^ the trait `Happy` is not implemented for `&mut Baz` + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0277`.