Skip to content

Commit a63119a

Browse files
authored
Merge pull request #165 from sunjay/compatible-overlap
Compatible Overlap Check in Chalk
2 parents 5287ba6 + 6c041af commit a63119a

File tree

5 files changed

+157
-81
lines changed

5 files changed

+157
-81
lines changed

src/coherence/solve.rs

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use itertools::Itertools;
55
use errors::*;
66
use ir::*;
77
use cast::*;
8-
use solve::SolverChoice;
8+
use solve::{SolverChoice, Solution};
99

1010
struct DisjointSolver {
1111
env: Arc<ProgramEnvironment>,
@@ -83,33 +83,38 @@ impl Program {
8383
}
8484

8585
impl DisjointSolver {
86-
// Test if two impls are disjoint. If the test does not succeed, there is an overlap.
86+
// Test if the set of types that these two impls apply to overlap. If the test succeeds, these
87+
// two impls are disjoint.
8788
//
88-
// We combine the binders of the two impls & treat them as existential
89-
// quantifiers. Then we attempt to unify the input types to the trait provided
90-
// by each impl, as well as prove that the where clauses from both impls all
91-
// hold. At the end, we negate the query because we only want to return `true` if
92-
// it is provable that there is no overlap.
89+
// We combine the binders of the two impls & treat them as existential quantifiers. Then we
90+
// attempt to unify the input types to the trait provided by each impl, as well as prove that
91+
// the where clauses from both impls all hold. At the end, we apply the `compatible` modality
92+
// and negate the query. Negating the query means that we are asking chalk to prove that no
93+
// such overlapping impl exists. By applying `compatible { G }`, chalk attempts to prove that
94+
// "there exists a compatible world where G is provable." When we negate compatible, it turns
95+
// into the statement "for all compatible worlds, G is not provable." This is exactly what we
96+
// want since we want to ensure that there is no overlap in *all* compatible worlds, not just
97+
// that there is no overlap in *some* compatible world.
9398
//
9499
// Examples:
95100
//
96101
// Impls:
97102
// impl<T> Foo for T { }
98103
// impl Foo for i32 { }
99104
// Generates:
100-
// not { exists<T> { T = i32 } }
105+
// not { compatible { exists<T> { T = i32 } } }
101106
//
102107
// Impls:
103108
// impl<T1, U> Foo<T1> for Vec<U> { }
104109
// impl<T2> Foo<T2> for Vec<i32> { }
105110
// Generates:
106-
// not { exists<T1, U, T2> { Vec<U> = Vec<i32>, T1 = T2 } }
111+
// not { compatible { exists<T1, U, T2> { Vec<U> = Vec<i32>, T1 = T2 } } }
107112
//
108113
// Impls:
109114
// impl<T> Foo for Vec<T> where T: Bar { }
110115
// impl<U> Foo for Vec<U> where U: Baz { }
111116
// Generates:
112-
// not { exists<T, U> { Vec<T> = Vec<U>, T: Bar, U: Baz } }
117+
// not { compatible { exists<T, U> { Vec<T> = Vec<U>, T: Bar, U: Baz } } }
113118
//
114119
fn disjoint(&self, lhs: &ImplDatum, rhs: &ImplDatum) -> bool {
115120
debug_heading!("overlaps(lhs={:#?}, rhs={:#?})", lhs, rhs);
@@ -150,15 +155,22 @@ impl DisjointSolver {
150155
.fold1(|goal, leaf| Goal::And(Box::new(goal), Box::new(leaf)))
151156
.expect("Every trait takes at least one input type")
152157
.quantify(QuantifierKind::Exists, binders)
153-
.negate()
154-
.compatible();
158+
.compatible()
159+
.negate();
155160

156-
// Unless we can prove NO solution, we consider things to overlap.
157161
let canonical_goal = &goal.into_closed_goal();
158-
let result = self.solver_choice
162+
let solution = self.solver_choice
159163
.solve_root_goal(&self.env, canonical_goal)
160-
.unwrap()
161-
.is_some();
164+
.unwrap(); // internal errors in the solver are fatal
165+
let result = match solution {
166+
// Goal was proven with a unique solution, so no impl was found that causes these two
167+
// to overlap
168+
Some(Solution::Unique(_)) => true,
169+
// Goal was ambiguous, so there *may* be overlap
170+
Some(Solution::Ambig(_)) |
171+
// Goal cannot be proven, so there is some impl that causes overlap
172+
None => false,
173+
};
162174
debug!("overlaps: result = {:?}", result);
163175
result
164176
}

src/coherence/test.rs

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -62,21 +62,18 @@ fn two_blanket_impls() {
6262
}
6363

6464
#[test]
65-
// FIXME This should be an error
66-
// We currently assume a closed universe always, but overlaps checking should
67-
// assume an open universe - what if a client implemented both Bar and Baz
68-
//
69-
// In other words, this should have the same behavior as the two_blanket_impls
70-
// test.
7165
fn two_blanket_impls_open_ended() {
72-
lowering_success! {
66+
lowering_error! {
7367
program {
7468
trait Foo { }
7569
trait Bar { }
7670
trait Baz { }
7771
impl<T> Foo for T where T: Bar { }
7872
impl<T> Foo for T where T: Baz { }
7973
}
74+
error_msg {
75+
"overlapping impls of trait \"Foo\""
76+
}
8077
}
8178
}
8279

@@ -227,6 +224,39 @@ fn overlapping_negative_impls() {
227224
}
228225
}
229226

227+
#[test]
228+
fn downstream_impl_of_fundamental_43355() {
229+
// Regression test for issue 43355 which exposed an unsoundness in the original implementation
230+
// with regards to how fundamental types were handled for potential downstream impls. This case
231+
// fails exactly the way we want it to using chalk's overlap check rules.
232+
// https://github.com/rust-lang/rust/issues/43355
233+
lowering_error! {
234+
program {
235+
#[upstream]
236+
#[fundamental]
237+
struct Box<T> { }
238+
239+
trait Trait1<X> { }
240+
trait Trait2<X> { }
241+
242+
struct A { }
243+
244+
impl<X, T> Trait1<X> for T where T: Trait2<X> { }
245+
impl<X> Trait1<Box<X>> for A { }
246+
247+
// So how do these impls overlap? Consider a downstream crate that adds this code:
248+
//
249+
// struct B;
250+
// impl Trait2<Box<B>> for A {}
251+
//
252+
// This makes the first impl now apply to A, which means that both of these impls now
253+
// overlap for A even though they didn't overlap in the original crate where A is defined.
254+
} error_msg {
255+
"overlapping impls of trait \"Trait1\""
256+
}
257+
}
258+
}
259+
230260
#[test]
231261
fn orphan_check() {
232262
// These tests are largely adapted from the compile-fail coherence-*.rs tests from rustc

src/rules.rs

Lines changed: 90 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,8 @@ impl StructDatum {
319319
//
320320
// forall<T> { IsLocal(Box<T>) :- IsLocal(T) }
321321
// forall<T> { IsUpstream(Box<T>) :- IsUpstream(T) }
322+
// // Generated for both upstream and local fundamental types
323+
// forall<T> { DownstreamType(Box<T>) :- DownstreamType(T) }
322324

323325
let wf = self.binders.map_ref(|bound_datum| {
324326
ProgramClauseImplication {
@@ -343,6 +345,32 @@ impl StructDatum {
343345

344346
let mut clauses = vec![wf, is_fully_visible];
345347

348+
// Fundamental types often have rules in the form of:
349+
// Goal(FundamentalType<T>) :- Goal(T)
350+
// This macro makes creating that kind of clause easy
351+
macro_rules! fundamental_rule {
352+
($goal:ident) => {
353+
// Fundamental types must always have at least one type parameter for this rule to
354+
// make any sense. We currently do not have have any fundamental types with more than
355+
// one type parameter, nor do we know what the behaviour for that should be. Thus, we
356+
// are asserting here that there is only a single type parameter until the day when
357+
// someone makes a decision about how that should behave.
358+
assert_eq!(self.binders.value.self_ty.len_type_parameters(), 1,
359+
"Only fundamental types with a single parameter are supported");
360+
361+
clauses.push(self.binders.map_ref(|bound_datum| ProgramClauseImplication {
362+
consequence: DomainGoal::$goal(bound_datum.self_ty.clone().cast()),
363+
conditions: vec![
364+
DomainGoal::$goal(
365+
// This unwrap is safe because we asserted above for the presence of a type
366+
// parameter
367+
bound_datum.self_ty.first_type_parameter().unwrap()
368+
).cast(),
369+
],
370+
}).cast());
371+
};
372+
}
373+
346374
// Types that are not marked `#[upstream]` satisfy IsLocal(TypeName)
347375
if !self.binders.value.flags.upstream {
348376
// `IsLocalTy(Ty)` depends *only* on whether the type is marked #[upstream] and nothing else
@@ -355,33 +383,6 @@ impl StructDatum {
355383
} else if self.binders.value.flags.fundamental {
356384
// If a type is `#[upstream]`, but is also `#[fundamental]`, it satisfies IsLocal
357385
// if and only if its parameters satisfy IsLocal
358-
359-
// Fundamental types must always have at least one type parameter for this rule to
360-
// make any sense. We currently do not have have any fundamental types with more than
361-
// one type parameter, nor do we know what the behaviour for that should be. Thus, we
362-
// are asserting here that there is only a single type parameter until the day when
363-
// someone makes a decision about how that should behave.
364-
assert_eq!(self.binders.value.self_ty.len_type_parameters(), 1,
365-
"Only fundamental types with a single parameter are supported");
366-
367-
// Fundamental types often have rules in the form of:
368-
// Goal(FundamentalType<T>) :- Goal(T)
369-
// This macro makes creating that kind of clause easy
370-
macro_rules! fundamental_rule {
371-
($goal:ident) => {
372-
clauses.push(self.binders.map_ref(|bound_datum| ProgramClauseImplication {
373-
consequence: DomainGoal::$goal(bound_datum.self_ty.clone().cast()),
374-
conditions: vec![
375-
DomainGoal::$goal(
376-
// This unwrap is safe because we asserted above for the presence of a type
377-
// parameter
378-
bound_datum.self_ty.first_type_parameter().unwrap()
379-
).cast(),
380-
],
381-
}).cast());
382-
};
383-
}
384-
385386
fundamental_rule!(IsLocal);
386387
fundamental_rule!(IsUpstream);
387388
} else {
@@ -395,6 +396,10 @@ impl StructDatum {
395396
clauses.push(is_upstream);
396397
}
397398

399+
if self.binders.value.flags.fundamental {
400+
fundamental_rule!(DownstreamType);
401+
}
402+
398403
let condition = DomainGoal::FromEnv(
399404
FromEnv::Ty(self.binders.value.self_ty.clone().cast())
400405
);
@@ -481,6 +486,25 @@ impl TraitDatum {
481486
// IsFullyVisible(U),
482487
// IsLocal(V)
483488
// }
489+
//
490+
// The overlap check uses compatible { ... } mode to ensure that it accounts for impls that
491+
// may exist in some other *compatible* world. For every upstream trait, we add a rule to
492+
// account for the fact that upstream crates are able to compatibly add impls of upstream
493+
// traits for upstream types.
494+
//
495+
// // For `#[upstream] trait Foo<T, U, V> where Self: Eq<T> { ... }`
496+
// forall<Self, T, U, V> {
497+
// Implemented(Self: Foo<T, U, V>) :-
498+
// Implemented(Self: Eq<T>), // where clauses
499+
// Compatible, // compatible modality
500+
// IsUpstream(Self),
501+
// IsUpstream(T),
502+
// IsUpstream(U),
503+
// IsUpstream(V),
504+
// CannotProve, // returns ambiguous
505+
// }
506+
507+
let mut clauses = Vec::new();
484508

485509
let trait_ref = self.binders.value.trait_ref.clone();
486510

@@ -503,8 +527,32 @@ impl TraitDatum {
503527
},
504528
}
505529
}).cast();
530+
clauses.push(wf);
531+
532+
// The number of parameters will always be at least 1 because of the Self parameter
533+
// that is automatically added to every trait. This is important because otherwise
534+
// the added program clauses would not have any conditions.
535+
let type_parameters: Vec<_> = self.binders.value.trait_ref.type_parameters().collect();
536+
537+
// Add all cases for potential downstream impls that could exist
538+
for i in 0..type_parameters.len() {
539+
let impl_may_exist = self.binders.map_ref(|bound_datum|
540+
ProgramClauseImplication {
541+
consequence: DomainGoal::Holds(WhereClause::Implemented(bound_datum.trait_ref.clone())),
542+
conditions: bound_datum.where_clauses
543+
.iter()
544+
.cloned()
545+
.casted()
546+
.chain(iter::once(DomainGoal::Compatible(()).cast()))
547+
.chain((0..i).map(|j| DomainGoal::IsFullyVisible(type_parameters[j].clone()).cast()))
548+
.chain(iter::once(DomainGoal::DownstreamType(type_parameters[i].clone()).cast()))
549+
.chain(iter::once(Goal::CannotProve(())))
550+
.collect(),
551+
}
552+
).cast();
506553

507-
let mut clauses = vec![wf];
554+
clauses.push(impl_may_exist);
555+
}
508556

509557
if !self.binders.value.flags.upstream {
510558
let impl_allowed = self.binders.map_ref(|bound_datum|
@@ -516,12 +564,6 @@ impl TraitDatum {
516564

517565
clauses.push(impl_allowed);
518566
} else {
519-
// The number of parameters will always be at least 1 because of the Self parameter
520-
// that is automatically added to every trait. This is important because otherwise
521-
// the added program clauses would not have any conditions.
522-
523-
let type_parameters: Vec<_> = self.binders.value.trait_ref.type_parameters().collect();
524-
525567
for i in 0..type_parameters.len() {
526568
let impl_maybe_allowed = self.binders.map_ref(|bound_datum|
527569
ProgramClauseImplication {
@@ -535,6 +577,20 @@ impl TraitDatum {
535577

536578
clauses.push(impl_maybe_allowed);
537579
}
580+
581+
let impl_may_exist = self.binders.map_ref(|bound_datum| ProgramClauseImplication {
582+
consequence: DomainGoal::Holds(WhereClause::Implemented(bound_datum.trait_ref.clone())),
583+
conditions: bound_datum.where_clauses
584+
.iter()
585+
.cloned()
586+
.casted()
587+
.chain(iter::once(DomainGoal::Compatible(()).cast()))
588+
.chain(bound_datum.trait_ref.type_parameters().map(|ty| DomainGoal::IsUpstream(ty).cast()))
589+
.chain(iter::once(Goal::CannotProve(())))
590+
.collect(),
591+
}).cast();
592+
593+
clauses.push(impl_may_exist);
538594
}
539595

540596
let condition = DomainGoal::FromEnv(FromEnv::Trait(trait_ref.clone()));

src/solve/slg/test.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -744,10 +744,10 @@ fn example_3_3_EWFS() {
744744
delayed_literals: DelayedLiteralSet {
745745
delayed_literals: {
746746
Negative(
747-
TableIndex(4)
747+
TableIndex(1)
748748
),
749749
Negative(
750-
TableIndex(1)
750+
TableIndex(6)
751751
)
752752
}
753753
}

src/solve/test.rs

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1936,28 +1936,6 @@ fn mixed_semantics() {
19361936
}
19371937
}
19381938

1939-
#[test]
1940-
fn partial_overlap_1() {
1941-
test! {
1942-
program {
1943-
trait Marker {}
1944-
trait Foo {}
1945-
trait Bar {}
1946-
1947-
impl<T> Marker for T where T: Foo {}
1948-
impl<T> Marker for T where T: Bar {}
1949-
}
1950-
1951-
goal {
1952-
forall<T> {
1953-
if (T: Foo; T: Bar) { T: Marker }
1954-
}
1955-
} yields {
1956-
"Unique"
1957-
}
1958-
}
1959-
}
1960-
19611939
#[test]
19621940
fn partial_overlap_2() {
19631941
test! {

0 commit comments

Comments
 (0)