feat(frontends/lean): add version of 'exact' tactic (sexact) that enforces goal type during term elaboration

This commit is contained in:
Leonardo de Moura 2015-03-06 16:48:48 -08:00
parent bd8c4315f1
commit 1490bdad49
17 changed files with 93 additions and 29 deletions

View file

@ -62,6 +62,8 @@ opaque definition clear (e : expr) : tactic := builtin
opaque definition revert (e : expr) : tactic := builtin opaque definition revert (e : expr) : tactic := builtin
opaque definition unfold (e : expr) : tactic := builtin opaque definition unfold (e : expr) : tactic := builtin
opaque definition exact (e : expr) : tactic := builtin opaque definition exact (e : expr) : tactic := builtin
-- rexact is similar to exact, but the goal type is enforced during elaboration
opaque definition sexact (e : expr) : tactic := builtin
opaque definition trace (s : string) : tactic := builtin opaque definition trace (s : string) : tactic := builtin
inductive expr_list : Type := inductive expr_list : Type :=

View file

@ -61,6 +61,8 @@ opaque definition clear (e : expr) : tactic := builtin
opaque definition revert (e : expr) : tactic := builtin opaque definition revert (e : expr) : tactic := builtin
opaque definition unfold (e : expr) : tactic := builtin opaque definition unfold (e : expr) : tactic := builtin
opaque definition exact (e : expr) : tactic := builtin opaque definition exact (e : expr) : tactic := builtin
-- rexact is similar to exact, but the goal type is enforced during elaboration
opaque definition sexact (e : expr) : tactic := builtin
opaque definition trace (s : string) : tactic := builtin opaque definition trace (s : string) : tactic := builtin
inductive expr_list : Type := inductive expr_list : Type :=

View file

