feat(library/tactic/rewrite_tactic): improve rewrite tactic error messages
closes #471
This commit is contained in:
parent
14aeac180a
commit
cda19f5aa6
3 changed files with 180 additions and 12 deletions
|
@ -11,6 +11,7 @@ Author: Leonardo de Moura
|
||||||
#include "util/rb_map.h"
|
#include "util/rb_map.h"
|
||||||
#include "util/sexpr/option_declarations.h"
|
#include "util/sexpr/option_declarations.h"
|
||||||
#include "kernel/instantiate.h"
|
#include "kernel/instantiate.h"
|
||||||
|
#include "kernel/error_msgs.h"
|
||||||
#include "kernel/abstract.h"
|
#include "kernel/abstract.h"
|
||||||
#include "kernel/replace_fn.h"
|
#include "kernel/replace_fn.h"
|
||||||
#include "kernel/for_each_fn.h"
|
#include "kernel/for_each_fn.h"
|
||||||
|
@ -41,10 +42,14 @@ Author: Leonardo de Moura
|
||||||
#define LEAN_DEFAULT_REWRITER_SYNTACTIC false
|
#define LEAN_DEFAULT_REWRITER_SYNTACTIC false
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifndef LEAN_DEFAULT_REWRITER_TRACE
|
||||||
|
#define LEAN_DEFAULT_REWRITER_TRACE true
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace lean {
|
namespace lean {
|
||||||
static name * g_rewriter_max_iterations = nullptr;
|
static name * g_rewriter_max_iterations = nullptr;
|
||||||
static name * g_rewriter_syntactic = nullptr;
|
static name * g_rewriter_syntactic = nullptr;
|
||||||
|
static name * g_rewriter_trace = nullptr;
|
||||||
|
|
||||||
unsigned get_rewriter_max_iterations(options const & opts) {
|
unsigned get_rewriter_max_iterations(options const & opts) {
|
||||||
return opts.get_unsigned(*g_rewriter_max_iterations, LEAN_DEFAULT_REWRITER_MAX_ITERATIONS);
|
return opts.get_unsigned(*g_rewriter_max_iterations, LEAN_DEFAULT_REWRITER_MAX_ITERATIONS);
|
||||||
|
@ -54,6 +59,10 @@ bool get_rewriter_syntactic(options const & opts) {
|
||||||
return opts.get_bool(*g_rewriter_syntactic, LEAN_DEFAULT_REWRITER_SYNTACTIC);
|
return opts.get_bool(*g_rewriter_syntactic, LEAN_DEFAULT_REWRITER_SYNTACTIC);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool get_rewriter_trace(options const & opts) {
|
||||||
|
return opts.get_bool(*g_rewriter_trace, LEAN_DEFAULT_REWRITER_TRACE);
|
||||||
|
}
|
||||||
|
|
||||||
class unfold_info {
|
class unfold_info {
|
||||||
list<name> m_names;
|
list<name> m_names;
|
||||||
location m_location;
|
location m_location;
|
||||||
|
@ -448,6 +457,7 @@ class rewrite_fn {
|
||||||
substitution m_subst;
|
substitution m_subst;
|
||||||
expr m_expr_loc; // auxiliary expression used for error localization
|
expr m_expr_loc; // auxiliary expression used for error localization
|
||||||
|
|
||||||
|
bool m_use_trace;
|
||||||
unsigned m_max_iter;
|
unsigned m_max_iter;
|
||||||
|
|
||||||
buffer<optional<level>> m_lsubst; // auxiliary buffer for pattern matching
|
buffer<optional<level>> m_lsubst; // auxiliary buffer for pattern matching
|
||||||
|
@ -825,6 +835,135 @@ class rewrite_fn {
|
||||||
some_expr(type), m_expr_loc.get_tag(), cfg, nullptr);
|
some_expr(type), m_expr_loc.get_tag(), cfg, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// target, new_target, H : represents the rewrite (H : target = new_target) for hypothesis
|
||||||
|
// and (H : new_target = target) for goals
|
||||||
|
typedef optional<std::tuple<expr, expr, expr>> find_result;
|
||||||
|
|
||||||
|
struct failure {
|
||||||
|
enum kind { Unification, Exception, HasMetavars };
|
||||||
|
expr m_elab_lemma;
|
||||||
|
expr m_subterm;
|
||||||
|
kind m_kind;
|
||||||
|
failure(expr const & elab_lemma, expr const & subterm, kind k):
|
||||||
|
m_elab_lemma(elab_lemma), m_subterm(subterm), m_kind(k) {}
|
||||||
|
|
||||||
|
format pp(formatter const & fmt) const {
|
||||||
|
format r;
|
||||||
|
switch (m_kind) {
|
||||||
|
case Unification:
|
||||||
|
r = compose(line(), format("-fail to unify equation source"));
|
||||||
|
r += pp_indent_expr(fmt, m_elab_lemma);
|
||||||
|
r += compose(line(), format("with subterm"));
|
||||||
|
r += pp_indent_expr(fmt, m_subterm);
|
||||||
|
return r;
|
||||||
|
case Exception:
|
||||||
|
r = compose(line(), format("-an exception occurred when unifying the subterm"));
|
||||||
|
r += pp_indent_expr(fmt, m_subterm);
|
||||||
|
return r;
|
||||||
|
case HasMetavars:
|
||||||
|
r = compose(line(), format("-lemma still contains meta-variables"));
|
||||||
|
r += pp_indent_expr(fmt, m_elab_lemma);
|
||||||
|
r += compose(line(), format("after the equation source has been unified with subterm"));
|
||||||
|
r += pp_indent_expr(fmt, m_subterm);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
lean_unreachable();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Store information for a goal or hypothesis being rewritten
|
||||||
|
struct target_trace {
|
||||||
|
bool m_is_hypothesis;
|
||||||
|
expr m_target; // term being rewritten
|
||||||
|
list<failure> m_failures; // sub-terms that matched but failed to unify
|
||||||
|
optional<expr> m_matched; // sub-term that matched and unified
|
||||||
|
|
||||||
|
target_trace(expr const & t, bool is_hyp): m_is_hypothesis(is_hyp), m_target(t) {}
|
||||||
|
|
||||||
|
format pp(formatter const & fmt) const {
|
||||||
|
if (m_matched)
|
||||||
|
return format();
|
||||||
|
if (!m_failures) {
|
||||||
|
if (m_is_hypothesis)
|
||||||
|
return line() + format("no subterm in the hypothesis '") + fmt(m_target) + format("' matched the pattern");
|
||||||
|
else
|
||||||
|
return line() + format("no subterm in the goal matched the pattern");
|
||||||
|
}
|
||||||
|
format r;
|
||||||
|
if (m_is_hypothesis)
|
||||||
|
r = line() + format("matching failures in the hypothesis '") + fmt(m_target) + format("'");
|
||||||
|
else
|
||||||
|
r = line() + format("matching failures in the goal");
|
||||||
|
buffer<failure> b;
|
||||||
|
unsigned indent = get_pp_indent(fmt.get_options());
|
||||||
|
to_buffer(m_failures, b);
|
||||||
|
unsigned i = b.size();
|
||||||
|
while (i > 0) {
|
||||||
|
--i;
|
||||||
|
r += nest(indent, b[i].pp(fmt));
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct trace {
|
||||||
|
expr m_lemma; // lemma being used for rewriting
|
||||||
|
expr m_pattern; // extracted or given pattern
|
||||||
|
buffer<target_trace> m_targets; // trace for each element we tried to rewrite using m_lemma
|
||||||
|
|
||||||
|
format pp(formatter const & fmt) const {
|
||||||
|
format r("rewrite step failed using pattern");
|
||||||
|
r += pp_indent_expr(fmt, m_pattern);
|
||||||
|
for (target_trace const & t : m_targets) {
|
||||||
|
r += t.pp(fmt);
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
bool m_trace_initialized;
|
||||||
|
trace m_trace; // temporary object used to store execution trace for a rewrite step
|
||||||
|
|
||||||
|
void clear_trace() {
|
||||||
|
m_trace_initialized = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void init_trace(expr const & lemma, expr const & pattern) {
|
||||||
|
if (m_use_trace) {
|
||||||
|
m_trace_initialized = true;
|
||||||
|
m_trace.m_lemma = lemma;
|
||||||
|
m_trace.m_pattern = pattern;
|
||||||
|
m_trace.m_targets.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void add_target(expr const & t, bool is_hyp) {
|
||||||
|
if (m_use_trace)
|
||||||
|
m_trace.m_targets.push_back(target_trace(t, is_hyp));
|
||||||
|
}
|
||||||
|
|
||||||
|
target_trace & latest_target() {
|
||||||
|
lean_assert(m_use_trace);
|
||||||
|
lean_assert(!m_trace.m_targets.empty());
|
||||||
|
return m_trace.m_targets.back();
|
||||||
|
}
|
||||||
|
|
||||||
|
void add_target_failure(expr const & elab_term, expr const & subterm, failure::kind k) {
|
||||||
|
if (m_use_trace) {
|
||||||
|
target_trace & tt = latest_target();
|
||||||
|
lean_assert(!tt.m_matched);
|
||||||
|
tt.m_failures = cons(failure(elab_term, subterm, k), tt.m_failures);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void add_target_match(expr const & m) {
|
||||||
|
if (m_use_trace) {
|
||||||
|
target_trace & tt = latest_target();
|
||||||
|
lean_assert(!tt.m_matched);
|
||||||
|
tt.m_matched = m;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// rule, new_t
|
// rule, new_t
|
||||||
typedef optional<pair<expr, expr>> unify_result;
|
typedef optional<pair<expr, expr>> unify_result;
|
||||||
|
|
||||||
|
@ -859,8 +998,10 @@ class rewrite_fn {
|
||||||
src = app_arg(rule_type);
|
src = app_arg(rule_type);
|
||||||
else
|
else
|
||||||
src = app_arg(app_fn(rule_type));
|
src = app_arg(app_fn(rule_type));
|
||||||
if (!m_tc->is_def_eq(t, src, justification(), cs_seq))
|
if (!m_tc->is_def_eq(t, src, justification(), cs_seq)) {
|
||||||
|
add_target_failure(src, t, failure::Unification);
|
||||||
return unify_result();
|
return unify_result();
|
||||||
|
}
|
||||||
cs_seq.linearize(cs);
|
cs_seq.linearize(cs);
|
||||||
unifier_config cfg;
|
unifier_config cfg;
|
||||||
cfg.m_kind = unifier_kind::Liberal;
|
cfg.m_kind = unifier_kind::Liberal;
|
||||||
|
@ -870,11 +1011,14 @@ class rewrite_fn {
|
||||||
substitution new_subst = p->first.first;
|
substitution new_subst = p->first.first;
|
||||||
rule = new_subst.instantiate_all(rule);
|
rule = new_subst.instantiate_all(rule);
|
||||||
rule_type = new_subst.instantiate_all(rule_type);
|
rule_type = new_subst.instantiate_all(rule_type);
|
||||||
if (has_expr_metavar_strict(rule) || has_expr_metavar_strict(rule_type))
|
if (has_expr_metavar_strict(rule) || has_expr_metavar_strict(rule_type)) {
|
||||||
|
add_target_failure(rule, t, failure::HasMetavars);
|
||||||
return unify_result(); // rule was not completely instantiated.
|
return unify_result(); // rule was not completely instantiated.
|
||||||
|
}
|
||||||
m_subst = new_subst;
|
m_subst = new_subst;
|
||||||
expr lhs = app_arg(app_fn(rule_type));
|
expr lhs = app_arg(app_fn(rule_type));
|
||||||
expr rhs = app_arg(rule_type);
|
expr rhs = app_arg(rule_type);
|
||||||
|
add_target_match(t);
|
||||||
if (is_goal) {
|
if (is_goal) {
|
||||||
if (symm) {
|
if (symm) {
|
||||||
return unify_result(rule, lhs);
|
return unify_result(rule, lhs);
|
||||||
|
@ -890,14 +1034,14 @@ class rewrite_fn {
|
||||||
return unify_result(rule, rhs);
|
return unify_result(rule, rhs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
} catch (exception&) {}
|
add_target_failure(src, t, failure::Unification);
|
||||||
|
return unify_result();
|
||||||
|
}
|
||||||
|
} catch (exception&) {}
|
||||||
|
add_target_failure(orig_elem, t, failure::Exception);
|
||||||
return unify_result();
|
return unify_result();
|
||||||
}
|
}
|
||||||
|
|
||||||
// target, new_target, H : represents the rewrite (H : target = new_target) for hypothesis
|
|
||||||
// and (H : new_target = target) for goals
|
|
||||||
typedef optional<std::tuple<expr, expr, expr>> find_result;
|
|
||||||
|
|
||||||
// Search for \c pattern in \c e. If \c t is a match, then try to unify the type of the rule
|
// Search for \c pattern in \c e. If \c t is a match, then try to unify the type of the rule
|
||||||
// in the rewrite step \c orig_elem with \c t.
|
// in the rewrite step \c orig_elem with \c t.
|
||||||
|
@ -930,6 +1074,7 @@ class rewrite_fn {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool process_rewrite_hypothesis(expr const & hyp, expr const & orig_elem, expr const & pattern, occurrence const & occ) {
|
bool process_rewrite_hypothesis(expr const & hyp, expr const & orig_elem, expr const & pattern, occurrence const & occ) {
|
||||||
|
add_target(hyp, true);
|
||||||
expr Pa = mlocal_type(hyp);
|
expr Pa = mlocal_type(hyp);
|
||||||
bool is_goal = false;
|
bool is_goal = false;
|
||||||
if (auto it = find_target(Pa, pattern, orig_elem, is_goal)) {
|
if (auto it = find_target(Pa, pattern, orig_elem, is_goal)) {
|
||||||
|
@ -978,6 +1123,7 @@ class rewrite_fn {
|
||||||
|
|
||||||
bool process_rewrite_goal(expr const & orig_elem, expr const & pattern, occurrence const & occ) {
|
bool process_rewrite_goal(expr const & orig_elem, expr const & pattern, occurrence const & occ) {
|
||||||
expr Pa = m_g.get_type();
|
expr Pa = m_g.get_type();
|
||||||
|
add_target(Pa, false);
|
||||||
bool is_goal = true;
|
bool is_goal = true;
|
||||||
if (auto it = find_target(Pa, pattern, orig_elem, is_goal)) {
|
if (auto it = find_target(Pa, pattern, orig_elem, is_goal)) {
|
||||||
expr a, Heq, b;
|
expr a, Heq, b;
|
||||||
|
@ -1043,6 +1189,7 @@ class rewrite_fn {
|
||||||
bool process_rewrite_step(expr const & elem, expr const & orig_elem) {
|
bool process_rewrite_step(expr const & elem, expr const & orig_elem) {
|
||||||
lean_assert(is_rewrite_step(elem));
|
lean_assert(is_rewrite_step(elem));
|
||||||
expr pattern = get_pattern(elem);
|
expr pattern = get_pattern(elem);
|
||||||
|
init_trace(orig_elem, pattern);
|
||||||
// regular(m_env, m_ios) << "pattern: " << pattern << "\n";
|
// regular(m_env, m_ios) << "pattern: " << pattern << "\n";
|
||||||
rewrite_info const & info = get_rewrite_info(elem);
|
rewrite_info const & info = get_rewrite_info(elem);
|
||||||
unsigned i, num;
|
unsigned i, num;
|
||||||
|
@ -1086,6 +1233,7 @@ class rewrite_fn {
|
||||||
// Process the given rewrite element/step. This method destructively update
|
// Process the given rewrite element/step. This method destructively update
|
||||||
// m_g, m_subst, m_ngen. It returns true if it succeeded and false otherwise.
|
// m_g, m_subst, m_ngen. It returns true if it succeeded and false otherwise.
|
||||||
bool process_step(expr const & elem) {
|
bool process_step(expr const & elem) {
|
||||||
|
clear_trace();
|
||||||
if (is_rewrite_unfold_step(elem)) {
|
if (is_rewrite_unfold_step(elem)) {
|
||||||
return process_unfold_step(elem);
|
return process_unfold_step(elem);
|
||||||
} else if (is_rewrite_fold_step(elem)) {
|
} else if (is_rewrite_fold_step(elem)) {
|
||||||
|
@ -1161,6 +1309,7 @@ public:
|
||||||
update_goal(head(gs));
|
update_goal(head(gs));
|
||||||
m_subst = m_ps.get_subst();
|
m_subst = m_ps.get_subst();
|
||||||
m_max_iter = get_rewriter_max_iterations(ios.get_options());
|
m_max_iter = get_rewriter_max_iterations(ios.get_options());
|
||||||
|
m_use_trace = get_rewriter_trace(ios.get_options());
|
||||||
}
|
}
|
||||||
|
|
||||||
proof_state_seq operator()(buffer<expr> const & elems) {
|
proof_state_seq operator()(buffer<expr> const & elems) {
|
||||||
|
@ -1169,8 +1318,18 @@ public:
|
||||||
if (!process_step(elem)) {
|
if (!process_step(elem)) {
|
||||||
if (m_ps.report_failure()) {
|
if (m_ps.report_failure()) {
|
||||||
proof_state curr_ps(m_ps, cons(m_g, tail(m_ps.get_goals())), m_subst, m_ngen);
|
proof_state curr_ps(m_ps, cons(m_g, tail(m_ps.get_goals())), m_subst, m_ngen);
|
||||||
|
if (!m_use_trace || !m_trace_initialized) {
|
||||||
throw tactic_exception("rewrite step failed", some_expr(elem), curr_ps,
|
throw tactic_exception("rewrite step failed", some_expr(elem), curr_ps,
|
||||||
[](formatter const &) { return format("invalid 'rewrite' tactic, rewrite step failed"); });
|
[](formatter const &) { return format("invalid 'rewrite' tactic, rewrite step failed"); });
|
||||||
|
} else {
|
||||||
|
trace saved_trace = m_trace;
|
||||||
|
throw tactic_exception("rewrite step failed", some_expr(elem), curr_ps,
|
||||||
|
[=](formatter const & fmt) {
|
||||||
|
format r = format("invalid 'rewrite' tactic, ");
|
||||||
|
r += saved_trace.pp(fmt);
|
||||||
|
return r;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return proof_state_seq();
|
return proof_state_seq();
|
||||||
}
|
}
|
||||||
|
@ -1204,6 +1363,9 @@ void initialize_rewrite_tactic() {
|
||||||
g_rewriter_syntactic = new name{"rewriter", "syntactic"};
|
g_rewriter_syntactic = new name{"rewriter", "syntactic"};
|
||||||
register_bool_option(*g_rewriter_syntactic, LEAN_DEFAULT_REWRITER_SYNTACTIC,
|
register_bool_option(*g_rewriter_syntactic, LEAN_DEFAULT_REWRITER_SYNTACTIC,
|
||||||
"(rewriter tactic) if true tactic will not unfold any constant when performing pattern matching");
|
"(rewriter tactic) if true tactic will not unfold any constant when performing pattern matching");
|
||||||
|
g_rewriter_trace = new name{"rewriter", "trace"};
|
||||||
|
register_bool_option(*g_rewriter_trace, LEAN_DEFAULT_REWRITER_TRACE,
|
||||||
|
"(rewriter tactic) if true tactic will generate a trace for rewrite step failures");
|
||||||
name rewrite_tac_name{"tactic", "rewrite_tac"};
|
name rewrite_tac_name{"tactic", "rewrite_tac"};
|
||||||
g_rewrite_tac = new expr(Const(rewrite_tac_name));
|
g_rewrite_tac = new expr(Const(rewrite_tac_name));
|
||||||
g_rewrite_reduce_name = new name("rewrite_reduce");
|
g_rewrite_reduce_name = new name("rewrite_reduce");
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
quasireducible.lean:11:11: error:invalid 'rewrite' tactic, rewrite step failed
|
quasireducible.lean:11:11: error:invalid 'rewrite' tactic, rewrite step failed using pattern
|
||||||
|
f a
|
||||||
|
no subterm in the goal matched the pattern
|
||||||
proof state:
|
proof state:
|
||||||
a : nat,
|
a : nat,
|
||||||
H : f a = a
|
H : f a = a
|
||||||
|
@ -10,7 +12,9 @@ H : f a = a
|
||||||
quasireducible.lean:11:3: error: failed to add declaration '14.1' to environment, value has metavariables
|
quasireducible.lean:11:3: error: failed to add declaration '14.1' to environment, value has metavariables
|
||||||
λ (a : nat) (H : f a = a),
|
λ (a : nat) (H : f a = a),
|
||||||
?M_1
|
?M_1
|
||||||
quasireducible.lean:16:11: error:invalid 'rewrite' tactic, rewrite step failed
|
quasireducible.lean:16:11: error:invalid 'rewrite' tactic, rewrite step failed using pattern
|
||||||
|
f a
|
||||||
|
no subterm in the goal matched the pattern
|
||||||
proof state:
|
proof state:
|
||||||
a : nat,
|
a : nat,
|
||||||
H : f a = a
|
H : f a = a
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
rewrite_fail.lean:5:15: error:invalid 'rewrite' tactic, rewrite step failed
|
rewrite_fail.lean:5:15: error:invalid 'rewrite' tactic, rewrite step failed using pattern
|
||||||
|
a
|
||||||
|
no subterm in the goal matched the pattern
|
||||||
proof state:
|
proof state:
|
||||||
a b : ℕ,
|
a b : ℕ,
|
||||||
Ha : a = 0,
|
Ha : a = 0,
|
||||||
|
|
Loading…
Reference in a new issue