Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Try to suggest dereferences on trait selection failed #72456

Merged
merged 3 commits into from
Jun 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
229 changes: 229 additions & 0 deletions src/librustc_trait_selection/autoderef.rs
Original file line number Diff line number Diff line change
@@ -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<traits::PredicateObligation<'tcx>>,
}

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<Self::Item> {
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<Ty<'tcx>> {
debug!("overloaded_deref_ty({:?})", ty);

let tcx = self.infcx.tcx;

// <ty as Deref>
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<traits::PredicateObligation<'tcx>> {
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();
}
}
1 change: 1 addition & 0 deletions src/librustc_trait_selection/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
1 change: 1 addition & 0 deletions src/librustc_trait_selection/traits/error_reporting/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use super::{
SelectionContext,
};

use crate::autoderef::Autoderef;
use crate::infer::InferCtxt;
use crate::traits::normalize_projection_type;

Expand All @@ -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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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(
Expand Down
Loading