@ -213,13 +213,17 @@ static expr parse_begin_end_core(parser & p, pos_info const & pos, name const &
} else { } else {
throw parser_error("invalid 'have' tactic, 'by', 'begin', 'proof', or 'from' expected", p.pos()); throw parser_error("invalid 'have' tactic, 'by', 'begin', 'proof', or 'from' expected", p.pos());
} }
} else if (p.curr_is_token(get_show_tk()) || p.curr_is_token(get_match_tk()) || } else if (p.curr_is_token(get_show_tk())) {
p.curr_is_token(get_assume_tk()) || p.curr_is_token(get_take_tk()) ||
p.curr_is_token(get_fun_tk())) {
auto pos = p.pos(); auto pos = p.pos();
expr t = p.parse_expr(); expr t = p.parse_expr();
t = p.mk_app(get_exact_tac_fn(), t, pos); t = p.mk_app(get_exact_tac_fn(), t, pos);
add_tac(t, pos); add_tac(t, pos);
} else if (p.curr_is_token(get_match_tk()) || p.curr_is_token(get_assume_tk()) ||
p.curr_is_token(get_take_tk()) || p.curr_is_token(get_fun_tk())) {
auto pos = p.pos();
expr t = p.parse_expr();
t = p.mk_app(get_sexact_tac_fn(), t, pos);
add_tac(t, pos);
} else { } else {
auto pos = p.pos(); auto pos = p.pos();
expr t = p.parse_tactic(); expr t = p.parse_tactic();

View file

@ -1464,12 +1464,14 @@ optional<expr> elaborator::get_pre_tactic_for(expr const & mvar) {
optional<tactic> elaborator::pre_tactic_to_tactic(expr const & pre_tac) { optional<tactic> elaborator::pre_tactic_to_tactic(expr const & pre_tac) {
try { try {
bool relax = m_relax_main_opaque; bool relax = m_relax_main_opaque;
auto fn = [=](goal const & g, name_generator const & ngen, expr const & e, bool report_unassigned) { auto fn = [=](goal const & g, name_generator const & ngen, expr const & e, optional<expr> const & expected_type,
bool report_unassigned) {
elaborator aux_elaborator(m_ctx, ngen); elaborator aux_elaborator(m_ctx, ngen);
// Disable tactic hints when processing expressions nested in tactics. // Disable tactic hints when processing expressions nested in tactics.
// We must do it otherwise, it is easy to make the system loop. // We must do it otherwise, it is easy to make the system loop.
bool use_tactic_hints = false; bool use_tactic_hints = false;
return aux_elaborator.elaborate_nested(g.to_context(), e, relax, use_tactic_hints, report_unassigned); return aux_elaborator.elaborate_nested(g.to_context(), expected_type, e,
relax, use_tactic_hints, report_unassigned);
}; };
return optional<tactic>(expr_to_tactic(env(), fn, pre_tac, pip())); return optional<tactic>(expr_to_tactic(env(), fn, pre_tac, pip()));
} catch (expr_to_tactic_exception & ex) { } catch (expr_to_tactic_exception & ex) {
@ -1870,14 +1872,18 @@ static expr translate(environment const & env, list<expr> const & ctx, expr cons
} }
/** \brief Elaborate expression \c e in context \c ctx. */ /** \brief Elaborate expression \c e in context \c ctx. */
pair<expr, constraints> elaborator::elaborate_nested(list<expr> const & ctx, expr const & n, pair<expr, constraints> elaborator::elaborate_nested(list<expr> const & ctx, optional<expr> const & expected_type,
bool relax, bool use_tactic_hints, bool report_unassigned) { expr const & n, bool relax, bool use_tactic_hints,
bool report_unassigned) {
if (infom()) { if (infom()) {
if (auto ps = get_info_tactic_proof_state()) { if (auto ps = get_info_tactic_proof_state()) {
save_proof_state_info(*ps, n); save_proof_state_info(*ps, n);
} }
} }
expr e = translate(env(), ctx, n); expr e = translate(env(), ctx, n);
if (expected_type)
e = copy_tag(e, mk_typed_expr(mk_as_is(*expected_type), e));
m_context.set_ctx(ctx);
m_context.set_ctx(ctx); m_context.set_ctx(ctx);
m_full_context.set_ctx(ctx); m_full_context.set_ctx(ctx);
flet<bool> set_relax(m_relax_main_opaque, relax); flet<bool> set_relax(m_relax_main_opaque, relax);

View file

@ -157,7 +157,7 @@ class elaborator : public coercion_info_manager {
void check_sort_assignments(substitution const & s); void check_sort_assignments(substitution const & s);
expr apply(substitution & s, expr const & e, name_set & univ_params, buffer<name> & new_params); expr apply(substitution & s, expr const & e, name_set & univ_params, buffer<name> & new_params);
std::tuple<expr, level_param_names> apply(substitution & s, expr const & e); std::tuple<expr, level_param_names> apply(substitution & s, expr const & e);
pair<expr, constraints> elaborate_nested(list<expr> const & g, expr const & e, pair<expr, constraints> elaborate_nested(list<expr> const & ctx, optional<expr> const & expected_type, expr const & e,
bool relax, bool use_tactic_hints, bool report_unassigned); bool relax, bool use_tactic_hints, bool report_unassigned);
expr const & get_equation_fn(expr const & eq) const; expr const & get_equation_fn(expr const & eq) const;

View file

@ -36,7 +36,7 @@ tactic mk_info_tactic(elaborate_fn const & fn, expr const & e) {
// create dummy variable just to communicate position to the elaborator // create dummy variable just to communicate position to the elaborator
expr dummy = mk_sort(mk_level_zero(), e.get_tag()); expr dummy = mk_sort(mk_level_zero(), e.get_tag());
scoped_info_tactic_proof_state scope(ps); scoped_info_tactic_proof_state scope(ps);
fn(goal(), name_generator("dummy"), dummy, false); fn(goal(), name_generator("dummy"), dummy, none_expr(), false);
return ps; return ps;
}); });
} }

View file

@ -94,6 +94,7 @@ name const * g_tactic_now = nullptr;
name const * g_tactic_opt_expr_list = nullptr; name const * g_tactic_opt_expr_list = nullptr;
name const * g_tactic_or_else = nullptr; name const * g_tactic_or_else = nullptr;
name const * g_tactic_par = nullptr; name const * g_tactic_par = nullptr;
name const * g_tactic_sexact = nullptr;
name const * g_tactic_state = nullptr; name const * g_tactic_state = nullptr;
name const * g_tactic_rename = nullptr; name const * g_tactic_rename = nullptr;
name const * g_tactic_repeat = nullptr; name const * g_tactic_repeat = nullptr;
@ -204,6 +205,7 @@ void initialize_constants() {
g_tactic_opt_expr_list = new name{"tactic", "opt_expr_list"}; g_tactic_opt_expr_list = new name{"tactic", "opt_expr_list"};
g_tactic_or_else = new name{"tactic", "or_else"}; g_tactic_or_else = new name{"tactic", "or_else"};
g_tactic_par = new name{"tactic", "par"}; g_tactic_par = new name{"tactic", "par"};
g_tactic_sexact = new name{"tactic", "sexact"};
g_tactic_state = new name{"tactic", "state"}; g_tactic_state = new name{"tactic", "state"};
g_tactic_rename = new name{"tactic", "rename"}; g_tactic_rename = new name{"tactic", "rename"};
g_tactic_repeat = new name{"tactic", "repeat"}; g_tactic_repeat = new name{"tactic", "repeat"};
@ -315,6 +317,7 @@ void finalize_constants() {
delete g_tactic_opt_expr_list; delete g_tactic_opt_expr_list;
delete g_tactic_or_else; delete g_tactic_or_else;
delete g_tactic_par; delete g_tactic_par;
delete g_tactic_sexact;
delete g_tactic_state; delete g_tactic_state;
delete g_tactic_rename; delete g_tactic_rename;
delete g_tactic_repeat; delete g_tactic_repeat;
@ -425,6 +428,7 @@ name const & get_tactic_now_name() { return *g_tactic_now; }
name const & get_tactic_opt_expr_list_name() { return *g_tactic_opt_expr_list; } name const & get_tactic_opt_expr_list_name() { return *g_tactic_opt_expr_list; }
name const & get_tactic_or_else_name() { return *g_tactic_or_else; } name const & get_tactic_or_else_name() { return *g_tactic_or_else; }
name const & get_tactic_par_name() { return *g_tactic_par; } name const & get_tactic_par_name() { return *g_tactic_par; }
name const & get_tactic_sexact_name() { return *g_tactic_sexact; }
name const & get_tactic_state_name() { return *g_tactic_state; } name const & get_tactic_state_name() { return *g_tactic_state; }
name const & get_tactic_rename_name() { return *g_tactic_rename; } name const & get_tactic_rename_name() { return *g_tactic_rename; }
name const & get_tactic_repeat_name() { return *g_tactic_repeat; } name const & get_tactic_repeat_name() { return *g_tactic_repeat; }

View file

@ -96,6 +96,7 @@ name const & get_tactic_now_name();
name const & get_tactic_opt_expr_list_name(); name const & get_tactic_opt_expr_list_name();
name const & get_tactic_or_else_name(); name const & get_tactic_or_else_name();
name const & get_tactic_par_name(); name const & get_tactic_par_name();
name const & get_tactic_sexact_name();
name const & get_tactic_state_name(); name const & get_tactic_state_name();
name const & get_tactic_rename_name(); name const & get_tactic_rename_name();
name const & get_tactic_repeat_name(); name const & get_tactic_repeat_name();

View file

@ -89,6 +89,7 @@ tactic.now
tactic.opt_expr_list tactic.opt_expr_list
tactic.or_else tactic.or_else
tactic.par tactic.par
tactic.sexact
tactic.state tactic.state
tactic.rename tactic.rename
tactic.repeat tactic.repeat

View file

@ -210,7 +210,7 @@ tactic apply_tactic_core(elaborate_fn const & elab, expr const & e, subgoals_act
name_generator ngen = s.get_ngen(); name_generator ngen = s.get_ngen();
expr new_e; expr new_e;
buffer<constraint> cs; buffer<constraint> cs;
auto ecs = elab(g, ngen.mk_child(), e, false); auto ecs = elab(g, ngen.mk_child(), e, none_expr(), false);
new_e = ecs.first; new_e = ecs.first;
to_buffer(ecs.second, cs); to_buffer(ecs.second, cs);
to_buffer(s.get_postponed(), cs); to_buffer(s.get_postponed(), cs);

View file

@ -31,18 +31,24 @@ bool solve_constraints(environment const & env, io_state const & ios, proof_stat
} }
optional<expr> elaborate_with_respect_to(environment const & env, io_state const & ios, elaborate_fn const & elab, optional<expr> elaborate_with_respect_to(environment const & env, io_state const & ios, elaborate_fn const & elab,
proof_state & s, expr const & e, optional<expr> const & expected_type, proof_state & s, expr const & e, optional<expr> const & _expected_type,
bool report_unassigned) { bool report_unassigned, bool enforce_type_during_elaboration) {
name_generator ngen = s.get_ngen(); name_generator ngen = s.get_ngen();
substitution subst = s.get_subst(); substitution subst = s.get_subst();
goals const & gs = s.get_goals(); goals const & gs = s.get_goals();
optional<expr> expected_type = _expected_type;
if (expected_type)
expected_type = subst.instantiate(*expected_type);
if (empty(gs)) if (empty(gs))
return none_expr(); return none_expr();
auto ecs = elab(head(gs), ngen.mk_child(), e, report_unassigned); optional<expr> elab_expected_type;
if (enforce_type_during_elaboration)
elab_expected_type = expected_type;
auto ecs = elab(head(gs), ngen.mk_child(), e, elab_expected_type, report_unassigned);
expr new_e = ecs.first; expr new_e = ecs.first;
buffer<constraint> cs; buffer<constraint> cs;
to_buffer(ecs.second, cs); to_buffer(ecs.second, cs);
if (cs.empty() && !expected_type) { if (cs.empty() && (!expected_type || enforce_type_during_elaboration)) {
// easy case: no constraints to be solved // easy case: no constraints to be solved
s = proof_state(s, ngen); s = proof_state(s, ngen);
return some_expr(new_e); return some_expr(new_e);
@ -51,7 +57,7 @@ optional<expr> elaborate_with_respect_to(environment const & env, io_state const
if (expected_type) { if (expected_type) {
auto tc = mk_type_checker(env, ngen.mk_child()); auto tc = mk_type_checker(env, ngen.mk_child());
auto e_t_cs = tc->infer(new_e); auto e_t_cs = tc->infer(new_e);
expr t = subst.instantiate(*expected_type); expr t = *expected_type;
e_t_cs.second.linearize(cs); e_t_cs.second.linearize(cs);
auto d_cs = tc->is_def_eq(e_t_cs.first, t); auto d_cs = tc->is_def_eq(e_t_cs.first, t);
if (!d_cs.first) { if (!d_cs.first) {

View file

@ -16,9 +16,11 @@ bool solve_constraints(environment const & env, io_state const & ios, proof_stat
1- goal that will provide the context where the expression will be elaborated 1- goal that will provide the context where the expression will be elaborated
2- name generator 2- name generator
3- expression to be elaborated 3- expression to be elaborated
4- a flag indicating whether the elaborator should report unassigned/unsolved placeholders 4- expected type
5- a flag indicating whether the elaborator should report unassigned/unsolved placeholders
*/ */
typedef std::function<pair<expr, constraints>(goal const &, name_generator const &, expr const &, bool)> elaborate_fn; typedef std::function<pair<expr, constraints>(goal const &, name_generator const &, expr const &,
optional<expr> const &, bool)> elaborate_fn;
/** \brief Try to elaborate expression \c e using the elaboration function \c elab. The elaboration is performed /** \brief Try to elaborate expression \c e using the elaboration function \c elab. The elaboration is performed
with respect to (local context of) the first goal in \c s. The constraints generated during elaboration with respect to (local context of) the first goal in \c s. The constraints generated during elaboration
@ -31,6 +33,7 @@ typedef std::function<pair<expr, constraints>(goal const &, name_generator const
is not modified. is not modified.
*/ */
optional<expr> elaborate_with_respect_to(environment const & env, io_state const & ios, elaborate_fn const & elab, optional<expr> elaborate_with_respect_to(environment const & env, io_state const & ios, elaborate_fn const & elab,
proof_state & s, expr const & e, optional<expr> const & expected_type = optional<expr>(), proof_state & s, expr const & e,
bool report_unassigned = false); optional<expr> const & expected_type = optional<expr>(),
bool report_unassigned = false, bool enforce_type_during_elaboration = false);
} }

View file

@ -11,7 +11,7 @@ Author: Leonardo de Moura
#include "library/tactic/expr_to_tactic.h" #include "library/tactic/expr_to_tactic.h"
namespace lean { namespace lean {
tactic exact_tactic(elaborate_fn const & elab, expr const & e) { tactic exact_tactic(elaborate_fn const & elab, expr const & e, bool enforce_type_during_elaboration) {
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; proof_state new_s = s;
goals const & gs = new_s.get_goals(); goals const & gs = new_s.get_goals();
@ -21,7 +21,8 @@ tactic exact_tactic(elaborate_fn const & elab, expr const & e) {
} }
expr t = head(gs).get_type(); expr t = head(gs).get_type();
bool report_unassigned = false; bool report_unassigned = false;
if (auto new_e = elaborate_with_respect_to(env, ios, elab, new_s, e, some_expr(t), report_unassigned)) { if (auto new_e = elaborate_with_respect_to(env, ios, elab, new_s, e, some_expr(t),
report_unassigned, enforce_type_during_elaboration)) {
goals const & gs = new_s.get_goals(); goals const & gs = new_s.get_goals();
goal const & g = head(gs); goal const & g = head(gs);
expr new_p = g.abstract(*new_e); expr new_p = g.abstract(*new_e);
@ -35,14 +36,23 @@ tactic exact_tactic(elaborate_fn const & elab, expr const & e) {
} }
static expr * g_exact_tac_fn = nullptr; static expr * g_exact_tac_fn = nullptr;
expr const & get_exact_tac_fn() { return *g_exact_tac_fn; } static expr * g_sexact_tac_fn = nullptr;
expr const & get_exact_tac_fn() { return *g_exact_tac_fn; }
expr const & get_sexact_tac_fn() { return *g_sexact_tac_fn; }
void initialize_exact_tactic() { void initialize_exact_tactic() {
name const & exact_tac_name = get_tactic_exact_name(); name const & exact_tac_name = get_tactic_exact_name();
name const & sexact_tac_name = get_tactic_sexact_name();
g_exact_tac_fn = new expr(Const(exact_tac_name)); g_exact_tac_fn = new expr(Const(exact_tac_name));
g_sexact_tac_fn = new expr(Const(sexact_tac_name));
register_tac(exact_tac_name, register_tac(exact_tac_name,
[](type_checker &, elaborate_fn const & fn, expr const & e, pos_info_provider const *) { [](type_checker &, elaborate_fn const & fn, expr const & e, pos_info_provider const *) {
check_tactic_expr(app_arg(e), "invalid 'exact' tactic, invalid argument"); check_tactic_expr(app_arg(e), "invalid 'exact' tactic, invalid argument");
return exact_tactic(fn, get_tactic_expr_expr(app_arg(e))); return exact_tactic(fn, get_tactic_expr_expr(app_arg(e)), false);
});
register_tac(sexact_tac_name,
[](type_checker &, elaborate_fn const & fn, expr const & e, pos_info_provider const *) {
check_tactic_expr(app_arg(e), "invalid 'exact' tactic, invalid argument");
return exact_tactic(fn, get_tactic_expr_expr(app_arg(e)), true);
}); });
} }
void finalize_exact_tactic() { void finalize_exact_tactic() {

View file

@ -9,8 +9,8 @@ Author: Leonardo de Moura
namespace lean { namespace lean {
expr const & get_exact_tac_fn(); expr const & get_exact_tac_fn();
expr const & get_sexact_tac_fn();
/** \brief Solve first goal iff it is definitionally equal to type of \c e */ /** \brief Solve first goal iff it is definitionally equal to type of \c e */
tactic exact_tactic(expr const & e);
void initialize_exact_tactic(); void initialize_exact_tactic();
void finalize_exact_tactic(); void finalize_exact_tactic();
} }

View file

@ -579,7 +579,7 @@ class rewrite_fn {
} }
optional<expr> fold(expr const & type, expr const & e, occurrence const & occ) { optional<expr> fold(expr const & type, expr const & e, occurrence const & occ) {
auto ecs = m_elab(m_g, m_ngen.mk_child(), e, false); auto ecs = m_elab(m_g, m_ngen.mk_child(), e, none_expr(), false);
expr new_e = ecs.first; expr new_e = ecs.first;
if (ecs.second) if (ecs.second)
return none_expr(); // contain constraints... return none_expr(); // contain constraints...
@ -651,7 +651,7 @@ class rewrite_fn {
} }
optional<expr> unify_with(expr const & t, expr const & e) { optional<expr> unify_with(expr const & t, expr const & e) {
auto ecs = m_elab(m_g, m_ngen.mk_child(), e, false); auto ecs = m_elab(m_g, m_ngen.mk_child(), e, none_expr(), false);
expr new_e = ecs.first; expr new_e = ecs.first;
buffer<constraint> cs; buffer<constraint> cs;
to_buffer(ecs.second, cs); to_buffer(ecs.second, cs);
@ -832,7 +832,7 @@ class rewrite_fn {
unify_result unify_target(expr const & t, expr const & orig_elem, bool is_goal) { unify_result unify_target(expr const & t, expr const & orig_elem, bool is_goal) {
try { try {
expr rule = get_rewrite_rule(orig_elem); expr rule = get_rewrite_rule(orig_elem);
auto rcs = m_elab(m_g, m_ngen.mk_child(), rule, false); auto rcs = m_elab(m_g, m_ngen.mk_child(), rule, none_expr(), false);
rule = rcs.first; rule = rcs.first;
buffer<constraint> cs; buffer<constraint> cs;
to_buffer(rcs.second, cs); to_buffer(rcs.second, cs);
@ -1096,11 +1096,11 @@ class rewrite_fn {
expr rule = get_rewrite_rule(elem); expr rule = get_rewrite_rule(elem);
expr new_elem; expr new_elem;
if (has_rewrite_pattern(elem)) { if (has_rewrite_pattern(elem)) {
expr pattern = m_elab(m_g, m_ngen.mk_child(), get_rewrite_pattern(elem), false).first; expr pattern = m_elab(m_g, m_ngen.mk_child(), get_rewrite_pattern(elem), none_expr(), false).first;
expr new_args[2] = { rule, pattern }; expr new_args[2] = { rule, pattern };
new_elem = mk_macro(macro_def(elem), 2, new_args); new_elem = mk_macro(macro_def(elem), 2, new_args);
} else { } else {
rule = m_elab(m_g, m_ngen.mk_child(), rule, false).first; rule = m_elab(m_g, m_ngen.mk_child(), rule, none_expr(), false).first;
new_elem = mk_macro(macro_def(elem), 1, &rule); new_elem = mk_macro(macro_def(elem), 1, &rule);
} }
return process_rewrite_step(new_elem, elem); return process_rewrite_step(new_elem, elem);

View file

@ -0,0 +1,12 @@
example (a b c : Prop) : a ∧ b ↔ b ∧ a :=
begin
apply iff.intro,
{intro H,
match H with
| and.intro H₁ H₂ := by apply and.intro; assumption
end},
{intro H,
match H with
| and.intro H₁ H₂ := by apply and.intro; assumption
end},
end

View file

@ -0,0 +1,13 @@
import data.nat
open nat
theorem tst (a b : nat) (H : a = 0) : a + b = b :=
begin
revert H,
match a with
| zero := λ H, by rewrite zero_add
| (n+1) := λ H, nat.no_confusion H
end
end
print definition tst