Skip to content

Add a more precise error message for issue #35976 #43600

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

Merged
merged 4 commits into from
Aug 4, 2017
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
2 changes: 1 addition & 1 deletion src/librustc/ty/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ pub struct ImplHeader<'tcx> {
pub predicates: Vec<Predicate<'tcx>>,
}

#[derive(Copy, Clone, Debug)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct AssociatedItem {
pub def_id: DefId,
pub name: Name,
Expand Down
53 changes: 48 additions & 5 deletions src/librustc_typeck/check/method/confirm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ impl<'a, 'gcx, 'tcx> Deref for ConfirmContext<'a, 'gcx, 'tcx> {
}
}

pub struct ConfirmResult<'tcx> {
pub callee: MethodCallee<'tcx>,
pub illegal_sized_bound: bool,
}

impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
pub fn confirm_method(&self,
span: Span,
Expand All @@ -46,7 +51,7 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
unadjusted_self_ty: Ty<'tcx>,
pick: probe::Pick<'tcx>,
segment: &hir::PathSegment)
-> MethodCallee<'tcx> {
-> ConfirmResult<'tcx> {
debug!("confirm(unadjusted_self_ty={:?}, pick={:?}, generic_args={:?})",
unadjusted_self_ty,
pick,
Expand Down Expand Up @@ -75,7 +80,7 @@ impl<'a, 'gcx, 'tcx> ConfirmContext<'a, 'gcx, 'tcx> {
unadjusted_self_ty: Ty<'tcx>,
pick: probe::Pick<'tcx>,
segment: &hir::PathSegment)
-> MethodCallee<'tcx> {
-> ConfirmResult<'tcx> {
// Adjust the self expression the user provided and obtain the adjusted type.
let self_ty = self.adjust_self_ty(unadjusted_self_ty, &pick);

Expand All @@ -91,12 +96,26 @@ impl<'a, 'gcx, 'tcx> ConfirmContext<'a, 'gcx, 'tcx> {
// Create the final signature for the method, replacing late-bound regions.
let (method_sig, method_predicates) = self.instantiate_method_sig(&pick, all_substs);

// If there is a `Self: Sized` bound and `Self` is a trait object, it is possible that
// something which derefs to `Self` actually implements the trait and the caller
// wanted to make a static dispatch on it but forgot to import the trait.
// See test `src/test/ui/issue-35976.rs`.
//
// In that case, we'll error anyway, but we'll also re-run the search with all traits
// in scope, and if we find another method which can be used, we'll output an
// appropriate hint suggesting to import the trait.
let illegal_sized_bound = self.predicates_require_illegal_sized_bound(&method_predicates);

// Unify the (adjusted) self type with what the method expects.
self.unify_receivers(self_ty, method_sig.inputs()[0]);

// Add any trait/regions obligations specified on the method's type parameters.
let method_ty = self.tcx.mk_fn_ptr(ty::Binder(method_sig));
self.add_obligations(method_ty, all_substs, &method_predicates);
// We won't add these if we encountered an illegal sized bound, so that we can use
// a custom error in that case.
if !illegal_sized_bound {
let method_ty = self.tcx.mk_fn_ptr(ty::Binder(method_sig));
self.add_obligations(method_ty, all_substs, &method_predicates);
}

// Create the final `MethodCallee`.
let callee = MethodCallee {
Expand All @@ -109,7 +128,7 @@ impl<'a, 'gcx, 'tcx> ConfirmContext<'a, 'gcx, 'tcx> {
self.convert_lvalue_derefs_to_mutable();
}

callee
ConfirmResult { callee, illegal_sized_bound }
}

///////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -533,6 +552,30 @@ impl<'a, 'gcx, 'tcx> ConfirmContext<'a, 'gcx, 'tcx> {
///////////////////////////////////////////////////////////////////////////
// MISCELLANY

fn predicates_require_illegal_sized_bound(&self,
predicates: &ty::InstantiatedPredicates<'tcx>)
-> bool {
let sized_def_id = match self.tcx.lang_items.sized_trait() {
Some(def_id) => def_id,
None => return false,
};

traits::elaborate_predicates(self.tcx, predicates.predicates.clone())
.filter_map(|predicate| {
match predicate {
ty::Predicate::Trait(trait_pred) if trait_pred.def_id() == sized_def_id =>
Some(trait_pred),
_ => None,
}
})
.any(|trait_pred| {
match trait_pred.0.self_ty().sty {
ty::TyDynamic(..) => true,
_ => false,
}
})
}

fn enforce_illegal_method_limitations(&self, pick: &probe::Pick) {
// Disallow calls to the method `drop` defined in the `Drop` trait.
match pick.item.container {
Expand Down
78 changes: 65 additions & 13 deletions src/librustc_typeck/check/method/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ mod confirm;
pub mod probe;
mod suggest;

use self::probe::IsSuggestion;
use self::probe::{IsSuggestion, ProbeScope};

#[derive(Clone, Copy, Debug)]
pub struct MethodCallee<'tcx> {
Expand All @@ -60,6 +60,10 @@ pub enum MethodError<'tcx> {

// Found an applicable method, but it is not visible.
PrivateMatch(Def),

// Found a `Self: Sized` bound where `Self` is a trait object, also the caller may have
// forgotten to import a trait.
IllegalSizedBound(Vec<DefId>),
}

// Contains a list of static methods that may apply, a list of unsatisfied trait predicates which
Expand Down Expand Up @@ -106,12 +110,13 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
-> bool {
let mode = probe::Mode::MethodCall;
match self.probe_for_name(span, mode, method_name, IsSuggestion(false),
self_ty, call_expr_id) {
self_ty, call_expr_id, ProbeScope::TraitsInScope) {
Ok(..) => true,
Err(NoMatch(..)) => false,
Err(Ambiguity(..)) => true,
Err(ClosureAmbiguity(..)) => true,
Err(PrivateMatch(..)) => allow_private,
Err(IllegalSizedBound(..)) => true,
}
}

Expand Down Expand Up @@ -142,10 +147,13 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
call_expr,
self_expr);

let mode = probe::Mode::MethodCall;
let self_ty = self.resolve_type_vars_if_possible(&self_ty);
let pick = self.probe_for_name(span, mode, segment.name, IsSuggestion(false),
self_ty, call_expr.id)?;
let pick = self.lookup_probe(
span,
segment.name,
self_ty,
call_expr,
ProbeScope::TraitsInScope
)?;

if let Some(import_id) = pick.import_id {
let import_def_id = self.tcx.hir.local_def_id(import_id);
Expand All @@ -155,12 +163,56 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {

self.tcx.check_stability(pick.item.def_id, call_expr.id, span);

Ok(self.confirm_method(span,
self_expr,
call_expr,
self_ty,
pick,
segment))
let result = self.confirm_method(span,
self_expr,
call_expr,
self_ty,
pick.clone(),
segment);

if result.illegal_sized_bound {
// We probe again, taking all traits into account (not only those in scope).
let candidates =
match self.lookup_probe(span,
segment.name,
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()],
Err(Ambiguity(ref sources)) => {
sources.iter()
.filter_map(|source| {
match *source {
// Note: this cannot come from an inherent impl,
// because the first probing succeeded.
ImplSource(def) => self.tcx.trait_id_of_impl(def),
TraitSource(_) => None,
}
})
.collect()
}
_ => Vec::new(),
};

return Err(IllegalSizedBound(candidates));
}

Ok(result.callee)
}

fn lookup_probe(&self,
span: Span,
method_name: ast::Name,
self_ty: ty::Ty<'tcx>,
call_expr: &'gcx hir::Expr,
scope: ProbeScope)
-> probe::PickResult<'tcx> {
let mode = probe::Mode::MethodCall;
let self_ty = self.resolve_type_vars_if_possible(&self_ty);
self.probe_for_name(span, mode, method_name, IsSuggestion(false),
self_ty, call_expr.id, scope)
}

/// `lookup_method_in_trait` is used for overloaded operators.
Expand Down Expand Up @@ -299,7 +351,7 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
-> Result<Def, MethodError<'tcx>> {
let mode = probe::Mode::Path;
let pick = self.probe_for_name(span, mode, method_name, IsSuggestion(false),
self_ty, expr_id)?;
self_ty, expr_id, ProbeScope::TraitsInScope)?;

if let Some(import_id) = pick.import_id {
let import_def_id = self.tcx.hir.local_def_id(import_id);
Expand Down
30 changes: 24 additions & 6 deletions src/librustc_typeck/check/method/probe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ enum CandidateKind<'tcx> {
ty::PolyTraitRef<'tcx>),
}

#[derive(Debug)]
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Pick<'tcx> {
pub item: ty::AssociatedItem,
pub kind: PickKind<'tcx>,
Expand All @@ -130,7 +130,7 @@ pub struct Pick<'tcx> {
pub unsize: Option<Ty<'tcx>>,
}

