feat(library/tactic): automate "generalize-intro-induction/cases" idiom
closes #645
This commit is contained in:
parent
e5a82ef516
commit
ca110012d8
9 changed files with 148 additions and 55 deletions
|
@ -91,9 +91,9 @@ definition rewrite_tac (e : expr_list) : tactic := builtin
|
||||||
definition xrewrite_tac (e : expr_list) : tactic := builtin
|
definition xrewrite_tac (e : expr_list) : tactic := builtin
|
||||||
definition krewrite_tac (e : expr_list) : tactic := builtin
|
definition krewrite_tac (e : expr_list) : tactic := builtin
|
||||||
|
|
||||||
definition cases (id : identifier) (ids : opt_identifier_list) : tactic := builtin
|
definition cases (h : expr) (ids : opt_identifier_list) : tactic := builtin
|
||||||
|
|
||||||
definition induction (h : identifier) (rec : using_expr) (ids : opt_identifier_list) : tactic := builtin
|
definition induction (h : expr) (rec : using_expr) (ids : opt_identifier_list) : tactic := builtin
|
||||||
|
|
||||||
definition intros (ids : opt_identifier_list) : tactic := builtin
|
definition intros (ids : opt_identifier_list) : tactic := builtin
|
||||||
|
|
||||||
|
|
|
@ -91,9 +91,9 @@ definition rewrite_tac (e : expr_list) : tactic := builtin
|
||||||
definition xrewrite_tac (e : expr_list) : tactic := builtin
|
definition xrewrite_tac (e : expr_list) : tactic := builtin
|
||||||
definition krewrite_tac (e : expr_list) : tactic := builtin
|
definition krewrite_tac (e : expr_list) : tactic := builtin
|
||||||
|
|
||||||
definition cases (id : identifier) (ids : opt_identifier_list) : tactic := builtin
|
definition cases (h : expr) (ids : opt_identifier_list) : tactic := builtin
|
||||||
|
|
||||||
definition induction (h : identifier) (rec : using_expr) (ids : opt_identifier_list) : tactic := builtin
|
definition induction (h : expr) (rec : using_expr) (ids : opt_identifier_list) : tactic := builtin
|
||||||
|
|
||||||
definition intros (ids : opt_identifier_list) : tactic := builtin
|
definition intros (ids : opt_identifier_list) : tactic := builtin
|
||||||
|
|
||||||
|
|
|
@ -1588,7 +1588,8 @@ expr parser::parse_tactic_nud() {
|
||||||
r = mk_app(r, parse_tactic_option_num(), id_pos);
|
r = mk_app(r, parse_tactic_option_num(), id_pos);
|
||||||
} else {
|
} else {
|
||||||
unsigned rbp;
|
unsigned rbp;
|
||||||
if (arity == 1 || (arity == 2 && i == 0 && is_tactic_opt_identifier_list_type(ds[1])))
|
if ((arity == 1) ||
|
||||||
|
(arity >= 2 && i == 0 && (is_tactic_opt_identifier_list_type(ds[1]) || is_tactic_using_expr(ds[1]))))
|
||||||
rbp = 0;
|
rbp = 0;
|
||||||
else
|
else
|
||||||
rbp = get_max_prec();
|
rbp = get_max_prec();
|
||||||
|
|
|
@ -6,6 +6,7 @@ Author: Leonardo de Moura
|
||||||
*/
|
*/
|
||||||
#include "library/constants.h"
|
#include "library/constants.h"
|
||||||
#include "kernel/abstract.h"
|
#include "kernel/abstract.h"
|
||||||
|
#include "kernel/instantiate.h"
|
||||||
#include "kernel/kernel_exception.h"
|
#include "kernel/kernel_exception.h"
|
||||||
#include "library/reducible.h"
|
#include "library/reducible.h"
|
||||||
#include "library/util.h"
|
#include "library/util.h"
|
||||||
|
@ -18,50 +19,73 @@ expr mk_generalize_tactic_expr(expr const & e, name const & id) {
|
||||||
e, mk_constant(id));
|
e, mk_constant(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
optional<proof_state> generalize_core(environment const & env, io_state const & ios, elaborate_fn const & elab,
|
||||||
|
expr const & e, name const & x, proof_state const & s, name const & tac_name,
|
||||||
|
bool intro) {
|
||||||
|
proof_state new_s = s;
|
||||||
|
if (auto new_e = elaborate_with_respect_to(env, ios, elab, new_s, e)) {
|
||||||
|
name_generator ngen = new_s.get_ngen();
|
||||||
|
substitution subst = new_s.get_subst();
|
||||||
|
goals const & gs = new_s.get_goals();
|
||||||
|
goal const & g = head(gs);
|
||||||
|
auto tc = mk_type_checker(env, ngen.mk_child());
|
||||||
|
auto e_t_cs = tc->infer(*new_e);
|
||||||
|
if (e_t_cs.second) {
|
||||||
|
throw_tactic_exception_if_enabled(s, sstream() << "invalid '" << tac_name << "' tactic, unification constraints "
|
||||||
|
"have been generated when inferring type");
|
||||||
|
return none_proof_state(); // constraints were generated
|
||||||
|
}
|
||||||
|
expr e_t = e_t_cs.first;
|
||||||
|
expr t = subst.instantiate(g.get_type());
|
||||||
|
name n;
|
||||||
|
if (is_constant(e))
|
||||||
|
n = const_name(e);
|
||||||
|
else if (is_local(e))
|
||||||
|
n = local_pp_name(e);
|
||||||
|
else
|
||||||
|
n = x;
|
||||||
|
expr new_t, new_m, new_val;
|
||||||
|
if (intro) {
|
||||||
|
buffer<expr> hyps;
|
||||||
|
g.get_hyps(hyps);
|
||||||
|
expr new_h = mk_local(ngen.next(), get_unused_name(x, hyps), e_t, binder_info());
|
||||||
|
new_t = instantiate(abstract(t, *new_e), new_h);
|
||||||
|
new_m = mk_metavar(ngen.next(), Pi(hyps, Pi(new_h, new_t)));
|
||||||
|
new_m = mk_app(new_m, hyps);
|
||||||
|
new_val = mk_app(new_m, *new_e);
|
||||||
|
new_m = mk_app(new_m, new_h);
|
||||||
|
} else {
|
||||||
|
new_t = mk_pi(n, e_t, abstract(t, *new_e));
|
||||||
|
new_m = g.mk_meta(ngen.next(), new_t);
|
||||||
|
new_val = mk_app(new_m, *new_e);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
check_term(*tc, g.abstract(new_t));
|
||||||
|
} catch (kernel_exception const & ex) {
|
||||||
|
std::shared_ptr<kernel_exception> ex_ptr(static_cast<kernel_exception*>(ex.clone()));
|
||||||
|
throw_tactic_exception_if_enabled(s, [=](formatter const & fmt) {
|
||||||
|
format r = format("invalid '") + format(tac_name) + format("' tactic, type error");
|
||||||
|
r += line();
|
||||||
|
r += ex_ptr->pp(fmt);
|
||||||
|
return r;
|
||||||
|
});
|
||||||
|
return none_proof_state();
|
||||||
|
}
|
||||||
|
assign(subst, g, new_val);
|
||||||
|
goal new_g(new_m, new_t);
|
||||||
|
return some(proof_state(new_s, goals(new_g, tail(gs)), subst, ngen));
|
||||||
|
}
|
||||||
|
return none_proof_state();
|
||||||
|
}
|
||||||
|
|
||||||
|
optional<proof_state> generalize(environment const & env, io_state const & ios, elaborate_fn const & elab,
|
||||||
|
expr const & e, name const & x, proof_state const & s) {
|
||||||
|
return generalize_core(env, ios, elab, e, x, s, "generalize", false);
|
||||||
|
}
|
||||||
|
|
||||||
tactic generalize_tactic(elaborate_fn const & elab, expr const & e, name const & x) {
|
tactic generalize_tactic(elaborate_fn const & elab, expr const & e, name const & x) {
|
||||||
return tactic01([=](environment const & env, io_state const & ios, proof_state const & s) {
|
return tactic01([=](environment const & env, io_state const & ios, proof_state const & s) {
|
||||||
proof_state new_s = s;
|
return generalize(env, ios, elab, e, x, s);
|
||||||
if (auto new_e = elaborate_with_respect_to(env, ios, elab, new_s, e)) {
|
|
||||||
name_generator ngen = new_s.get_ngen();
|
|
||||||
substitution subst = new_s.get_subst();
|
|
||||||
goals const & gs = new_s.get_goals();
|
|
||||||
goal const & g = head(gs);
|
|
||||||
auto tc = mk_type_checker(env, ngen.mk_child());
|
|
||||||
auto e_t_cs = tc->infer(*new_e);
|
|
||||||
if (e_t_cs.second) {
|
|
||||||
throw_tactic_exception_if_enabled(s, "invalid 'generalize' tactic, unification constraints "
|
|
||||||
"have been generated when inferring type");
|
|
||||||
return none_proof_state(); // constraints were generated
|
|
||||||
}
|
|
||||||
expr e_t = e_t_cs.first;
|
|
||||||
expr t = subst.instantiate(g.get_type());
|
|
||||||
name n;
|
|
||||||
if (is_constant(e))
|
|
||||||
n = const_name(e);
|
|
||||||
else if (is_local(e))
|
|
||||||
n = local_pp_name(e);
|
|
||||||
else
|
|
||||||
n = x;
|
|
||||||
expr new_t = mk_pi(n, e_t, abstract(t, *new_e));
|
|
||||||
expr new_m = g.mk_meta(ngen.next(), new_t);
|
|
||||||
try {
|
|
||||||
check_term(*tc, g.abstract(new_t));
|
|
||||||
} catch (kernel_exception const & ex) {
|
|
||||||
std::shared_ptr<kernel_exception> ex_ptr(static_cast<kernel_exception*>(ex.clone()));
|
|
||||||
throw_tactic_exception_if_enabled(s, [=](formatter const & fmt) {
|
|
||||||
format r = format("invalid 'generalize' tactic, type error");
|
|
||||||
r += line();
|
|
||||||
r += ex_ptr->pp(fmt);
|
|
||||||
return r;
|
|
||||||
});
|
|
||||||
return none_proof_state();
|
|
||||||
}
|
|
||||||
|
|
||||||
assign(subst, g, mk_app(new_m, *new_e));
|
|
||||||
goal new_g(new_m, new_t);
|
|
||||||
return some(proof_state(new_s, goals(new_g, tail(gs)), subst, ngen));
|
|
||||||
}
|
|
||||||
return none_proof_state();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,9 @@ Author: Leonardo de Moura
|
||||||
|
|
||||||
namespace lean {
|
namespace lean {
|
||||||
expr mk_generalize_tactic_expr(expr const & e, name const & id);
|
expr mk_generalize_tactic_expr(expr const & e, name const & id);
|
||||||
|
optional<proof_state> generalize_core(environment const & env, io_state const & ios, elaborate_fn const & elab,
|
||||||
|
expr const & e, name const & x, proof_state const & s, name const & tac_name,
|
||||||
|
bool intro);
|
||||||
void initialize_generalize_tactic();
|
void initialize_generalize_tactic();
|
||||||
void finalize_generalize_tactic();
|
void finalize_generalize_tactic();
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ Author: Leonardo de Moura
|
||||||
#include "library/locals.h"
|
#include "library/locals.h"
|
||||||
#include "library/tactic/tactic.h"
|
#include "library/tactic/tactic.h"
|
||||||
#include "library/tactic/expr_to_tactic.h"
|
#include "library/tactic/expr_to_tactic.h"
|
||||||
|
#include "library/tactic/generalize_tactic.h"
|
||||||
#include "library/tactic/class_instance_synth.h"
|
#include "library/tactic/class_instance_synth.h"
|
||||||
|
|
||||||
namespace lean {
|
namespace lean {
|
||||||
|
@ -386,14 +387,30 @@ tactic induction_tactic(name const & H, optional<name> rec, list<name> const & i
|
||||||
return tactic01(fn);
|
return tactic01(fn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tactic induction_tactic(elaborate_fn const & elab, expr const & H, optional<name> rec, list<name> const & ids, expr const & ref) {
|
||||||
|
auto fn = [=](environment const & env, io_state const & ios, proof_state const & ps) {
|
||||||
|
name Haux{"H", "ind"};
|
||||||
|
auto new_ps = generalize_core(env, ios, elab, H, Haux, ps, "induction", true);
|
||||||
|
if (!new_ps)
|
||||||
|
return proof_state_seq();
|
||||||
|
goal g = head(new_ps->get_goals());
|
||||||
|
expr new_H = app_arg(g.get_meta());
|
||||||
|
lean_assert(is_local(new_H));
|
||||||
|
name H_name = local_pp_name(new_H);
|
||||||
|
return induction_tactic(H_name, rec, ids, ref)(env, ios, *new_ps);
|
||||||
|
};
|
||||||
|
return tactic(fn);
|
||||||
|
}
|
||||||
|
|
||||||
void initialize_induction_tactic() {
|
void initialize_induction_tactic() {
|
||||||
register_tac(name{"tactic", "induction"},
|
register_tac(name{"tactic", "induction"},
|
||||||
[](type_checker &, elaborate_fn const &, expr const & e, pos_info_provider const *) {
|
[](type_checker &, elaborate_fn const & elab, expr const & e, pos_info_provider const *) {
|
||||||
buffer<expr> args;
|
buffer<expr> args;
|
||||||
get_app_args(e, args);
|
get_app_args(e, args);
|
||||||
if (args.size() != 3)
|
if (args.size() != 3)
|
||||||
throw expr_to_tactic_exception(e, "invalid 'induction' tactic, insufficient number of arguments");
|
throw expr_to_tactic_exception(e, "invalid 'induction' tactic, insufficient number of arguments");
|
||||||
name H = tactic_expr_to_id(args[0], "invalid 'induction' tactic, argument must be an identifier");
|
check_tactic_expr(args[0], "invalid 'induction' tactic, argument must be an expression");
|
||||||
|
expr H = get_tactic_expr_expr(args[0]);
|
||||||
buffer<name> ids;
|
buffer<name> ids;
|
||||||
get_tactic_id_list_elements(args[2], ids, "invalid 'induction' tactic, list of identifiers expected");
|
get_tactic_id_list_elements(args[2], ids, "invalid 'induction' tactic, list of identifiers expected");
|
||||||
check_tactic_expr(args[1], "invalid 'induction' tactic, invalid argument");
|
check_tactic_expr(args[1], "invalid 'induction' tactic, invalid argument");
|
||||||
|
@ -403,9 +420,15 @@ void initialize_induction_tactic() {
|
||||||
}
|
}
|
||||||
name const & cname = const_name(rec);
|
name const & cname = const_name(rec);
|
||||||
if (cname == get_tactic_none_expr_name()) {
|
if (cname == get_tactic_none_expr_name()) {
|
||||||
return induction_tactic(H, optional<name>(), to_list(ids.begin(), ids.end()), e);
|
if (is_local(H))
|
||||||
|
return induction_tactic(local_pp_name(H), optional<name>(), to_list(ids.begin(), ids.end()), e);
|
||||||
|
else
|
||||||
|
return induction_tactic(elab, H, optional<name>(), to_list(ids.begin(), ids.end()), e);
|
||||||
} else {
|
} else {
|
||||||
return induction_tactic(H, optional<name>(cname), to_list(ids.begin(), ids.end()), e);
|
if (is_local(H))
|
||||||
|
return induction_tactic(local_pp_name(H), optional<name>(cname), to_list(ids.begin(), ids.end()), e);
|
||||||
|
else
|
||||||
|
return induction_tactic(elab, H, optional<name>(), to_list(ids.begin(), ids.end()), e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ Author: Leonardo de Moura
|
||||||
#include "library/tactic/expr_to_tactic.h"
|
#include "library/tactic/expr_to_tactic.h"
|
||||||
#include "library/tactic/class_instance_synth.h"
|
#include "library/tactic/class_instance_synth.h"
|
||||||
#include "library/tactic/inversion_tactic.h"
|
#include "library/tactic/inversion_tactic.h"
|
||||||
|
#include "library/tactic/generalize_tactic.h"
|
||||||
#include "library/tactic/clear_tactic.h"
|
#include "library/tactic/clear_tactic.h"
|
||||||
|
|
||||||
namespace lean {
|
namespace lean {
|
||||||
|
@ -1112,13 +1113,32 @@ tactic inversion_tactic(name const & n, list<name> const & ids) {
|
||||||
return tactic01(fn);
|
return tactic01(fn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tactic inversion_tactic(elaborate_fn const & elab, expr const & H, list<name> const & ids) {
|
||||||
|
auto fn = [=](environment const & env, io_state const & ios, proof_state const & ps) {
|
||||||
|
name Haux{"H", "cases"};
|
||||||
|
auto new_ps = generalize_core(env, ios, elab, H, Haux, ps, "cases", true);
|
||||||
|
if (!new_ps)
|
||||||
|
return proof_state_seq();
|
||||||
|
goal g = head(new_ps->get_goals());
|
||||||
|
expr new_H = app_arg(g.get_meta());
|
||||||
|
lean_assert(is_local(new_H));
|
||||||
|
name H_name = local_pp_name(new_H);
|
||||||
|
return inversion_tactic(H_name, ids)(env, ios, *new_ps);
|
||||||
|
};
|
||||||
|
return tactic(fn);
|
||||||
|
}
|
||||||
|
|
||||||
void initialize_inversion_tactic() {
|
void initialize_inversion_tactic() {
|
||||||
register_tac(get_tactic_cases_name(),
|
register_tac(get_tactic_cases_name(),
|
||||||
[](type_checker &, elaborate_fn const &, expr const & e, pos_info_provider const *) {
|
[](type_checker &, elaborate_fn const & elab, expr const & e, pos_info_provider const *) {
|
||||||
name n = tactic_expr_to_id(app_arg(app_fn(e)), "invalid 'cases' tactic, argument must be an identifier");
|
check_tactic_expr(app_arg(app_fn(e)), "invalid 'cases' tactic, argument must be an expression");
|
||||||
|
expr H = get_tactic_expr_expr(app_arg(app_fn(e)));
|
||||||
buffer<name> ids;
|
buffer<name> ids;
|
||||||
get_tactic_id_list_elements(app_arg(e), ids, "invalid 'cases' tactic, list of identifiers expected");
|
get_tactic_id_list_elements(app_arg(e), ids, "invalid 'cases' tactic, list of identifiers expected");
|
||||||
return inversion_tactic(n, to_list(ids.begin(), ids.end()));
|
if (is_local(H))
|
||||||
|
return inversion_tactic(local_pp_name(H), to_list(ids.begin(), ids.end()));
|
||||||
|
else
|
||||||
|
return inversion_tactic(elab, H, to_list(ids.begin(), ids.end()));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
void finalize_inversion_tactic() {}
|
void finalize_inversion_tactic() {}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
errors.lean:4:0: error: unknown identifier 'a'
|
errors.lean:4:0: error: unknown identifier 'a'
|
||||||
tst1 : nat → nat → nat
|
tst1 : nat → nat → nat
|
||||||
errors.lean:12:16: error: invalid 'begin-end' expression, ',' expected
|
errors.lean:12:8: error: unknown identifier 'add'
|
||||||
errors.lean:22:12: error: unknown identifier 'b'
|
errors.lean:22:12: error: unknown identifier 'b'
|
||||||
tst3 : A → A → A
|
tst3 : A → A → A
|
||||||
foo.tst1 : ℕ → ℕ → ℕ
|
foo.tst1 : ℕ → ℕ → ℕ
|
||||||
|
|
22
tests/lean/run/645a.lean
Normal file
22
tests/lean/run/645a.lean
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
open bool
|
||||||
|
|
||||||
|
definition to_pred {A : Type} (p : A → bool) : A → Prop :=
|
||||||
|
λ a, p a = tt
|
||||||
|
|
||||||
|
definition to_pred_dec₁ [instance] {A : Type} (p : A → bool)
|
||||||
|
: decidable_pred (to_pred p) :=
|
||||||
|
begin
|
||||||
|
intro a, unfold to_pred,
|
||||||
|
induction p a,
|
||||||
|
right, contradiction,
|
||||||
|
left, reflexivity
|
||||||
|
end
|
||||||
|
|
||||||
|
definition to_pred_dec₂ [instance] {A : Type} (p : A → bool)
|
||||||
|
: decidable_pred (to_pred p) :=
|
||||||
|
begin
|
||||||
|
intro a, unfold to_pred,
|
||||||
|
cases p a,
|
||||||
|
right, contradiction,
|
||||||
|
left, reflexivity
|
||||||
|
end
|
Loading…
Reference in a new issue