From e6a1936d26d03759d80f2f53411c883cc3462ef4 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Fri, 17 Oct 2014 08:51:43 -0400 Subject: [PATCH] Implement "method matching". Similar to trait matching, but with the twist that we have to take the transformed self type into account, and allow for coercion between the receiver and the actual type declared on the impl. --- src/librustc/middle/traits/doc.rs | 7 + src/librustc/middle/traits/mod.rs | 18 +- src/librustc/middle/traits/select.rs | 447 +++++++++++++++++++++++++-- 3 files changed, 422 insertions(+), 50 deletions(-) diff --git a/src/librustc/middle/traits/doc.rs b/src/librustc/middle/traits/doc.rs index 742c4cb5de031..f24121d9a3a5f 100644 --- a/src/librustc/middle/traits/doc.rs +++ b/src/librustc/middle/traits/doc.rs @@ -272,4 +272,11 @@ nested obligation `int : Bar` to find out that `U=uint`. It would be good to only do *just as much* nested resolution as necessary. Currently, though, we just do a full resolution. +## Method matching + +Method dispach follows a slightly different path than normal trait +selection. This is because it must account for the transformed self +type of the receiver and various other complications. The procedure is +described in `select.rs` in the "METHOD MATCHING" section. + */ diff --git a/src/librustc/middle/traits/mod.rs b/src/librustc/middle/traits/mod.rs index f0282804540f5..76715561b0397 100644 --- a/src/librustc/middle/traits/mod.rs +++ b/src/librustc/middle/traits/mod.rs @@ -24,6 +24,8 @@ use syntax::codemap::{Span, DUMMY_SP}; pub use self::fulfill::FulfillmentContext; pub use self::select::SelectionContext; pub use self::select::SelectionCache; +pub use self::select::{MethodMatchResult, MethodMatched, MethodAmbiguous, MethodDidNotMatch}; +pub use self::select::{MethodMatchedData}; // intentionally don't export variants pub use self::util::supertraits; pub use self::util::transitive_bounds; pub use self::util::Supertraits; @@ -219,22 +221,6 @@ pub struct VtableParamData { pub bound: Rc, } -pub fn evaluate_obligation<'a,'tcx>(infcx: &InferCtxt<'a,'tcx>, - param_env: &ty::ParameterEnvironment, - obligation: &Obligation, - typer: &Typer<'tcx>) - -> bool -{ - /*! - * Attempts to resolve the obligation given. Returns `None` if - * we are unable to resolve, either because of ambiguity or - * due to insufficient inference. - */ - - let mut selcx = select::SelectionContext::new(infcx, param_env, typer); - selcx.evaluate_obligation(obligation) -} - pub fn select_inherent_impl<'a,'tcx>(infcx: &InferCtxt<'a,'tcx>, param_env: &ty::ParameterEnvironment, typer: &Typer<'tcx>, diff --git a/src/librustc/middle/traits/select.rs b/src/librustc/middle/traits/select.rs index aca7054018dc6..90aabca29812c 100644 --- a/src/librustc/middle/traits/select.rs +++ b/src/librustc/middle/traits/select.rs @@ -62,6 +62,23 @@ pub struct SelectionCache { hashmap: RefCell, SelectionResult>>, } +pub enum MethodMatchResult { + MethodMatched(MethodMatchedData), + MethodAmbiguous(/* list of impls that could apply */ Vec), + MethodDidNotMatch, +} + +#[deriving(Show)] +pub enum MethodMatchedData { + // In the case of a precise match, we don't really need to store + // how the match was found. So don't. + PreciseMethodMatch, + + // In the case of a coercion, we need to know the precise impl so + // that we can determine the type to which things were coerced. + CoerciveMethodMatch(/* impl we matched */ ast::DefId) +} + /** * The selection process begins by considering all impls, where * clauses, and so forth that might resolve an obligation. Sometimes @@ -190,27 +207,51 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { /////////////////////////////////////////////////////////////////////////// // EVALUATION // - // Tests whether an obligation can be selected or whether an impl can be - // applied to particular types. It skips the "confirmation" step and - // hence completely ignores output type parameters. + // Tests whether an obligation can be selected or whether an impl + // can be applied to particular types. It skips the "confirmation" + // step and hence completely ignores output type parameters. // // The result is "true" if the obliation *may* hold and "false" if // we can be sure it does not. - pub fn evaluate_obligation(&mut self, - obligation: &Obligation) - -> bool + pub fn evaluate_obligation_intercrate(&mut self, + obligation: &Obligation) + -> bool { /*! * Evaluates whether the obligation `obligation` can be - * satisfied (by any means). + * satisfied (by any means). This "intercrate" version allows + * for the possibility that unbound type variables may be + * instantiated with types from another crate. This is + * important for coherence. In practice this means that + * unbound type variables must always be considered ambiguous. */ - debug!("evaluate_obligation({})", + debug!("evaluate_obligation_intercrate({})", obligation.repr(self.tcx())); let stack = self.push_stack(None, obligation); - self.evaluate_stack(&stack).may_apply() + self.evaluate_stack_intercrate(&stack).may_apply() + } + + pub fn evaluate_obligation_intracrate(&mut self, + obligation: &Obligation) + -> bool + { + /*! + * Evaluates whether the obligation `obligation` can be + * satisfied (by any means). This "intracrate" version does + * not allow for the possibility that unbound type variables + * may be instantiated with types from another crate; hence, + * if there are unbound inputs but no crates locally visible, + * it considers the result to be unimplemented. + */ + + debug!("evaluate_obligation_intracrate({})", + obligation.repr(self.tcx())); + + let stack = self.push_stack(None, obligation); + self.evaluate_stack_intracrate(&stack).may_apply() } fn evaluate_builtin_bound_recursively(&mut self, @@ -246,12 +287,26 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { obligation.repr(self.tcx())); let stack = self.push_stack(previous_stack.map(|x| x), obligation); - let result = self.evaluate_stack(&stack); + + // FIXME(#17901) -- Intercrate vs intracrate resolution is a + // tricky question here. For coherence, we want + // intercrate. Also, there was a nasty cycle around impls like + // `impl Eq for Vec` (which would wind up checking + // whether `$0:Eq`, where $0 was the value substituted for + // `T`, which could then be checked against the very same + // impl). This problem is avoided by the stricter rules around + // unbound type variables by intercrate. I suspect that in the + // latter case a more fine-grained rule would suffice (i.e., + // consider it ambiguous if even 1 impl matches, no need to + // figure out which one, but call it unimplemented if 0 impls + // match). + let result = self.evaluate_stack_intercrate(&stack); + debug!("result: {}", result); result } - fn evaluate_stack(&mut self, + fn evaluate_stack_intercrate(&mut self, stack: &ObligationStack) -> EvaluationResult { @@ -259,13 +314,20 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { // an impl. Even if there are no impls in this crate, perhaps // the type would be unified with something from another crate // that does provide an impl. - let input_types = &stack.skol_trait_ref.substs.types; + let input_types = self.input_types(&*stack.skol_trait_ref); if input_types.iter().any(|&t| ty::type_is_skolemized(t)) { - debug!("evaluate_stack({}) --> unbound argument, must be ambiguous", + debug!("evaluate_stack_intercrate({}) --> unbound argument, must be ambiguous", stack.skol_trait_ref.repr(self.tcx())); return EvaluatedToAmbig; } + self.evaluate_stack_intracrate(stack) + } + + fn evaluate_stack_intracrate(&mut self, + stack: &ObligationStack) + -> EvaluationResult + { // If there is any previous entry on the stack that precisely // matches this obligation, then we can assume that the // obligation is satisfied for now (still all other conditions @@ -290,7 +352,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { .skip(1) // skip top-most frame .any(|prev| stack.skol_trait_ref == prev.skol_trait_ref) { - debug!("evaluate_stack({}) --> recursive", + debug!("evaluate_stack_intracrate({}) --> recursive", stack.skol_trait_ref.repr(self.tcx())); return EvaluatedToOk; } @@ -320,7 +382,311 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { self.infcx.probe(|| { match self.match_impl(impl_def_id, obligation) { Ok(substs) => { - let vtable_impl = self.vtable_impl(impl_def_id, substs, obligation.cause, 0); + let vtable_impl = self.vtable_impl(impl_def_id, + substs, + obligation.cause, + obligation.recursion_depth + 1); + self.winnow_selection(None, VtableImpl(vtable_impl)).may_apply() + } + Err(()) => { + false + } + } + }) + } + + /////////////////////////////////////////////////////////////////////////// + // METHOD MATCHING + // + // Method matching is a variation on the normal select/evaluation + // situation. In this scenario, rather than having a full trait + // reference to select from, we start with an expression like + // `receiver.method(...)`. This means that we have `rcvr_ty`, the + // type of the receiver, and we have a possible trait that + // supplies `method`. We must determine whether the receiver is + // applicable, taking into account the transformed self type + // declared on `method`. We also must consider the possibility + // that `receiver` can be *coerced* into a suitable type (for + // example, a receiver type like `&(Any+Send)` might be coerced + // into a receiver like `&Any` to allow for method dispatch). See + // the body of `evaluate_method_obligation()` for more details on + // the algorithm. + + pub fn evaluate_method_obligation(&mut self, + rcvr_ty: ty::t, + xform_self_ty: ty::t, + obligation: &Obligation) + -> MethodMatchResult + { + /*! + * Determine whether a trait-method is applicable to a receiver of + * type `rcvr_ty`. *Does not affect the inference state.* + * + * - `rcvr_ty` -- type of the receiver + * - `xform_self_ty` -- transformed self type declared on the method, with `Self` + * to a fresh type variable + * - `obligation` -- a reference to the trait where the method is declared, with + * the input types on the trait replaced with fresh type variables + */ + + // Here is the situation. We have a trait method declared (say) like so: + // + // trait TheTrait { + // fn the_method(self: Rc, ...) { ... } + // } + // + // And then we have a call looking (say) like this: + // + // let x: Rc = ...; + // x.the_method() + // + // Now we want to decide if `TheTrait` is applicable. As a + // human, we can see that `TheTrait` is applicable if there is + // an impl for the type `Foo`. But how does the compiler know + // what impl to look for, given that our receiver has type + // `Rc`? We need to take the method's self type into + // account. + // + // On entry to this function, we have the following inputs: + // + // - `rcvr_ty = Rc` + // - `xform_self_ty = Rc<$0>` + // - `obligation = $0 as TheTrait` + // + // We do the match in two phases. The first is a *precise + // match*, which means that no coercion is required. This is + // the preferred way to match. It works by first making + // `rcvr_ty` a subtype of `xform_self_ty`. This unifies `$0` + // and `Foo`. We can then evaluate (roughly as normal) the + // trait reference `Foo as TheTrait`. + // + // If this fails, we fallback to a coercive match, described below. + + match self.infcx.probe(|| self.match_method_precise(rcvr_ty, xform_self_ty, obligation)) { + Ok(()) => { return MethodMatched(PreciseMethodMatch); } + Err(_) => { } + } + + // Coercive matches work slightly differently and cannot + // completely reuse the normal trait matching machinery + // (though they employ many of the same bits and pieces). To + // see how it works, let's continue with our previous example, + // but with the following declarations: + // + // ``` + // trait Foo : Bar { .. } + // trait Bar : Baz { ... } + // trait Baz { ... } + // impl TheTrait for Bar { + // fn the_method(self: Rc, ...) { ... } + // } + // ``` + // + // Now we see that the receiver type `Rc` is actually an + // object type. And in fact the impl we want is an impl on the + // supertrait `Rc`. The precise matching procedure won't + // find it, however, because `Rc` is not a subtype of + // `Rc` -- it is *coercible* to `Rc` (actually, such + // coercions are not yet implemented, but let's leave that + // aside for now). + // + // To handle this case, we employ a different procedure. Recall + // that our initial state is as follows: + // + // - `rcvr_ty = Rc` + // - `xform_self_ty = Rc<$0>` + // - `obligation = $0 as TheTrait` + // + // We now go through each impl and instantiate all of its type + // variables, yielding the trait reference that the impl + // provides. In our example, the impl would provide `Bar as + // TheTrait`. Next we (try to) unify the trait reference that + // the impl provides with the input obligation. This would + // unify `$0` and `Bar`. Now we can see whether the receiver + // type (`Rc`) is *coercible to* the transformed self + // type (`Rc<$0> == Rc`). In this case, the answer is + // yes, so the impl is considered a candidate. + // + // Note that there is the possibility of ambiguity here, even + // when all types are known. In our example, this might occur + // if there was *also* an impl of `TheTrait` for `Baz`. In + // this case, `Rc` would be coercible to both `Rc` + // and `Rc`. (Note that it is not a *coherence violation* + // to have impls for both `Bar` and `Baz`, despite this + // ambiguity). In this case, we report an error, listing all + // the applicable impls. The use can explicitly "up-coerce" + // to the type they want. + // + // Note that this coercion step only considers actual impls + // found in the source. This is because all the + // compiler-provided impls (such as those for unboxed + // closures) do not have relevant coercions. This simplifies + // life immensly. + + let mut impls = + self.assemble_method_candidates_from_impls(rcvr_ty, xform_self_ty, obligation); + + if impls.len() > 1 { + impls.retain(|&c| self.winnow_method_impl(c, rcvr_ty, xform_self_ty, obligation)); + } + + if impls.len() > 1 { + return MethodAmbiguous(impls); + } + + match impls.pop() { + Some(def_id) => MethodMatched(CoerciveMethodMatch(def_id)), + None => MethodDidNotMatch + } + } + + pub fn confirm_method_match(&mut self, + rcvr_ty: ty::t, + xform_self_ty: ty::t, + obligation: &Obligation, + data: MethodMatchedData) + { + /*! + * Given the successful result of a method match, this + * function "confirms" the result, which basically repeats the + * various matching operations, but outside of any snapshot so + * that their effects are committed into the inference state. + */ + + let is_ok = match data { + PreciseMethodMatch => { + self.match_method_precise(rcvr_ty, xform_self_ty, obligation).is_ok() + } + + CoerciveMethodMatch(impl_def_id) => { + self.match_method_coerce(impl_def_id, rcvr_ty, xform_self_ty, obligation).is_ok() + } + }; + + if !is_ok { + self.tcx().sess.span_bug( + obligation.cause.span, + format!("match not repeatable: {}, {}, {}, {}", + rcvr_ty.repr(self.tcx()), + xform_self_ty.repr(self.tcx()), + obligation.repr(self.tcx()), + data)[]); + } + } + + fn match_method_precise(&mut self, + rcvr_ty: ty::t, + xform_self_ty: ty::t, + obligation: &Obligation) + -> Result<(),()> + { + /*! + * Implements the *precise method match* procedure described in + * `evaluate_method_obligation()`. + */ + + self.infcx.commit_if_ok(|| { + match self.infcx.sub_types(false, infer::Misc(obligation.cause.span), + rcvr_ty, xform_self_ty) { + Ok(()) => { } + Err(_) => { return Err(()); } + } + + if self.evaluate_obligation_intracrate(obligation) { + Ok(()) + } else { + Err(()) + } + }) + } + + fn assemble_method_candidates_from_impls(&mut self, + rcvr_ty: ty::t, + xform_self_ty: ty::t, + obligation: &Obligation) + -> Vec + { + /*! + * Assembles a list of potentially applicable impls using the + * *coercive match* procedure described in + * `evaluate_method_obligation()`. + */ + + let mut candidates = Vec::new(); + + let all_impls = self.all_impls(obligation.trait_ref.def_id); + for &impl_def_id in all_impls.iter() { + self.infcx.probe(|| { + match self.match_method_coerce(impl_def_id, rcvr_ty, xform_self_ty, obligation) { + Ok(_) => { candidates.push(impl_def_id); } + Err(_) => { } + } + }); + } + + candidates + } + + fn match_method_coerce(&mut self, + impl_def_id: ast::DefId, + rcvr_ty: ty::t, + xform_self_ty: ty::t, + obligation: &Obligation) + -> Result + { + /*! + * Applies the *coercive match* procedure described in + * `evaluate_method_obligation()` to a particular impl. + */ + + // This is almost always expected to succeed. It + // causes the impl's self-type etc to be unified with + // the type variable that is shared between + // obligation/xform_self_ty. In our example, after + // this is done, the type of `xform_self_ty` would + // change from `Rc<$0>` to `Rc` (because $0 is + // unified with `Foo`). + let substs = try!(self.match_impl(impl_def_id, obligation)); + + // Next, check whether we can coerce. For now we require + // that the coercion be a no-op. + let origin = infer::Misc(obligation.cause.span); + match infer::mk_coercety(self.infcx, true, origin, + rcvr_ty, xform_self_ty) { + Ok(None) => { /* Fallthrough */ } + Ok(Some(_)) | Err(_) => { return Err(()); } + } + + Ok(substs) + } + + fn winnow_method_impl(&mut self, + impl_def_id: ast::DefId, + rcvr_ty: ty::t, + xform_self_ty: ty::t, + obligation: &Obligation) + -> bool + { + /*! + * A version of `winnow_impl` applicable to coerice method + * matching. This is basically the same as `winnow_impl` but + * it uses the method matching procedure and is specific to + * impls. + */ + + debug!("winnow_method_impl: impl_def_id={} rcvr_ty={} xform_self_ty={} obligation={}", + impl_def_id.repr(self.tcx()), + rcvr_ty.repr(self.tcx()), + xform_self_ty.repr(self.tcx()), + obligation.repr(self.tcx())); + + self.infcx.probe(|| { + match self.match_method_coerce(impl_def_id, rcvr_ty, xform_self_ty, obligation) { + Ok(substs) => { + let vtable_impl = self.vtable_impl(impl_def_id, + substs, + obligation.cause, + obligation.recursion_depth + 1); self.winnow_selection(None, VtableImpl(vtable_impl)).may_apply() } Err(()) => { @@ -456,24 +822,17 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { return Ok(None); } - // If there are *NO* candidates, that might mean either that - // there is no impl or just that we can't know anything for - // sure. + // If there are *NO* candidates, that there are no impls -- + // that we know of, anyway. Note that in the case where there + // are unbound type variables within the obligation, it might + // be the case that you could still satisfy the obligation + // from another crate by instantiating the type variables with + // a type from another crate that does have an impl. This case + // is checked for in `evaluate_obligation` (and hence users + // who might care about this case, like coherence, should use + // that function). if candidates.len() == 0 { - // Annoying edge case: if there are no impls, then there - // is no way that this trait reference is implemented, - // *unless* it contains unbound variables. In that case, - // it is possible that one of those unbound variables will - // be bound to a new type from some other crate which will - // also contain impls. - let skol_obligation_self_ty = self.infcx.skolemize(stack.obligation.self_ty()); - return if !self.contains_skolemized_types(skol_obligation_self_ty) { - debug!("0 matches, unimpl"); - Err(Unimplemented) - } else { - debug!("0 matches, ambig"); - Ok(None) - }; + return Err(Unimplemented); } // Just one candidate left. @@ -491,7 +850,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { // scope. Otherwise, use the generic tcx cache, since the // result holds across all environments. if - cache_skol_trait_ref.substs.types.iter().any( + self.input_types(&**cache_skol_trait_ref).iter().any( |&t| ty::type_has_self(t) || ty::type_has_params(t)) { &self.param_env.selection_cache @@ -1291,8 +1650,9 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { // it'll do for now until we get the new trait-bound // region skolemization working. let (_, new_signature) = - regionmanip::replace_late_bound_regions_in_fn_sig( + regionmanip::replace_late_bound_regions( self.tcx(), + closure_type.sig.binder_id, &closure_type.sig, |br| self.infcx.next_region_var( infer::LateBoundRegion(obligation.cause.span, br))); @@ -1365,6 +1725,10 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { trait_ref: Rc) -> Result<(),()> { + debug!("match_trait_refs: obligation={} trait_ref={}", + obligation.repr(self.tcx()), + trait_ref.repr(self.tcx())); + let origin = infer::RelateOutputImplTypes(obligation.cause.span); match self.infcx.sub_trait_refs(false, origin, @@ -1591,6 +1955,11 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { found_skol } + + fn input_types<'a>(&self, trait_ref: &'a ty::TraitRef) -> &'a [ty::t] { + // Select only the "input types" from a trait-reference. + trait_ref.substs.types.as_slice() + } } impl Repr for Candidate { @@ -1648,3 +2017,13 @@ impl EvaluationResult { } } } + +impl MethodMatchResult { + pub fn may_apply(&self) -> bool { + match *self { + MethodMatched(_) => true, + MethodAmbiguous(_) => true, + MethodDidNotMatch => false, + } + } +}