1489 lines
60 KiB
C++
1489 lines
60 KiB
C++
/*
|
|
Copyright (c) 2015 Daniel Selsam. All rights reserved.
|
|
Released under Apache 2.0 license as described in the file LICENSE.
|
|
Author: Daniel Selsam
|
|
*/
|
|
#include <functional>
|
|
#include <iostream>
|
|
#include "util/flet.h"
|
|
#include "util/freset.h"
|
|
#include "util/pair.h"
|
|
#include "util/optional.h"
|
|
#include "util/interrupt.h"
|
|
#include "util/sexpr/option_declarations.h"
|
|
#include "kernel/abstract.h"
|
|
#include "kernel/expr_maps.h"
|
|
#include "kernel/find_fn.h"
|
|
#include "kernel/instantiate.h"
|
|
#include "library/trace.h"
|
|
#include "library/constants.h"
|
|
#include "library/normalize.h"
|
|
#include "library/expr_lt.h"
|
|
#include "library/locals.h"
|
|
#include "library/num.h"
|
|
#include "library/util.h"
|
|
#include "library/norm_num.h"
|
|
#include "library/attribute_manager.h"
|
|
#include "library/defeq_canonizer.h"
|
|
#include "library/class_instance_resolution.h"
|
|
#include "library/relation_manager.h"
|
|
#include "library/app_builder.h"
|
|
#include "library/congr_lemma.h"
|
|
#include "library/fun_info.h"
|
|
#include "library/vm/vm_expr.h"
|
|
#include "library/vm/vm_list.h"
|
|
#include "library/vm/vm_name.h"
|
|
#include "library/tactic/tactic_state.h"
|
|
#include "library/tactic/ac_tactics.h"
|
|
#include "library/tactic/app_builder_tactics.h"
|
|
#include "library/tactic/simplifier/simplifier.h"
|
|
#include "library/tactic/simplifier/simp_lemmas.h"
|
|
#include "library/tactic/simplifier/simp_extensions.h"
|
|
#include "library/tactic/simplifier/theory_simplifier.h"
|
|
#include "library/tactic/simplifier/ceqv.h"
|
|
#include "library/tactic/simplifier/util.h"
|
|
|
|
#ifndef LEAN_DEFAULT_SIMPLIFY_MAX_STEPS
|
|
#define LEAN_DEFAULT_SIMPLIFY_MAX_STEPS 1000
|
|
#endif
|
|
#ifndef LEAN_DEFAULT_SIMPLIFY_NARY_ASSOC
|
|
#define LEAN_DEFAULT_SIMPLIFY_NARY_ASSOC true
|
|
#endif
|
|
#ifndef LEAN_DEFAULT_SIMPLIFY_MEMOIZE
|
|
#define LEAN_DEFAULT_SIMPLIFY_MEMOIZE true
|
|
#endif
|
|
#ifndef LEAN_DEFAULT_SIMPLIFY_CONTEXTUAL
|
|
#define LEAN_DEFAULT_SIMPLIFY_CONTEXTUAL true
|
|
#endif
|
|
#ifndef LEAN_DEFAULT_SIMPLIFY_USER_EXTENSIONS
|
|
#define LEAN_DEFAULT_SIMPLIFY_USER_EXTENSIONS true
|
|
#endif
|
|
#ifndef LEAN_DEFAULT_SIMPLIFY_REWRITE
|
|
#define LEAN_DEFAULT_SIMPLIFY_REWRITE true
|
|
#endif
|
|
#ifndef LEAN_DEFAULT_SIMPLIFY_UNSAFE_NARY
|
|
#define LEAN_DEFAULT_SIMPLIFY_UNSAFE_NARY false
|
|
#endif
|
|
#ifndef LEAN_DEFAULT_SIMPLIFY_THEORY
|
|
#define LEAN_DEFAULT_SIMPLIFY_THEORY true
|
|
#endif
|
|
#ifndef LEAN_DEFAULT_SIMPLIFY_TOPDOWN
|
|
#define LEAN_DEFAULT_SIMPLIFY_TOPDOWN false
|
|
#endif
|
|
#ifndef LEAN_DEFAULT_SIMPLIFY_LIFT_EQ
|
|
#define LEAN_DEFAULT_SIMPLIFY_LIFT_EQ true
|
|
#endif
|
|
#ifndef LEAN_DEFAULT_SIMPLIFY_DEFEQ_CANONIZE_INSTANCES_FIXED_POINT
|
|
#define LEAN_DEFAULT_SIMPLIFY_DEFEQ_CANONIZE_INSTANCES_FIXED_POINT false
|
|
#endif
|
|
#ifndef LEAN_DEFAULT_SIMPLIFY_DEFEQ_CANONIZE_PROOFS_FIXED_POINT
|
|
#define LEAN_DEFAULT_SIMPLIFY_DEFEQ_CANONIZE_PROOFS_FIXED_POINT false
|
|
#endif
|
|
#ifndef LEAN_DEFAULT_SIMPLIFY_CANONIZE_SUBSINGLETONS
|
|
#define LEAN_DEFAULT_SIMPLIFY_CANONIZE_SUBSINGLETONS true
|
|
#endif
|
|
|
|
namespace lean {
|
|
|
|
/* Options */
|
|
|
|
static name * g_simplify_max_steps = nullptr;
|
|
static name * g_simplify_nary_assoc = nullptr;
|
|
static name * g_simplify_memoize = nullptr;
|
|
static name * g_simplify_contextual = nullptr;
|
|
static name * g_simplify_user_extensions = nullptr;
|
|
static name * g_simplify_rewrite = nullptr;
|
|
static name * g_simplify_unsafe_nary = nullptr;
|
|
static name * g_simplify_theory = nullptr;
|
|
static name * g_simplify_topdown = nullptr;
|
|
static name * g_simplify_lift_eq = nullptr;
|
|
static name * g_simplify_canonize_instances_fixed_point = nullptr;
|
|
static name * g_simplify_canonize_proofs_fixed_point = nullptr;
|
|
static name * g_simplify_canonize_subsingletons = nullptr;
|
|
|
|
static unsigned get_simplify_max_steps(options const & o) {
|
|
return o.get_unsigned(*g_simplify_max_steps, LEAN_DEFAULT_SIMPLIFY_MAX_STEPS);
|
|
}
|
|
|
|
static unsigned get_simplify_nary_assoc(options const & o) {
|
|
return o.get_unsigned(*g_simplify_nary_assoc, LEAN_DEFAULT_SIMPLIFY_NARY_ASSOC);
|
|
}
|
|
|
|
static bool get_simplify_memoize(options const & o) {
|
|
return o.get_bool(*g_simplify_memoize, LEAN_DEFAULT_SIMPLIFY_MEMOIZE);
|
|
}
|
|
|
|
static bool get_simplify_contextual(options const & o) {
|
|
return o.get_bool(*g_simplify_contextual, LEAN_DEFAULT_SIMPLIFY_CONTEXTUAL);
|
|
}
|
|
|
|
static bool get_simplify_user_extensions(options const & o) {
|
|
return o.get_bool(*g_simplify_user_extensions, LEAN_DEFAULT_SIMPLIFY_USER_EXTENSIONS);
|
|
}
|
|
|
|
static bool get_simplify_rewrite(options const & o) {
|
|
return o.get_bool(*g_simplify_rewrite, LEAN_DEFAULT_SIMPLIFY_REWRITE);
|
|
}
|
|
|
|
static bool get_simplify_unsafe_nary(options const & o) {
|
|
return o.get_bool(*g_simplify_unsafe_nary, LEAN_DEFAULT_SIMPLIFY_UNSAFE_NARY);
|
|
}
|
|
|
|
static bool get_simplify_theory(options const & o) {
|
|
return o.get_bool(*g_simplify_theory, LEAN_DEFAULT_SIMPLIFY_THEORY);
|
|
}
|
|
|
|
static bool get_simplify_topdown(options const & o) {
|
|
return o.get_bool(*g_simplify_topdown, LEAN_DEFAULT_SIMPLIFY_TOPDOWN);
|
|
}
|
|
|
|
static bool get_simplify_lift_eq(options const & o) {
|
|
return o.get_bool(*g_simplify_lift_eq, LEAN_DEFAULT_SIMPLIFY_LIFT_EQ);
|
|
}
|
|
|
|
static bool get_simplify_canonize_instances_fixed_point(options const & o) {
|
|
return o.get_bool(*g_simplify_canonize_instances_fixed_point, LEAN_DEFAULT_SIMPLIFY_DEFEQ_CANONIZE_INSTANCES_FIXED_POINT);
|
|
}
|
|
|
|
static bool get_simplify_canonize_proofs_fixed_point(options const & o) {
|
|
return o.get_bool(*g_simplify_canonize_proofs_fixed_point, LEAN_DEFAULT_SIMPLIFY_DEFEQ_CANONIZE_PROOFS_FIXED_POINT);
|
|
}
|
|
|
|
static bool get_simplify_canonize_subsingletons(options const & o) {
|
|
return o.get_bool(*g_simplify_canonize_subsingletons, LEAN_DEFAULT_SIMPLIFY_CANONIZE_SUBSINGLETONS);
|
|
}
|
|
|
|
#define lean_simp_trace(tctx, n, code) lean_trace(n, scope_trace_env _scope1(tctx.env(), tctx); code)
|
|
|
|
/* Main simplifier class */
|
|
|
|
class simplifier {
|
|
type_context m_tctx;
|
|
theory_simplifier m_theory_simplifier;
|
|
|
|
name m_rel;
|
|
|
|
simp_lemmas m_slss;
|
|
simp_lemmas m_ctx_slss;
|
|
|
|
optional<expr> m_curr_nary_op;
|
|
|
|
vm_obj m_prove_fn;
|
|
|
|
/* Logging */
|
|
unsigned m_num_steps{0};
|
|
|
|
bool m_need_restart{false};
|
|
|
|
/* Options */
|
|
unsigned m_max_steps;
|
|
unsigned m_nary_assoc;
|
|
bool m_memoize;
|
|
bool m_contextual;
|
|
bool m_user_extensions;
|
|
bool m_rewrite;
|
|
bool m_unsafe_nary;
|
|
bool m_theory;
|
|
bool m_topdown;
|
|
bool m_lift_eq;
|
|
bool m_canonize_instances_fixed_point;
|
|
bool m_canonize_proofs_fixed_point;
|
|
bool m_canonize_subsingletons;
|
|
|
|
/* Cache */
|
|
struct key {
|
|
name m_rel;
|
|
expr m_e;
|
|
unsigned m_hash;
|
|
|
|
key(name const & rel, expr const & e):
|
|
m_rel(rel), m_e(e), m_hash(hash(rel.hash(), e.hash())) { }
|
|
};
|
|
|
|
struct key_hash_fn {
|
|
unsigned operator()(key const & k) const { return k.m_hash; }
|
|
};
|
|
|
|
struct key_eq_fn {
|
|
bool operator()(key const & k1, key const & k2) const {
|
|
return k1.m_rel == k2.m_rel && k1.m_e == k2.m_e;
|
|
}
|
|
};
|
|
|
|
typedef std::unordered_map<key, simp_result, key_hash_fn, key_eq_fn> simplify_cache;
|
|
simplify_cache m_cache;
|
|
optional<simp_result> cache_lookup(expr const & e);
|
|
void cache_save(expr const & e, simp_result const & r);
|
|
|
|
/* Basic helpers */
|
|
environment const & env() const { return m_tctx.env(); }
|
|
|
|
simp_result join(simp_result const & r1, simp_result const & r2) { return ::lean::join(m_tctx, m_rel, r1, r2); }
|
|
|
|
bool using_eq() { return m_rel == get_eq_name(); }
|
|
|
|
bool is_dependent_fn(expr const & f) {
|
|
expr f_type = m_tctx.whnf(m_tctx.infer(f));
|
|
lean_assert(is_pi(f_type));
|
|
return has_free_vars(binding_body(f_type));
|
|
}
|
|
|
|
simp_lemmas add_to_slss(simp_lemmas const & _slss, buffer<expr> const & ls) {
|
|
simp_lemmas slss = _slss;
|
|
for (unsigned i = 0; i < ls.size(); i++) {
|
|
expr const & l = ls[i];
|
|
try {
|
|
// TODO(Leo,Daniel): should we allow the user to set the priority of local lemmas?
|
|
slss = add(m_tctx, slss, mlocal_name(l), m_tctx.infer(l), l, LEAN_DEFAULT_PRIORITY);
|
|
lean_trace_d(name({"simplifier", "context"}), tout() << mlocal_name(l) << " : " << m_tctx.infer(l) << "\n";);
|
|
} catch (exception e) {
|
|
}
|
|
}
|
|
return slss;
|
|
}
|
|
|
|
bool should_defeq_canonize() {
|
|
return m_canonize_instances_fixed_point || m_canonize_proofs_fixed_point;
|
|
}
|
|
|
|
void get_app_nary_args(expr const & op, expr const & e, buffer<expr> & nary_args) {
|
|
if (m_unsafe_nary)
|
|
::lean::unsafe_get_app_nary_args(op, e, nary_args);
|
|
else
|
|
::lean::get_app_nary_args(m_tctx, op, e, nary_args);
|
|
}
|
|
|
|
bool ops_are_the_same(expr const & op1, expr const & op2) {
|
|
if (m_unsafe_nary)
|
|
return get_app_fn(op1) == get_app_fn(op2);
|
|
else
|
|
return m_tctx.is_def_eq(op1, op2);
|
|
}
|
|
|
|
bool instantiate_emetas(tmp_type_context & tmp_tctx, unsigned num_emeta, list<expr> const & emetas, list<bool> const & instances);
|
|
|
|
/* Simp_Results */
|
|
simp_result lift_from_eq(expr const & old_e, simp_result const & r_eq);
|
|
|
|
/* Main simplify method */
|
|
simp_result simplify(expr const & old_e) {
|
|
m_num_steps++;
|
|
lean_trace_inc_depth("simplifier");
|
|
lean_trace_d("simplifier", tout() << m_rel << ": " << old_e << "\n";);
|
|
|
|
if (m_num_steps > m_max_steps) {
|
|
lean_trace(name({"simplifier", "failed"}), tout() << m_rel << ": " << old_e << "\n";);
|
|
throw exception("simplifier failed, maximum number of steps exceeded");
|
|
}
|
|
|
|
if (m_memoize) {
|
|
if (auto it = cache_lookup(old_e))
|
|
return *it;
|
|
}
|
|
|
|
expr e = whnf_eta(old_e);
|
|
simp_result r;
|
|
|
|
optional<pair<expr, expr> > assoc;
|
|
if (m_nary_assoc && using_eq())
|
|
assoc = is_assoc(m_tctx, e);
|
|
|
|
if (assoc) {
|
|
bool use_congr_only = assoc && m_curr_nary_op && *m_curr_nary_op == assoc->second;
|
|
flet<optional<expr> > in_nary_subtree(m_curr_nary_op, some_expr(assoc->second));
|
|
r = simplify_nary(assoc->first, assoc->second, e, use_congr_only);
|
|
} else {
|
|
flet<optional<expr> > not_in_nary_subtree(m_curr_nary_op, none_expr());
|
|
r = simplify_binary(e);
|
|
}
|
|
|
|
if (!r.is_done())
|
|
r = join(r, simplify(r.get_new()));
|
|
|
|
if (m_lift_eq && !using_eq()) {
|
|
simp_result r_eq;
|
|
{
|
|
flet<name> use_eq(m_rel, get_eq_name());
|
|
r_eq = simplify(r.get_new());
|
|
}
|
|
if (r_eq.get_new() != r.get_new()) {
|
|
r = join(r, lift_from_eq(r.get_new(), r_eq));
|
|
r = join(r, simplify(r.get_new()));
|
|
}
|
|
}
|
|
|
|
if (m_memoize)
|
|
cache_save(old_e, r);
|
|
|
|
return r;
|
|
}
|
|
|
|
// Binary simplify methods
|
|
simp_result simplify_user_extensions_binary(expr const & old_e) {
|
|
expr op = get_app_fn(old_e);
|
|
if (!is_constant(op)) return simp_result(old_e);
|
|
name head = const_name(op);
|
|
buffer<unsigned> ext_ids;
|
|
get_simp_extensions_for(m_tctx.env(), head, ext_ids);
|
|
for (unsigned ext_id : ext_ids) {
|
|
expr old_e_type = m_tctx.infer(old_e);
|
|
metavar_context mctx = m_tctx.mctx();
|
|
expr result_mvar = mctx.mk_metavar_decl(m_tctx.lctx(), old_e_type);
|
|
m_tctx.set_mctx(mctx); // the app-builder needs to know about these metavars
|
|
expr goal_type = mk_app(m_tctx, m_rel, old_e, result_mvar);
|
|
expr goal_mvar = mctx.mk_metavar_decl(m_tctx.lctx(), goal_type);
|
|
vm_obj s = to_obj(tactic_state(m_tctx.env(), m_tctx.get_options(), mctx, list<expr>(goal_mvar), goal_mvar));
|
|
vm_obj simp_ext_result = invoke(ext_id, s);
|
|
optional<tactic_state> s_new = is_tactic_success(simp_ext_result);
|
|
// TODO(dhs): create a bool metavar for the `done` flag
|
|
if (s_new) {
|
|
m_tctx.set_mctx(s_new->mctx());
|
|
expr result = m_tctx.instantiate_mvars(result_mvar);
|
|
expr proof = m_tctx.instantiate_mvars(goal_mvar);
|
|
if (is_app_of(proof, get_eq_refl_name(), 2) || is_app_of(proof, get_rfl_name(), 2)) {
|
|
return simp_result(result);
|
|
} else {
|
|
return simp_result(result, proof);
|
|
}
|
|
} else {
|
|
lean_trace(name({"simplifier", "extensions"}),
|
|
tout() << "extension failed on goal " << goal_type << "\n";);
|
|
}
|
|
}
|
|
return simp_result(old_e);
|
|
}
|
|
|
|
simp_result simplify_rewrite_binary(expr const & e) {
|
|
simp_lemmas slss = ::lean::join(m_slss, m_ctx_slss);
|
|
simp_lemmas_for const * sr = slss.find(m_rel);
|
|
if (!sr) return simp_result(e);
|
|
|
|
list<simp_lemma> const * srs = sr->find_simp(e);
|
|
if (!srs) {
|
|
lean_trace(name({"debug", "simplifier", "try_rewrite"}), tout() << "no simp lemmas for: " << e << "\n";);
|
|
return simp_result(e);
|
|
}
|
|
|
|
for (simp_lemma const & lemma : *srs) {
|
|
simp_result r = rewrite_binary(e, lemma);
|
|
if (r.has_proof())
|
|
return r;
|
|
}
|
|
return simp_result(e);
|
|
}
|
|
|
|
simp_result rewrite_binary(expr const & e, simp_lemma const & sl) {
|
|
tmp_type_context tmp_tctx(m_tctx, sl.get_num_umeta(), sl.get_num_emeta());
|
|
if (!tmp_tctx.is_def_eq(e, sl.get_lhs()))
|
|
return simp_result(e);
|
|
|
|
if (!instantiate_emetas(tmp_tctx, sl.get_num_emeta(), sl.get_emetas(), sl.get_instances()))
|
|
return simp_result(e);
|
|
|
|
for (unsigned i = 0; i < sl.get_num_umeta(); i++) {
|
|
if (!tmp_tctx.is_uassigned(i))
|
|
return simp_result(e);
|
|
}
|
|
|
|
expr new_lhs = tmp_tctx.instantiate_mvars(sl.get_lhs());
|
|
expr new_rhs = tmp_tctx.instantiate_mvars(sl.get_rhs());
|
|
if (sl.is_perm()) {
|
|
if (!is_lt(new_rhs, new_lhs, false)) {
|
|
lean_simp_trace(tmp_tctx, name({"simplifier", "perm"}),
|
|
tout() << "perm rejected: " << new_rhs << " !< " << new_lhs << "\n";);
|
|
return simp_result(e);
|
|
}
|
|
}
|
|
expr pf = tmp_tctx.instantiate_mvars(sl.get_proof());
|
|
return simp_result(new_rhs, pf);
|
|
}
|
|
|
|
simp_result simplify_subterms_app_binary(expr const & _e) {
|
|
lean_assert(is_app(_e));
|
|
expr e = should_defeq_canonize() ? defeq_canonize_args_step(_e) : _e;
|
|
|
|
// (1) Try user-defined congruences
|
|
simp_result r_user = try_congrs(e);
|
|
if (r_user.has_proof()) {
|
|
if (using_eq()) {
|
|
return join(r_user, simplify_operator_of_app(r_user.get_new()));
|
|
} else {
|
|
return r_user;
|
|
}
|
|
}
|
|
|
|
// (2) Synthesize congruence lemma
|
|
if (using_eq()) {
|
|
optional<simp_result> r_args = synth_congr(e);
|
|
if (r_args) return join(*r_args, simplify_operator_of_app(r_args->get_new()));
|
|
}
|
|
// (3) Fall back on generic binary congruence
|
|
if (using_eq()) {
|
|
expr const & f = app_fn(e);
|
|
expr const & arg = app_arg(e);
|
|
|
|
simp_result r_f = simplify(f);
|
|
|
|
if (is_dependent_fn(f)) {
|
|
if (r_f.has_proof()) return congr_fun(r_f, arg);
|
|
else return mk_app(r_f.get_new(), arg);
|
|
} else {
|
|
simp_result r_arg = simplify(arg);
|
|
return congr_fun_arg(r_f, r_arg);
|
|
}
|
|
}
|
|
return simp_result(e);
|
|
}
|
|
|
|
|
|
// Main binary simplify method
|
|
simp_result simplify_binary(expr const & e) {
|
|
if (m_topdown) {
|
|
if (m_rewrite) {
|
|
simp_result r_rewrite = simplify_rewrite_binary(e);
|
|
if (r_rewrite.get_new() != e) {
|
|
lean_trace_d(name({"simplifier", "rewrite"}), tout() << e << " ==> " << r_rewrite.get_new() << "\n";);
|
|
return r_rewrite;
|
|
}
|
|
}
|
|
|
|
if (m_user_extensions) {
|
|
simp_result r_user = simplify_user_extensions_binary(e);
|
|
if (r_user.get_new() != e) {
|
|
lean_trace_d(name({"simplifier", "user_extensions"}), tout() << e << " ==> " << r_user.get_new() << "\n";);
|
|
return r_user;
|
|
}
|
|
}
|
|
}
|
|
|
|
// [1] Simplify subterms using congruence
|
|
simp_result r(e);
|
|
|
|
if (!using_eq() || !m_theory_simplifier.owns(e)) {
|
|
switch (r.get_new().kind()) {
|
|
case expr_kind::Local:
|
|
case expr_kind::Meta:
|
|
case expr_kind::Sort:
|
|
case expr_kind::Constant:
|
|
case expr_kind::Macro:
|
|
// no-op
|
|
break;
|
|
case expr_kind::Lambda:
|
|
if (using_eq())
|
|
r = simplify_subterms_lambda(r.get_new());
|
|
break;
|
|
case expr_kind::Pi:
|
|
r = simplify_subterms_pi(r.get_new());
|
|
break;
|
|
case expr_kind::App:
|
|
r = simplify_subterms_app_binary(r.get_new());
|
|
break;
|
|
case expr_kind::Let:
|
|
// whnf unfolds let-expressions
|
|
// TODO(dhs, leo): add flag for type_context not to unfold let-expressions
|
|
lean_unreachable();
|
|
case expr_kind::Var:
|
|
lean_unreachable();
|
|
}
|
|
|
|
if (r.get_new() != e) {
|
|
lean_trace_d(name({"simplifier", "congruence"}), tout() << e << " ==> " << r.get_new() << "\n";);
|
|
if (m_topdown)
|
|
return r;
|
|
}
|
|
}
|
|
|
|
if (!m_topdown) {
|
|
if (m_rewrite) {
|
|
simp_result r_rewrite = simplify_rewrite_binary(r.get_new());
|
|
if (r_rewrite.get_new() != r.get_new()) {
|
|
lean_trace_d(name({"simplifier", "rewrite"}), tout() << r.get_new() << " ==> " << r_rewrite.get_new() << "\n";);
|
|
return join(r, r_rewrite);
|
|
}
|
|
}
|
|
|
|
if (m_user_extensions) {
|
|
simp_result r_user = simplify_user_extensions_binary(r.get_new());
|
|
if (r_user.get_new() != r.get_new()) {
|
|
lean_trace_d(name({"simplifier", "user_extensions"}), tout() << r.get_new() << " ==> " << r_user.get_new() << "\n";);
|
|
simp_result r_join_user = join(r, r_user);
|
|
if (r_user.is_done())
|
|
r_join_user.set_done();
|
|
return r_join_user;
|
|
}
|
|
}
|
|
}
|
|
|
|
// [5] Simplify with the theory simplifier
|
|
// Note: the theory simplifier guarantees that no new subterms are introduced that need to be simplified.
|
|
// Thus we never need to repeat unless something is simplified downstream of here.
|
|
if (using_eq() && m_theory) {
|
|
simp_result r_theory = m_theory_simplifier.simplify_binary(r.get_new());
|
|
if (r_theory.get_new() != r.get_new()) {
|
|
lean_trace_d(name({"simplifier", "theory"}), tout() << r.get_new() << " ==> " << r_theory.get_new() << "\n";);
|
|
r = join(r, r_theory);
|
|
}
|
|
}
|
|
|
|
r.set_done();
|
|
return r;
|
|
}
|
|
|
|
// n-ary methods
|
|
optional<simp_result> simplify_rewrite_nary(expr const & assoc, expr const & old_e, expr const & op, buffer<expr> const & nary_args) {
|
|
simp_lemmas slss = ::lean::join(m_slss, m_ctx_slss);
|
|
simp_lemmas_for const * sr = slss.find(m_rel);
|
|
if (!sr)
|
|
return optional<simp_result>();
|
|
|
|
list<simp_lemma> const * srs = sr->find_simp(op);
|
|
if (!srs) {
|
|
return optional<simp_result>();
|
|
}
|
|
|
|
for (simp_lemma const & lemma : *srs) {
|
|
if (optional<simp_result> r = rewrite_nary(assoc, old_e, op, nary_args, lemma))
|
|
return r;
|
|
}
|
|
return optional<simp_result>();
|
|
}
|
|
|
|
optional<simp_result> rewrite_nary(expr const & assoc, expr const & old_e, expr const & op, buffer<expr> const & nary_args, simp_lemma const & sl) {
|
|
optional<expr> pattern_op = get_binary_op(sl.get_lhs());
|
|
if (!pattern_op)
|
|
return optional<simp_result>();
|
|
|
|
tmp_type_context tmp_tctx(m_tctx, sl.get_num_umeta(), sl.get_num_emeta());
|
|
if (!tmp_tctx.is_def_eq(op, *pattern_op))
|
|
return optional<simp_result>();
|
|
|
|
buffer<expr> nary_pattern_args;
|
|
get_app_nary_args(*pattern_op, sl.get_lhs(), nary_pattern_args);
|
|
|
|
unsigned num_patterns = nary_pattern_args.size();
|
|
|
|
if (nary_args.size() < num_patterns)
|
|
return optional<simp_result>();
|
|
|
|
// TODO(dhs): return an n-ary macro, and have simplify(e) unfold these at the end
|
|
// Reason: multiple rewrites and theory steps could avoid reconstructing the term
|
|
// Reason for postponing: easy to support and may not be a bottleneck
|
|
for (unsigned i = 0; i <= nary_args.size() - num_patterns; ++i) {
|
|
if (optional<simp_result> r = rewrite_nary_step(nary_args, nary_pattern_args, i, sl)) {
|
|
lean_assert(r->has_proof());
|
|
unsigned j = nary_args.size() - 1;
|
|
expr new_e = (j >= i + num_patterns) ? nary_args[j] : r->get_new();
|
|
j = (j >= i + num_patterns) ? (j - 1) : (j - num_patterns);
|
|
while (j + 1 > 0) {
|
|
if (j >= i + num_patterns || j < i) {
|
|
new_e = mk_app(op, nary_args[j], new_e);
|
|
j -= 1;
|
|
} else {
|
|
new_e = mk_app(op, r->get_new(), new_e);
|
|
j -= num_patterns;
|
|
}
|
|
}
|
|
return optional<simp_result>(simp_result(new_e, mk_rewrite_assoc_proof(assoc, mk_eq(m_tctx, old_e, new_e),
|
|
i, num_patterns, nary_args.size(), r->get_new(), r->get_proof())));
|
|
}
|
|
}
|
|
return optional<simp_result>();
|
|
}
|
|
|
|
optional<simp_result> rewrite_nary_step(buffer<expr> const & nary_args, buffer<expr> const & nary_pattern_args, unsigned offset, simp_lemma const & sl) {
|
|
tmp_type_context tmp_tctx(m_tctx, sl.get_num_umeta(), sl.get_num_emeta());
|
|
for (unsigned i = 0; i < nary_pattern_args.size(); ++i) {
|
|
if (!tmp_tctx.is_def_eq(nary_args[offset+i], nary_pattern_args[i]))
|
|
return optional<simp_result>();
|
|
}
|
|
|
|
if (!instantiate_emetas(tmp_tctx, sl.get_num_emeta(), sl.get_emetas(), sl.get_instances()))
|
|
return optional<simp_result>();
|
|
|
|
for (unsigned i = 0; i < sl.get_num_umeta(); i++) {
|
|
if (!tmp_tctx.is_uassigned(i))
|
|
return optional<simp_result>();
|
|
}
|
|
|
|
expr new_lhs = tmp_tctx.instantiate_mvars(sl.get_lhs());
|
|
expr new_rhs = tmp_tctx.instantiate_mvars(sl.get_rhs());
|
|
|
|
if (sl.is_perm()) {
|
|
if (!is_lt(new_rhs, new_lhs, false)) {
|
|
lean_simp_trace(tmp_tctx, name({"simplifier", "perm"}),
|
|
tout() << "perm rejected: " << new_rhs << " !< " << new_lhs << "\n";);
|
|
return optional<simp_result>();
|
|
}
|
|
}
|
|
|
|
expr pf = tmp_tctx.instantiate_mvars(sl.get_proof());
|
|
return optional<simp_result>(simp_result(new_rhs, pf));
|
|
}
|
|
|
|
optional<simp_result> simplify_user_extensions_nary(expr const & assoc, expr const & old_e, expr const & op, buffer<expr> const & nary_args) {
|
|
// For now, user extensions are not aware of the binary/nary distinction, except that they are guaranteed that
|
|
// if an operator is an instance of [is_associative] for the relation in question, the user extension will only be
|
|
// called on the outermost applications.
|
|
simp_result r = simplify_user_extensions_binary(old_e);
|
|
if (r.get_new() != old_e)
|
|
return optional<simp_result>(r);
|
|
else
|
|
return optional<simp_result>();
|
|
}
|
|
|
|
simp_result simplify_subterms_app_nary_core(expr const & old_op, expr const & new_op, optional<expr> const & pf_op, expr const & e) {
|
|
expr arg1, arg2;
|
|
optional<expr> op = get_binary_op(e, arg1, arg2);
|
|
if (op && ops_are_the_same(*op, old_op)) {
|
|
simp_result r(e);
|
|
if (pf_op)
|
|
r = join(r, simp_result(mk_app(new_op, arg1, arg2), mk_congr_bin_op(m_tctx, *pf_op, arg1, arg2)));
|
|
|
|
simp_result r1 = simplify_subterms_app_nary_core(old_op, new_op, pf_op, arg1);
|
|
simp_result r2 = simplify_subterms_app_nary_core(old_op, new_op, pf_op, arg2);
|
|
|
|
expr new_e = mk_app(new_op, r1.get_new(), r2.get_new());
|
|
if (r1.has_proof() && r2.has_proof()) {
|
|
return join(r, simp_result(new_e, mk_congr_bin_args(m_tctx, new_op, r1.get_proof(), r2.get_proof())));
|
|
} else if (r1.has_proof()) {
|
|
return join(r, simp_result(new_e, mk_congr_bin_arg1(m_tctx, new_op, r1.get_proof(), r2.get_new())));
|
|
} else if (r2.has_proof()) {
|
|
return join(r, simp_result(new_e, mk_congr_bin_arg2(m_tctx, new_op, r1.get_new(), r2.get_proof())));
|
|
} else {
|
|
r.update(new_e);
|
|
return r;
|
|
}
|
|
} else {
|
|
return simplify(e);
|
|
}
|
|
}
|
|
|
|
simp_result simplify_subterms_app_nary(expr const & old_op, expr const & e) {
|
|
simp_result pf_op = simplify(old_op);
|
|
return simplify_subterms_app_nary_core(old_op, old_op, pf_op.get_optional_proof(), e);
|
|
}
|
|
|
|
// Main n-ary simplify method
|
|
simp_result simplify_nary(expr const & assoc, expr const & op, expr const & old_e, bool use_congr_only) {
|
|
lean_assert(using_eq());
|
|
|
|
if (m_topdown && !use_congr_only) {
|
|
buffer<expr> nary_args;
|
|
get_app_nary_args(op, old_e, nary_args);
|
|
|
|
expr flat_e = mk_nary_app(op, nary_args);
|
|
simp_result r_flat(flat_e, mk_flat_proof(assoc, mk_eq(m_tctx, old_e, flat_e)));
|
|
|
|
if (m_rewrite) {
|
|
if (optional<simp_result> r_rewrite = simplify_rewrite_nary(assoc, r_flat.get_new(), op, nary_args)) {
|
|
lean_trace_d(name({"simplifier", "rewrite"}), tout() << old_e << " ==> " << r_rewrite->get_new() << "\n";);
|
|
return join(r_flat, *r_rewrite);
|
|
}
|
|
}
|
|
if (m_user_extensions) {
|
|
if (optional<simp_result> r_user = simplify_user_extensions_nary(assoc, r_flat.get_new(), op, nary_args)) {
|
|
lean_trace_d(name({"simplifier", "user_extensions"}), tout() << old_e << " ==> " << r_user->get_new() << "\n";);
|
|
return join(r_flat, *r_user);
|
|
}
|
|
}
|
|
}
|
|
|
|
simp_result r_congr = simplify_subterms_app_nary(op, old_e);
|
|
expr new_op = *get_binary_op(r_congr.get_new());
|
|
|
|
buffer<expr> new_nary_args;
|
|
get_app_nary_args(new_op, r_congr.get_new(), new_nary_args);
|
|
expr congr_flat_e = mk_nary_app(new_op, new_nary_args);
|
|
simp_result r_congr_flat = join(r_congr, simp_result(congr_flat_e, mk_flat_proof(assoc, mk_eq(m_tctx, r_congr.get_new(), congr_flat_e))));
|
|
|
|
if (m_topdown && r_congr_flat.get_new() != old_e)
|
|
return r_congr_flat;
|
|
|
|
if (!m_topdown && !use_congr_only) {
|
|
if (m_rewrite) {
|
|
if (optional<simp_result> r_rewrite = simplify_rewrite_nary(assoc, r_congr_flat.get_new(), new_op, new_nary_args)) {
|
|
lean_trace_d(name({"simplifier", "rewrite"}), tout() << old_e << " ==> " << r_rewrite->get_new() << "\n";);
|
|
return join(r_congr_flat, *r_rewrite);
|
|
}
|
|
}
|
|
if (m_user_extensions) {
|
|
if (optional<simp_result> r_user = simplify_user_extensions_nary(assoc, r_congr_flat.get_new(), new_op, new_nary_args)) {
|
|
lean_trace_d(name({"simplifier", "user_extensions"}), tout() << old_e << " ==> " << r_user->get_new() << "\n";);
|
|
return join(r_congr_flat, *r_user);
|
|
}
|
|
}
|
|
}
|
|
|
|
// [5] Simplify with the theory simplifier
|
|
// Note: the theory simplifier guarantees that no new subterms are introduced that need to be simplified.
|
|
// Thus we never need to repeat unless something is simplified downstream of here.
|
|
if (m_theory && !use_congr_only) {
|
|
if (optional<simp_result> r_theory = m_theory_simplifier.simplify_nary(assoc, r_congr_flat.get_new(), new_op, new_nary_args)) {
|
|
lean_trace_d(name({"simplifier", "theory"}), tout() << old_e << " ==> " << r_theory->get_new() << "\n";);
|
|
simp_result r = join(r_congr_flat, *r_theory);
|
|
r.set_done();
|
|
return r;
|
|
}
|
|
}
|
|
|
|
r_congr_flat.set_done();
|
|
return r_congr_flat;
|
|
}
|
|
|
|
/* Simplifying the necessary subterms */
|
|
simp_result simplify_subterms_lambda(expr const & e);
|
|
simp_result simplify_subterms_pi(expr const & e);
|
|
simp_result simplify_subterms_app(expr const & e);
|
|
|
|
/* Called from simplify_subterms_app */
|
|
simp_result simplify_operator_of_app(expr const & e);
|
|
|
|
/* Proving */
|
|
optional<expr> prove(expr const & thm);
|
|
|
|
/* Canonicalize */
|
|
expr defeq_canonize_args_step(expr const & e);
|
|
|
|
expr_struct_map<expr> m_subsingleton_elem_map;
|
|
simp_result canonize_subsingleton_args(expr const & e);
|
|
|
|
/* Congruence */
|
|
simp_result congr_fun_arg(simp_result const & r_f, simp_result const & r_arg);
|
|
simp_result congr(simp_result const & r_f, simp_result const & r_arg);
|
|
simp_result congr_fun(simp_result const & r_f, expr const & arg);
|
|
simp_result congr_arg(expr const & f, simp_result const & r_arg);
|
|
simp_result congr_funs(simp_result const & r_f, buffer<expr> const & args);
|
|
simp_result funext(simp_result const & r, expr const & l);
|
|
|
|
simp_result try_congrs(expr const & e);
|
|
simp_result try_congr(expr const & e, user_congr_lemma const & cr);
|
|
|
|
optional<simp_result> synth_congr(expr const & e);
|
|
|
|
expr remove_unnecessary_casts(expr const & e);
|
|
|
|
/* Apply whnf and eta-reduction
|
|
\remark We want (Sum n (fun x, f x)) and (Sum n f) to be the same.
|
|
\remark We may want to switch to eta-expansion later (see paper: "The Virtues of Eta-expansion").
|
|
TODO(Daniel, Leo): should we add an option for disabling/enabling eta? */
|
|
expr whnf_eta(expr const & e);
|
|
|
|
public:
|
|
simplifier(type_context & tctx, name const & rel, simp_lemmas const & slss, vm_obj const & prove_fn):
|
|
m_tctx(tctx), m_theory_simplifier(tctx), m_rel(rel), m_slss(slss), m_prove_fn(prove_fn),
|
|
/* Options */
|
|
m_max_steps(get_simplify_max_steps(tctx.get_options())),
|
|
m_nary_assoc(get_simplify_nary_assoc(tctx.get_options())),
|
|
m_memoize(get_simplify_memoize(tctx.get_options())),
|
|
m_contextual(get_simplify_contextual(tctx.get_options())),
|
|
m_user_extensions(get_simplify_user_extensions(tctx.get_options())),
|
|
m_rewrite(get_simplify_rewrite(tctx.get_options())),
|
|
m_unsafe_nary(get_simplify_unsafe_nary(tctx.get_options())),
|
|
m_theory(get_simplify_theory(tctx.get_options())),
|
|
m_topdown(get_simplify_topdown(tctx.get_options())),
|
|
m_lift_eq(get_simplify_lift_eq(tctx.get_options())),
|
|
m_canonize_instances_fixed_point(get_simplify_canonize_instances_fixed_point(tctx.get_options())),
|
|
m_canonize_proofs_fixed_point(get_simplify_canonize_proofs_fixed_point(tctx.get_options())),
|
|
m_canonize_subsingletons(get_simplify_canonize_subsingletons(tctx.get_options()))
|
|
{ }
|
|
|
|
simp_result operator()(expr const & e) {
|
|
scope_trace_env scope(env(), m_tctx.get_options(), m_tctx);
|
|
simp_result r(e);
|
|
while (true) {
|
|
m_need_restart = false;
|
|
r = join(r, simplify(r.get_new()));
|
|
if (!m_need_restart || !should_defeq_canonize())
|
|
return r;
|
|
m_cache.clear();
|
|
}
|
|
return simplify(e);
|
|
}
|
|
};
|
|
|
|
/* Cache */
|
|
|
|
optional<simp_result> simplifier::cache_lookup(expr const & e) {
|
|
auto it = m_cache.find(key(m_rel, e));
|
|
if (it == m_cache.end()) return optional<simp_result>();
|
|
return optional<simp_result>(it->second);
|
|
}
|
|
|
|
void simplifier::cache_save(expr const & e, simp_result const & r) {
|
|
m_cache.insert(mk_pair(key(m_rel, e), r));
|
|
}
|
|
|
|
/* Simp_Results */
|
|
|
|
simp_result simplifier::lift_from_eq(expr const & old_e, simp_result const & r_eq) {
|
|
if (!r_eq.has_proof()) return r_eq;
|
|
lean_assert(r_eq.has_proof());
|
|
/* r_eq.get_proof() : old_e = r_eq.get_new() */
|
|
/* goal : old_e <m_rel> r_eq.get_new() */
|
|
type_context::tmp_locals local_factory(m_tctx);
|
|
expr local = local_factory.push_local(name(), m_tctx.infer(old_e));
|
|
expr motive_local = mk_app(m_tctx, m_rel, old_e, local);
|
|
/* motive = fun y, old_e <m_rel> y */
|
|
expr motive = local_factory.mk_lambda(motive_local);
|
|
/* Rxx = x <m_rel> x */
|
|
expr Rxx = mk_refl(m_tctx, m_rel, old_e);
|
|
expr pf = mk_eq_rec(m_tctx, motive, Rxx, r_eq.get_proof());
|
|
return simp_result(r_eq.get_new(), pf);
|
|
}
|
|
|
|
/* Whnf + Eta */
|
|
expr simplifier::whnf_eta(expr const & e) {
|
|
return try_eta(m_tctx.whnf(e));
|
|
}
|
|
|
|
simp_result simplifier::simplify_subterms_lambda(expr const & old_e) {
|
|
lean_assert(is_lambda(old_e));
|
|
expr e = old_e;
|
|
|
|
buffer<expr> ls;
|
|
while (is_lambda(e)) {
|
|
expr d = instantiate_rev(binding_domain(e), ls.size(), ls.data());
|
|
expr l = m_tctx.push_local(binding_name(e), d, binding_info(e));
|
|
ls.push_back(l);
|
|
e = instantiate(binding_body(e), l);
|
|
}
|
|
|
|
simp_result r = simplify(e);
|
|
expr new_e = r.get_new();
|
|
expr new_e_type = m_tctx.infer(new_e);
|
|
if (auto inst = m_tctx.mk_subsingleton_instance(new_e_type)) {
|
|
auto it = m_subsingleton_elem_map.find(new_e_type);
|
|
if (it != m_subsingleton_elem_map.end()) {
|
|
// We do not canonize when there is an unfavourable locals mismatch
|
|
// TODO(dhs): maintain a list of canonical elements as we do in the defeq-canonizer
|
|
if (it->second != new_e && locals_subset(it->second, new_e)) {
|
|
expr proof = mk_ss_elim(m_tctx, new_e_type, *inst, new_e, it->second);
|
|
r = join(r, simp_result(it->second, proof));
|
|
lean_trace_d(name({"simplifier", "subsingleton"}), tout() << new_e << " ==> " << it->second << "\n";);
|
|
}
|
|
} else {
|
|
m_subsingleton_elem_map.insert(mk_pair(new_e_type, new_e));
|
|
}
|
|
}
|
|
|
|
if (r.get_new() == e)
|
|
return old_e;
|
|
|
|
if (!r.has_proof())
|
|
return m_tctx.mk_lambda(ls, r.get_new());
|
|
|
|
for (int i = ls.size() - 1; i >= 0; --i)
|
|
r = funext(r, ls[i]);
|
|
|
|
return r;
|
|
}
|
|
|
|
simp_result simplifier::simplify_subterms_pi(expr const & e) {
|
|
lean_assert(is_pi(e));
|
|
return try_congrs(e);
|
|
}
|
|
|
|
expr simplifier::defeq_canonize_args_step(expr const & e) {
|
|
buffer<expr> args;
|
|
bool modified = false;
|
|
expr f = get_app_args(e, args);
|
|
fun_info info = get_fun_info(m_tctx, f, args.size());
|
|
unsigned i = 0;
|
|
for (param_info const & pinfo : info.get_params_info()) {
|
|
lean_assert(i < args.size());
|
|
expr new_a;
|
|
if ((m_canonize_instances_fixed_point && pinfo.is_inst_implicit()) || (m_canonize_proofs_fixed_point && pinfo.is_prop())) {
|
|
new_a = ::lean::defeq_canonize(m_tctx, args[i], m_need_restart);
|
|
lean_trace(name({"simplifier", "canonize"}),
|
|
tout() << "\n" << args[i] << "\n==>\n" << new_a << "\n";);
|
|
if (new_a != args[i]) {
|
|
modified = true;
|
|
args[i] = new_a;
|
|
}
|
|
}
|
|
i++;
|
|
}
|
|
|
|
if (!modified)
|
|
return e;
|
|
else
|
|
return mk_app(f, args);
|
|
}
|
|
|
|
simp_result simplifier::simplify_operator_of_app(expr const & e) {
|
|
lean_assert(is_app(e));
|
|
buffer<expr> args;
|
|
expr const & f = get_app_args(e, args);
|
|
simp_result r_f = simplify(f);
|
|
return congr_funs(r_f, args);
|
|
}
|
|
|
|
/* Proving */
|
|
optional<expr> simplifier::prove(expr const & goal) {
|
|
metavar_context mctx = m_tctx.mctx();
|
|
expr goal_mvar = mctx.mk_metavar_decl(m_tctx.lctx(), goal);
|
|
lean_trace(name({"simplifier", "prove"}), tout() << "goal: " << goal_mvar << " : " << goal << "\n";);
|
|
vm_obj s = to_obj(tactic_state(m_tctx.env(), m_tctx.get_options(), mctx, list<expr>(goal_mvar), goal_mvar));
|
|
vm_obj prove_fn_result = invoke(m_prove_fn, s);
|
|
optional<tactic_state> s_new = is_tactic_success(prove_fn_result);
|
|
if (s_new) {
|
|
if (!s_new->mctx().is_assigned(goal_mvar)) {
|
|
lean_trace(name({"simplifier", "prove"}), tout() << "prove_fn succeeded but did not return a proof\n";);
|
|
return none_expr();
|
|
}
|
|
metavar_context smctx = s_new->mctx();
|
|
expr proof = smctx.instantiate_mvars(goal_mvar);
|
|
optional<expr> new_metavar = find(proof, [&](expr const & e, unsigned) {
|
|
return is_metavar(e) && !static_cast<bool>(m_tctx.mctx().get_metavar_decl(e));
|
|
});
|
|
if (new_metavar) {
|
|
lean_trace(name({"simplifier", "prove"}),
|
|
tout() << "prove_fn succeeded but left an unrecognized metavariable of type "
|
|
<< smctx.get_metavar_decl(*new_metavar)->get_type() << " in proof\n";);
|
|
return none_expr();
|
|
}
|
|
m_tctx.set_mctx(s_new->mctx());
|
|
lean_trace(name({"simplifier", "prove"}), tout() << "success: " << proof << " : " << m_tctx.infer(proof) << "\n";);
|
|
return some_expr(proof);
|
|
} else {
|
|
lean_trace(name({"simplifier", "prove"}), tout() << "prove_fn failed to prove " << goal << "\n";);
|
|
return none_expr();
|
|
}
|
|
}
|
|
|
|
/* Congruence */
|
|
|
|
simp_result simplifier::congr_fun_arg(simp_result const & r_f, simp_result const & r_arg) {
|
|
if (!r_f.has_proof() && !r_arg.has_proof()) return simp_result(mk_app(r_f.get_new(), r_arg.get_new()));
|
|
else if (!r_f.has_proof()) return congr_arg(r_f.get_new(), r_arg);
|
|
else if (!r_arg.has_proof()) return congr_fun(r_f, r_arg.get_new());
|
|
else return congr(r_f, r_arg);
|
|
}
|
|
|
|
simp_result simplifier::congr(simp_result const & r_f, simp_result const & r_arg) {
|
|
lean_assert(r_f.has_proof() && r_arg.has_proof());
|
|
// theorem congr {A B : Type} {f₁ f₂ : A → B} {a₁ a₂ : A} (H₁ : f₁ = f₂) (H₂ : a₁ = a₂) : f₁ a₁ = f₂ a₂
|
|
expr e = mk_app(r_f.get_new(), r_arg.get_new());
|
|
expr pf = mk_congr(m_tctx, r_f.get_proof(), r_arg.get_proof());
|
|
return simp_result(e, pf);
|
|
}
|
|
|
|
simp_result simplifier::congr_fun(simp_result const & r_f, expr const & arg) {
|
|
lean_assert(r_f.has_proof());
|
|
// theorem congr_fun {A : Type} {B : A → Type} {f g : Π x, B x} (H : f = g) (a : A) : f a = g a
|
|
expr e = mk_app(r_f.get_new(), arg);
|
|
expr pf = mk_congr_fun(m_tctx, r_f.get_proof(), arg);
|
|
return simp_result(e, pf);
|
|
}
|
|
|
|
simp_result simplifier::congr_arg(expr const & f, simp_result const & r_arg) {
|
|
lean_assert(r_arg.has_proof());
|
|
// theorem congr_arg {A B : Type} {a₁ a₂ : A} (f : A → B) : a₁ = a₂ → f a₁ = f a₂
|
|
expr e = mk_app(f, r_arg.get_new());
|
|
expr pf = mk_congr_arg(m_tctx, f, r_arg.get_proof());
|
|
return simp_result(e, pf);
|
|
}
|
|
|
|
/* Note: handles reflexivity */
|
|
simp_result simplifier::congr_funs(simp_result const & r_f, buffer<expr> const & args) {
|
|
expr e = r_f.get_new();
|
|
for (unsigned i = 0; i < args.size(); ++i) {
|
|
e = mk_app(e, args[i]);
|
|
}
|
|
if (!r_f.has_proof())
|
|
return simp_result(e);
|
|
expr pf = r_f.get_proof();
|
|
for (unsigned i = 0; i < args.size(); ++i) {
|
|
pf = mk_congr_fun(m_tctx, pf, args[i]);
|
|
}
|
|
return simp_result(e, pf);
|
|
}
|
|
|
|
simp_result simplifier::funext(simp_result const & r, expr const & l) {
|
|
expr e = m_tctx.mk_lambda(l, r.get_new());
|
|
if (!r.has_proof())
|
|
return simp_result(e);
|
|
expr lam_pf = m_tctx.mk_lambda(l, r.get_proof());
|
|
expr pf = mk_funext(m_tctx, lam_pf);
|
|
return simp_result(e, pf);
|
|
}
|
|
|
|
simp_result simplifier::try_congrs(expr const & e) {
|
|
simp_lemmas_for const * sls = m_slss.find(m_rel);
|
|
if (!sls) return simp_result(e);
|
|
|
|
list<user_congr_lemma> const * cls = sls->find_congr(e);
|
|
if (!cls) return simp_result(e);
|
|
|
|
for (user_congr_lemma const & cl : *cls) {
|
|
simp_result r = try_congr(e, cl);
|
|
if (r.get_new() != e)
|
|
return r;
|
|
}
|
|
return simp_result(e);
|
|
}
|
|
|
|
simp_result simplifier::try_congr(expr const & e, user_congr_lemma const & cl) {
|
|
tmp_type_context tmp_tctx(m_tctx, cl.get_num_umeta(), cl.get_num_emeta());
|
|
if (!tmp_tctx.is_def_eq(e, cl.get_lhs()))
|
|
return simp_result(e);
|
|
|
|
lean_simp_trace(tmp_tctx, name({"debug", "simplifier", "try_congruence"}),
|
|
tout() << "(" << cl.get_id() << ") " << e << "\n";);
|
|
|
|
bool simplified = false;
|
|
|
|
buffer<expr> congr_hyps;
|
|
to_buffer(cl.get_congr_hyps(), congr_hyps);
|
|
|
|
buffer<simp_result> congr_hyp_results;
|
|
buffer<type_context::tmp_locals> factories;
|
|
buffer<name> relations;
|
|
for (expr const & m : congr_hyps) {
|
|
factories.emplace_back(m_tctx);
|
|
type_context::tmp_locals & local_factory = factories.back();
|
|
expr m_type = tmp_tctx.instantiate_mvars(tmp_tctx.infer(m));
|
|
|
|
while (is_pi(m_type)) {
|
|
expr d = instantiate_rev(binding_domain(m_type), local_factory.as_buffer().size(), local_factory.as_buffer().data());
|
|
expr l = local_factory.push_local(binding_name(m_type), d, binding_info(m_type));
|
|
lean_assert(!has_metavar(l));
|
|
m_type = binding_body(m_type);
|
|
}
|
|
m_type = instantiate_rev(m_type, local_factory.as_buffer().size(), local_factory.as_buffer().data());
|
|
|
|
expr h_rel, h_lhs, h_rhs;
|
|
lean_verify(is_simp_relation(tmp_tctx.env(), m_type, h_rel, h_lhs, h_rhs) && is_constant(h_rel));
|
|
{
|
|
flet<name> set_name(m_rel, const_name(h_rel));
|
|
relations.push_back(m_rel);
|
|
flet<simp_lemmas> set_ctx_slss(m_ctx_slss, m_contextual ? add_to_slss(m_ctx_slss, local_factory.as_buffer()) : m_ctx_slss);
|
|
|
|
h_lhs = tmp_tctx.instantiate_mvars(h_lhs);
|
|
lean_assert(!has_metavar(h_lhs));
|
|
|
|
if (m_contextual) {
|
|
freset<simplify_cache> reset_cache(m_cache);
|
|
congr_hyp_results.emplace_back(simplify(h_lhs));
|
|
} else {
|
|
congr_hyp_results.emplace_back(simplify(h_lhs));
|
|
}
|
|
simp_result const & r_congr_hyp = congr_hyp_results.back();
|
|
|
|
if (r_congr_hyp.has_proof())
|
|
simplified = true;
|
|
|
|
lean_assert(is_meta(h_rhs));
|
|
buffer<expr> new_val_meta_args;
|
|
expr new_val_meta = get_app_args(h_rhs, new_val_meta_args);
|
|
lean_assert(is_metavar(new_val_meta));
|
|
expr new_val = tmp_tctx.mk_lambda(new_val_meta_args, r_congr_hyp.get_new());
|
|
tmp_tctx.assign(new_val_meta, new_val);
|
|
}
|
|
}
|
|
|
|
if (!simplified)
|
|
return simp_result(e);
|
|
|
|
lean_assert(congr_hyps.size() == congr_hyp_results.size());
|
|
for (unsigned i = 0; i < congr_hyps.size(); ++i) {
|
|
expr const & pf_meta = congr_hyps[i];
|
|
simp_result const & r_congr_hyp = congr_hyp_results[i];
|
|
name const & rel = relations[i];
|
|
type_context::tmp_locals & local_factory = factories[i];
|
|
expr hyp = finalize(m_tctx, rel, r_congr_hyp).get_proof();
|
|
// This is the current bottleneck
|
|
// Can address somewhat by keeping the proofs as small as possible using macros
|
|
expr pf = local_factory.mk_lambda(hyp);
|
|
tmp_tctx.assign(pf_meta, pf);
|
|
}
|
|
|
|
if (!instantiate_emetas(tmp_tctx, cl.get_num_emeta(), cl.get_emetas(), cl.get_instances()))
|
|
return simp_result(e);
|
|
|
|
for (unsigned i = 0; i < cl.get_num_umeta(); i++) {
|
|
if (!tmp_tctx.is_uassigned(i))
|
|
return simp_result(e);
|
|
}
|
|
|
|
expr e_s = tmp_tctx.instantiate_mvars(cl.get_rhs());
|
|
expr pf = tmp_tctx.instantiate_mvars(cl.get_proof());
|
|
|
|
simp_result r(e_s, pf);
|
|
if (is_app(e_s))
|
|
r = join(r, canonize_subsingleton_args(r.get_new()));
|
|
|
|
lean_simp_trace(tmp_tctx, name({"simplifier", "congruence"}),
|
|
tout() << "(" << cl.get_id() << ") "
|
|
<< "[" << e << " ==> " << e_s << "]\n";);
|
|
|
|
return r;
|
|
}
|
|
|
|
bool simplifier::instantiate_emetas(tmp_type_context & tmp_tctx, unsigned num_emeta, list<expr> const & emetas, list<bool> const & instances) {
|
|
bool failed = false;
|
|
unsigned i = num_emeta;
|
|
for_each2(emetas, instances, [&](expr const & m, bool const & is_instance) {
|
|
i--;
|
|
if (failed) return;
|
|
expr m_type = tmp_tctx.instantiate_mvars(tmp_tctx.infer(m));
|
|
lean_assert(!has_metavar(m_type));
|
|
|
|
if (tmp_tctx.is_eassigned(i)) return;
|
|
|
|
if (is_instance) {
|
|
if (auto v = m_tctx.mk_class_instance(m_type)) {
|
|
if (!tmp_tctx.is_def_eq(m, *v)) {
|
|
lean_simp_trace(tmp_tctx, name({"simplifier", "failure"}),
|
|
tout() << "unable to assign instance for: " << m_type << "\n";);
|
|
failed = true;
|
|
return;
|
|
}
|
|
} else {
|
|
lean_simp_trace(tmp_tctx, name({"simplifier", "failure"}),
|
|
tout() << "unable to synthesize instance for: " << m_type << "\n";);
|
|
failed = true;
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (tmp_tctx.is_eassigned(i)) return;
|
|
|
|
// Note: m_type has no metavars
|
|
if (m_tctx.is_prop(m_type)) {
|
|
if (auto pf = prove(m_type)) {
|
|
lean_verify(tmp_tctx.is_def_eq(m, *pf));
|
|
return;
|
|
}
|
|
}
|
|
|
|
lean_simp_trace(tmp_tctx, name({"simplifier", "failure"}),
|
|
tout() << "failed to assign: " << m << " : " << m_type << "\n";);
|
|
|
|
failed = true;
|
|
return;
|
|
});
|
|
|
|
return !failed;
|
|
}
|
|
|
|
optional<simp_result> simplifier::synth_congr(expr const & e) {
|
|
lean_assert(is_app(e));
|
|
buffer<expr> args;
|
|
expr f = get_app_args(e, args);
|
|
auto congr_lemma = mk_specialized_congr_simp(m_tctx, e);
|
|
if (!congr_lemma) return optional<simp_result>();
|
|
|
|
buffer<simp_result> r_args;
|
|
buffer<expr> new_args;
|
|
bool has_proof = false;
|
|
bool has_cast = false;
|
|
bool has_simplified = false;
|
|
|
|
unsigned i = 0;
|
|
|
|
// First pass, try to simplify all the Eq arguments
|
|
for (congr_arg_kind const & ckind : congr_lemma->get_arg_kinds()) {
|
|
switch (ckind) {
|
|
case congr_arg_kind::HEq:
|
|
lean_unreachable();
|
|
case congr_arg_kind::Fixed:
|
|
case congr_arg_kind::FixedNoParam:
|
|
new_args.emplace_back(args[i]);
|
|
break;
|
|
case congr_arg_kind::Eq:
|
|
{
|
|
r_args.emplace_back(simplify(args[i]));
|
|
simp_result & r_arg = r_args.back();
|
|
new_args.emplace_back(r_arg.get_new());
|
|
if (r_arg.has_proof())
|
|
has_proof = true;
|
|
if (r_arg.get_new() != args[i])
|
|
has_simplified = true;
|
|
}
|
|
break;
|
|
case congr_arg_kind::Cast:
|
|
has_cast = true;
|
|
new_args.emplace_back(args[i]);
|
|
break;
|
|
}
|
|
i++;
|
|
}
|
|
|
|
if (!has_simplified) {
|
|
simp_result r = simp_result(e);
|
|
if (has_cast)
|
|
r = join(r, canonize_subsingleton_args(r.get_new()));
|
|
return optional<simp_result>(r);
|
|
}
|
|
|
|
if (!has_proof) {
|
|
simp_result r = simp_result(mk_app(f, new_args));
|
|
if (has_cast)
|
|
r = join(r, canonize_subsingleton_args(r.get_new()));
|
|
return optional<simp_result>(r);
|
|
}
|
|
|
|
// We have a proof, so we need to build the congruence lemma
|
|
expr proof = congr_lemma->get_proof();
|
|
expr type = congr_lemma->get_type();
|
|
buffer<expr> subst;
|
|
|
|
i = 0;
|
|
unsigned i_eq = 0;
|
|
for (congr_arg_kind const & ckind : congr_lemma->get_arg_kinds()) {
|
|
switch (ckind) {
|
|
case congr_arg_kind::HEq:
|
|
lean_unreachable();
|
|
case congr_arg_kind::Fixed:
|
|
proof = mk_app(proof, args[i]);
|
|
subst.push_back(args[i]);
|
|
type = binding_body(type);
|
|
break;
|
|
case congr_arg_kind::FixedNoParam:
|
|
break;
|
|
case congr_arg_kind::Eq:
|
|
proof = mk_app(proof, args[i]);
|
|
subst.push_back(args[i]);
|
|
type = binding_body(type);
|
|
{
|
|
simp_result r_arg = finalize(m_tctx, m_rel, r_args[i_eq]);
|
|
proof = mk_app(proof, r_arg.get_new(), r_arg.get_proof());
|
|
subst.push_back(r_arg.get_new());
|
|
subst.push_back(r_arg.get_proof());
|
|
}
|
|
type = binding_body(binding_body(type));
|
|
i_eq++;
|
|
break;
|
|
case congr_arg_kind::Cast:
|
|
lean_assert(has_cast);
|
|
proof = mk_app(proof, args[i]);
|
|
subst.push_back(args[i]);
|
|
type = binding_body(type);
|
|
break;
|
|
}
|
|
i++;
|
|
}
|
|
lean_assert(is_eq(type));
|
|
expr rhs = instantiate_rev(app_arg(type), subst.size(), subst.data());
|
|
simp_result r(rhs, proof);
|
|
|
|
if (has_cast) {
|
|
r.update(remove_unnecessary_casts(r.get_new()));
|
|
r = join(r, canonize_subsingleton_args(r.get_new()));
|
|
}
|
|
|
|
return optional<simp_result>(r);
|
|
}
|
|
|
|
// Given a function application \c e, replace arguments that are subsingletons with a
|
|
// representative
|
|
simp_result simplifier::canonize_subsingleton_args(expr const & e) {
|
|
// TODO(dhs): flag to skip this step
|
|
buffer<expr> args;
|
|
get_app_args(e, args);
|
|
auto congr_lemma = mk_specialized_congr(m_tctx, e);
|
|
if (!congr_lemma)
|
|
return simp_result(e);
|
|
expr proof = congr_lemma->get_proof();
|
|
expr type = congr_lemma->get_type();
|
|
unsigned i = 0;
|
|
bool modified = false;
|
|
for_each(congr_lemma->get_arg_kinds(), [&](congr_arg_kind const & ckind) {
|
|
expr rfl;
|
|
switch (ckind) {
|
|
case congr_arg_kind::HEq:
|
|
lean_unreachable();
|
|
case congr_arg_kind::Fixed:
|
|
proof = mk_app(proof, args[i]);
|
|
type = instantiate(binding_body(type), args[i]);
|
|
break;
|
|
case congr_arg_kind::FixedNoParam:
|
|
break;
|
|
case congr_arg_kind::Eq:
|
|
proof = mk_app(proof, args[i]);
|
|
type = instantiate(binding_body(type), args[i]);
|
|
rfl = mk_eq_refl(m_tctx, args[i]);
|
|
proof = mk_app(proof, args[i], rfl);
|
|
type = instantiate(binding_body(type), args[i]);
|
|
type = instantiate(binding_body(type), rfl);
|
|
break;
|
|
case congr_arg_kind::Cast:
|
|
proof = mk_app(proof, args[i]);
|
|
type = instantiate(binding_body(type), args[i]);
|
|
expr const & arg_type = binding_domain(type);
|
|
expr new_arg;
|
|
if (!m_tctx.is_prop(arg_type)) {
|
|
auto it = m_subsingleton_elem_map.find(arg_type);
|
|
if (it != m_subsingleton_elem_map.end()) {
|
|
modified = (it->second != args[i]);
|
|
new_arg = it->second;
|
|
lean_trace_d(name({"simplifier", "subsingleton"}), tout() << args[i] << " ==> " << new_arg << "\n";);
|
|
} else {
|
|
new_arg = args[i];
|
|
m_subsingleton_elem_map.insert(mk_pair(arg_type, args[i]));
|
|
}
|
|
} else {
|
|
new_arg = args[i];
|
|
}
|
|
proof = mk_app(proof, new_arg);
|
|
type = instantiate(binding_body(type), new_arg);
|
|
break;
|
|
}
|
|
i++;
|
|
});
|
|
if (!modified)
|
|
return simp_result(e);
|
|
|
|
lean_assert(is_eq(type));
|
|
buffer<expr> type_args;
|
|
get_app_args(type, type_args);
|
|
expr e_new = type_args[2];
|
|
return simp_result(e_new, proof);
|
|
}
|
|
|
|
expr simplifier::remove_unnecessary_casts(expr const & e) {
|
|
buffer<expr> args;
|
|
expr f = get_app_args(e, args);
|
|
ss_param_infos ss_infos = get_specialized_subsingleton_info(m_tctx, e);
|
|
int i = -1;
|
|
bool updated = false;
|
|
for (ss_param_info const & ss_info : ss_infos) {
|
|
i++;
|
|
if (ss_info.is_subsingleton()) {
|
|
while (is_constant(get_app_fn(args[i]))) {
|
|
buffer<expr> cast_args;
|
|
expr f_cast = get_app_args(args[i], cast_args);
|
|
name n_f = const_name(f_cast);
|
|
if (n_f == get_eq_rec_name() || n_f == get_eq_drec_name() || n_f == get_eq_nrec_name()) {
|
|
lean_assert(cast_args.size() == 6);
|
|
expr major_premise = cast_args[5];
|
|
expr f_major_premise = get_app_fn(major_premise);
|
|
if (is_constant(f_major_premise) && const_name(f_major_premise) == get_eq_refl_name()) {
|
|
args[i] = cast_args[3];
|
|
updated = true;
|
|
} else {
|
|
break;
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!updated)
|
|
return e;
|
|
|
|
expr new_e = mk_app(f, args);
|
|
lean_trace(name({"debug", "simplifier", "remove_casts"}), tout() << e << " ==> " << new_e << "\n";);
|
|
|
|
return mk_app(f, args);
|
|
}
|
|
|
|
vm_obj tactic_simplify_core(vm_obj const & prove_fn, vm_obj const & rel_name, vm_obj const & lemmas, vm_obj const & e, vm_obj const & s0) {
|
|
tactic_state const & s = to_tactic_state(s0);
|
|
try {
|
|
type_context tctx = mk_type_context_for(s, transparency_mode::Reducible);
|
|
name rel = to_name(rel_name);
|
|
expr old_e = to_expr(e);
|
|
simp_result result = simplify(tctx, rel, to_simp_lemmas(lemmas), prove_fn, old_e);
|
|
|
|
if (result.get_new() != old_e) {
|
|
result = finalize(tctx, rel, result);
|
|
return mk_tactic_success(mk_vm_pair(to_obj(result.get_new()), to_obj(result.get_proof())), s);
|
|
} else {
|
|
return mk_tactic_exception("simp tactic failed to simplify", s);
|
|
}
|
|
} catch (exception & e) {
|
|
return mk_tactic_exception(e, s);
|
|
}
|
|
}
|
|
|
|
/* Setup and teardown */
|
|
void initialize_simplifier() {
|
|
register_trace_class(name({"simplifier", "congruence"}));
|
|
register_trace_class(name({"simplifier", "user_extensions"}));
|
|
register_trace_class(name({"simplifier", "failure"}));
|
|
register_trace_class(name({"simplifier", "failed"}));
|
|
register_trace_class(name({"simplifier", "perm"}));
|
|
register_trace_class(name({"simplifier", "canonize"}));
|
|
register_trace_class(name({"simplifier", "context"}));
|
|
register_trace_class(name({"simplifier", "prove"}));
|
|
register_trace_class(name({"simplifier", "rewrite"}));
|
|
register_trace_class(name({"simplifier", "rewrite", "assoc"}));
|
|
register_trace_class(name({"simplifier", "theory"}));
|
|
register_trace_class(name({"simplifier", "subsingleton"}));
|
|
register_trace_class(name({"debug", "simplifier", "try_rewrite"}));
|
|
register_trace_class(name({"debug", "simplifier", "try_rewrite", "assoc"}));
|
|
register_trace_class(name({"debug", "simplifier", "try_congruence"}));
|
|
register_trace_class(name({"debug", "simplifier", "remove_casts"}));
|
|
|
|
g_simplify_max_steps = new name{"simplify", "max_steps"};
|
|
g_simplify_nary_assoc = new name{"simplify", "nary_assoc"};
|
|
g_simplify_memoize = new name{"simplify", "memoize"};
|
|
g_simplify_contextual = new name{"simplify", "contextual"};
|
|
g_simplify_user_extensions = new name{"simplify", "user_extensions"};
|
|
g_simplify_rewrite = new name{"simplify", "rewrite"};
|
|
g_simplify_unsafe_nary = new name{"simplify", "unsafe_nary"};
|
|
g_simplify_theory = new name{"simplify", "theory"};
|
|
g_simplify_topdown = new name{"simplify", "topdown"};
|
|
g_simplify_lift_eq = new name{"simplify", "lift_eq"};
|
|
g_simplify_canonize_instances_fixed_point = new name{"simplify", "canonize_instances_fixed_point"};
|
|
g_simplify_canonize_proofs_fixed_point = new name{"simplify", "canonize_proofs_fixed_point"};
|
|
g_simplify_canonize_subsingletons = new name{"simplify", "canonize_subsingletons"};
|
|
|
|
register_unsigned_option(*g_simplify_max_steps, LEAN_DEFAULT_SIMPLIFY_MAX_STEPS,
|
|
"(simplify) max number of (large) steps in simplification");
|
|
register_bool_option(*g_simplify_nary_assoc, LEAN_DEFAULT_SIMPLIFY_NARY_ASSOC,
|
|
"(simplify) use special treatment for operators that are instances of the is_associative typeclass");
|
|
register_bool_option(*g_simplify_memoize, LEAN_DEFAULT_SIMPLIFY_MEMOIZE,
|
|
"(simplify) memoize simplifications");
|
|
register_bool_option(*g_simplify_contextual, LEAN_DEFAULT_SIMPLIFY_CONTEXTUAL,
|
|
"(simplify) use contextual simplification");
|
|
register_bool_option(*g_simplify_user_extensions, LEAN_DEFAULT_SIMPLIFY_USER_EXTENSIONS,
|
|
"(simplify) simplify with user_extensions");
|
|
register_bool_option(*g_simplify_rewrite, LEAN_DEFAULT_SIMPLIFY_REWRITE,
|
|
"(simplify) rewrite with simp_lemmas");
|
|
register_bool_option(*g_simplify_unsafe_nary, LEAN_DEFAULT_SIMPLIFY_UNSAFE_NARY,
|
|
"(simplify) assume all nested applications of associative operators "
|
|
"with the same head symbol are definitionally equal. "
|
|
"Will yield to invalid proofs if this assumption is not valid. "
|
|
"The kernel will detect these errors but only at a high-enough trust level.");
|
|
register_bool_option(*g_simplify_theory, LEAN_DEFAULT_SIMPLIFY_THEORY,
|
|
"(simplify) use built-in theory simplification");
|
|
register_bool_option(*g_simplify_topdown, LEAN_DEFAULT_SIMPLIFY_TOPDOWN,
|
|
"(simplify) use topdown simplification");
|
|
register_bool_option(*g_simplify_lift_eq, LEAN_DEFAULT_SIMPLIFY_LIFT_EQ,
|
|
"(simplify) try simplifying with equality when no progress over other relation");
|
|
register_bool_option(*g_simplify_canonize_instances_fixed_point, LEAN_DEFAULT_SIMPLIFY_CANONIZE_INSTANCES_FIXED_POINT,
|
|
"(simplify) canonize instances, replacing with the smallest seen so far until reaching a fixed point");
|
|
register_bool_option(*g_simplify_canonize_proofs_fixed_point, LEAN_DEFAULT_SIMPLIFY_CANONIZE_PROOFS_FIXED_POINT,
|
|
"(simplify) canonize proofs, replacing with the smallest seen so far until reaching a fixed point. ");
|
|
register_bool_option(*g_simplify_canonize_subsingletons, LEAN_DEFAULT_SIMPLIFY_CANONIZE_SUBSINGLETONS,
|
|
"(simplify) canonize_subsingletons");
|
|
|
|
DECLARE_VM_BUILTIN(name({"tactic", "simplify_core"}), tactic_simplify_core);
|
|
}
|
|
|
|
void finalize_simplifier() {
|
|
delete g_simplify_canonize_subsingletons;
|
|
delete g_simplify_canonize_instances_fixed_point;
|
|
delete g_simplify_canonize_proofs_fixed_point;
|
|
delete g_simplify_lift_eq;
|
|
delete g_simplify_topdown;
|
|
delete g_simplify_theory;
|
|
delete g_simplify_unsafe_nary;
|
|
delete g_simplify_rewrite;
|
|
delete g_simplify_user_extensions;
|
|
delete g_simplify_contextual;
|
|
delete g_simplify_memoize;
|
|
delete g_simplify_nary_assoc;
|
|
delete g_simplify_max_steps;
|
|
}
|
|
|
|
/* Entry point */
|
|
simp_result simplify(type_context & ctx, name const & rel, simp_lemmas const & simp_lemmas, vm_obj const & prove_fn, expr const & e) {
|
|
return simplifier(ctx, rel, simp_lemmas, prove_fn)(e);
|
|
}
|
|
|
|
}
|