/*
Copyright (c) 2016 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author: Leonardo de Moura
*/
#include "kernel/instantiate.h"
#include "library/util.h"
#include "library/vm/vm_expr.h"
#include "library/tactic/tactic_state.h"
namespace lean {
static unsigned get_expect_num_args(type_context & ctx, expr e) {
type_context::tmp_locals locals(ctx);
unsigned r = 0;
while (true) {
e = ctx.relaxed_whnf(e);
if (!is_pi(e))
return r;
// TODO(Leo): try to avoid the following instantiate.
expr local = locals.push_local(binding_name(e), binding_domain(e), binding_info(e));
e = instantiate(binding_body(e), local);
r++;
}
}
/** \brief Given a sequence metas/goals: (?m_1 ...) (?m_2 ... ) ... (?m_k ...),
we say ?m_i is "redundant" if it occurs in the type of some ?m_j.
This procedure removes from metas any redundant element. */
static void remove_redundant_goals(metavar_context & mctx, buffer & metas) {
unsigned k = 0;
for (unsigned i = 0; i < metas.size(); i++) {
bool found = false;
for (unsigned j = 0; j < metas.size(); j++) {
if (j != i) {
if (occurs(metas[i], mctx.get_metavar_decl(metas[j])->get_type())) {
found = true;
break;
}
}
}
if (!found) {
metas[k] = metas[i];
k++;
}
}
metas.shrink(k);
}
vm_obj apply_core(transparency_mode md, bool add_all, bool use_instances, expr e, tactic_state const & s) {
try {
optional g = s.get_main_goal_decl();
if (!g) return mk_no_goals_exception(s);
type_context ctx = mk_type_context_for(s, md);
local_context lctx = g->get_context();
expr target = g->get_type();
expr e_type = ctx.infer(e);
unsigned num_e_t = get_expect_num_args(ctx, e_type);
unsigned num_t = get_expect_num_args(ctx, target);
if (num_t <= num_e_t)
num_e_t -= num_t;
else
num_e_t = 0;
/* Generate meta-variables for arguments */
buffer metas;
buffer is_instance;
for (unsigned i = 0; i < num_e_t; i++) {
e_type = ctx.relaxed_whnf(e_type);
expr meta = ctx.mk_metavar_decl(lctx, binding_domain(e_type));
is_instance.push_back(binding_info(e_type).is_inst_implicit());
metas.push_back(meta);
e = mk_app(e, meta);
e_type = instantiate(binding_body(e_type), meta);
}
/* Unify */
lean_assert(metas.size() == is_instance.size());
if (!ctx.is_def_eq(target, e_type)) {
format msg = format("invalid apply tactic, failed to unify");
msg += pp_indented_expr(s, target);
msg += line() + format("with");
msg += pp_indented_expr(s, e_type);
return mk_tactic_exception(msg, s);
}
/* Synthesize type class instances */
if (use_instances) {
unsigned i = is_instance.size();
while (i > 0) {
--i;
if (!is_instance[i]) continue;
expr const & meta = metas[i];
if (ctx.is_assigned(meta)) continue;
expr meta_type = ctx.instantiate_mvars(ctx.infer(meta));
optional inst = ctx.mk_class_instance(meta_type);
if (!inst) {
return mk_tactic_exception(sstream() << "invalid apply tactic, failed to synthesize type class instance for #"
<< (i+1) << " argument", s);
}
if (!ctx.is_def_eq(meta, *inst)) {
return mk_tactic_exception(sstream() << "invalid apply tactic, failed to assign type class instance for #"
<< (i+1) << " argument", s);
}
}
}
/* Collect unassigned meta-variables */
buffer new_goals;
for (auto m : metas) {
if (!ctx.is_assigned(m)) {
ctx.instantiate_mvars_at_type_of(m);
new_goals.push_back(m);
}
}
metavar_context mctx = ctx.mctx();
if (!add_all)
remove_redundant_goals(mctx, new_goals);
/* Assign, and create new tactic_state */
e = mctx.instantiate_mvars(e);
mctx.assign(head(s.goals()), e);
return mk_tactic_success(set_mctx_goals(s, mctx,
to_list(new_goals.begin(), new_goals.end(), tail(s.goals()))));
} catch(exception & ex) {
return mk_tactic_exception(ex, s);
}
}
vm_obj tactic_apply_core(vm_obj const & md, vm_obj const & all, vm_obj const & insts, vm_obj const & e, vm_obj const & s) {
return apply_core(static_cast(cidx(md)), to_bool(all), to_bool(insts), to_expr(e), to_tactic_state(s));
}
void initialize_apply_tactic() {
DECLARE_VM_BUILTIN(name({"tactic", "apply_core"}), tactic_apply_core);
}
void finalize_apply_tactic() {
}
}