/* Copyright (c) 2015 Daniel Selsam. All rights reserved. Released under Apache 2.0 license as described in the file LICENSE. Author: Daniel Selsam */ #include #include #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 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 simplify_cache; simplify_cache m_cache; optional 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 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 & 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 const & emetas, list 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 > 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 > in_nary_subtree(m_curr_nary_op, some_expr(assoc->second)); r = simplify_nary(assoc->first, assoc->second, e, use_congr_only); } else { flet > 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 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 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(goal_mvar), goal_mvar)); vm_obj simp_ext_result = invoke(ext_id, s); optional 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 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 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 simplify_rewrite_nary(expr const & assoc, expr const & old_e, expr const & op, buffer 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(); list const * srs = sr->find_simp(op); if (!srs) { return optional(); } for (simp_lemma const & lemma : *srs) { if (optional r = rewrite_nary(assoc, old_e, op, nary_args, lemma)) return r; } return optional(); } optional rewrite_nary(expr const & assoc, expr const & old_e, expr const & op, buffer const & nary_args, simp_lemma const & sl) { optional pattern_op = get_binary_op(sl.get_lhs()); if (!pattern_op) return optional(); 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(); buffer 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(); // 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 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(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(); } optional rewrite_nary_step(buffer const & nary_args, buffer 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(); } if (!instantiate_emetas(tmp_tctx, sl.get_num_emeta(), sl.get_emetas(), sl.get_instances())) return optional(); for (unsigned i = 0; i < sl.get_num_umeta(); i++) { if (!tmp_tctx.is_uassigned(i)) return optional(); } 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(); } } expr pf = tmp_tctx.instantiate_mvars(sl.get_proof()); return optional(simp_result(new_rhs, pf)); } optional simplify_user_extensions_nary(expr const & assoc, expr const & old_e, expr const & op, buffer 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(r); else return optional(); } simp_result simplify_subterms_app_nary_core(expr const & old_op, expr const & new_op, optional const & pf_op, expr const & e) { expr arg1, arg2; optional 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 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 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 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 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 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 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 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 prove(expr const & thm); /* Canonicalize */ expr defeq_canonize_args_step(expr const & e); expr_struct_map 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 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 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 simplifier::cache_lookup(expr const & e) { auto it = m_cache.find(key(m_rel, e)); if (it == m_cache.end()) return optional(); return optional(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 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 y */ expr motive = local_factory.mk_lambda(motive_local); /* Rxx = x 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 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 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 args; expr const & f = get_app_args(e, args); simp_result r_f = simplify(f); return congr_funs(r_f, args); } /* Proving */ optional 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(goal_mvar), goal_mvar)); vm_obj prove_fn_result = invoke(m_prove_fn, s); optional 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 new_metavar = find(proof, [&](expr const & e, unsigned) { return is_metavar(e) && !static_cast(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 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 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 congr_hyps; to_buffer(cl.get_congr_hyps(), congr_hyps); buffer congr_hyp_results; buffer factories; buffer 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 set_name(m_rel, const_name(h_rel)); relations.push_back(m_rel); flet 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 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 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 const & emetas, list 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 simplifier::synth_congr(expr const & e) { lean_assert(is_app(e)); buffer args; expr f = get_app_args(e, args); auto congr_lemma = mk_specialized_congr_simp(m_tctx, e); if (!congr_lemma) return optional(); buffer r_args; buffer 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(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(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 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(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 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 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 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 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); } }