diff --git a/library/init/cc_lemmas.lean b/library/init/cc_lemmas.lean index a7fd6f77cb..71cb72532b 100644 --- a/library/init/cc_lemmas.lean +++ b/library/init/cc_lemmas.lean @@ -109,3 +109,9 @@ eq_false_intro (λ ha, absurd ha (eq.mpr h trivial)) cc_config.em is tt. -/ lemma eq_true_of_not_eq_false {a : Prop} (h : (not a) = false) : a = true := eq_true_intro (classical.by_contradiction (λ hna, eq.mp h hna)) + +lemma ne_of_eq_of_ne {α : Type u} {a b c : α} (h₁ : a = b) (h₂ : b ≠ c) : a ≠ c := +h₁^.symm ▸ h₂ + +lemma ne_of_ne_of_eq {α : Type u} {a b c : α} (h₁ : a ≠ b) (h₂ : b = c) : a ≠ c := +h₂ ▸ h₁ diff --git a/src/library/constructions/constructor.cpp b/src/library/constructions/constructor.cpp index 9ab87795e1..db1e9e5168 100644 --- a/src/library/constructions/constructor.cpp +++ b/src/library/constructions/constructor.cpp @@ -27,6 +27,15 @@ optional mk_constructor_eq_constructor_inconsistency_proof(type_context & return some_expr(pr); } +optional mk_constructor_ne_constructor_proof(type_context & ctx, expr const & e1, expr const & e2) { + type_context::tmp_locals locals(ctx); + expr h = locals.push_local("_h", mk_eq(ctx, e1, e2)); + if (optional pr = mk_constructor_eq_constructor_inconsistency_proof(ctx, e1, e2, h)) + return some_expr(locals.mk_lambda(*pr)); + else + return none_expr(); +} + optional mk_constructor_eq_constructor_implied_core(type_context & ctx, expr const & e1, expr const & e2, expr const & h, buffer & implied_pairs) { // TODO(Leo, Daniel): add support for generalized inductive datatypes // TODO(Leo): add a definition for this proof at inductive datatype declaration time? diff --git a/src/library/constructions/constructor.h b/src/library/constructions/constructor.h index 18e20de750..28d89a8261 100644 --- a/src/library/constructions/constructor.h +++ b/src/library/constructions/constructor.h @@ -9,6 +9,11 @@ namespace lean { /* If e1 and e2 are constructor applications, the constructors are different, and (h : e1 = e2), then return a proof for false. */ optional mk_constructor_eq_constructor_inconsistency_proof(type_context & ctx, expr const & e1, expr const & e2, expr const & h); + +/* If e1 and e2 are constructor applications, the constructors are different, then + return a proof for not (e1 = e2). */ +optional mk_constructor_ne_constructor_proof(type_context & ctx, expr const & e1, expr const & e2); + /* If e1 and e2 are constructor applications for the same constructor, and (h : e1 = e2), then return true, and store the triples (a_i, b_i, hab_i) at result, where (hab_i : a_i = b_i) and a_i (b_i) are constructor fields of e1 (e2). */ diff --git a/src/library/tactic/smt/congruence_closure.cpp b/src/library/tactic/smt/congruence_closure.cpp index 1a84923841..f3fe755b98 100644 --- a/src/library/tactic/smt/congruence_closure.cpp +++ b/src/library/tactic/smt/congruence_closure.cpp @@ -1460,6 +1460,51 @@ bool congruence_closure::may_propagate(expr const & e) { is_iff(e) || is_and(e) || is_or(e) || is_not(e) || is_arrow(e) || is_ite(e); } +static name * g_ne_of_eq_of_ne = nullptr; +static name * g_ne_of_ne_of_eq = nullptr; + +optional congruence_closure::mk_ne_of_eq_of_ne(expr const & a, expr const & a1, expr const & a1_ne_b) { + lean_assert(is_eqv(a, a1)); + if (a == a1) + return some_expr(a1_ne_b); + auto a_eq_a1 = get_eq_proof(a, a1); + if (!a_eq_a1) return none_expr(); // failed to build proof + return some_expr(mk_app(m_ctx, *g_ne_of_eq_of_ne, 6, *a_eq_a1, a1_ne_b)); +} + +optional congruence_closure::mk_ne_of_ne_of_eq(expr const & a_ne_b1, expr const & b1, expr const & b) { + lean_assert(is_eqv(b, b1)); + if (b == b1) + return some_expr(a_ne_b1); + auto b1_eq_b = get_eq_proof(b1, b); + if (!b1_eq_b) return none_expr(); // failed to build proof + return some_expr(mk_app(m_ctx, *g_ne_of_ne_of_eq, 6, a_ne_b1, *b1_eq_b)); +} + +void congruence_closure::propagate_eq_up(expr const & e) { + /* Remark: the positive case is implemented at check_eq_true + for any reflexive relation. */ + expr a, b; + lean_verify(is_eq(e, a, b)); + expr ra = get_root(a); + expr rb = get_root(b); + if (ra != rb) { + optional ra_ne_rb; + if (is_interpreted_value(ra) && is_interpreted_value(rb)) { + ra_ne_rb = mk_val_ne_proof(m_ctx, ra, rb); + } else { + if (optional c1 = is_constructor_app(env(), ra)) + if (optional c2 = is_constructor_app(env(), rb)) + if (c1 && c2 && *c1 != *c2) + ra_ne_rb = mk_constructor_ne_constructor_proof(m_ctx, ra, rb); + } + if (ra_ne_rb) + if (auto a_ne_rb = mk_ne_of_eq_of_ne(a, ra, *ra_ne_rb)) + if (auto a_ne_b = mk_ne_of_ne_of_eq(*a_ne_rb, rb, b)) + push_eq(e, mk_false(), mk_eq_false_intro(m_ctx, *a_ne_b)); + } +} + void congruence_closure::propagate_up(expr const & e) { if (m_state.m_inconsistent) return; if (is_iff(e)) { @@ -1474,6 +1519,8 @@ void congruence_closure::propagate_up(expr const & e) { propagate_imp_up(e); } else if (is_ite(e)) { propagate_ite_up(e); + } else if (is_eq(e)) { + propagate_eq_up(e); } } @@ -1957,6 +2004,9 @@ void initialize_congruence_closure() { g_eq_false_of_not_eq_true = new expr(mk_constant("eq_false_of_not_eq_true")); g_eq_true_of_not_eq_false = new expr(mk_constant("eq_true_of_not_eq_false")); + + g_ne_of_eq_of_ne = new name("ne_of_eq_of_ne"); + g_ne_of_ne_of_eq = new name("ne_of_ne_of_eq"); } void finalize_congruence_closure() { @@ -2004,5 +2054,8 @@ void finalize_congruence_closure() { delete g_eq_false_of_not_eq_true; delete g_eq_true_of_not_eq_false; + + delete g_ne_of_eq_of_ne; + delete g_ne_of_ne_of_eq; } } diff --git a/src/library/tactic/smt/congruence_closure.h b/src/library/tactic/smt/congruence_closure.h index cc7a1fa14a..eb9daeab25 100644 --- a/src/library/tactic/smt/congruence_closure.h +++ b/src/library/tactic/smt/congruence_closure.h @@ -230,6 +230,9 @@ private: void propagate_not_up(expr const & e); void propagate_imp_up(expr const & e); void propagate_ite_up(expr const & e); + optional mk_ne_of_eq_of_ne(expr const & a, expr const & a1, expr const & a1_ne_b); + optional mk_ne_of_ne_of_eq(expr const & a_ne_b1, expr const & b1, expr const & b); + void propagate_eq_up(expr const & e); void propagate_up(expr const & e); void propagate_and_down(expr const & e); void propagate_or_down(expr const & e); diff --git a/tests/lean/run/using_smt1.lean b/tests/lean/run/using_smt1.lean index a75f31f2f3..4e5e343fc2 100644 --- a/tests/lean/run/using_smt1.lean +++ b/tests/lean/run/using_smt1.lean @@ -16,3 +16,35 @@ by using_smt $ return () example (a b c d : nat) : a ≠ d → a = b ∧ b = c → a = c := by using_smt $ return () + +set_option pp.all false + +example (f : nat → nat) (a b c : nat) : + f a = 0 → + f b = 1 → + f a = f b ∨ f a = c → + c = 0 := +by using_smt $ return() + +example (f : nat → nat) (a b c d e: nat) : + f a = 0 → + f a = d → + f b = e → + f b = 1 → + d = e ∨ f a = c → + c = 0 := +by using_smt $ return() + +example (f : bool → bool) (a b c : bool) : + f a = ff → + f b = tt → + f a = f b ∨ f a = c → + c = ff := +by using_smt $ return() + +example (f : list nat → list nat) (a b c : list nat) : + f a = [] → + f b = [0, 1] → + f a = f b ∨ f a = c → + c = [] := +by using_smt $ return()