Skip to content

Commit f374015

Browse files
committed
clean up the coherence rules
joint work with lcnr + errs
1 parent 83d3ec5 commit f374015

File tree

13 files changed

+335
-114
lines changed

13 files changed

+335
-114
lines changed

Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ formality-check = { version = "0.1.0", path = "crates/formality-check" }
3030
formality-prove = { version = "0.1.0", path = "crates/formality-prove" }
3131
formality-core = { version = "0.1.0", path = "crates/formality-core" }
3232
formality-smir = { version = "0.1.0", path = "crates/formality-smir" }
33+
expect-test = "1.4.0"
3334

3435
[workspace]
3536
members = [

crates/formality-check/src/coherence.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ impl Check<'_> {
2424
self.orphan_check_neg(impl_a)?;
2525
}
2626

27-
// check for duplicate impls in the current crate
27+
// check for duplicate impls in the current crate;
28+
// the cartesian product below would otherwise consider every impl I
29+
// as overlapping with itself.
2830
for (impl_a, i) in current_crate_impls.iter().zip(0..) {
2931
if current_crate_impls[i + 1..].contains(impl_a) {
3032
bail!("duplicate impl in current crate: {:?}", impl_a)

crates/formality-core/src/judgment/proven_set.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ impl<T: Ord + Debug> ProvenSet<T> {
9191
}
9292
}
9393

94-
/// Convert to a non-empty set of proven results (if ok) or an error (otherwise).
94+
/// Iterate through all solutions.
9595
pub fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = &'a T> + 'a> {
9696
match &self.data {
9797
Data::Failure(_) => Box::new(std::iter::empty()),

crates/formality-prove/src/prove/combinators.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::decls::Decls;
2-
use formality_core::{ProvenSet, Upcast};
2+
use formality_core::ProvenSet;
33
use formality_types::rust::Term;
44

55
use super::{Constraints, Env};
@@ -20,7 +20,7 @@ where
2020
assert_eq!(a.len(), b.len());
2121

2222
if a.is_empty() && b.is_empty() {
23-
return ProvenSet::singleton(Constraints::none(env.upcast()));
23+
return ProvenSet::singleton(Constraints::none(env));
2424
}
2525

2626
let a0 = a.remove(0);
@@ -45,7 +45,7 @@ where
4545
C: Term,
4646
{
4747
if a.is_empty() {
48-
return ProvenSet::singleton(Constraints::none(env.upcast()));
48+
return ProvenSet::singleton(Constraints::none(env));
4949
}
5050

5151
let a0 = a[0].clone();

crates/formality-prove/src/prove/constraints.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ where
2525
}
2626

2727
impl Constraints {
28-
pub fn none(env: Env) -> Self {
28+
pub fn none(env: impl Upcast<Env>) -> Self {
2929
let v: Vec<(Variable, Parameter)> = vec![];
3030
Self::from(env, v)
3131
}
@@ -42,9 +42,10 @@ impl Constraints {
4242
}
4343

4444
pub fn from(
45-
env: Env,
45+
env: impl Upcast<Env>,
4646
iter: impl IntoIterator<Item = (impl Upcast<Variable>, impl Upcast<Parameter>)>,
4747
) -> Self {
48+
let env = env.upcast();
4849
let substitution: Substitution = iter.into_iter().collect();
4950
assert!(env.encloses(substitution.range()));
5051
assert!(env.encloses(substitution.domain()));
@@ -72,7 +73,7 @@ impl Constraints {
7273
}
7374
}
7475

75-
/// Given constraings from solving the subparts of `(A /\ B)`, yield combined constraints.
76+
/// Given constraints from solving the subparts of `(A /\ B)`, yield combined constraints.
7677
///
7778
/// # Parameters
7879
///

crates/formality-prove/src/prove/is_local.rs

Lines changed: 164 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
use formality_core::{judgment_fn, set, Set};
1+
use formality_core::{judgment_fn, ProvenSet, Upcast};
22
use formality_types::grammar::{
3-
Lt, Parameter, RigidName, RigidTy, TraitRef, TyData, Variable, Wcs,
3+
AliasTy, BoundVar, Lt, Parameter, RigidName, RigidTy, TraitRef, TyData, Variable, Wcs,
44
};
55

66
use crate::{
@@ -37,24 +37,166 @@ use crate::{
3737
// `Vec<Foo>` is not. `LocalType<ForeignType>` is local. Type aliases and trait
3838
// aliases do not affect locality.
3939

40-
/// True if `goal` may be remote. This is
41-
pub fn may_be_remote(decls: Decls, env: Env, assumptions: Wcs, goal: TraitRef) -> Set<Constraints> {
40+
judgment_fn! {
41+
/// True if `goal` may be implemented in a crate that is not the current one.
42+
/// This could be a downstream crate that we cannot see, or it could be a future
43+
/// (semver-compatible) version of an upstream crate.
44+
///
45+
/// Note that per RFC #2451, upstream crates are not permitted to add blanket impls
46+
/// and so new upstream impls for local types cannot be added.
47+
pub fn may_be_remote(
48+
decls: Decls,
49+
env: Env,
50+
assumptions: Wcs,
51+
goal: TraitRef,
52+
) => () {
53+
debug(assumptions, goal, decls, env)
54+
55+
(
56+
(may_be_downstream_trait_ref(decls, env, assumptions, goal) => ())
57+
--- ("may be defined downstream")
58+
(may_be_remote(decls, env, assumptions, goal) => ())
59+
)
60+
61+
(
62+
// In principle this rule could be removed and preserve soundness,
63+
// but then we would accept code that is very prone to semver failures.
64+
(may_not_be_provable(is_local_trait_ref(decls, env, assumptions, goal)) => ())
65+
--- ("may be added by upstream in a minor release")
66+
(may_be_remote(decls, env, assumptions, goal) => ())
67+
)
68+
}
69+
}
70+
71+
judgment_fn! {
72+
/// True if an impl defining this trait-reference could appear in a downstream crate.
73+
fn may_be_downstream_trait_ref(
74+
decls: Decls,
75+
env: Env,
76+
assumptions: Wcs,
77+
goal: TraitRef,
78+
) => () {
79+
debug(goal, assumptions, env, decls)
80+
81+
(
82+
// There may be a downstream parameter at position i...
83+
(&goal.parameters => p)
84+
(may_be_downstream_parameter(&decls, &env, &assumptions, p) => ())
85+
--- ("may_be_downstream_trait_ref")
86+
(may_be_downstream_trait_ref(decls, env, assumptions, goal) => ())
87+
)
88+
}
89+
}
90+
91+
judgment_fn! {
92+
fn may_be_downstream_parameter(
93+
decls: Decls,
94+
env: Env,
95+
assumptions: Wcs,
96+
parameter: Parameter,
97+
) => () {
98+
debug(parameter, assumptions, env, decls)
99+
100+
(
101+
// existential variables *could* be inferred to downstream types; depends on the substitution
102+
// we ultimately have.
103+
--- ("type variable")
104+
(may_be_downstream_parameter(_decls, _env, _assumptions, TyData::Variable(Variable::ExistentialVar(_))) => ())
105+
)
106+
107+
// If we can normalize `goal` to something else, and that normalized
108+
// form may be downstream.
109+
(
110+
// (a) there is some parameter in the alias that may be downstream
111+
(parameters.iter() => p)
112+
(if may_contain_downstream_type(&decls, &env, &assumptions, p))
113+
114+
// (b) the alias cannot be normalized to something that may not be downstream
115+
(may_not_be_provable(normalizes_to_not_downstream(&decls, &env, &assumptions, AliasTy { name: name.clone(), parameters: parameters.clone() })) => ())
116+
--- ("via normalize")
117+
(may_be_downstream_parameter(decls, env, assumptions, AliasTy { name, parameters }) => ())
118+
)
119+
}
120+
}
121+
122+
fn may_contain_downstream_type(
123+
decls: &Decls,
124+
env: &Env,
125+
assumptions: &Wcs,
126+
parameter: impl Upcast<Parameter>,
127+
) -> bool {
42128
assert!(env.is_in_coherence_mode());
129+
let parameter = parameter.upcast();
43130

44-
let c = is_local_trait_ref(decls, &env, assumptions, goal);
131+
let Parameter::Ty(ty) = parameter else {
132+
return false;
133+
};
45134

46-
if !c.is_proven() {
47-
// Cannot possibly be local, so always remote.
48-
return set![Constraints::none(env)];
135+
match ty.data() {
136+
TyData::RigidTy(RigidTy {
137+
name: _,
138+
parameters,
139+
}) => parameters
140+
.iter()
141+
.any(|p| may_contain_downstream_type(decls, env, assumptions, p)),
142+
TyData::AliasTy(_) => prove_normalize(decls, env, assumptions, ty)
143+
.iter()
144+
.any(|(c, p)| {
145+
let assumptions = c.substitution().apply(assumptions);
146+
may_contain_downstream_type(decls, env, &assumptions, p)
147+
}),
148+
TyData::PredicateTy(p) => match p {
149+
formality_types::grammar::PredicateTy::ForAll(binder) => {
150+
let (_, ty) = binder.open();
151+
may_contain_downstream_type(decls, env, assumptions, ty)
152+
}
153+
},
154+
TyData::Variable(v) => match v {
155+
Variable::ExistentialVar(_) => true,
156+
Variable::UniversalVar(_) => panic!("universals are unexpected"),
157+
Variable::BoundVar(BoundVar {
158+
debruijn,
159+
var_index: _,
160+
kind: _,
161+
}) => {
162+
assert!(debruijn.is_none(), "must have been opened on the way down");
163+
true
164+
}
165+
},
49166
}
167+
}
50168

51-
if c.iter().any(Constraints::unconditionally_true) {
52-
// If this is unconditionally known to be local, then it is never remote.
53-
return set![];
169+
fn may_not_be_provable(op: ProvenSet<Constraints>) -> ProvenSet<()> {
170+
if let Some(constraints) = op
171+
.iter()
172+
.find(|constraints| constraints.unconditionally_true())
173+
{
174+
ProvenSet::failed(
175+
"may_not_be_provable",
176+
format!("found a solution {constraints:?}"),
177+
)
178+
} else {
179+
ProvenSet::singleton(())
54180
}
181+
}
55182

56-
// Otherwise it is ambiguous
57-
set![Constraints::none(env).ambiguous()]
183+
judgment_fn! {
184+
fn normalizes_to_not_downstream(
185+
decls: Decls,
186+
env: Env,
187+
assumptions: Wcs,
188+
parameter: Parameter,
189+
) => Constraints {
190+
debug(parameter, assumptions, env, decls)
191+
192+
(
193+
(prove_normalize(&decls, &env, &assumptions, parameter) => (c1, parameter))
194+
(let assumptions = c1.substitution().apply(&assumptions))
195+
(is_not_downstream(&decls, &env, assumptions, parameter) => c2)
196+
--- ("ambiguous")
197+
(normalizes_to_not_downstream(decls, env, assumptions, parameter) => c1.seq(c2))
198+
)
199+
}
58200
}
59201

60202
judgment_fn! {
@@ -73,26 +215,29 @@ judgment_fn! {
73215
)
74216

75217
(
218+
// There is a local parameter at position i...
76219
(0 .. goal.parameters.len() => i)
77220
(is_local_parameter(&decls, &env, &assumptions, &goal.parameters[i]) => c1)
221+
222+
// ...and in positions 0..i, there are no downstream parameters.
78223
(let assumptions = c1.substitution().apply(&assumptions))
79224
(let goal = c1.substitution().apply(&goal))
80-
(for_all(&decls, &env, &assumptions, &goal.parameters[..i], &not_downstream) => c2)
225+
(for_all(&decls, &env, &assumptions, &goal.parameters[..i], &is_not_downstream) => c2)
81226
--- ("local parameter")
82227
(is_local_trait_ref(decls, env, assumptions, goal) => c1.seq(c2))
83228
)
84229
}
85230
}
86231

87232
judgment_fn! {
88-
/// "not_downstream(..., P)" means that `P` cannot be instantiated with a type from
233+
/// `is_not_downstream(..., P)` means that `P` cannot be instantiated with a type from
89234
/// a downstream crate (i.e., a crate that has us as a dependency).
90235
///
91236
/// NB. Since RFC 2451, the judgment applies to the outermost type only. In other words,
92237
/// the judgment holds for (e.g.) `Vec<T>`, which could be instantiated
93238
/// with something like `Vec<DownstreamType>`, but that is not considered downstream
94239
/// as the outermost type (`Vec`) is upstream.
95-
fn not_downstream(
240+
fn is_not_downstream(
96241
decls: Decls,
97242
env: Env,
98243
assumptions: Wcs,
@@ -104,20 +249,20 @@ judgment_fn! {
104249
// Since https://rust-lang.github.io/rfcs/2451-re-rebalancing-coherence.html,
105250
// any rigid type is adequate.
106251
--- ("rigid")
107-
(not_downstream(_decls, env, _assumptions, RigidTy { .. }) => Constraints::none(env))
252+
(is_not_downstream(_decls, env, _assumptions, RigidTy { .. }) => Constraints::none(env))
108253
)
109254

110255
(
111256
// Lifetimes are not relevant.
112257
--- ("lifetime")
113-
(not_downstream(_decls, env, _assumptions, _l: Lt) => Constraints::none(env))
258+
(is_not_downstream(_decls, env, _assumptions, _l: Lt) => Constraints::none(env))
114259
)
115260

116261
(
117262
// existential variables *could* be inferred to downstream types; depends on the substitution
118263
// we ultimately have.
119264
--- ("type variable")
120-
(not_downstream(_decls, env, _assumptions, TyData::Variable(Variable::ExistentialVar(_))) => Constraints::none(env).ambiguous())
265+
(is_not_downstream(_decls, env, _assumptions, TyData::Variable(Variable::ExistentialVar(_))) => Constraints::none(env).ambiguous())
121266
)
122267
}
123268
}

crates/formality-prove/src/prove/prove_eq.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ fn equate_variable(
165165
// For each universal variable that we replaced with an existential variable
166166
// above, we now have to prove that goal. e.g., if we had `X = Vec<!Y>`, we would replace `!Y` with `?Z`
167167
// (where `?Z` is in a lower universe than `X`), but now we must prove that `!Y = ?Z`
168-
// (this may be posible due to assumptions).
168+
// (this may be possible due to assumptions).
169169
let goals: Wcs = universe_subst
170170
.iter()
171171
.filter(|(v, _)| v.is_a::<UniversalVar>())

crates/formality-prove/src/prove/prove_wc.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,9 @@ judgment_fn! {
6161

6262
(
6363
(if env.is_in_coherence_mode())!
64-
(may_be_remote(decls, env, assumptions, trait_ref) => c)
64+
(may_be_remote(decls, &env, assumptions, trait_ref) => ())
6565
----------------------------- ("coherence / remote impl")
66-
(prove_wc(decls, env, assumptions, Predicate::IsImplemented(trait_ref)) => c.ambiguous())
66+
(prove_wc(decls, env, assumptions, Predicate::IsImplemented(trait_ref)) => Constraints::none(&env).ambiguous())
6767
)
6868

6969
(

crates/formality-types/src/grammar/formulas.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -215,10 +215,10 @@ impl TraitId {
215215
/// We need a better name for this lol.
216216
#[term]
217217
pub enum PR {
218-
#[cast]
219-
Predicate(Predicate),
220218
#[cast]
221219
Relation(Relation),
220+
#[cast]
221+
Predicate(Predicate),
222222
}
223223

224224
impl PR {

0 commit comments

Comments
 (0)