Skip to content

Commit 3222d32

Browse files
authored
Merge pull request #171 from lcnr/coherence-cont
continue coherence work
2 parents 3966e06 + 48556d9 commit 3222d32

31 files changed

+604
-402
lines changed

book/src/formality_core/judgment_fn.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Judgment functions and inference rules
22

3-
The next thing is the `judgement_fn!` macro. This lets you write a *judgment* using *inference rules*. A "judgment" just means some kind of predicate that the computer can judge to hold or not hold. Inference rules are those rules you may have seen in papers and things:
3+
The next thing is the `judgment_fn!` macro. This lets you write a *judgment* using *inference rules*. A "judgment" just means some kind of predicate that the computer can judge to hold or not hold. Inference rules are those rules you may have seen in papers and things:
44

55
```
66
premise1

crates/formality-check/src/coherence.rs

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,14 @@ impl Check<'_> {
5353
let a = env.instantiate_universally(&impl_a.binder);
5454
let trait_ref = a.trait_ref();
5555

56-
self.prove_goal(
57-
&env.with_coherence_mode(true),
58-
&a.where_clauses,
59-
trait_ref.is_local(),
60-
)
56+
// The orphan check passes if
57+
// ∀P. ⌐ (coherence_mode => (wf(Ts) && cannot_be_proven(is_local_trait_ref)))
58+
//
59+
// TODO: feels like we do want a general "not goal", flipping existentials
60+
// and universals and the coherence mode
61+
// self.prove_not_goal(&env, &(Wcs::wf))
62+
63+
self.prove_goal(&env, &a.where_clauses, trait_ref.is_local())
6164
}
6265

6366
#[context("orphan_check_neg({impl_a:?})")]
@@ -67,11 +70,7 @@ impl Check<'_> {
6770
let a = env.instantiate_universally(&impl_a.binder);
6871
let trait_ref = a.trait_ref();
6972

70-
self.prove_goal(
71-
&env.with_coherence_mode(true),
72-
&a.where_clauses,
73-
trait_ref.is_local(),
74-
)
73+
self.prove_goal(&env, &a.where_clauses, trait_ref.is_local())
7574
}
7675

7776
#[tracing::instrument(level = "Debug", skip(self))]
@@ -100,7 +99,7 @@ impl Check<'_> {
10099
//
101100
// ∀P_a, ∀P_b. ⌐ (coherence_mode => (Ts_a = Ts_b && WC_a && WC_b))
102101
if let Ok(()) = self.prove_not_goal(
103-
&env.with_coherence_mode(true),
102+
&env,
104103
(),
105104
(
106105
Wcs::all_eq(&trait_ref_a.parameters, &trait_ref_b.parameters),

crates/formality-check/src/lib.rs

Lines changed: 13 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
use std::{collections::VecDeque, fmt::Debug};
44

55
use anyhow::bail;
6-
use formality_prove::{Decls, Env};
6+
use formality_prove::{is_definitely_not_proveable, Decls, Env};
77
use formality_rust::{
88
grammar::{Crate, CrateItem, Program, Test, TestBoundData},
99
prove::ToWcs,
1010
};
11-
use formality_types::grammar::{Fallible, Substitution, Wcs};
11+
use formality_types::grammar::{Fallible, Wcs};
1212

1313
/// Check all crates in the program. The crates must be in dependency order
1414
/// such that any prefix of the crates is a complete program.
@@ -115,58 +115,24 @@ impl Check<'_> {
115115
}
116116

117117
#[tracing::instrument(level = "Debug", skip(self, assumptions, goal))]
118-
fn prove_not_goal(
119-
&self,
120-
env: &Env,
121-
assumptions: impl ToWcs,
122-
goal: impl ToWcs + Debug,
123-
) -> Fallible<()> {
118+
fn prove_not_goal(&self, env: &Env, assumptions: impl ToWcs, goal: impl ToWcs) -> Fallible<()> {
124119
let goal: Wcs = goal.to_wcs();
125120
let assumptions: Wcs = assumptions.to_wcs();
126121

127-
tracing::debug!("assumptions = {assumptions:?}");
128-
tracing::debug!("goal = {goal:?}");
129-
130122
assert!(env.only_universal_variables());
131123
assert!(env.encloses((&assumptions, &goal)));
132124

133-
// Proving `∀X. not(F(X))` is the same as proving: `not(∃X. F(X))`.
134-
// Therefore, since we have only universal variables, we can change them all to
135-
// existential and then try to prove. If we get back no solutions, we know that
136-
// we've proven the negation. (This is called the "negation as failure" property,
137-
// and it relies on our solver being complete -- i.e., if there is a solution,
138-
// we'll find it, or at least return ambiguous.)
139-
let mut existential_env = Env::default().with_coherence_mode(env.is_in_coherence_mode());
140-
let universal_to_existential: Substitution = env
141-
.variables()
142-
.iter()
143-
.map(|v| {
144-
assert!(v.is_universal());
145-
let v1 = existential_env.fresh_existential(v.kind());
146-
(v, v1)
147-
})
148-
.collect();
149-
150-
let existential_assumptions = universal_to_existential.apply(&assumptions);
151-
let existential_goal = universal_to_existential.apply(&goal);
152-
153-
let cs = formality_prove::prove(
154-
self.decls,
155-
&existential_env,
156-
existential_assumptions.to_wcs(),
157-
&existential_goal,
125+
let cs = is_definitely_not_proveable(
126+
env,
127+
&assumptions,
128+
goal.clone(),
129+
|env, assumptions, goal| formality_prove::prove(self.decls, env, &assumptions, &goal),
158130
);
159-
160-
match cs.into_set() {
161-
Ok(proofs) => {
162-
bail!(
163-
"failed to disprove\n {goal:?}\ngiven\n {assumptions:?}\ngot\n{proofs:?}"
164-
)
165-
}
166-
Err(err) => {
167-
tracing::debug!("Proved not goal, error = {err}");
168-
return Ok(());
169-
}
131+
let cs = cs.into_set()?;
132+
if cs.iter().any(|c| c.unconditionally_true()) {
133+
return Ok(());
170134
}
135+
136+
bail!("failed to prove {goal:?} given {assumptions:?}, got {cs:?}")
171137
}
172138
}

crates/formality-core/src/test_util.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ use std::fmt::{Debug, Display};
4141
/// with `"src/file.rs:LL:CC"`. This makes error messages resilient against changes to the source code.
4242
pub fn normalize_paths(s: impl Display) -> String {
4343
let s = s.to_string();
44-
let re = regex::Regex::new(r"\([^():]+.rs:\d+:\d+\)").unwrap();
44+
let re = regex::Regex::new(r"\([^()]+.rs:\d+:\d+\)").unwrap();
4545
re.replace_all(&s, "(src/file.rs:LL:CC)").to_string()
4646
}
4747

crates/formality-core/src/visit.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::sync::Arc;
22

33
use crate::{collections::Set, language::Language, variable::CoreVariable};
44

5-
pub trait CoreVisit<L: Language> {
5+
pub trait CoreVisit<L: Language>: std::fmt::Debug {
66
/// Extract the list of free variables (for the purposes of this function, defined by `Variable::is_free`).
77
/// The list may contain duplicates and must be in a determinstic order (though the order itself isn't important).
88
fn free_variables(&self) -> Vec<CoreVariable<L>>;

crates/formality-prove/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ mod prove;
88
pub use decls::*;
99
pub use prove::prove;
1010
pub use prove::Constraints;
11-
pub use prove::Env;
11+
pub use prove::{is_definitely_not_proveable, may_not_be_provable, negation_via_failure};
12+
pub use prove::{Bias, Env};
1213

1314
#[cfg(test)]
1415
mod test;

crates/formality-prove/src/prove.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ mod constraints;
33
mod env;
44
mod is_local;
55
mod minimize;
6+
mod negation;
67
mod prove_after;
78
mod prove_eq;
89
mod prove_normalize;
@@ -19,8 +20,9 @@ use tracing::Level;
1920

2021
use crate::decls::Decls;
2122

22-
pub use self::env::Env;
23+
pub use self::env::{Bias, Env};
2324
use self::prove_wc_list::prove_wc_list;
25+
pub use negation::{is_definitely_not_proveable, may_not_be_provable, negation_via_failure};
2426

2527
/// Top-level entry point for proving things; other rules recurse to this one.
2628
pub fn prove(

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

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,6 @@ impl Constraints {
3030
Self::from(env, v)
3131
}
3232

33-
pub fn with_coherence_mode(self, b: bool) -> Self {
34-
Self {
35-
env: self.env.with_coherence_mode(b),
36-
..self
37-
}
38-
}
39-
4033
pub fn unconditionally_true(&self) -> bool {
4134
self.known_true && self.substitution.is_empty()
4235
}

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

Lines changed: 48 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,56 @@ use formality_types::{
77
rust::{Fold, Visit},
88
};
99

10+
/// Whether the solver has to be sound or complete. While it should
11+
/// ideally be both at all times, we have some rules which break these
12+
/// properties, so we have to make sure they are only used while in
13+
/// the proper `Env`. Regardless of the current bias, it's always valid
14+
/// to return an ambiguous result.
15+
#[derive(Default, Debug, Clone, Copy, Hash, Ord, Eq, PartialEq, PartialOrd)]
16+
pub enum Bias {
17+
/// Whenever we're able to prove something, it has to definitely be
18+
/// *truly true*. We must never be able to prove somethingwhich is not
19+
/// *truly true*. This is the default unless we're currently trying to
20+
/// prove the negation via "negation as failure".
21+
///
22+
/// By convention, functions which may only be used with a bias
23+
/// for soundness are called `fn is_X` or `fn is_definitely_X`.
24+
#[default]
25+
Soundness,
26+
/// If we are unable to find a solution for some goal, this goal must
27+
/// definitely not be proveable, it is known to not be *truly true*. This
28+
/// implies that we must only add constraints if they are definitely
29+
/// necessary as we can otherwise use these constraints to incorrectly
30+
/// disprove something.
31+
///
32+
/// Most notably, this is used by coherence checking to make sure that impls
33+
/// definitely do not overlap.
34+
///
35+
/// By convention, functions which may only be used with a bias for
36+
/// completeness are called `fn may_X`.
37+
Completeness,
38+
}
39+
1040
#[derive(Default, Debug, Clone, Hash, Ord, Eq, PartialEq, PartialOrd)]
1141
pub struct Env {
1242
variables: Vec<Variable>,
13-
coherence_mode: bool,
43+
bias: Bias,
1444
}
1545

1646
impl Env {
47+
pub fn new_with_bias(bias: Bias) -> Self {
48+
Env {
49+
variables: Default::default(),
50+
bias,
51+
}
52+
}
53+
1754
pub fn only_universal_variables(&self) -> bool {
1855
self.variables.iter().all(|v| v.is_universal())
1956
}
2057

21-
pub fn is_in_coherence_mode(&self) -> bool {
22-
self.coherence_mode
23-
}
24-
25-
pub fn with_coherence_mode(&self, b: bool) -> Env {
26-
Env {
27-
coherence_mode: b,
28-
..self.clone()
29-
}
58+
pub fn bias(&self) -> Bias {
59+
self.bias
3060
}
3161
}
3262

@@ -75,6 +105,13 @@ impl Env {
75105
v
76106
}
77107

108+
pub fn fresh_universal(&mut self, kind: ParameterKind) -> UniversalVar {
109+
let var_index = self.fresh_index();
110+
let v = UniversalVar { kind, var_index };
111+
self.variables.push(v.upcast());
112+
v
113+
}
114+
78115
pub fn insert_fresh_before(&mut self, kind: ParameterKind, rank: Universe) -> ExistentialVar {
79116
let var_index = self.fresh_index();
80117
let v = ExistentialVar { kind, var_index };
@@ -208,7 +245,7 @@ impl Env {
208245
.iter()
209246
.map(|&v| vs.map_var(v).unwrap_or(v))
210247
.collect(),
211-
coherence_mode: self.coherence_mode,
248+
bias: self.bias,
212249
}
213250
}
214251

0 commit comments

Comments
 (0)