/* Copyright (c) 2014 Microsoft Corporation. All rights reserved. Released under Apache 2.0 license as described in the file LICENSE. Author: Leonardo de Moura */ #include #include #include "util/flet.h" #include "util/freset.h" #include "util/interrupt.h" #include "util/luaref.h" #include "util/script_state.h" #include "kernel/type_checker.h" #include "kernel/free_vars.h" #include "kernel/instantiate.h" #include "kernel/abstract.h" #include "kernel/normalizer.h" #include "kernel/kernel.h" #include "kernel/max_sharing.h" #include "kernel/occurs.h" #include "library/kernel_bindings.h" #include "library/expr_pair.h" #include "library/hop_match.h" #include "library/expr_lt.h" #include "library/simplifier/rewrite_rule_set.h" #include "library/simplifier/simplifier.h" #ifndef LEAN_SIMPLIFIER_PROOFS #define LEAN_SIMPLIFIER_PROOFS true #endif #ifndef LEAN_SIMPLIFIER_CONTEXTUAL #define LEAN_SIMPLIFIER_CONTEXTUAL true #endif #ifndef LEAN_SIMPLIFIER_SINGLE_PASS #define LEAN_SIMPLIFIER_SINGLE_PASS false #endif #ifndef LEAN_SIMPLIFIER_BETA #define LEAN_SIMPLIFIER_BETA true #endif #ifndef LEAN_SIMPLIFIER_ETA #define LEAN_SIMPLIFIER_ETA true #endif #ifndef LEAN_SIMPLIFIER_EVAL #define LEAN_SIMPLIFIER_EVAL true #endif #ifndef LEAN_SIMPLIFIER_UNFOLD #define LEAN_SIMPLIFIER_UNFOLD false #endif #ifndef LEAN_SIMPLIFIER_CONDITIONAL #define LEAN_SIMPLIFIER_CONDITIONAL true #endif #ifndef LEAN_SIMPLIFIER_MEMOIZE #define LEAN_SIMPLIFIER_MEMOIZE true #endif #ifndef LEAN_SIMPLIFIER_HEQ #define LEAN_SIMPLIFIER_HEQ false #endif #ifndef LEAN_SIMPLIFIER_PRESERVE_BINDER_NAMES #define LEAN_SIMPLIFIER_PRESERVE_BINDER_NAMES true #endif #ifndef LEAN_SIMPLIFIER_MAX_STEPS #define LEAN_SIMPLIFIER_MAX_STEPS std::numeric_limits::max() #endif namespace lean { static name g_simplifier_proofs {"simplifier", "proofs"}; static name g_simplifier_contextual {"simplifier", "contextual"}; static name g_simplifier_single_pass {"simplifier", "single_pass"}; static name g_simplifier_beta {"simplifier", "beta"}; static name g_simplifier_eta {"simplifier", "eta"}; static name g_simplifier_eval {"simplifier", "eval"}; static name g_simplifier_unfold {"simplifier", "unfold"}; static name g_simplifier_conditional {"simplifier", "conditional"}; static name g_simplifier_memoize {"simplifier", "memoize"}; static name g_simplifier_max_steps {"simplifier", "max_steps"}; static name g_simplifier_heq {"simplifier", "heq"}; static name g_simplifier_preserve_binder_names {"simplifier", "preserve_binder_names"}; RegisterBoolOption(g_simplifier_proofs, LEAN_SIMPLIFIER_PROOFS, "(simplifier) generate proofs"); RegisterBoolOption(g_simplifier_contextual, LEAN_SIMPLIFIER_CONTEXTUAL, "(simplifier) contextual simplification"); RegisterBoolOption(g_simplifier_single_pass, LEAN_SIMPLIFIER_SINGLE_PASS, "(simplifier) if false then the simplifier keeps applying simplifications as long as possible"); RegisterBoolOption(g_simplifier_beta, LEAN_SIMPLIFIER_BETA, "(simplifier) use beta-reduction"); RegisterBoolOption(g_simplifier_eta, LEAN_SIMPLIFIER_ETA, "(simplifier) use eta-reduction"); RegisterBoolOption(g_simplifier_eval, LEAN_SIMPLIFIER_EVAL, "(simplifier) apply reductions based on computation"); RegisterBoolOption(g_simplifier_unfold, LEAN_SIMPLIFIER_UNFOLD, "(simplifier) unfolds non-opaque definitions"); RegisterBoolOption(g_simplifier_conditional, LEAN_SIMPLIFIER_CONDITIONAL, "(simplifier) conditional rewriting"); RegisterBoolOption(g_simplifier_memoize, LEAN_SIMPLIFIER_MEMOIZE, "(simplifier) memoize/cache intermediate results"); RegisterBoolOption(g_simplifier_preserve_binder_names, LEAN_SIMPLIFIER_PRESERVE_BINDER_NAMES, "(simplifier) (try to) preserve binder names when applying higher-order rewrite rules"); RegisterBoolOption(g_simplifier_heq, LEAN_SIMPLIFIER_HEQ, "(simplifier) use heterogeneous equality support"); RegisterUnsignedOption(g_simplifier_max_steps, LEAN_SIMPLIFIER_MAX_STEPS, "(simplifier) maximum number of steps"); bool get_simplifier_proofs(options const & opts) { return opts.get_bool(g_simplifier_proofs, LEAN_SIMPLIFIER_PROOFS); } bool get_simplifier_contextual(options const & opts) { return opts.get_bool(g_simplifier_contextual, LEAN_SIMPLIFIER_CONTEXTUAL); } bool get_simplifier_single_pass(options const & opts) { return opts.get_bool(g_simplifier_single_pass, LEAN_SIMPLIFIER_SINGLE_PASS); } bool get_simplifier_beta(options const & opts) { return opts.get_bool(g_simplifier_beta, LEAN_SIMPLIFIER_BETA); } bool get_simplifier_eta(options const & opts) { return opts.get_bool(g_simplifier_eta, LEAN_SIMPLIFIER_ETA); } bool get_simplifier_eval(options const & opts) { return opts.get_bool(g_simplifier_eval, LEAN_SIMPLIFIER_EVAL); } bool get_simplifier_unfold(options const & opts) { return opts.get_bool(g_simplifier_unfold, LEAN_SIMPLIFIER_UNFOLD); } bool get_simplifier_conditional(options const & opts) { return opts.get_bool(g_simplifier_conditional, LEAN_SIMPLIFIER_CONDITIONAL); } bool get_simplifier_memoize(options const & opts) { return opts.get_bool(g_simplifier_memoize, LEAN_SIMPLIFIER_MEMOIZE); } bool get_simplifier_heq(options const & opts) { return opts.get_bool(g_simplifier_heq, LEAN_SIMPLIFIER_HEQ); } bool get_simplifier_preserve_binder_names(options const & opts) { return opts.get_bool(g_simplifier_preserve_binder_names, LEAN_SIMPLIFIER_PRESERVE_BINDER_NAMES); } unsigned get_simplifier_max_steps(options const & opts) { return opts.get_unsigned(g_simplifier_max_steps, LEAN_SIMPLIFIER_MAX_STEPS); } static name g_local("local"); static name g_C("C"); static name g_H("H"); static name g_x("x"); static name g_unique = name::mk_internal_unique_name(); class simplifier_cell::imp { friend class simplifier_cell; friend class simplifier; typedef std::vector rule_sets; typedef expr_map cache; typedef std::vector congr_thms; typedef cache const_map; std::weak_ptr m_this; ro_environment m_env; options m_options; type_checker m_tc; rule_sets m_rule_sets; cache m_cache; max_sharing_fn m_max_sharing; const_map m_const_map; // mapping from old to new constants in hfunext and hpiext congr_thms m_congr_thms; unsigned m_next_idx; // index used to create fresh constants unsigned m_num_steps; // number of steps performed unsigned m_depth; // recursion depth name_map m_name_subst; cached_ro_metavar_env m_menv; std::shared_ptr m_monitor; // Configuration bool m_proofs_enabled; bool m_contextual; bool m_single_pass; bool m_beta; bool m_eta; bool m_eval; bool m_unfold; bool m_conditional; bool m_memoize; bool m_preserve_binder_names; bool m_use_heq; unsigned m_max_steps; struct updt_rule_set { imp & m_fn; rewrite_rule_set m_old; freset m_reset_cache; // must reset the cache whenever we update the rule set. /** \brief Update the rule set using a constant H : P, where P is a proposition. \pre const_type(H) */ updt_rule_set(imp & fn, expr const & H): m_fn(fn), m_old(m_fn.m_rule_sets[0]), m_reset_cache(m_fn.m_cache) { lean_assert(const_type(H)); m_fn.m_rule_sets[0].insert(g_local, *const_type(H), H, m_fn.m_menv.to_some_menv()); } ~updt_rule_set() { m_fn.m_rule_sets[0] = m_old; // Remark: m_reset_cache destructor will restore the cache } }; struct updt_const_map { imp & m_fn; expr const & m_old_x; updt_const_map(imp & fn, expr const & old_x, expr const & new_x, expr const & H): m_fn(fn), m_old_x(old_x) { m_fn.m_const_map[old_x] = result(new_x, H, true); } ~updt_const_map() { m_fn.m_const_map.erase(m_old_x); } }; static expr mk_lambda(name const & n, expr const & d, expr const & b) { return ::lean::mk_lambda(n, d, b); } /** \brief Return a lambda with body \c new_body, and name and domain from abst. */ static expr mk_lambda(expr const & abst, expr const & new_body) { return mk_lambda(abst_name(abst), abst_domain(abst), new_body); } bool is_proposition(expr const & e) { return m_tc.is_proposition(e, context(), m_menv.to_some_menv()); } bool is_convertible(expr const & t1, expr const & t2) { return m_tc.is_convertible(t1, t2, context(), m_menv.to_some_menv()); } bool is_definitionally_equal(expr const & t1, expr const & t2) { return m_tc.is_definitionally_equal(t1, t2, context(), m_menv.to_some_menv()); } expr infer_type(expr const & e) { return m_tc.infer_type(e, context(), m_menv.to_some_menv()); } expr ensure_pi(expr const & e) { return m_tc.ensure_pi(e, context(), m_menv.to_some_menv()); } expr normalize(expr const & e) { normalizer & proc = m_tc.get_normalizer(); return proc(e, context(), m_menv.to_some_menv()); } expr lift_free_vars(expr const & e, unsigned s, unsigned d) { return ::lean::lift_free_vars(e, s, d, m_menv.to_some_menv()); } expr lower_free_vars(expr const & e, unsigned s, unsigned d) { return ::lean::lower_free_vars(e, s, d, m_menv.to_some_menv()); } expr instantiate(expr const & e, expr const & s) { return ::lean::instantiate(e, s, m_menv.to_some_menv()); } expr instantiate(expr const & e, unsigned n, expr const * s) { return ::lean::instantiate(e, n, s, m_menv.to_some_menv()); } expr head_beta_reduce(expr const & t) { return ::lean::head_beta_reduce(t, m_menv.to_some_menv()); } expr mk_fresh_const(expr const & type) { m_next_idx++; return mk_constant(name(g_unique, m_next_idx), type); } /** \brief Auxiliary method for converting a proof H of (@eq A a b) into (@eq B a b) when type A is convertible to B, but not definitionally equal. */ expr translate_eq_proof(expr const & A, expr const & a, expr const & b, expr const & H, expr const & B) { if (A != B) return mk_to_eq_th(B, a, b, mk_to_heq_th(A, a, b, H)); else return H; } expr mk_congr1_th(expr const & f_type, expr const & f, expr const & new_f, expr const & a, expr const & Heq_f) { expr const & A = abst_domain(f_type); expr B = lower_free_vars(abst_body(f_type), 1, 1); return ::lean::mk_congr1_th(A, B, f, new_f, a, Heq_f); } expr mk_congr2_th(expr const & f_type, expr const & a, expr const & new_a, expr const & f, expr Heq_a) { expr const & A = abst_domain(f_type); expr B = lower_free_vars(abst_body(f_type), 1, 1); expr a_type = infer_type(a); if (!is_definitionally_equal(A, a_type)) Heq_a = translate_eq_proof(a_type, a, new_a, Heq_a, A); return ::lean::mk_congr2_th(A, B, a, new_a, f, Heq_a); } expr mk_congr_th(expr const & f_type, expr const & f, expr const & new_f, expr const & a, expr const & new_a, expr const & Heq_f, expr Heq_a) { expr const & A = abst_domain(f_type); expr B = lower_free_vars(abst_body(f_type), 1, 1); expr a_type = infer_type(a); if (!is_definitionally_equal(A, a_type)) Heq_a = translate_eq_proof(a_type, a, new_a, Heq_a, A); return ::lean::mk_congr_th(A, B, f, new_f, a, new_a, Heq_f, Heq_a); } expr get_proof(result const & rhs) { if (rhs.m_proof) { return *rhs.m_proof; } else { // lhs and rhs are definitionally equal return mk_refl_th(infer_type(rhs.m_expr), rhs.m_expr); } } /** \brief Return true if \c e is definitionally equal to (Type U) This is an approximated solution. It may return false for cases where \c e is definitionally to TypeU. */ bool is_TypeU(expr const & e) { if (is_type(e)) { return e == TypeU; } else if (is_constant(e)) { auto obj = m_env->find_object(const_name(e)); return obj && obj->is_definition() && is_TypeU(obj->get_value()); } else { return false; } } /** \brief Create heterogeneous congruence proof. */ optional mk_hcongr_th(expr const & f_type, expr const & new_f_type, expr const & f, expr const & new_f, expr const & Heq_f, expr const & a, result const & new_a, // the following two arguments are used only for invoking the simplifier monitor expr const & e, unsigned i) { expr const & A = abst_domain(f_type); expr const & new_A = abst_domain(new_f_type); expr a_type = infer_type(a); expr new_a_type = infer_type(new_a.m_expr); if (!is_convertible(new_a_type, new_A)) { if (m_monitor) m_monitor->failed_app_eh(ro_simplifier(m_this), e, i, simplifier_monitor::failure_kind::TypeMismatch); return none_expr(); // failed } expr Heq_a = get_proof(new_a); if (!new_a.is_heq_proof()) Heq_a = mk_to_heq_th(a_type, a, new_a.m_expr, Heq_a); return some_expr(::lean::mk_hcongr_th(A, new_A, mk_lambda(f_type, abst_body(f_type)), mk_lambda(new_f_type, abst_body(new_f_type)), f, new_f, a, new_a.m_expr, Heq_f, Heq_a)); } /** \brief Given a = b_res.m_expr with proof b_res.m_proof b_res.m_expr = c with proof H_bc This method returns a new result r s.t. r.m_expr == c and a proof of a = c */ result mk_trans_result(expr const & a, result const & b_res, expr const & c, expr H_bc) { if (m_proofs_enabled) { if (!b_res.m_proof) { // The proof of a = b is reflexivity return result(c, H_bc); } else { expr const & b = b_res.m_expr; expr new_proof; bool heq_proof = false; if (b_res.is_heq_proof()) { expr b_type = infer_type(b); new_proof = ::lean::mk_htrans_th(infer_type(a), b_type, b_type, /* b and c must have the same type */ a, b, c, *b_res.m_proof, mk_to_heq_th(b_type, b, c, H_bc)); heq_proof = true; } else { new_proof = ::lean::mk_trans_th(infer_type(a), a, b, c, *b_res.m_proof, H_bc); } return result(c, new_proof, heq_proof); } } else { return result(c); } } /** \brief Given a = b_res.m_expr with proof b_res.m_proof b_res.m_expr = c_res.m_expr with proof c_res.m_proof This method returns a new result r s.t. r.m_expr == c and a proof of a = c_res.m_expr */ result mk_trans_result(expr const & a, result const & b_res, result const & c_res) { if (m_proofs_enabled) { if (!b_res.m_proof) { // the proof of a == b is reflexivity return c_res; } else if (!c_res.m_proof) { // the proof of b == c is reflexivity return b_res.update_expr(c_res.m_expr); } else { bool heq_proof = b_res.is_heq_proof() || c_res.is_heq_proof(); expr new_proof; expr const & b = b_res.m_expr; expr const & c = c_res.m_expr; if (heq_proof) { expr a_type = infer_type(a); expr b_type = infer_type(b); expr c_type = infer_type(c); expr H_ab = *b_res.m_proof; if (!b_res.is_heq_proof()) H_ab = mk_to_heq_th(a_type, a, b, H_ab); expr H_bc = *c_res.m_proof; if (!c_res.is_heq_proof()) H_bc = mk_to_heq_th(b_type, b, c, H_bc); new_proof = ::lean::mk_htrans_th(a_type, b_type, c_type, a, b, c, H_ab, H_bc); } else { new_proof = ::lean::mk_trans_th(infer_type(a), a, b, c, *b_res.m_proof, *c_res.m_proof); } return result(c, new_proof, heq_proof); } } else { // proof generation is disabled return c_res; } } expr mk_app_prefix(unsigned i, expr const & a) { lean_assert(i > 0); if (i == 1) return arg(a, 0); else return mk_app(i, &arg(a, 0)); } expr mk_app_prefix(unsigned i, buffer const & args) { lean_assert(i > 0); if (i == 1) return args[0]; else return mk_app(i, args.data()); } result simplify_app(expr const & e) { if (m_use_heq && is_cast(e)) { // e is of the form (cast A B H a) // a : A // e : B expr A = arg(e, 1); expr B = arg(e, 2); expr H = arg(e, 3); expr a = arg(e, 4); if (m_proofs_enabled) { result res_a = simplify(a); expr c = res_a.m_expr; if (res_a.m_proof) { expr Hec; expr Hac = *res_a.m_proof; if (!res_a.is_heq_proof()) { Hec = ::lean::mk_htrans_th(B, A, A, e, a, c, update_app(e, 0, mk_cast_heq_fn()), // cast A B H a == a mk_to_heq_th(infer_type(a), a, c, Hac)); // a == c } else { Hec = ::lean::mk_htrans_th(B, A, infer_type(c), e, a, c, update_app(e, 0, mk_cast_heq_fn()), // cast A B H a == a Hac); // a == c } return result(c, Hec, true); } else { // c is definitionally equal to a // So, we use cast_heq theorem cast_heq : cast A B H a == a return result(c, update_app(e, 0, mk_cast_heq_fn()), true); } } else { return simplify(arg(e, 4)); } } if (m_contextual) { expr const & f = arg(e, 0); for (auto congr_th : m_congr_thms) { if (congr_th->get_fun() == f) return simplify_app_congr(e, *congr_th); } } return simplify_app_default(e); } /** \brief Make sure the proof in rhs is using homogeneous equality, and return true. If it is not possible to transform it in a homogeneous equality proof, then return false. */ bool ensure_homogeneous(expr const & lhs, result & rhs) { if (rhs.is_heq_proof()) { // try to convert back to homogeneous lean_assert(rhs.m_proof); expr lhs_type = infer_type(lhs); expr rhs_type = infer_type(rhs.m_expr); if (is_definitionally_equal(lhs_type, rhs_type)) { // move back to homogeneous equality using to_eq rhs.m_proof = mk_to_eq_th(lhs_type, lhs, rhs.m_expr, *rhs.m_proof); return true; } else { return false; } } else { return true; } } void ensure_heterogeneous(expr const & lhs, result & rhs) { if (!rhs.is_heq_proof()) { if (!rhs.m_proof) rhs.m_proof = mk_hrefl_th(infer_type(lhs), lhs); else rhs.m_proof = mk_to_heq_th(infer_type(lhs), lhs, rhs.m_expr, *rhs.m_proof); rhs.m_heq_proof = true; } } /** \brief Simplify \c e under the new assumption \c H. \remark \c H must be a constant of type P, where P is a proposition. \pre is_constant(H) && const_type(H) */ result simplify_using(expr const & e, expr const & H) { lean_assert(is_constant(H) && const_type(H)); updt_rule_set update(*this, H); return simplify(e); } /** \brief Simplify \c e using H : old_x == new_x */ result simplify_remapping_constant(expr const & e, expr const & old_x, expr const & new_x, expr const & H) { updt_const_map update(*this, old_x, new_x, H); return simplify(e); } /** \brief Simplify \c e using the given congruence theorem. See congr.h for a description of congr_theorem_info. */ result simplify_app_congr(expr const & e, congr_theorem_info const & cg_thm) { lean_assert(is_app(e)); lean_assert(arg(e, 0) == cg_thm.get_fun()); buffer new_args; bool changed = false; new_args.resize(num_args(e)); new_args[0] = arg(e, 0); buffer proof_args_buf; expr * proof_args = nullptr; if (m_proofs_enabled) { proof_args_buf.resize(cg_thm.get_num_proof_args() + 1); proof_args_buf[0] = cg_thm.get_proof(); proof_args = proof_args_buf.data()+1; } for (auto const & info : cg_thm.get_arg_info()) { unsigned pos = info.get_arg_pos(); expr const & a = arg(e, pos); if (info.should_simplify()) { optional const & ctx = info.get_context(); if (!ctx) { // argument does not have a context result res_a = simplify(a); new_args[pos] = res_a.m_expr; if (m_proofs_enabled) { if (!ensure_homogeneous(a, res_a)) return simplify_app_default(e); // fallback to default congruence proof_args[info.get_pos_at_proof()] = a; proof_args[*info.get_new_pos_at_proof()] = new_args[pos]; proof_args[*info.get_proof_pos_at_proof()] = get_proof(res_a); } } else { unsigned dep_pos = ctx->get_arg_pos(); expr C = ctx->use_new_val() ? new_args[dep_pos] : arg(e, dep_pos); if (!ctx->is_pos_dep()) C = mk_not(C); // We will simplify the \c a under the hypothesis C expr H = mk_fresh_const(C); result res_a = simplify_using(a, H); new_args[pos] = res_a.m_expr; if (m_proofs_enabled) { if (!ensure_homogeneous(a, res_a)) return simplify_app_default(e); // fallback to default congruence proof_args[info.get_pos_at_proof()] = a; proof_args[*info.get_new_pos_at_proof()] = new_args[pos]; name C_name(g_C, m_next_idx++); // H is a cryptic unique name proof_args[*info.get_proof_pos_at_proof()] = mk_lambda(C_name, C, abstract(get_proof(res_a), H)); } } if (new_args[pos] != a) changed = true; } else { // argument should not be simplified new_args[pos] = arg(e, pos); if (m_proofs_enabled) proof_args[info.get_pos_at_proof()] = arg(e, pos); } } if (!changed) { return rewrite_app(e, result(e)); } else if (!m_proofs_enabled) { return rewrite_app(e, result(mk_app(new_args))); } else { return rewrite_app(e, result(mk_app(new_args), mk_app(proof_args_buf))); } } result simplify_app_default(expr const & e) { lean_assert(is_app(e)); buffer new_args; buffer> proofs; // used only if m_proofs_enabled buffer f_types, new_f_types; // used only if m_proofs_enabled buffer heq_proofs; // used only if m_use_heq && m_proofs_enabled bool changed = false; expr f = arg(e, 0); expr f_type = infer_type(f); result res_f = simplify(f); expr new_f = res_f.m_expr; expr new_f_type; if (new_f != f) changed = true; new_args.push_back(new_f); if (m_proofs_enabled) { proofs.push_back(res_f.m_proof); f_types.push_back(f_type); new_f_type = res_f.is_heq_proof() ? infer_type(new_f) : f_type; new_f_types.push_back(new_f_type); if (m_use_heq) heq_proofs.push_back(res_f.is_heq_proof()); } unsigned num = num_args(e); for (unsigned i = 1; i < num; i++) { f_type = ensure_pi(f_type); bool f_arrow = is_arrow(f_type); expr const & a = arg(e, i); result res_a(a); if (m_use_heq || f_arrow) { res_a = simplify(a); if (res_a.m_expr != a) changed = true; } expr new_a = res_a.m_expr; new_args.push_back(new_a); if (m_proofs_enabled) { proofs.push_back(res_a.m_proof); if (m_use_heq) heq_proofs.push_back(res_a.is_heq_proof()); bool changed_f_type = !is_eqp(f_type, new_f_type); if (f_arrow) { f_type = lower_free_vars(abst_body(f_type), 1, 1); new_f_type = changed_f_type ? lower_free_vars(abst_body(new_f_type), 1, 1) : f_type; } else if (is_eqp(a, new_a)) { f_type = pi_body_at(f_type, a); new_f_type = changed_f_type ? pi_body_at(new_f_type, a) : f_type; } else { f_type = pi_body_at(f_type, a); new_f_type = pi_body_at(new_f_type, new_a); } f_types.push_back(f_type); new_f_types.push_back(new_f_type); } } if (!changed) { return rewrite_app(e, result(e)); } else if (!m_proofs_enabled) { return rewrite_app(e, result(mk_app(new_args))); } else { expr out = mk_app(new_args); unsigned i = 0; while (i < num && !proofs[i]) { // skip "reflexive" proofs i++; } if (i == num) return rewrite_app(e, result(out)); expr pr; bool heq_proof = false; if (i == 0) { pr = *(proofs[0]); heq_proof = m_use_heq && heq_proofs[0]; } else if (m_use_heq && (heq_proofs[i] || !is_arrow(f_types[i-1]))) { expr f = mk_app_prefix(i, new_args); expr pr_i = *proofs[i]; auto new_pr = mk_hcongr_th(f_types[i-1], f_types[i-1], f, f, mk_hrefl_th(f_types[i-1], f), arg(e, i), result(new_args[i], pr_i, heq_proofs[i]), e, i); if (!new_pr) return rewrite_app(e, result(e)); // failed to create congruence proof pr = *new_pr; heq_proof = true; } else { expr f = mk_app_prefix(i, new_args); pr = mk_congr2_th(f_types[i-1], arg(e, i), new_args[i], f, *(proofs[i])); } i++; for (; i < num; i++) { expr f = mk_app_prefix(i, e); expr new_f = mk_app_prefix(i, new_args); if (proofs[i]) { expr pr_i = *proofs[i]; if (m_use_heq && heq_proofs[i]) { if (!heq_proof) pr = mk_to_heq_th(f_types[i], f, new_f, pr); auto new_pr = mk_hcongr_th(f_types[i-1], new_f_types[i-1], f, new_f, pr, arg(e, i), result(new_args[i], pr_i, true), e, i); if (!new_pr) return rewrite_app(e, result(e)); // failed to create congruence proof pr = *new_pr; heq_proof = true; } else if (heq_proof) { lean_assert(!heq_proofs[i]); auto new_pr = mk_hcongr_th(f_types[i-1], new_f_types[i-1], f, new_f, pr, arg(e, i), result(new_args[i], pr_i, false), e, i); if (!new_pr) return rewrite_app(e, result(e)); // failed to create congruence proof pr = *new_pr; } else { pr = mk_congr_th(f_types[i-1], f, new_f, arg(e, i), new_args[i], pr, pr_i); } } else if (heq_proof) { auto new_pr = mk_hcongr_th(f_types[i-1], new_f_types[i-1], f, new_f, pr, arg(e, i), result(arg(e, i)), e, i); if (!new_pr) return rewrite_app(e, result(e)); // failed to create congruence proof pr = *new_pr; } else { lean_assert(!heq_proof); pr = mk_congr1_th(f_types[i-1], f, new_f, arg(e, i), pr); } } return rewrite_app(e, result(out, pr, heq_proof)); } } /** \brief Return true when \c e is a value from the point of view of the simplifier */ static bool is_value(expr const & e) { // Currently only semantic attachments are treated as value. // We may relax that in the future. return ::lean::is_value(e) || is_true(e) || is_false(e); } /** \brief Return true iff the simplifier should use the evaluator/normalizer to reduce application */ bool evaluate_app(expr const & e) const { lean_assert(is_app(e)); // only evaluate if it is enabled if (!m_eval) return false; // if all arguments are values, we should evaluate if (std::all_of(args(e).begin()+1, args(e).end(), [](expr const & a) { return is_value(a); })) return true; // The previous test fails for equality/disequality because the first arguments are types. // Should we have something more general for cases like that? // Some possibilities: // - We have a table mapping constants to argument positions. The positions tell the simplifier // which arguments must be value to trigger evaluation. // - We have an external predicate that is invoked by the simplifier to decide whether to normalize/evaluate an // expression. unsigned num = num_args(e); return (is_eq(e) || is_neq(e) || is_heq(e)) && is_value(arg(e, num-2)) && is_value(arg(e, num-1)); } /** \brief Given (applications) lhs and rhs s.t. lhs = rhs.m_expr with proof rhs.m_proof, this method applies rewrite rules, beta and evaluation to \c rhs.m_expr, and return a new result object new_rhs s.t. lhs = new_rhs.m_expr with proof new_rhs.m_proof \pre is_app(lhs) \pre is_app(rhs.m_expr) */ result rewrite_app(expr const & lhs, result const & rhs) { lean_assert(is_app(rhs.m_expr)); lean_assert(is_app(lhs)); if (evaluate_app(rhs.m_expr)) { // try to evaluate if all arguments are values. expr new_rhs = normalize(rhs.m_expr); if (is_value(new_rhs)) { // We don't need to create a new proof term since rhs.m_expr and new_rhs are // definitionally equal. return rewrite(lhs, rhs.update_expr(new_rhs)); } } expr f = arg(rhs.m_expr, 0); if (m_beta && is_lambda(f)) { expr new_rhs = head_beta_reduce(rhs.m_expr); // rhs.m_expr and new_rhs are also definitionally equal return rewrite(lhs, rhs.update_expr(new_rhs)); } return rewrite(lhs, rhs); } bool found_all_args(unsigned num, buffer> const & subst, buffer & new_args) { for (unsigned i = 0; i < num; i++) { if (!subst[i]) return false; new_args[i+1] = *subst[i]; } return true; } struct rename_binder_names_fn : public replace_visitor { name_map const & m_name_subst; rename_binder_names_fn(name_map const & s):m_name_subst(s) {} virtual expr visit_abst(expr const & e, context const & ctx) { auto it = m_name_subst.find(abst_name(e)); if (it != m_name_subst.end()) { if (is_lambda(e)) return replace_visitor::visit_abst(mk_lambda(it->second, abst_domain(e), abst_body(e)), ctx); else return replace_visitor::visit_abst(mk_pi(it->second, abst_domain(e), abst_body(e)), ctx); } else { return replace_visitor::visit_abst(e, ctx); } } }; expr rename_binder_names(expr const & e, name_map const & name_subst) { if (m_preserve_binder_names && !name_subst.empty()) return rename_binder_names_fn(name_subst)(e); else return e; } /** \brief Given lhs and rhs s.t. lhs = rhs.m_expr with proof rhs.m_proof, this method applies rewrite rules, beta and evaluation to \c rhs.m_expr, and return a new result object new_rhs s.t. lhs = new_rhs.m_expr with proof new_rhs.m_proof */ result rewrite(expr const & lhs, result const & rhs) { expr target = rhs.m_expr; buffer> subst; buffer new_args; expr new_rhs; expr new_proof; auto check_rule_fn = [&](rewrite_rule const & rule) -> bool { unsigned num = rule.get_num_args(); subst.clear(); subst.resize(num); m_name_subst.clear(); if (hop_match(rule.get_lhs(), target, subst, optional(m_env), m_menv.to_some_menv(), &m_name_subst)) { new_args.clear(); new_args.resize(num+1); if (found_all_args(num, subst, new_args)) { // easy case: all arguments found expr rhs = rename_binder_names(rule.get_rhs(), m_name_subst); new_rhs = instantiate(rhs, num, new_args.data() + 1); if (rule.is_permutation() && !is_lt(new_rhs, target, false)) return false; if (rule.must_check_types()) { // This check is needed because of universe cumulativity. // Consider the following example: // // universe U >= 2 // variable f (A : (Type 1)) : (Type 1) // axiom Ax1 (a : Type) : f a = a // rewrite_set S // add_rewrite Ax1 eq_id : S // theorem T1 (A : (Type 1)) : f A = A // := by simp S // // The axiom Ax1 is only for arguments convertible to Type (i.e., Type 0), but // argument A in T1 lives in (Type 1) // // In many cases, we can statically determine that this check is not needed. // By statically, we mean the time we are inserting ceqs into rewrite rule sets. expr ceq = rule.get_ceq(); unsigned i = 0; while (is_pi(ceq)) { expr arg = new_args[i+1]; if (!is_convertible(infer_type(arg), abst_domain(ceq))) { if (m_monitor) m_monitor->failed_rewrite_eh(ro_simplifier(m_this), target, rule.get_ceq(), rule.get_id(), i, simplifier_monitor::failure_kind::TypeMismatch); return false; } ceq = instantiate(abst_body(ceq), arg); i = i + 1; } } if (new_rhs == target) { if (m_monitor) m_monitor->failed_rewrite_eh(ro_simplifier(m_this), target, rule.get_ceq(), rule.get_id(), 0, simplifier_monitor::failure_kind::LoopPrevention); return false; } if (m_proofs_enabled) { if (num > 0) { new_args[0] = rule.get_proof(); new_proof = mk_app(new_args); } else { new_proof = rule.get_proof(); } } if (m_monitor) m_monitor->rewrite_eh(ro_simplifier(m_this), target, new_rhs, rule.get_ceq(), rule.get_id()); return true; } else { // Conditional rewriting: we try to fill the missing // arguments by trying to find a proof for ones that are // propositions. expr ceq = rename_binder_names(rule.get_ceq(), m_name_subst); buffer & proof_args = new_args; proof_args.clear(); if (m_proofs_enabled) proof_args.push_back(rule.get_proof()); for (unsigned i = 0; i < num; i++) { lean_assert(is_pi(ceq)); if (subst[i]) { if (rule.must_check_types() && !is_convertible(infer_type(*subst[i]), abst_domain(ceq))) { // See before the previous is_convertible if (m_monitor) m_monitor->failed_rewrite_eh(ro_simplifier(m_this), target, rule.get_ceq(), rule.get_id(), i, simplifier_monitor::failure_kind::TypeMismatch); return false; } ceq = instantiate(abst_body(ceq), *subst[i]); if (m_proofs_enabled) proof_args.push_back(*subst[i]); } else { expr d = abst_domain(ceq); if (is_proposition(d)) { result d_res = simplify(d); if (d_res.m_expr == True) { if (m_proofs_enabled) { expr d_proof; if (!d_res.m_proof) { // No proof available. So d should be definitionally equal to True d_proof = mk_trivial(); } else if (d_res.m_heq_proof) { d_proof = mk_heqt_elim_th(d, *d_res.m_proof); } else { d_proof = mk_eqt_elim_th(d, *d_res.m_proof); } ceq = instantiate(abst_body(ceq), d_proof); proof_args.push_back(d_proof); } else if (is_arrow(ceq)) { ceq = lower_free_vars(abst_body(ceq), 1, 1); } else { // The body of ceq depends on this argument, // but proof generation is not enabled. // So, we should fail if (m_monitor) m_monitor->failed_rewrite_eh(ro_simplifier(m_this), target, rule.get_ceq(), rule.get_id(), i, simplifier_monitor::failure_kind::Unsupported); return false; } } else { // failed to prove proposition if (m_monitor) m_monitor->failed_rewrite_eh(ro_simplifier(m_this), target, rule.get_ceq(), rule.get_id(), i, simplifier_monitor::failure_kind::AssumptionNotProved); return false; } } else { // failed, the argument is not a proposition if (m_monitor) m_monitor->failed_rewrite_eh(ro_simplifier(m_this), target, rule.get_ceq(), rule.get_id(), i, simplifier_monitor::failure_kind::MissingArgument); return false; } } } new_proof = mk_app(proof_args); new_rhs = arg(ceq, num_args(ceq) - 1); if (new_rhs == target || (rule.is_permutation() && !is_lt(new_rhs, target, false))) { if (m_monitor) m_monitor->failed_rewrite_eh(ro_simplifier(m_this), target, rule.get_ceq(), rule.get_id(), 0, simplifier_monitor::failure_kind::LoopPrevention); return false; } if (m_monitor) m_monitor->rewrite_eh(ro_simplifier(m_this), target, new_rhs, rule.get_ceq(), rule.get_id()); return true; } } return false; }; // Traverse all rule sets for (rewrite_rule_set const & rs : m_rule_sets) { if (rs.find_match(target, check_rule_fn)) { // the result is in new_rhs and proof at new_proof result new_r1 = mk_trans_result(lhs, rhs, new_rhs, new_proof); if (m_single_pass) { return new_r1; } else { result new_r2 = simplify(new_r1.m_expr); return mk_trans_result(lhs, new_r1, new_r2); } } } if (!m_single_pass && lhs != rhs.m_expr) { result new_rhs = simplify(rhs.m_expr); return mk_trans_result(lhs, rhs, new_rhs); } else { return rhs; } } result simplify_constant(expr const & e) { lean_assert(is_constant(e)); auto it = m_const_map.find(e); if (it != m_const_map.end()) { return it->second; } else if (m_unfold || m_eval) { auto obj = m_env->find_object(const_name(e)); if (obj) { if (m_unfold && should_unfold(obj)) { expr e = obj->get_value(); if (m_single_pass) { return result(e); } else { return simplify(e); } } if (m_eval && obj->is_builtin()) { return result(obj->get_value()); } } } return rewrite(e, result(e)); } /** \brief Return true iff Eta-reduction can be applied to \c e. \remark Actually this is a partial test. Given, fun x : T, f x This method does not check whether f has type Pi x : T, B x This check must be performed in the caller. Otherwise the proof (eta T (fun x : T, B x) f) will not type check. */ bool is_eta_target(expr const & e) const { if (is_lambda(e)) { expr b = abst_body(e); return is_app(b) && is_var(arg(b, num_args(b) - 1), 0) && std::all_of(begin_args(b), end_args(b) - 1, [](expr const & a) { return !has_free_var(a, 0); }); } else { return false; } } /** \brief Given (lambdas) lhs and rhs s.t. lhs = rhs.m_expr with proof rhs.m_proof, this method applies rewrite rules, and eta reduction, and return a new result object new_rhs s.t. lhs = new_rhs.m_expr with proof new_rhs.m_proof \pre is_lambda(lhs) \pre is_lambda(rhs.m_expr) */ result rewrite_lambda(expr const & lhs, result const & rhs) { lean_assert(is_lambda(lhs)); lean_assert(is_lambda(rhs.m_expr)); if (m_eta && is_eta_target(rhs.m_expr)) { expr b = abst_body(rhs.m_expr); expr new_rhs; if (num_args(b) > 2) { new_rhs = mk_app(num_args(b) - 1, &arg(b, 0)); } else { new_rhs = arg(b, 0); } new_rhs = lower_free_vars(new_rhs, 1, 1); expr new_rhs_type = ensure_pi(infer_type(new_rhs)); if (is_definitionally_equal(abst_domain(new_rhs_type), abst_domain(rhs.m_expr))) { if (m_proofs_enabled) { expr new_proof = mk_eta_th(abst_domain(rhs.m_expr), mk_lambda(rhs.m_expr, abst_body(new_rhs_type)), new_rhs); return rewrite(lhs, mk_trans_result(lhs, rhs, new_rhs, new_proof)); } else { return rewrite(lhs, result(new_rhs)); } } } return rewrite(lhs, rhs); } /** \brief Simplify only the body of the lambda expression, it does not 'touch' the domain. */ result simplify_lambda_body(expr const & e) { lean_assert(is_lambda(e)); expr const & d = abst_domain(e); expr fresh_const = mk_fresh_const(d); expr bi = instantiate(abst_body(e), fresh_const); result res_bi = simplify(bi); expr new_bi = res_bi.m_expr; if (is_eqp(new_bi, bi)) return rewrite_lambda(e, result(e)); expr new_e = mk_lambda(e, abstract(new_bi, fresh_const)); if (!m_proofs_enabled || !res_bi.m_proof) return rewrite_lambda(e, result(new_e)); if (res_bi.is_heq_proof()) { lean_assert(m_use_heq); // Using // theorem hsfunext {A : Type U+1} {B B' : A → TypeU} {f : ∀ x, B x} {f' : ∀ x, B' x} : // (∀ x, f x == f' x) → f == f' expr new_proof = mk_hsfunext_th(d, // A mk_lambda(e, infer_type(abst_body(e))), // B mk_lambda(e, abstract(infer_type(new_bi), fresh_const)), // B' e, // f new_e, // f' mk_lambda(g_x, d, abstract(*res_bi.m_proof, fresh_const))); return rewrite_lambda(e, result(new_e, new_proof, true)); } else { expr body_type = infer_type(abst_body(e)); // Using // axiom funext {A : Type U} {B : A → Type U} {f g : ∀ x : A, B x} (H : ∀ x : A, f x = g x) : f = g expr new_proof = mk_funext_th(d, mk_lambda(e, body_type), e, new_e, mk_lambda(e, abstract(*res_bi.m_proof, fresh_const))); return rewrite_lambda(e, result(new_e, new_proof)); } } /** \brief Simplify a lambda abstraction when the heq module is available. In this case, we can simplify the domain and body of the lambda expression. */ result simplify_lambda_with_heq(expr const & e) { expr const & d = abst_domain(e); result res_d = simplify(d); expr new_d = res_d.m_expr; if (is_eqp(d, new_d)) return simplify_lambda_body(e); if (is_definitionally_equal(d, new_d)) return simplify_lambda_body(update_lambda(e, new_d, abst_body(e))); // d and new_d are only provably equal, so we need to use hfunext expr x_old = mk_fresh_const(d); expr x_new = mk_fresh_const(new_d); expr x_old_eq_x_new = mk_heq(x_old, x_new); expr H_x_old_eq_x_new = mk_fresh_const(x_old_eq_x_new); expr bi = instantiate(abst_body(e), x_old); result res_bi = simplify_remapping_constant(bi, x_old, x_new, H_x_old_eq_x_new); expr new_bi = res_bi.m_expr; if (occurs(x_old, new_bi)) { // failed, simplifier didn't manage to replace x_old with x_new if (m_monitor) m_monitor->failed_abstraction_eh(ro_simplifier(m_this), e, simplifier_monitor::failure_kind::AbstractionBody); return rewrite(e, result(e)); } expr new_e = update_lambda(e, new_d, abstract(new_bi, x_new)); if (!m_proofs_enabled) return rewrite(e, result(new_e)); ensure_heterogeneous(d, res_d); ensure_heterogeneous(bi, res_bi); // Using // axiom hfunext {A A' : Type U+1} {B : A → Type U+1} {B' : A' → Type U+1} {f : ∀ x, B x} {f' : ∀ x, B' x} : // A == A' → (∀ x x', x == x' → f x == f' x') → f == f' expr new_proof = mk_hfunext_th(d, // A new_d, // A' Fun(x_old, d, infer_type(bi)), // B Fun(x_new, new_d, infer_type(new_bi)), // B' e, // f new_e, // f' *res_d.m_proof, // A == A' // fun (x_old : d) (x_new : new_d) (H : x_old == x_new), bi == new_bi mk_lambda(abst_name(e), d, mk_lambda(name(abst_name(e), 1), lift_free_vars(new_d, 0, 1), mk_lambda(name(g_H, m_next_idx++), abstract(x_old_eq_x_new, {x_old, x_new}), abstract(*res_bi.m_proof, {x_old, x_new, H_x_old_eq_x_new}))))); return rewrite(e, result(new_e, new_proof, true)); } result simplify_lambda(expr const & e) { lean_assert(is_lambda(e)); if (m_use_heq) { return simplify_lambda_with_heq(e); } else { return simplify_lambda_body(e); } } /** \brief Simplify A -> B when A -> B is a proposition. */ result simplify_implication(expr const & e) { expr const & d = abst_domain(e); expr b = abst_body(e); if (m_contextual) { // Contextual simplification for A -> B // Rewrite A to A' // And rewrite B to B' using A' result res_d = simplify(d); ensure_homogeneous(d, res_d); expr new_d = res_d.m_expr; expr H = mk_fresh_const(new_d); expr bi = lower_free_vars(b, 1, 1); result res_bi = simplify_using(bi, H); expr new_bi = res_bi.m_expr; ensure_homogeneous(bi, res_bi); if (is_eqp(new_d, d) && is_eqp(new_bi, bi)) return rewrite(e, result(e)); expr new_e = update_pi(e, new_d, lift_free_vars(new_bi, 0, 1)); if (!m_proofs_enabled) return rewrite(e, result(new_e)); name C_name(g_C, m_next_idx++); expr new_proof = mk_imp_congr_th(d, bi, new_d, new_bi, get_proof(res_d), mk_lambda(C_name, new_d, abstract(get_proof(res_bi), H))); return rewrite(e, result(new_e, new_proof)); } else { // Simplify A -> B (when m_contextual == false) result res_d = simplify(d); expr new_d = res_d.m_expr; ensure_homogeneous(d, res_d); expr bi = lower_free_vars(b, 1, 1); result res_bi = simplify(bi); expr new_bi = res_bi.m_expr; ensure_homogeneous(bi, res_bi); if (is_eqp(new_d, d) && is_eqp(new_bi, bi)) return rewrite(e, result(e)); expr new_e = update_pi(e, new_d, lift_free_vars(new_bi, 0, 1)); if (!m_proofs_enabled) return rewrite(e, result(new_e)); expr new_proof = mk_imp_congr_th(d, bi, new_d, new_bi, get_proof(res_d), mk_lambda(g_H, new_d, lift_free_vars(get_proof(res_bi), 0, 1))); return rewrite(e, result(new_e, new_proof)); } } /** \brief Simplify the domain of an arrow type A -> B when it is not a proposition. This procedure can be used even when the heq module is not available. */ result simplify_arrow_domain(expr const & e) { lean_assert(is_arrow(e)); expr const & A = abst_domain(e); result res_A = simplify(A); expr const & new_A = res_A.m_expr; if (is_eqp(A, new_A)) { return result(e); } else if (!m_proofs_enabled || is_definitionally_equal(A, new_A)) { return result(update_pi(e, new_A, abst_body(e))); } else { expr e_type = infer_type(e); if (is_TypeU(e_type) || !ensure_homogeneous(A, res_A)) { if (m_monitor) m_monitor->failed_abstraction_eh(ro_simplifier(m_this), e, simplifier_monitor::failure_kind::TypeMismatch); return result(e); // failed, we can't use subst theorem } else { expr H = get_proof(res_A); // We create the following proof term for (@eq (e_type) (A -> B) (new_A -> B)) // @subst A_type A new_A (fun x : A_type, (@eq e_type (A -> B) (x -> B))) (@refl e_type (A -> B)) H expr A_type = infer_type(A); expr x_arrow_B = update_pi(e, Var(0), abst_body(e)); expr new_proof = mk_subst_th(A_type, A, new_A, mk_lambda(g_x, A_type, mk_eq(e_type, e, x_arrow_B)), mk_refl_th(e_type, e), H); return result(update_pi(e, new_A, abst_body(e)), new_proof); } } } /** \brief Simplify the body of an arrow type A -> B when it is not a proposition. This procedure can be used even when the heq module is not available. */ result simplify_arrow_body(expr const & e) { lean_assert(is_arrow(e)); expr const & B = lower_free_vars(abst_body(e), 1, 1); result res_B = simplify(B); expr const & new_B = res_B.m_expr; if (is_eqp(B, new_B)) { return result(e); } else if (!m_proofs_enabled || is_definitionally_equal(B, new_B)) { return result(update_pi(e, abst_domain(e), lift_free_vars(new_B, 1, 1))); } else { expr e_type = infer_type(e); if (is_TypeU(e_type) || !ensure_homogeneous(B, res_B)) { if (m_monitor) m_monitor->failed_abstraction_eh(ro_simplifier(m_this), e, simplifier_monitor::failure_kind::TypeMismatch); return result(e); // failed, we can't use subst theorem } else { expr H = get_proof(res_B); // We create the following proof term for (@eq (e_type) (A -> B) (A -> new_B)) // @subst B_type B new_B (fun x : B_type, (@eq e_type (A -> B) (A -> x))) (@refl e_type (A -> B)) H expr B_type = infer_type(B); expr A_arrow_x = update_pi(e, abst_domain(e), Var(1)); expr new_proof = mk_subst_th(B_type, B, new_B, mk_lambda(g_x, B_type, mk_eq(e_type, e, A_arrow_x)), mk_refl_th(e_type, e), H); return result(update_pi(e, abst_domain(e), lift_free_vars(new_B, 1, 1)), new_proof); } } } /** \brief Simplify A -> B when A -> B is a not proposition. */ result simplify_arrow(expr const & e) { result r1 = simplify_arrow_body(e); result r2 = simplify_arrow_domain(r1.m_expr); return rewrite(e, mk_trans_result(e, r1, r2)); } /** \brief Simplify the body of (forall x : A, P x), when P x is a proposition. */ result simplify_forall_body(expr const & e) { lean_assert(is_pi(e) && is_proposition(e)); expr fresh_const = mk_fresh_const(abst_domain(e)); expr const & d = abst_domain(e); expr b = abst_body(e); expr bi = instantiate(b, fresh_const); result res_bi = simplify(bi); expr new_bi = res_bi.m_expr; if (is_eqp(new_bi, bi)) return rewrite(e, result(e)); expr new_e = mk_pi(abst_name(e), d, abstract(new_bi, fresh_const)); if (!m_proofs_enabled || !res_bi.m_proof) return rewrite(e, result(new_e)); ensure_homogeneous(bi, res_bi); expr new_proof = mk_allext_th(d, mk_lambda(e, b), mk_lambda(e, abst_body(new_e)), mk_lambda(e, abstract(*res_bi.m_proof, fresh_const))); return rewrite(e, result(new_e, new_proof)); } /** \brief Simplify (forall x : A, P x) proposition when the heq module is available. In this case, we can simplify the domain and body of the forall expression. */ result simplify_forall_with_heq(expr const & e) { lean_assert(is_pi(e) && is_proposition(e)); // We don't support Pi's that are not proposition yet. // The problem is that // axiom hpiext {A A' : Type U+1} {B : A → Type U+1} {B' : A' → Type U+1} : // A == A' → (∀ x x', x == x' → B x == B' x') → (∀ x, B x) == (∀ x, B' x) // produces an equality in TypeM even if A, A', B and B' live in smaller universes. // // This limitation does not seem to be a big problem in practice. expr const & d = abst_domain(e); result res_d = simplify(d); expr new_d = res_d.m_expr; if (is_eqp(d, new_d)) return simplify_forall_body(e); if (is_definitionally_equal(d, new_d)) return simplify_forall_body(update_pi(e, new_d, abst_body(e))); // d and new_d are only provably equal, so we need to use hpiext or hallext expr x_old = mk_fresh_const(d); expr x_new = mk_fresh_const(new_d); expr x_old_eq_x_new = mk_heq(x_old, x_new); expr H_x_old_eq_x_new = mk_fresh_const(x_old_eq_x_new); expr bi = instantiate(abst_body(e), x_old); result res_bi = simplify_remapping_constant(bi, x_old, x_new, H_x_old_eq_x_new); expr new_bi = res_bi.m_expr; if (occurs(x_old, new_bi)) { // failed, simplifier didn't manage to replace x_old with x_new if (m_monitor) m_monitor->failed_abstraction_eh(ro_simplifier(m_this), e, simplifier_monitor::failure_kind::AbstractionBody); return rewrite(e, result(e)); } expr new_e = update_pi(e, new_d, abstract(new_bi, x_new)); if (!m_proofs_enabled || is_definitionally_equal(e, new_e)) { if (m_monitor) m_monitor->failed_abstraction_eh(ro_simplifier(m_this), e, simplifier_monitor::failure_kind::TypeMismatch); return rewrite(e, result(new_e)); } ensure_heterogeneous(d, res_d); ensure_homogeneous(bi, res_bi); // Remark: the argument with type A = A' in hallext and hpiext is actually @eq TypeM A A', // so we need to translate the proof d_eq_new_d_proof : d = new_d to a TypeM equality proof expr bi_eq_new_bi_proof = get_proof(res_bi); // Heqb : (∀ x x', x == x' → B x = B' x') expr Heqb = mk_lambda(abst_name(e), d, mk_lambda(name(abst_name(e), 1), lift_free_vars(new_d, 0, 1), mk_lambda(name(g_H, m_next_idx++), abstract(x_old_eq_x_new, {x_old, x_new}), abstract(bi_eq_new_bi_proof, {x_old, x_new, H_x_old_eq_x_new})))); // Using // theorem hallext {A A' : Type U+1} {B : A → Bool} {B' : A' → Bool} : // A == A' → (∀ x x', x == x' → B x = B' x') → (∀ x, B x) = (∀ x, B' x) expr new_proof = mk_hallext_th(d, new_d, Fun(x_old, d, bi), // B Fun(x_new, new_d, new_bi), // B' *res_d.m_proof, // A == A' Heqb); return rewrite(e, result(new_e, new_proof)); } result simplify_pi(expr const & e) { lean_assert(is_pi(e)); if (is_arrow(e)) { if (is_proposition(abst_domain(e)) && is_proposition(abst_body(e))) return simplify_implication(e); else return simplify_arrow(e); } else if (is_proposition(e)) { if (m_use_heq) return simplify_forall_with_heq(e); else return simplify_forall_body(e); } else { // We currently do simplify (forall x : A, B x) when it is not a proposition. if (m_monitor) m_monitor->failed_abstraction_eh(ro_simplifier(m_this), e, simplifier_monitor::failure_kind::Unsupported); return result(e); } } result rewrite_heq(expr const & old_heq, result const & new_heq) { expr const & new_lhs = heq_lhs(new_heq.m_expr); expr const & new_rhs = heq_rhs(new_heq.m_expr); if (new_lhs == new_rhs) { // We currently cannot use heq_id as rewrite rule. // heq_id (A : (Type U+1)) (a : A) : (a == a) ↔ true // The problem is that A does not occur (even implicitly) in the left-hand-side, // and it is not a proposition. // In principle, we could extend the rewrite method to use type inference for computing A. // IF we find more instances of this problem, we will do it. Before that, it is easier to test // if the theorem is applicable here. if (!m_proofs_enabled) { return result(True); } else { return mk_trans_result(old_heq, new_heq, result(True, mk_heq_id_th(infer_type(new_lhs), new_lhs))); } } else { return rewrite(old_heq, new_heq); } } result simplify_heq(expr const & e) { lean_assert(is_heq(e)); expr const & lhs = heq_lhs(e); expr const & rhs = heq_rhs(e); result new_lhs = simplify(lhs); result new_rhs = simplify(rhs); if (is_eqp(lhs, new_lhs.m_expr) && is_eqp(rhs, new_rhs.m_expr)) { return rewrite_heq(e, result(e)); } else { expr new_heq = mk_heq(new_lhs.m_expr, new_rhs.m_expr); if (!m_proofs_enabled) { return rewrite_heq(e, result(new_heq)); } else { expr A_prime = infer_type(new_lhs.m_expr); expr B_prime = infer_type(new_rhs.m_expr); if (!new_lhs.is_heq_proof() && !new_rhs.is_heq_proof() && !is_TypeU(A_prime) && !is_TypeU(B_prime)) { return rewrite_heq(e, result(new_heq, mk_heq_congr_th(A_prime, B_prime, lhs, new_lhs.m_expr, rhs, new_rhs.m_expr, get_proof(new_lhs), get_proof(new_rhs)))); } else { ensure_heterogeneous(lhs, new_lhs); ensure_heterogeneous(rhs, new_rhs); return rewrite_heq(e, result(new_heq, mk_hheq_congr_th(infer_type(lhs), A_prime, infer_type(rhs), B_prime, lhs, new_lhs.m_expr, rhs, new_rhs.m_expr, *new_lhs.m_proof, *new_rhs.m_proof))); } } } } result save(expr const & e, result const & r) { if (m_memoize) { result new_r = r.update_expr(m_max_sharing(r.m_expr)); m_cache.insert(mk_pair(e, new_r)); if (m_monitor) m_monitor->step_eh(ro_simplifier(m_this), e, new_r.m_expr, new_r.m_proof); return new_r; } else { return r; } } result simplify(expr e) { check_system("simplifier"); m_num_steps++; flet inc_depth(m_depth, m_depth+1); if (m_num_steps > m_max_steps) throw exception("simplifier failed, maximum number of steps exceeded"); if (m_memoize) { e = m_max_sharing(e); auto it = m_cache.find(e); if (it != m_cache.end()) { return it->second; } } if (m_monitor) m_monitor->pre_eh(ro_simplifier(m_this), e); switch (e.kind()) { case expr_kind::Var: return result(e); case expr_kind::Constant: return save(e, simplify_constant(e)); case expr_kind::Type: return result(e); case expr_kind::MetaVar: case expr_kind::Value: return rewrite(e, result(e)); case expr_kind::App: return save(e, simplify_app(e)); case expr_kind::Lambda: return save(e, simplify_lambda(e)); case expr_kind::Pi: return save(e, simplify_pi(e)); case expr_kind::Let: return save(e, simplify(instantiate(let_body(e), let_value(e)))); case expr_kind::HEq: return save(e, simplify_heq(e)); } lean_unreachable(); } void collect_congr_thms() { if (m_contextual) { for (auto const & rs : m_rule_sets) { rs.for_each_congr([&](congr_theorem_info const & info) { if (std::all_of(m_congr_thms.begin(), m_congr_thms.end(), [&](congr_theorem_info const * info2) { return info2->get_fun() != info.get_fun(); })) { m_congr_thms.push_back(&info); } }); } } } void set_options(options const & o) { m_proofs_enabled = get_simplifier_proofs(o); m_contextual = get_simplifier_contextual(o); m_single_pass = get_simplifier_single_pass(o); m_beta = get_simplifier_beta(o); m_eta = get_simplifier_eta(o); m_eval = get_simplifier_eval(o); m_unfold = get_simplifier_unfold(o); m_conditional = get_simplifier_conditional(o); m_memoize = get_simplifier_memoize(o); m_max_steps = get_simplifier_max_steps(o); m_use_heq = get_simplifier_heq(o); m_preserve_binder_names = get_simplifier_preserve_binder_names(o); } public: imp(ro_environment const & env, options const & o, unsigned num_rs, rewrite_rule_set const * rs, std::shared_ptr const & monitor): m_env(env), m_options(o), m_tc(env), m_monitor(monitor) { set_options(o); if (m_contextual) { // We need an extra rule set if we are performing contextual rewriting m_rule_sets.push_back(rewrite_rule_set(env)); } m_rule_sets.insert(m_rule_sets.end(), rs, rs + num_rs); collect_congr_thms(); m_next_idx = 0; } result operator()(expr const & e, optional const & menv) { if (m_menv.update(menv)) m_cache.clear(); m_num_steps = 0; m_depth = 0; try { auto r = simplify(e); if (m_proofs_enabled && !r.get_proof()) return r.update_proof(get_proof(r)); else return r; } catch (stack_space_exception & ex) { throw simplifier_stack_space_exception(); } } }; simplifier_cell::simplifier_cell(ro_environment const & env, options const & o, unsigned num_rs, rewrite_rule_set const * rs, std::shared_ptr const & monitor): m_ptr(new imp(env, o, num_rs, rs, monitor)) { } simplifier_cell::result simplifier_cell::operator()(expr const & e, optional const & menv) { return m_ptr->operator()(e, menv); } void simplifier_cell::clear() { return m_ptr->m_cache.clear(); } unsigned simplifier_cell::get_depth() const { return m_ptr->m_depth; } ro_environment const & simplifier_cell::get_environment() const { return m_ptr->m_env; } options const & simplifier_cell::get_options() const { return m_ptr->m_options; } simplifier::simplifier(ro_environment const & env, options const & o, unsigned num_rs, rewrite_rule_set const * rs, std::shared_ptr const & monitor): m_ptr(std::make_shared(env, o, num_rs, rs, monitor)) { m_ptr->m_ptr->m_this = m_ptr; } ro_simplifier::ro_simplifier(simplifier const & env): m_ptr(env.m_ptr) { } ro_simplifier::ro_simplifier(weak_ref const & r) { if (r.expired()) throw exception("weak reference to simplifier object has expired (i.e., the simplifier has been deleted)"); m_ptr = r.lock(); } simplifier::result simplify(expr const & e, ro_environment const & env, options const & opts, unsigned num_rs, rewrite_rule_set const * rs, optional const & menv, std::shared_ptr const & monitor) { return simplifier(env, opts, num_rs, rs, monitor)(e, menv); } simplifier::result simplify(expr const & e, ro_environment const & env, options const & opts, unsigned num_ns, name const * ns, optional const & menv, std::shared_ptr const & monitor) { buffer rules; for (unsigned i = 0; i < num_ns; i++) rules.push_back(get_rewrite_rule_set(env, ns[i])); return simplify(e, env, opts, num_ns, rules.data(), menv, monitor); } simplifier_stack_space_exception::simplifier_stack_space_exception():stack_space_exception("simplifier") {} char const * simplifier_stack_space_exception::what() const noexcept { return "deep recursion was detected at 'simplifier', this is probably due to a non-terminating set of rewrite rules, you may use a simplifier_monitor object to track the simplifier behavior (see the simplifier manual); if your problem is very big, you may try to increase stack space in your system"; } exception * simplifier_stack_space_exception::clone() const { return new simplifier_stack_space_exception(); } void simplifier_stack_space_exception::rethrow() const { throw *this; } DECL_UDATA(simplifier) DECL_UDATA(ro_simplifier) /** \brief Simplifier monitor implemented using Lua functions */ class lua_simplifier_monitor : public simplifier_monitor { optional m_pre_eh; optional m_step_eh; optional m_rewrite_eh; optional m_failed_app_eh; optional m_failed_rewrite_eh; optional m_failed_abstraction_eh; public: lua_simplifier_monitor(optional const & pre_eh, optional const & step_eh, optional const & rewrite_eh, optional const & failed_app_eh, optional const & failed_rewrite_eh, optional const & failed_abstraction_eh): m_pre_eh(pre_eh), m_step_eh(step_eh), m_rewrite_eh(rewrite_eh), m_failed_app_eh(failed_app_eh), m_failed_rewrite_eh(failed_rewrite_eh), m_failed_abstraction_eh(failed_abstraction_eh) { } virtual ~lua_simplifier_monitor() {} virtual void pre_eh(ro_simplifier const & s, expr const & e) { if (m_pre_eh) { lua_State * L = m_pre_eh->get_state(); m_pre_eh->push(); push_ro_simplifier(L, s); push_expr(L, e); pcall(L, 2, 0, 0); } } virtual void step_eh(ro_simplifier const & s, expr const & e, expr const & new_e, optional const & pr) { if (m_step_eh) { lua_State * L = m_step_eh->get_state(); m_step_eh->push(); push_ro_simplifier(L, s); push_expr(L, e); push_expr(L, new_e); push_optional_expr(L, pr); pcall(L, 4, 0, 0); } } virtual void rewrite_eh(ro_simplifier const & s, expr const & e, expr const & new_e, expr const & ceq, name const & ceq_id) { if (m_rewrite_eh) { lua_State * L = m_rewrite_eh->get_state(); m_rewrite_eh->push(); push_ro_simplifier(L, s); push_expr(L, e); push_expr(L, new_e); push_expr(L, ceq); push_name(L, ceq_id); pcall(L, 5, 0, 0); } } virtual void failed_app_eh(ro_simplifier const & s, expr const & e, unsigned i, failure_kind k) { if (m_failed_app_eh) { lua_State * L = m_failed_app_eh->get_state(); m_failed_app_eh->push(); push_ro_simplifier(L, s); push_expr(L, e); lua_pushinteger(L, i); lua_pushinteger(L, static_cast(k)); pcall(L, 4, 0, 0); } } virtual void failed_rewrite_eh(ro_simplifier const & s, expr const & e, expr const & ceq, name const & ceq_id, unsigned i, failure_kind k) { if (m_failed_rewrite_eh) { lua_State * L = m_failed_rewrite_eh->get_state(); m_failed_rewrite_eh->push(); push_ro_simplifier(L, s); push_expr(L, e); push_expr(L, ceq); push_name(L, ceq_id); lua_pushinteger(L, i); lua_pushinteger(L, static_cast(k)); pcall(L, 6, 0, 0); } } virtual void failed_abstraction_eh(ro_simplifier const & s, expr const & e, failure_kind k) { if (m_failed_abstraction_eh) { lua_State * L = m_failed_abstraction_eh->get_state(); m_failed_abstraction_eh->push(); push_ro_simplifier(L, s); push_expr(L, e); lua_pushinteger(L, static_cast(k)); pcall(L, 3, 0, 0); } } }; typedef std::shared_ptr simplifier_monitor_ptr; DECL_UDATA(simplifier_monitor_ptr) static const struct luaL_Reg simplifier_monitor_ptr_m[] = { {"__gc", simplifier_monitor_ptr_gc}, {0, 0} }; static optional get_opt_callback(lua_State * L, int i) { if (i > lua_gettop(L) || lua_isnil(L, i)) { return optional(); } else { luaL_checktype(L, i, LUA_TFUNCTION); // user-fun return optional(luaref(L, i)); } } static int mk_simplifier_monitor(lua_State * L) { simplifier_monitor_ptr r = std::make_shared(get_opt_callback(L, 1), get_opt_callback(L, 2), get_opt_callback(L, 3), get_opt_callback(L, 4), get_opt_callback(L, 5), get_opt_callback(L, 6)); return push_simplifier_monitor_ptr(L, r); } /** \brief Fill the the rewrite_rule_set \c rs using the object at position \c i in the Lua stack. */ static void get_rewrite_rule_set(lua_State * L, int i, ro_environment const & env, buffer & rs) { if (i > lua_gettop(L)) { rs.push_back(get_rewrite_rule_set(env)); } else if (lua_isstring(L, i)) { rs.push_back(get_rewrite_rule_set(env, to_name_ext(L, i))); } else { luaL_checktype(L, i, LUA_TTABLE); name r; int n = objlen(L, i); for (int j = 1; j <= n; j++) { lua_rawgeti(L, i, j); rs.push_back(get_rewrite_rule_set(env, to_name_ext(L, -1))); lua_pop(L, 1); } } } static int mk_simplifier(lua_State * L, ro_environment const & env) { int nargs = lua_gettop(L); buffer rules; get_rewrite_rule_set(L, 1, env, rules); options opts; if (nargs >= 2) opts = to_options(L, 2); simplifier_monitor_ptr monitor; if (nargs >= 3 && !lua_isnil(L, 3)) monitor = to_simplifier_monitor_ptr(L, 3); return push_simplifier(L, simplifier(env, opts, rules.size(), rules.data(), monitor)); } static int mk_simplifier(lua_State * L) { int nargs = lua_gettop(L); if (nargs <= 3) return mk_simplifier(L, ro_shared_environment(L)); else return mk_simplifier(L, ro_shared_environment(L, 4)); } static int simplifier_apply(lua_State * L) { int nargs = lua_gettop(L); simplifier::result r; if (nargs == 2) r = to_simplifier(L, 1)(to_expr(L, 2), none_ro_menv()); else r = to_simplifier(L, 1)(to_expr(L, 2), some_ro_menv(to_metavar_env(L, 3))); push_expr(L, r.get_expr()); push_optional_expr(L, r.get_proof()); lua_pushboolean(L, r.is_heq_proof()); return 3; } static int simplifier_clear(lua_State * L) { to_simplifier(L, 1)->clear(); return 0; } static int simplifier_depth(lua_State * L) { lua_pushinteger(L, to_simplifier(L, 1)->get_depth()); return 1; } static int simplifier_environment(lua_State * L) { return push_environment(L, to_simplifier(L, 1)->get_environment()); } static int simplifier_options(lua_State * L) { return push_options(L, to_simplifier(L, 1)->get_options()); } static int ro_simplifier_depth(lua_State * L) { lua_pushinteger(L, to_ro_simplifier(L, 1)->get_depth()); return 1; } static int ro_simplifier_environment(lua_State * L) { return push_environment(L, to_ro_simplifier(L, 1)->get_environment()); } static int ro_simplifier_options(lua_State * L) { return push_options(L, to_ro_simplifier(L, 1)->get_options()); } static const struct luaL_Reg simplifier_m[] = { {"__gc", simplifier_gc}, {"__call", safe_function}, {"clear", safe_function}, {"depth", safe_function}, {"get_environment", safe_function}, {"get_options", safe_function}, {0, 0} }; static const struct luaL_Reg ro_simplifier_m[] = { {"__gc", ro_simplifier_gc}, {"depth", safe_function}, {"get_environment", safe_function}, {"get_options", safe_function}, {0, 0} }; static int simplify_core(lua_State * L, ro_shared_environment const & env) { int nargs = lua_gettop(L); expr const & e = to_expr(L, 1); buffer rules; get_rewrite_rule_set(L, 2, env, rules); options opts; if (nargs >= 3) opts = to_options(L, 3); optional menv; if (nargs >= 5) menv = some_ro_menv(to_metavar_env(L, 5)); auto r = simplify(e, env, opts, rules.size(), rules.data(), menv, get_simplifier_monitor(L, 6)); push_expr(L, r.get_expr()); push_optional_expr(L, r.get_proof()); lua_pushboolean(L, r.is_heq_proof()); return 3; } static int simplify(lua_State * L) { int nargs = lua_gettop(L); if (nargs <= 4) return simplify_core(L, ro_shared_environment(L)); else return simplify_core(L, ro_shared_environment(L, 4)); } static char g_set_simplifier_monitor_key; void set_global_simplifier_monitor(lua_State * L, std::shared_ptr const & monitor) { lua_pushlightuserdata(L, static_cast(&g_set_simplifier_monitor_key)); push_simplifier_monitor_ptr(L, monitor); lua_settable(L, LUA_REGISTRYINDEX); } std::shared_ptr get_global_simplifier_monitor(lua_State * L) { lua_pushlightuserdata(L, static_cast(&g_set_simplifier_monitor_key)); lua_gettable(L, LUA_REGISTRYINDEX); if (lua_isnil(L, -1)) { lua_pop(L, 1); return std::shared_ptr(); } else { std::shared_ptr r(to_simplifier_monitor_ptr(L, -1)); lua_pop(L, 1); return r; } } std::shared_ptr get_simplifier_monitor(lua_State * L, int i) { if (i > lua_gettop(L) || lua_isnil(L, i)) return get_global_simplifier_monitor(L); else return to_simplifier_monitor_ptr(L, 3); } static int set_simplifier_monitor(lua_State * L) { set_global_simplifier_monitor(L, to_simplifier_monitor_ptr(L, 1)); return 0; } static int get_simplifier_monitor(lua_State * L) { auto r = get_global_simplifier_monitor(L); if (r) push_simplifier_monitor_ptr(L, r); else lua_pushnil(L); return 1; } void open_simplifier(lua_State * L) { luaL_newmetatable(L, simplifier_mt); lua_pushvalue(L, -1); lua_setfield(L, -2, "__index"); setfuncs(L, simplifier_m, 0); SET_GLOBAL_FUN(simplifier_pred, "is_simplifier"); SET_GLOBAL_FUN(mk_simplifier, "simplifier"); luaL_newmetatable(L, ro_simplifier_mt); lua_pushvalue(L, -1); lua_setfield(L, -2, "__index"); setfuncs(L, ro_simplifier_m, 0); SET_GLOBAL_FUN(ro_simplifier_pred, "is_ro_simplifier"); luaL_newmetatable(L, simplifier_monitor_ptr_mt); lua_pushvalue(L, -1); lua_setfield(L, -2, "__index"); setfuncs(L, simplifier_monitor_ptr_m, 0); SET_GLOBAL_FUN(simplifier_monitor_ptr_pred, "is_simplifier_monitor"); SET_GLOBAL_FUN(mk_simplifier_monitor, "simplifier_monitor"); lua_newtable(L); SET_ENUM("Unsupported", simplifier_monitor::failure_kind::Unsupported); SET_ENUM("TypeMismatch", simplifier_monitor::failure_kind::TypeMismatch); SET_ENUM("AssumptionNotProved", simplifier_monitor::failure_kind::AssumptionNotProved); SET_ENUM("MissingArgument", simplifier_monitor::failure_kind::MissingArgument); SET_ENUM("LoopPrevention", simplifier_monitor::failure_kind::LoopPrevention); SET_ENUM("AbstractionBody", simplifier_monitor::failure_kind::AbstractionBody); lua_setglobal(L, "simplifier_failure"); SET_GLOBAL_FUN(simplify, "simplify"); SET_GLOBAL_FUN(set_simplifier_monitor, "set_simplifier_monitor"); SET_GLOBAL_FUN(get_simplifier_monitor, "get_simplifier_monitor"); } }