#[derive(Clone,Debug)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum PickKind<'tcx> {
InherentImplPick,
ExtensionImplPick(// Impl
Expand All @@ -155,6 +155,15 @@ pub enum Mode {
Path,
}

#[derive(PartialEq, Eq, Copy, Clone, Debug)]
pub enum ProbeScope {
// Assemble candidates coming only from traits in scope.
TraitsInScope,

// Assemble candidates coming from all traits.
AllTraits,
}

impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
/// This is used to offer suggestions to users. It returns methods
/// that could have been called which have the desired return
Expand All @@ -175,14 +184,14 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
scope_expr_id);
let method_names =
self.probe_op(span, mode, LookingFor::ReturnType(return_type), IsSuggestion(true),
self_ty, scope_expr_id,
self_ty, scope_expr_id, ProbeScope::TraitsInScope,
|probe_cx| Ok(probe_cx.candidate_method_names()))
.unwrap_or(vec![]);
method_names
.iter()
.flat_map(|&method_name| {
match self.probe_for_name(span, mode, method_name, IsSuggestion(true), self_ty,
scope_expr_id) {
scope_expr_id, ProbeScope::TraitsInScope) {
Ok(pick) => Some(pick.item),
Err(_) => None,
}
Expand All @@ -196,7 +205,8 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
item_name: ast::Name,
is_suggestion: IsSuggestion,
self_ty: Ty<'tcx>,
scope_expr_id: ast::NodeId)
scope_expr_id: ast::NodeId,
scope: ProbeScope)
-> PickResult<'tcx> {
debug!("probe(self_ty={:?}, item_name={}, scope_expr_id={})",
self_ty,
Expand All @@ -208,6 +218,7 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
is_suggestion,
self_ty,
scope_expr_id,
scope,
|probe_cx| probe_cx.pick())
}

Expand All @@ -218,6 +229,7 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
is_suggestion: IsSuggestion,
self_ty: Ty<'tcx>,
scope_expr_id: ast::NodeId,
scope: ProbeScope,
op: OP)
-> Result<R, MethodError<'tcx>>
where OP: FnOnce(ProbeContext<'a, 'gcx, 'tcx>) -> Result<R, MethodError<'tcx>>
Expand Down Expand Up @@ -275,8 +287,14 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
let mut probe_cx =
ProbeContext::new(self, span, mode, looking_for,
steps, opt_simplified_steps);

probe_cx.assemble_inherent_candidates();
probe_cx.assemble_extension_candidates_for_traits_in_scope(scope_expr_id)?;
match scope {
ProbeScope::TraitsInScope =>
probe_cx.assemble_extension_candidates_for_traits_in_scope(scope_expr_id)?,
ProbeScope::AllTraits =>
probe_cx.assemble_extension_candidates_for_all_traits()?,
};
op(probe_cx)
})
}
Expand Down
Loading