Skip to content

Commit dfa094a

Browse files
arielb1Ariel Ben-Yehuda
authored and
Ariel Ben-Yehuda
committed
Select projections over impls in case of ambiguity. Fixes #23336.
1 parent ecf8c64 commit dfa094a

File tree

2 files changed

+99
-54
lines changed

2 files changed

+99
-54
lines changed

src/librustc/middle/traits/select.rs

+79-54
Original file line numberDiff line numberDiff line change
@@ -117,19 +117,48 @@ pub enum MethodMatchedData {
117117
/// candidate is one that might match or might not, depending on how
118118
/// type variables wind up being resolved. This only occurs during inference.
119119
///
120-
/// For selection to succeed, there must be exactly one non-ambiguous
121-
/// candidate. Usually, it is not possible to have more than one
122-
/// definitive candidate, due to the coherence rules. However, there is
123-
/// one case where it could occur: if there is a blanket impl for a
124-
/// trait (that is, an impl applied to all T), and a type parameter
125-
/// with a where clause. In that case, we can have a candidate from the
126-
/// where clause and a second candidate from the impl. This is not a
127-
/// problem because coherence guarantees us that the impl which would
128-
/// be used to satisfy the where clause is the same one that we see
129-
/// now. To resolve this issue, therefore, we ignore impls if we find a
130-
/// matching where clause. Part of the reason for this is that where
131-
/// clauses can give additional information (like, the types of output
132-
/// parameters) that would have to be inferred from the impl.
120+
/// For selection to succeed, there must be exactly one potential
121+
/// candidate. Coherence mostly ensures that (disregarding
122+
/// still-uninferred type variables), but there's a subtlety - coherence
123+
/// does not consider the parameter environment, so we can have a candidate
124+
/// from a where-clause (or associated-type bound) along with the
125+
/// global candidate.
126+
///
127+
/// For example, if we have a blanket impl
128+
/// impl<T> MyTrait for T
129+
/// and a where-clause
130+
/// fn f<T>(t: T) where T: MyTrait
131+
/// Then there are 2 candidates, but they are actually equivalent, so
132+
/// it doesn't really matter which one is chosen. Note that the impl
133+
/// can have bounds - even in this example, we have T: Sized, and the
134+
/// where-clause could just as well have been
135+
/// and the where-clause could have been Option<T> : MyTrait).
136+
///
137+
/// This gets subtler. The global candidate can have bounds that
138+
/// are not already known:
139+
/// impl<T: Copy> MyTrait for T
140+
/// If our type is known, we know that this impl will be the one chosen,
141+
/// so we can just mark a `T: Copy` obligation and move on. This helps
142+
/// inference in some cases. However, if we have a where-clause for
143+
/// MyTrait in scope, we can't make this assumption, because another crate
144+
/// could have a `impl MyTrait for FreshNonCopyType`, which was "passed"
145+
/// through the where-clause. This had caused several bugs, including #18453.
146+
///
147+
/// From the above, it would seem to be that the best option is to
148+
/// just pick the where-clause. This is what we do today. However,
149+
/// it is not a panacea. Assume we have:
150+
///
151+
/// impl<'a> MyTrait<'a> for T
152+
/// fn f<'s, T>(u: &'s T) where T: MyTrait<'s>
153+
///
154+
/// Then we know that, for all lifetimes 'a, `T: MyTrait<'a>`.
155+
/// However, because lifetimes are not known at trait-selection time,
156+
/// we pick the where-clause, and infer that the lifetime must
157+
/// be 's - the additional clause "poisons" selection. This could
158+
/// get subtler, as there could have been a `T: Copy` bound on
159+
/// the blanket impl, or multiple clauses on T:
160+
/// fn f<'s, T>(u: &'s T) where T: MyTrait<'s>+MyTrait<'tcx>
161+
/// We don't handle this at this moment.
133162
#[derive(PartialEq,Eq,Debug,Clone)]
134163
enum SelectionCandidate<'tcx> {
135164
PhantomFnCandidate,
@@ -1307,54 +1336,50 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
13071336
self.evaluate_predicates_recursively(stack, selection.iter_nested())
13081337
}
13091338

