Skip to content

Commit 2a12e51

Browse files
author
Ariel Ben-Yehuda
committed
Select projections over impls in case of ambiguity. Fixes #23336.
1 parent 05d5fca commit 2a12e51

File tree

2 files changed

+123
-67
lines changed

2 files changed

+123
-67
lines changed

src/librustc/middle/traits/select.rs

+103-67
Original file line numberDiff line numberDiff line change
@@ -117,23 +117,71 @@ pub enum MethodMatchedData {
117117
/// obligation is for `int`. In that case, we drop the impl out of the
118118
/// list. But the other cases are considered *candidates*.
119119
///
120-
/// Candidates can either be definitive or ambiguous. An ambiguous
121-
/// candidate is one that might match or might not, depending on how
122-
/// type variables wind up being resolved. This only occurs during inference.
120+
/// For selection to succeed, there must be exactly one matching
121+
/// candidate. If the obligation is fully known, this is guaranteed
122+
/// by coherence. However, if the obligation contains type parameters
123+
/// or variables, there may be multiple such impls.
123124
///
124-
/// For selection to succeed, there must be exactly one non-ambiguous
125-
/// candidate. Usually, it is not possible to have more than one
126-
/// definitive candidate, due to the coherence rules. However, there is
127-
/// one case where it could occur: if there is a blanket impl for a
128-
/// trait (that is, an impl applied to all T), and a type parameter
129-
/// with a where clause. In that case, we can have a candidate from the
130-
/// where clause and a second candidate from the impl. This is not a
131-
/// problem because coherence guarantees us that the impl which would
132-
/// be used to satisfy the where clause is the same one that we see
133-
/// now. To resolve this issue, therefore, we ignore impls if we find a
134-
/// matching where clause. Part of the reason for this is that where
135-
/// clauses can give additional information (like, the types of output
136-
/// parameters) that would have to be inferred from the impl.
125+
/// It is not a real problem if multiple matching impls exist because
126+
/// of type variables - it just means the obligation isn't sufficiently
127+
/// elaborated. In that case we report an ambiguity, and the caller can
128+
/// try again after more type information has been gathered or report a
129+
/// "type annotations required" error.
130+
///
131+
/// However, with type parameters, this can be a real problem - type
132+
/// parameters don't unify with regular types, but they *can* unify
133+
/// with variables from blanket impls, and (unless we know its bounds
134+
/// will always be satisfied) picking the blanket impl will be wrong
135+
/// for at least *some* substitutions. To make this concrete, if we have
136+
///
137+
/// trait AsDebug { type Out : fmt::Debug; fn debug(self) -> Self::Out; }
138+
/// impl<T: fmt::Debug> AsDebug for T {
139+
/// type Out = T;
140+
/// fn debug(self) -> fmt::Debug { self }
141+
/// }
142+
/// fn foo<T: AsDebug>(t: T) { println!("{:?}", <T as AsDebug>::debug(t)); }
143+
///
144+
/// we can't just use the impl to resolve the <T as AsDebug> obligation
145+
/// - a type from another crate (that doesn't implement fmt::Debug) could
146+
/// implement AsDebug.
147+
///
148+
/// Because where-clauses match the type exactly, multiple clauses can
149+
/// only match if there are unresolved variables, and we can mostly just
150+
/// report this ambiguity in that case. This is still a problem - we can't
151+
/// *do anything* with ambiguities that involve only regions. This is issue
152+
/// #21974.
153+
///
154+
/// If a single where-clause matches and there are no inference
155+
/// variables left, then it definitely matches and we can just select
156+
/// it.
157+
///
158+
/// In fact, we even select the where-clause when the obligation contains
159+
/// inference variables. The can lead to inference making "leaps of logic",
160+
/// for example in this situation:
161+
///
162+
/// pub trait Foo<T> { fn foo(&self) -> T; }
163+
/// impl<T> Foo<()> for T { fn foo(&self) { } }
164+
/// impl Foo<bool> for bool { fn foo(&self) -> bool { *self } }
165+
///
166+
/// pub fn foo<T>(t: T) where T: Foo<bool> {
167+
/// println!("{:?}", <T as Foo<_>>::foo(&t));
168+
/// }
169+
/// fn main() { foo(false); }
170+
///
171+
/// Here the obligation <T as Foo<$0>> can be matched by both the blanket
172+
/// impl and the where-clause. We select the where-clause and unify $0=bool,
173+
/// so the program prints "false". However, if the where-clause is omitted,
174+
/// the blanket impl is selected, we unify $0=(), and the program prints
175+
/// "()".
176+
///
177+
/// Exactly the same issues apply to projection and object candidates, except
178+
/// that we can have both a projection candidate and a where-clause candidate
179+
/// for the same obligation. In that case either would do (except that
180+
/// different "leaps of logic" would occur if inference variables are
181+
/// present), and we just pick the projection. This is, for example,
182+
/// required for associated types to work in default impls, as the bounds
183+
/// are visible both as projection bounds and as where-clauses from the
184+
/// parameter environment.
137185
#[derive(PartialEq,Eq,Debug,Clone)]
138186
enum SelectionCandidate<'tcx> {
139187
PhantomFnCandidate,
@@ -1350,63 +1398,51 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
13501398
/// Returns true if `candidate_i` should be dropped in favor of
13511399
/// `candidate_j`. Generally speaking we will drop duplicate
13521400
/// candidates and prefer where-clause candidates.
1401+
/// Returns true if `victim` should be dropped in favor of
1402+
/// `other`. Generally speaking we will drop duplicate
1403+
/// candidates and prefer where-clause candidates.
1404+
///
1405+
/// See the comment for "SelectionCandidate" for more details.
13531406
fn candidate_should_be_dropped_in_favor_of<'o>(&mut self,
1354-
candidate_i: &SelectionCandidate<'tcx>,
1355-
candidate_j: &SelectionCandidate<'tcx>)
1407+
victim: &SelectionCandidate<'tcx>,
1408+
other: &SelectionCandidate<'tcx>)
13561409
-> bool
13571410
{
1358-
if candidate_i == candidate_j {
1411+
if victim == other {
13591412
return true;
13601413
}
13611414

1362-
match (candidate_i, candidate_j) {
1363-
(&ImplCandidate(..), &ParamCandidate(..)) |
1364-
(&ClosureCandidate(..), &ParamCandidate(..)) |
1365-
(&FnPointerCandidate(..), &ParamCandidate(..)) |
1366-
(&BuiltinObjectCandidate(..), &ParamCandidate(_)) |
1367-
(&BuiltinCandidate(..), &ParamCandidate(..)) => {
1368-
// We basically prefer always prefer to use a
1369-
// where-clause over another option. Where clauses
1370-
// impose the burden of finding the exact match onto
1371-
// the caller. Using an impl in preference of a where
1372-
// clause can also lead us to "overspecialize", as in
1373-
// #18453.
1374-
true
1375-
}
1376-
(&ImplCandidate(..), &ObjectCandidate(..)) => {
1377-
// This means that we are matching an object of type
1378-
// `Trait` against the trait `Trait`. In that case, we
1379-
// always prefer to use the object vtable over the
1380-
// impl. Like a where clause, the impl may or may not
1381-
// be the one that is used by the object (because the
1382-
// impl may have additional where-clauses that the
1383-
// object's source might not meet) -- if it is, using
1384-
// the vtable is fine. If it is not, using the vtable
1385-
// is good. A win win!
1386-
true
1387-
}
1388-
(&DefaultImplCandidate(_), _) => {
1389-
// Prefer other candidates over default implementations.
1390-
self.tcx().sess.bug(
1391-
"default implementations shouldn't be recorded \
1392-
when there are other valid candidates");
1393-
}
1394-
(&ProjectionCandidate, &ParamCandidate(_)) => {
1395-
// FIXME(#20297) -- this gives where clauses precedent
1396-
// over projections. Really these are just two means
1397-
// of deducing information (one based on the where
1398-
// clauses on the trait definition; one based on those
1399-
// on the enclosing scope), and it'd be better to
1400-
// integrate them more intelligently. But for now this
1401-
// seems ok. If we DON'T give where clauses
1402-
// precedence, we run into trouble in default methods,
1403-
// where both the projection bounds for `Self::A` and
1404-
// the where clauses are in scope.
1405-
true
1406-
}
1407-
_ => {
1408-
false
1409-
}
1415+
match other {
1416+
&ObjectCandidate(..) |
1417+
&ParamCandidate(_) | &ProjectionCandidate => match victim {
1418+
&DefaultImplCandidate(..) => {
1419+
self.tcx().sess.bug(
1420+
"default implementations shouldn't be recorded \
1421+
when there are other valid candidates");
1422+
}
1423+
&PhantomFnCandidate => {
1424+
self.tcx().sess.bug("PhantomFn didn't short-circuit selection");
1425+
}
1426+
&ImplCandidate(..) |
1427+
&ClosureCandidate(..) |
1428+
&FnPointerCandidate(..) |
1429+
&BuiltinObjectCandidate(..) |
1430+
&DefaultImplObjectCandidate(..) |
1431+
&BuiltinCandidate(..) => {
1432+
// We have a where-clause so don't go around looking
1433+
// for impls.
1434+
true
1435+
}
1436+
&ObjectCandidate(..) |
1437+
&ProjectionCandidate => {
1438+
// Arbitrarily give param candidates priority
1439+
// over projection and object candidates.
1440+
true
1441+
},
1442+
&ParamCandidate(..) => false,
1443+
&ErrorCandidate => false // propagate errors
1444+
},
1445+
_ => false
14101446
}
14111447
}
14121448

src/test/run-pass/issue-23336.rs

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
pub trait Data { fn doit(&self) {} }
12+
impl<T> Data for T {}
13+
pub trait UnaryLogic { type D: Data; }
14+
impl UnaryLogic for () { type D = i32; }
15+
16+
pub fn crashes<T: UnaryLogic>(t: T::D) {
17+
t.doit();
18+
}
19+
20+
fn main() { crashes::<()>(0); }

0 commit comments

Comments
 (0)