2014-10-31 05:22:04 +00:00
|
|
|
/*
|
|
|
|
Copyright (c) 2014 Microsoft Corporation. All rights reserved.
|
|
|
|
Released under Apache 2.0 license as described in the file LICENSE.
|
|
|
|
|
|
|
|
Author: Leonardo de Moura
|
|
|
|
*/
|
2014-10-31 16:01:19 +00:00
|
|
|
#include "util/sexpr/option_declarations.h"
|
2014-10-31 05:22:04 +00:00
|
|
|
#include "kernel/free_vars.h"
|
|
|
|
#include "kernel/instantiate.h"
|
|
|
|
#include "library/unifier.h"
|
|
|
|
#include "library/reducible.h"
|
|
|
|
#include "library/metavar_closure.h"
|
2014-12-10 20:43:32 +00:00
|
|
|
#include "library/local_context.h"
|
2014-10-31 05:22:04 +00:00
|
|
|
#include "frontends/lean/util.h"
|
|
|
|
#include "frontends/lean/info_manager.h"
|
2014-10-31 06:24:09 +00:00
|
|
|
#include "frontends/lean/calc.h"
|
2014-10-31 16:39:20 +00:00
|
|
|
#include "frontends/lean/calc_proof_elaborator.h"
|
2015-02-01 19:05:38 +00:00
|
|
|
#include "frontends/lean/elaborator_exception.h"
|
2014-10-31 05:22:04 +00:00
|
|
|
|
2014-10-31 16:01:19 +00:00
|
|
|
#ifndef LEAN_DEFAULT_CALC_ASSISTANT
|
|
|
|
#define LEAN_DEFAULT_CALC_ASSISTANT true
|
|
|
|
#endif
|
|
|
|
|
2014-10-31 05:22:04 +00:00
|
|
|
namespace lean {
|
2014-10-31 16:01:19 +00:00
|
|
|
static name * g_elaborator_calc_assistant = nullptr;
|
|
|
|
|
|
|
|
void initialize_calc_proof_elaborator() {
|
|
|
|
g_elaborator_calc_assistant = new name{"elaborator", "calc_assistant"};
|
|
|
|
register_bool_option(*g_elaborator_calc_assistant, LEAN_DEFAULT_CALC_ASSISTANT,
|
|
|
|
"(elaborator) when needed lean automatically adds symmetry, "
|
|
|
|
"inserts missing arguments, and applies substitutions");
|
|
|
|
}
|
|
|
|
|
|
|
|
void finalize_calc_proof_elaborator() {
|
|
|
|
delete g_elaborator_calc_assistant;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool get_elaborator_calc_assistant(options const & o) {
|
|
|
|
return o.get_bool(*g_elaborator_calc_assistant, LEAN_DEFAULT_CALC_ASSISTANT);
|
|
|
|
}
|
|
|
|
|
2014-10-31 07:55:19 +00:00
|
|
|
static optional<pair<expr, expr>> mk_op(environment const & env, local_context & ctx, name_generator & ngen, type_checker_ptr & tc,
|
|
|
|
name const & op, unsigned nunivs, unsigned nargs, std::initializer_list<expr> const & explicit_args,
|
|
|
|
constraint_seq & cs, tag g) {
|
|
|
|
levels lvls;
|
|
|
|
for (unsigned i = 0; i < nunivs; i++)
|
|
|
|
lvls = levels(mk_meta_univ(ngen.next()), lvls);
|
|
|
|
expr c = mk_constant(op, lvls);
|
|
|
|
expr op_type = instantiate_type_univ_params(env.get(op), lvls);
|
|
|
|
buffer<expr> args;
|
|
|
|
for (unsigned i = 0; i < nargs; i++) {
|
|
|
|
if (!is_pi(op_type))
|
|
|
|
return optional<pair<expr, expr>>();
|
|
|
|
expr arg = ctx.mk_meta(ngen, some_expr(binding_domain(op_type)), g);
|
|
|
|
args.push_back(arg);
|
|
|
|
op_type = instantiate(binding_body(op_type), arg);
|
|
|
|
}
|
|
|
|
expr r = mk_app(c, args, g);
|
|
|
|
for (expr const & explicit_arg : explicit_args) {
|
|
|
|
if (!is_pi(op_type))
|
|
|
|
return optional<pair<expr, expr>>();
|
|
|
|
r = mk_app(r, explicit_arg);
|
|
|
|
expr type = tc->infer(explicit_arg, cs);
|
|
|
|
justification j = mk_app_justification(r, explicit_arg, binding_domain(op_type), type);
|
|
|
|
if (!tc->is_def_eq(binding_domain(op_type), type, j, cs))
|
|
|
|
return optional<pair<expr, expr>>();
|
|
|
|
op_type = instantiate(binding_body(op_type), explicit_arg);
|
|
|
|
}
|
|
|
|
return some(mk_pair(r, op_type));
|
|
|
|
}
|
|
|
|
|
|
|
|
static optional<pair<expr, expr>> apply_symmetry(environment const & env, local_context & ctx, name_generator & ngen, type_checker_ptr & tc,
|
|
|
|
expr const & e, expr const & e_type, constraint_seq & cs, tag g) {
|
2014-10-31 06:24:09 +00:00
|
|
|
buffer<expr> args;
|
|
|
|
expr const & op = get_app_args(e_type, args);
|
2014-11-01 14:30:04 +00:00
|
|
|
if (is_constant(op)) {
|
2014-10-31 06:24:09 +00:00
|
|
|
if (auto t = get_calc_symm_info(env, const_name(op))) {
|
|
|
|
name symm; unsigned nargs; unsigned nunivs;
|
|
|
|
std::tie(symm, nargs, nunivs) = *t;
|
2014-11-01 14:30:04 +00:00
|
|
|
return mk_op(env, ctx, ngen, tc, symm, nunivs, nargs-1, {e}, cs, g);
|
2014-10-31 07:55:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return optional<pair<expr, expr>>();
|
|
|
|
}
|
|
|
|
|
|
|
|
static optional<pair<expr, expr>> apply_subst(environment const & env, local_context & ctx, name_generator & ngen,
|
|
|
|
type_checker_ptr & tc, expr const & e, expr const & e_type,
|
|
|
|
expr const & pred, constraint_seq & cs, tag g) {
|
|
|
|
buffer<expr> pred_args;
|
|
|
|
get_app_args(pred, pred_args);
|
|
|
|
unsigned npargs = pred_args.size();
|
|
|
|
if (npargs < 2)
|
|
|
|
return optional<pair<expr, expr>>();
|
|
|
|
buffer<expr> args;
|
|
|
|
expr const & op = get_app_args(e_type, args);
|
|
|
|
if (is_constant(op) && args.size() >= 2) {
|
|
|
|
if (auto subst_it = get_calc_subst_info(env, const_name(op))) {
|
|
|
|
name subst; unsigned subst_nargs; unsigned subst_univs;
|
|
|
|
std::tie(subst, subst_nargs, subst_univs) = *subst_it;
|
|
|
|
if (auto refl_it = get_calc_refl_info(env, const_name(op))) {
|
|
|
|
name refl; unsigned refl_nargs; unsigned refl_univs;
|
|
|
|
std::tie(refl, refl_nargs, refl_univs) = *refl_it;
|
2014-10-31 16:39:20 +00:00
|
|
|
if (auto refl_pair = mk_op(env, ctx, ngen, tc, refl, refl_univs, refl_nargs-1,
|
|
|
|
{ pred_args[npargs-2] }, cs, g)) {
|
2014-10-31 07:55:19 +00:00
|
|
|
return mk_op(env, ctx, ngen, tc, subst, subst_univs, subst_nargs-2, {e, refl_pair->first}, cs, g);
|
|
|
|
}
|
|
|
|
}
|
2014-10-31 06:24:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return optional<pair<expr, expr>>();
|
|
|
|
}
|
|
|
|
|
2014-10-31 05:22:04 +00:00
|
|
|
/** \brief Create a "choice" constraint that postpones the resolution of a calc proof step.
|
|
|
|
|
|
|
|
By delaying it, we can perform quick fixes such as:
|
|
|
|
- adding symmetry
|
|
|
|
- adding !
|
|
|
|
- adding subst
|
|
|
|
*/
|
2014-10-31 16:01:19 +00:00
|
|
|
constraint mk_calc_proof_cnstr(environment const & env, options const & opts,
|
|
|
|
local_context const & _ctx, expr const & m, expr const & _e,
|
2014-10-31 05:22:04 +00:00
|
|
|
constraint_seq const & cs, unifier_config const & cfg,
|
2014-10-31 16:39:20 +00:00
|
|
|
info_manager * im, bool relax, update_type_info_fn const & fn) {
|
2014-10-31 05:22:04 +00:00
|
|
|
justification j = mk_failed_to_synthesize_jst(env, m);
|
2014-10-31 15:25:36 +00:00
|
|
|
auto choice_fn = [=](expr const & meta, expr const & meta_type, substitution const & _s,
|
2014-10-31 05:22:04 +00:00
|
|
|
name_generator const & _ngen) {
|
|
|
|
local_context ctx = _ctx;
|
|
|
|
expr e = _e;
|
2014-10-31 15:25:36 +00:00
|
|
|
substitution s = _s;
|
2014-10-31 05:22:04 +00:00
|
|
|
name_generator ngen(_ngen);
|
|
|
|
type_checker_ptr tc(mk_type_checker(env, ngen.mk_child(), relax));
|
|
|
|
constraint_seq new_cs = cs;
|
|
|
|
expr e_type = tc->infer(e, new_cs);
|
2014-10-31 15:25:36 +00:00
|
|
|
e_type = s.instantiate(e_type);
|
2014-10-31 05:22:04 +00:00
|
|
|
e_type = tc->whnf(e_type, new_cs);
|
|
|
|
tag g = e.get_tag();
|
2014-10-31 16:01:19 +00:00
|
|
|
bool calc_assistant = get_elaborator_calc_assistant(opts);
|
|
|
|
|
|
|
|
if (calc_assistant) {
|
|
|
|
// add '!' is needed
|
|
|
|
while (is_pi(e_type)) {
|
|
|
|
binder_info bi = binding_info(e_type);
|
|
|
|
if (!bi.is_implicit() && !bi.is_inst_implicit()) {
|
|
|
|
if (!has_free_var(binding_body(e_type), 0)) {
|
|
|
|
// if the rest of the type does not reference argument,
|
|
|
|
// then we also stop consuming arguments
|
|
|
|
break;
|
|
|
|
}
|
2014-10-31 05:22:04 +00:00
|
|
|
}
|
2014-10-31 16:01:19 +00:00
|
|
|
expr imp_arg = ctx.mk_meta(ngen, some_expr(binding_domain(e_type)), g);
|
|
|
|
e = mk_app(e, imp_arg, g);
|
|
|
|
e_type = tc->whnf(instantiate(binding_body(e_type), imp_arg), new_cs);
|
2014-10-31 05:22:04 +00:00
|
|
|
}
|
2014-10-31 16:39:20 +00:00
|
|
|
if (im)
|
|
|
|
fn(e);
|
2014-10-31 05:22:04 +00:00
|
|
|
}
|
|
|
|
|
2015-01-20 00:23:29 +00:00
|
|
|
auto try_alternative = [&](expr const & e, expr const & e_type, constraint_seq fcs, bool conservative) {
|
2015-02-01 19:05:38 +00:00
|
|
|
justification new_j = mk_type_mismatch_jst(e, e_type, meta_type);
|
2014-10-31 06:24:09 +00:00
|
|
|
if (!tc->is_def_eq(e_type, meta_type, new_j, fcs))
|
|
|
|
throw unifier_exception(new_j, s);
|
|
|
|
buffer<constraint> cs_buffer;
|
|
|
|
fcs.linearize(cs_buffer);
|
|
|
|
metavar_closure cls(meta);
|
|
|
|
cls.add(meta_type);
|
|
|
|
cls.mk_constraints(s, j, relax, cs_buffer);
|
|
|
|
cs_buffer.push_back(mk_eq_cnstr(meta, e, j, relax));
|
|
|
|
|
|
|
|
unifier_config new_cfg(cfg);
|
2015-01-20 00:23:29 +00:00
|
|
|
new_cfg.m_discard = false;
|
2015-03-05 06:12:49 +00:00
|
|
|
new_cfg.m_kind = conservative ? unifier_kind::Conservative : unifier_kind::Liberal;
|
2015-01-20 00:23:29 +00:00
|
|
|
unify_result_seq seq = unify(env, cs_buffer.size(), cs_buffer.data(), ngen, substitution(), new_cfg);
|
2014-10-31 06:24:09 +00:00
|
|
|
auto p = seq.pull();
|
|
|
|
lean_assert(p);
|
|
|
|
substitution new_s = p->first.first;
|
|
|
|
constraints postponed = map(p->first.second,
|
|
|
|
[&](constraint const & c) {
|
|
|
|
// we erase internal justifications
|
|
|
|
return update_justification(c, j);
|
|
|
|
});
|
2015-02-01 19:05:38 +00:00
|
|
|
if (conservative && has_expr_metavar_relaxed(new_s.instantiate_all(e)))
|
|
|
|
throw_elaborator_exception("solution contains metavariables", e);
|
2014-10-31 06:24:09 +00:00
|
|
|
if (im)
|
|
|
|
im->instantiate(new_s);
|
|
|
|
constraints r = cls.mk_constraints(new_s, j, relax);
|
|
|
|
return append(r, postponed);
|
|
|
|
};
|
|
|
|
|
2014-10-31 16:01:19 +00:00
|
|
|
if (!get_elaborator_calc_assistant(opts)) {
|
2015-01-20 00:23:29 +00:00
|
|
|
bool conservative = false;
|
|
|
|
return try_alternative(e, e_type, new_cs, conservative);
|
2014-10-31 16:01:19 +00:00
|
|
|
} else {
|
2015-02-01 05:29:34 +00:00
|
|
|
// TODO(Leo): after we have the simplifier and rewriter tactic, we should revise
|
|
|
|
// this code. It is "abusing" the higher-order unifier.
|
2014-10-31 07:55:19 +00:00
|
|
|
|
2015-02-01 05:29:34 +00:00
|
|
|
{
|
|
|
|
// Try the following possible intrepretations using a "conservative" unification procedure.
|
|
|
|
// That is, we only unfold definitions marked as reducible.
|
|
|
|
// Assume pr is the proof provided.
|
2014-10-31 05:22:04 +00:00
|
|
|
|
2015-02-01 05:29:34 +00:00
|
|
|
// 1. pr
|
2015-01-20 00:23:29 +00:00
|
|
|
bool conservative = true;
|
2015-02-01 05:29:34 +00:00
|
|
|
try { return try_alternative(e, e_type, new_cs, conservative); } catch (exception & ex) {}
|
2014-10-31 08:02:49 +00:00
|
|
|
|
2015-02-01 05:29:34 +00:00
|
|
|
// 2. eq.symm pr
|
|
|
|
constraint_seq symm_cs = new_cs;
|
|
|
|
auto symm = apply_symmetry(env, ctx, ngen, tc, e, e_type, symm_cs, g);
|
|
|
|
if (symm) {
|
|
|
|
try { return try_alternative(symm->first, symm->second, symm_cs, conservative); } catch (exception &) {}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 3. subst pr (eq.refl lhs)
|
|
|
|
constraint_seq subst_cs = new_cs;
|
|
|
|
if (auto subst = apply_subst(env, ctx, ngen, tc, e, e_type, meta_type, subst_cs, g)) {
|
2015-01-20 00:23:29 +00:00
|
|
|
try { return try_alternative(subst->first, subst->second, subst_cs, conservative); } catch (exception&) {}
|
2014-10-31 16:01:19 +00:00
|
|
|
}
|
2015-02-01 05:29:34 +00:00
|
|
|
|
|
|
|
// 4. subst (eq.symm pr) (eq.refl lhs)
|
|
|
|
if (symm) {
|
|
|
|
constraint_seq subst_cs = symm_cs;
|
|
|
|
if (auto subst = apply_subst(env, ctx, ngen, tc, symm->first, symm->second, meta_type, subst_cs, g)) {
|
|
|
|
try { return try_alternative(subst->first, subst->second, subst_cs, conservative); } catch (exception&) {}
|
|
|
|
}
|
|
|
|
}
|
2014-10-31 16:01:19 +00:00
|
|
|
}
|
|
|
|
|
2015-02-01 05:29:34 +00:00
|
|
|
{
|
|
|
|
// Try the following possible insterpretations using the default unification procedure.
|
|
|
|
|
|
|
|
// 1. pr
|
|
|
|
bool conservative = false;
|
|
|
|
std::unique_ptr<throwable> saved_ex;
|
|
|
|
try {
|
|
|
|
return try_alternative(e, e_type, new_cs, conservative);
|
|
|
|
} catch (exception & ex) {
|
|
|
|
saved_ex.reset(ex.clone());
|
|
|
|
}
|
|
|
|
|
|
|
|
// 2. eq.symm pr
|
|
|
|
constraint_seq symm_cs = new_cs;
|
|
|
|
auto symm = apply_symmetry(env, ctx, ngen, tc, e, e_type, symm_cs, g);
|
|
|
|
if (symm) {
|
|
|
|
try { return try_alternative(symm->first, symm->second, symm_cs, conservative); } catch (exception &) {}
|
|
|
|
}
|
|
|
|
|
|
|
|
// We use the exception for the first alternative as the error message
|
|
|
|
saved_ex->rethrow();
|
|
|
|
lean_unreachable();
|
|
|
|
}
|
2014-10-31 16:01:19 +00:00
|
|
|
}
|
2014-10-31 05:22:04 +00:00
|
|
|
};
|
|
|
|
bool owner = false;
|
|
|
|
return mk_choice_cnstr(m, choice_fn, to_delay_factor(cnstr_group::Epilogue), owner, j, relax);
|
|
|
|
}
|
|
|
|
}
|