1310-
/// Returns true if `candidate_i` should be dropped in favor of
1311-
/// `candidate_j`. Generally speaking we will drop duplicate
1339+
/// Returns true if `victim` should be dropped in favor of
1340+
/// `other`. Generally speaking we will drop duplicate
13121341
/// candidates and prefer where-clause candidates.
13131342
fn candidate_should_be_dropped_in_favor_of<'o>(&mut self,
1314-
candidate_i: &SelectionCandidate<'tcx>,
1315-
candidate_j: &SelectionCandidate<'tcx>)
1343+
victim: &SelectionCandidate<'tcx>,
1344+
other: &SelectionCandidate<'tcx>)
13161345
-> bool
13171346
{
1318-
if candidate_i == candidate_j {
1347+
if victim == other {
13191348
return true;
13201349
}
13211350

1322-
match (candidate_i, candidate_j) {
1323-
(&ImplCandidate(..), &ParamCandidate(..)) |
1324-
(&ClosureCandidate(..), &ParamCandidate(..)) |
1325-
(&FnPointerCandidate(..), &ParamCandidate(..)) |
1326-
(&BuiltinObjectCandidate(..), &ParamCandidate(_)) |
1327-
(&BuiltinCandidate(..), &ParamCandidate(..)) => {
1328-
// We basically prefer always prefer to use a
1329-
// where-clause over another option. Where clauses
1330-
// impose the burden of finding the exact match onto
1331-
// the caller. Using an impl in preference of a where
1332-
// clause can also lead us to "overspecialize", as in
1333-
// #18453.
1334-
true
1335-
}
1336-
(&DefaultImplCandidate(_), _) => {
1337-
// Prefer other candidates over default implementations.
1338-
self.tcx().sess.bug(
1339-
"default implementations shouldn't be recorded \
1340-
when there are other valid candidates");
1341-
}
1342-
(&ProjectionCandidate, &ParamCandidate(_)) => {
1343-
// FIXME(#20297) -- this gives where clauses precedent
1344-
// over projections. Really these are just two means
1345-
// of deducing information (one based on the where
1346-
// clauses on the trait definition; one based on those
1347-
// on the enclosing scope), and it'd be better to
1348-
// integrate them more intelligently. But for now this
1349-
// seems ok. If we DON'T give where clauses
1350-
// precedence, we run into trouble in default methods,
1351-
// where both the projection bounds for `Self::A` and
1352-
// the where clauses are in scope.
1353-
true
1354-
}
1355-
_ => {
1356-
false
1357-
}
1351+
match other {
1352+
&ParamCandidate(_) | &ProjectionCandidate => match victim {
1353+
&DefaultImplCandidate(..) => {
1354+
self.tcx().sess.bug(
1355+
"default implementations shouldn't be recorded \
1356+
when there are other valid candidates");
1357+
}
1358+
&PhantomFnCandidate => {
1359+
self.tcx().sess.bug("PhantomFn didn't short-circuit selection");
1360+
}
1361+
&ImplCandidate(..) |
1362+
&ClosureCandidate(..) |
1363+
&FnPointerCandidate(..) |
1364+
&BuiltinObjectCandidate(..) |
1365+
&ObjectCandidate(..) |
1366+
&BuiltinCandidate(..) => {
1367+
// A param/projection candidate is a sure match,
1368+
// choose it.
1369+
// FIXME: This isn't exactly correct because of
1370+
// lifetimes.
1371+
true
1372+
}
1373+
&ProjectionCandidate => {
1374+
// Arbitrarily give param candidates priority
1375+
// over projection candidates. Both are "sure"
1376+
// matches.
1377+
true
1378+
},
1379+
&ParamCandidate(..) => false,
1380+
&ErrorCandidate => false // propagate errors
1381+
},
1382+
_ => false
13581383
}
13591384
}
13601385

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)