diff --git a/src/library/blast/CMakeLists.txt b/src/library/blast/CMakeLists.txt index f2af40100e..2f0cc3ae73 100644 --- a/src/library/blast/CMakeLists.txt +++ b/src/library/blast/CMakeLists.txt @@ -2,4 +2,4 @@ add_library(blast OBJECT expr.cpp state.cpp blast.cpp blast_tactic.cpp init_module.cpp simplifier.cpp simple_actions.cpp intros_action.cpp proof_expr.cpp options.cpp choice_point.cpp simple_strategy.cpp backward_action.cpp util.cpp gexpr.cpp revert_action.cpp subst_action.cpp no_confusion_action.cpp - simplify_actions.cpp strategy.cpp) + simplify_actions.cpp strategy.cpp recursor_action.cpp) diff --git a/src/library/blast/blast.cpp b/src/library/blast/blast.cpp index 483a7e1aac..b1d6aa9e99 100644 --- a/src/library/blast/blast.cpp +++ b/src/library/blast/blast.cpp @@ -457,6 +457,7 @@ public: return m_tmp_local_generator.mk_tmp_local(type, bi); } expr whnf(expr const & e) { return m_tctx.whnf(e); } + expr relaxed_whnf(expr const & e) { return m_tctx.relaxed_whnf(e); } expr infer_type(expr const & e) { return m_tctx.infer(e); } bool is_prop(expr const & e) { return m_tctx.is_prop(e); } bool is_def_eq(expr const & e1, expr const & e2) { return m_tctx.is_def_eq(e1, e2); } @@ -565,11 +566,21 @@ bool is_relation(expr const & e, name & rop, expr & lhs, expr & rhs) { return g_blastenv->is_relation(e, rop, lhs, rhs); } +bool is_relation(expr const & e) { + name rop; expr lhs, rhs; + return is_relation(e, rop, lhs, rhs); +} + expr whnf(expr const & e) { lean_assert(g_blastenv); return g_blastenv->whnf(e); } +expr relaxed_whnf(expr const & e) { + lean_assert(g_blastenv); + return g_blastenv->relaxed_whnf(e); +} + expr infer_type(expr const & e) { lean_assert(g_blastenv); return g_blastenv->infer_type(e); diff --git a/src/library/blast/blast.h b/src/library/blast/blast.h index d1a85b2e5e..93d6efd1d4 100644 --- a/src/library/blast/blast.h +++ b/src/library/blast/blast.h @@ -37,9 +37,11 @@ projection_info const * get_projection_info(name const & n); /** \brief Return true iff \c e is a relation application, and store the relation name, lhs and rhs in the output arguments. */ bool is_relation(expr const & e, name & rop, expr & lhs, expr & rhs); +bool is_relation(expr const & e); /** \brief Put the given expression in weak-head-normal-form with respect to the current state being processed by the blast tactic. */ expr whnf(expr const & e); +expr relaxed_whnf(expr const & e); /** \brief Normalize the given expression using the blast type context. This procedure caches results. \remark This procedure is intended for normalizing instances that are not subsingletons. */ @@ -129,6 +131,15 @@ public: tmp_type_context & operator*() { return *m_ctx; } }; +class scope_curr_state { + state m_saved; + bool m_keep; +public: + scope_curr_state():m_saved(curr_state()), m_keep(false) {} + ~scope_curr_state() { if (!m_keep) curr_state() = m_saved; } + void commit() { m_keep = true; } +}; + /** \brief Convert an external expression into a blast expression It converts meta-variables to blast meta-variables, and ensures the expressions diff --git a/src/library/blast/recursor_action.cpp b/src/library/blast/recursor_action.cpp new file mode 100644 index 0000000000..ca47ccd4ce --- /dev/null +++ b/src/library/blast/recursor_action.cpp @@ -0,0 +1,289 @@ +/* +Copyright (c) 2015 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 "kernel/inductive/inductive.h" +#include "library/user_recursors.h" +#include "library/blast/revert_action.h" +#include "library/blast/blast.h" + +namespace lean { +namespace blast { +optional is_recursor_action_target(hypothesis_idx hidx) { + state & s = curr_state(); + hypothesis const * h = s.get_hypothesis_decl(hidx); + lean_assert(h); + expr const & type = h->get_type(); + if (!is_app(type) && !is_constant(type)) + return optional(); + if (is_relation(type)) + return optional(); // we don't apply recursors to equivalence relations: =, ~, <->, etc. + if (!h->is_assumption()) + return optional(); // we only consider assumptions + // TODO(Leo): more restrictions? + // TODO(Leo): consider user-provided hints + expr const & I = get_app_fn(type); + if (!is_constant(I)) + return optional(); + if (inductive::is_inductive_decl(env(), const_name(I))) + return optional(name(inductive::get_elim_name(const_name(I)))); // it is builtin recursive datatype + list Rs = get_recursors_for(env(), const_name(I)); + if (!Rs) + return optional(); + else + return optional(head(Rs)); // type has user-defined recursors +} + +unsigned get_num_minor_premises(name const & R) { + return get_recursor_info(env(), R).get_num_minors(); +} + +bool is_recursive_recursor(name const & R) { + return get_recursor_info(env(), R).is_recursive(); +} + +struct recursor_proof_step_cell : public proof_step_cell { + bool m_dep; + branch m_branch; // branch for backtracking + expr m_proof; // recursor-application (the position where the goal-proofs are marked by the local constants). + list m_goals; // type of each subgoal/branch encoded as a local constant + list m_goal_proofs; // proofs generated so far + + recursor_proof_step_cell(bool dep, branch const & b, expr const & pr, list const & goals, list const & goal_proofs): + m_dep(dep), m_branch(b), m_proof(pr), m_goals(goals), m_goal_proofs(goal_proofs) { + } + + virtual ~recursor_proof_step_cell() {} + + virtual action_result resolve(expr const & pr) const { + state & s = curr_state(); + s.set_branch(m_branch); + if (!m_dep) { + // It is not a dependent elimination, so if pr did not use new hypothesis, + // we don't need to investigate other branches. + // This is also a form of non-chronological backtracking. + expr const & goal = head(m_goals); + unsigned arity = get_arity(mlocal_type(goal)); + expr it = pr; + bool skip = true; + for (unsigned i = 0; i < arity; i++) { + if (!is_lambda(it)) { + skip = false; + break; + } + it = binding_body(it); + if (!closed(it)) { + skip = false; + break; + } + } + if (skip) { + lean_assert(closed(it)); + return action_result::solved(it); + } + } + list new_goals = tail(m_goals); + list new_prs = cons(pr, m_goal_proofs); + if (empty(new_goals)) { + buffer proof_args; + buffer gs; + to_buffer(m_goals, gs); + expr const & rec = get_app_args(m_proof, proof_args); + // update proof_args that are goals with their proofs + unsigned i = proof_args.size(); + while (i > 0) { + --i; + if (!gs.empty() && proof_args[i] == gs.back()) { + lean_assert(new_prs); + proof_args[i] = head(new_prs); + new_prs = tail(new_prs); + } + } + return action_result::solved(mk_app(rec, proof_args)); + } else { + s.pop_proof_step(); + s.push_proof_step(new recursor_proof_step_cell(m_dep, m_branch, m_proof, new_goals, new_prs)); + s.set_target(mlocal_type(head(new_goals))); + return action_result::new_branch(); + } + } +}; + +action_result recursor_action(hypothesis_idx hidx, name const & R) { + state & s = curr_state(); + hypothesis const * h = s.get_hypothesis_decl(hidx); + lean_assert(h); + expr const & type = h->get_type(); + lean_assert(is_constant(get_app_fn(type))); + + recursor_info rec_info = get_recursor_info(env(), R); + + if (!rec_info.has_dep_elim() && s.target_depends_on(hidx)) { + // recursor does does not support dependent elimination, but conclusion + // depends on major premise + return action_result::failed(); + } + + buffer type_args; + get_app_args(type, type_args); + buffer> params; + for (optional const & pos : rec_info.get_params_pos()) { + if (!pos) { + params.push_back(none_expr()); + } else if (*pos >= type_args.size()) { + return action_result::failed(); // major premise type is ill-formed + } else { + params.push_back(some_expr(type_args[*pos])); + } + } + + buffer indices; + list const & idx_pos = rec_info.get_indices_pos(); + for (unsigned pos : idx_pos) { + if (pos >= type_args.size()) { + return action_result::failed(); // major premise type is ill-formed"); + } + expr const & idx = type_args[pos]; + if (!is_href(idx)) { + return action_result::failed(); // argument of major premise type is not a href + } + for (unsigned i = 0; i < type_args.size(); i++) { + if (i != pos && is_local(type_args[i]) && mlocal_name(type_args[i]) == mlocal_name(idx)) { + return action_result::failed(); // argument of major premise is an index, but it occurs more than once + } + if (i > pos && // occurs after idx + std::find(idx_pos.begin(), idx_pos.end(), i) != idx_pos.end() && // it is also an index + is_href(type_args[i]) && // if it is not an index, it will fail anyway. + s.hidx_depends_on(href_index(idx), href_index(type_args[i]))) { + // argument of major premise type is an index, but its type depends on another index + return action_result::failed(); + } + } + indices.push_back(idx); + } + + scope_curr_state save_state; + hypothesis_idx_buffer_set to_revert; + s.collect_direct_forward_deps(hidx, to_revert); + for (auto i : indices) + s.collect_direct_forward_deps(href_index(i), to_revert); + revert_action(to_revert); + + expr target = s.get_target(); + auto target_level = get_type_context().get_level_core(target); + if (!target_level) return action_result::failed(); // failed to extract universe level of target + + buffer rec_lvls; + expr const & I = get_app_fn(type); + buffer I_lvls; + to_buffer(const_levels(I), I_lvls); + bool found_target_lvl = false; + for (unsigned idx : rec_info.get_universe_pos()) { + if (idx == recursor_info::get_motive_univ_idx()) { + rec_lvls.push_back(*target_level); + found_target_lvl = true; + } else { + if (idx >= I_lvls.size()) + return action_result::failed(); // ill-formed recursor + rec_lvls.push_back(I_lvls[idx]); + } + } + + if (!found_target_lvl && !is_zero(*target_level)) { + // recursor can only eliminate into Prop + return action_result::failed(); + } + expr rec = mk_constant(rec_info.get_name(), to_list(rec_lvls)); + + for (optional const & p : params) { + if (p) { + rec = mk_app(rec, *p); + } else { + // try type class resolution to synthesize argument + expr rec_type = relaxed_whnf(infer_type(rec)); + if (!is_pi(rec_type)) + return action_result::failed(); // ill-formed recursor + expr arg_type = binding_domain(rec_type); + if (auto inst = mk_class_instance(arg_type)) { + rec = mk_app(rec, *inst); + } else { + return action_result::failed(); // failed to generate instance + } + } + } + + expr motive = target; + if (rec_info.has_dep_elim()) + motive = s.mk_lambda(h->get_self(), motive); + motive = s.mk_lambda(indices, motive); + rec = mk_app(rec, motive); + + expr rec_type = relaxed_whnf(infer_type(rec)); + unsigned curr_pos = params.size() + 1 /* motive */; + unsigned first_idx_pos = rec_info.get_first_index_pos(); + bool consumed_major = false; + buffer new_goals; + + while (is_pi(rec_type) && curr_pos < rec_info.get_num_args()) { + if (first_idx_pos == curr_pos) { + for (expr const & idx : indices) { + rec = mk_app(rec, idx); + rec_type = relaxed_whnf(instantiate(binding_body(rec_type), idx)); + if (!is_pi(rec_type)) + return action_result::failed(); // ill-formed type + curr_pos++; + } + rec = mk_app(rec, h->get_self()); + rec_type = relaxed_whnf(instantiate(binding_body(rec_type), h->get_self())); + consumed_major = true; + curr_pos++; + } else { + expr new_type = binding_domain(rec_type); + expr rec_arg; + if (binding_info(rec_type).is_inst_implicit()) { + auto inst = mk_class_instance(new_type); + if (!inst) return action_result::failed(); // type class resolution failed + rec_arg = *inst; + } else { + rec_arg = mk_fresh_local(new_type); // placeholder + new_goals.push_back(rec_arg); + } + rec = mk_app(rec, rec_arg); + rec_type = relaxed_whnf(instantiate(binding_body(rec_type), rec_arg)); + curr_pos++; + } + } + + if (curr_pos != rec_info.get_num_args() || !consumed_major) + return action_result::failed(); // ill-formed recursor + + save_state.commit(); + + if (new_goals.empty()) + return action_result::solved(rec); + s.del_hypothesis(hidx); + s.push_proof_step(new recursor_proof_step_cell(rec_info.has_dep_elim(), s.get_branch(), rec, to_list(new_goals), list())); + s.set_target(mlocal_type(new_goals[0])); + return action_result::new_branch(); +} + +action_result recursor_action(hypothesis_idx hidx) { + state & s = curr_state(); + hypothesis const * h = s.get_hypothesis_decl(hidx); + lean_assert(h); + expr const & type = h->get_type(); + expr const & I = get_app_fn(type); + if (!is_constant(I)) + return action_result::failed(); + list Rs = get_recursors_for(env(), const_name(I)); + for (auto R : Rs) { + auto r = recursor_action(hidx, R); + if (!failed(r)) + return r; + } + return action_result::failed(); +} +}} diff --git a/src/library/blast/recursor_action.h b/src/library/blast/recursor_action.h new file mode 100644 index 0000000000..03b2b77d43 --- /dev/null +++ b/src/library/blast/recursor_action.h @@ -0,0 +1,20 @@ +/* +Copyright (c) 2015 Microsoft Corporation. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. + +Author: Leonardo de Moura +*/ +#pragma once +#include "library/blast/action_result.h" +#include "library/blast/hypothesis.h" +namespace lean { +namespace blast { +/** \brief Return the name of the recursor that can be used with the given hypothesis */ +optional is_recursor_action_target(hypothesis_idx hidx); + +/** \brief Return the number of minor premises of the given recursor */ +unsigned get_num_minor_premises(name const & R); + +action_result recursor_action(hypothesis_idx hidx, name const & R); +action_result recursor_action(hypothesis_idx hidx); +}} diff --git a/src/library/blast/simple_strategy.cpp b/src/library/blast/simple_strategy.cpp index 0c2378e942..6a389db52d 100644 --- a/src/library/blast/simple_strategy.cpp +++ b/src/library/blast/simple_strategy.cpp @@ -44,6 +44,16 @@ class simple_strategy : public strategy { return r; } + if (optional R = is_recursor_action_target(*hidx)) { + if (get_num_minor_premises(*R) == 1) { + action_result r = recursor_action(*hidx, *R); + if (!failed(r)) { + if (!preprocess) display_action("recursor"); + return r; + } + } + } + return action_result::new_branch(); } diff --git a/src/library/blast/state.h b/src/library/blast/state.h index de67a0eba2..fa2bfa1606 100644 --- a/src/library/blast/state.h +++ b/src/library/blast/state.h @@ -318,7 +318,8 @@ public: void set_target(expr const & t); expr const & get_target() const { return m_branch.m_target; } /** \brief Return true iff the target depends on the given hypothesis */ - bool target_depends_on(expr const & h) const { return m_branch.m_target_deps.contains(href_index(h)); } + bool target_depends_on(hypothesis_idx hidx) const { return m_branch.m_target_deps.contains(hidx); } + bool target_depends_on(expr const & h) const { return target_depends_on(href_index(h)); } /************************ Proof steps diff --git a/src/library/type_context.cpp b/src/library/type_context.cpp index 38e437153b..53240fddb4 100644 --- a/src/library/type_context.cpp +++ b/src/library/type_context.cpp @@ -1052,10 +1052,18 @@ expr type_context::infer_lambda(expr e) { return r; } -/** \brief Make sure \c e is a sort, if it is not throw an exception using \c ref as a reference */ -void type_context::ensure_sort(expr const & e, expr const & /* ref */) { - // Remark: for simplicity reasons, we just fail if \c e is not a sort. - if (!is_sort(e)) +optional type_context::get_level_core(expr const & A) { + expr A_type = relaxed_whnf(infer(A)); + if (is_sort(A_type)) + return some_level(sort_level(A_type)); + else + return none_level(); +} + +level type_context::get_level(expr const & A) { + if (auto r = get_level_core(A)) + return *r; + else throw exception("infer type failed, sort expected"); } @@ -1065,17 +1073,13 @@ expr type_context::infer_pi(expr const & e0) { expr e = e0; while (is_pi(e)) { expr d = instantiate_rev(binding_domain(e), ls.size(), ls.data()); - expr d_type = relaxed_whnf(infer(d)); - ensure_sort(d_type, e0); - us.push_back(sort_level(d_type)); + us.push_back(get_level(d)); expr l = mk_tmp_local(d, binding_info(e)); ls.push_back(l); e = binding_body(e); } e = instantiate_rev(e, ls.size(), ls.data()); - expr e_type = relaxed_whnf(infer(e)); - ensure_sort(e_type, e0); - level r = sort_level(e_type); + level r = get_level(e); unsigned i = ls.size(); bool imp = m_env.impredicative(); while (i > 0) { diff --git a/src/library/type_context.h b/src/library/type_context.h index 5dbd6bc052..9f0dbea59b 100644 --- a/src/library/type_context.h +++ b/src/library/type_context.h @@ -443,6 +443,11 @@ public: */ bool is_def_eq(expr const & e1, expr const & e2); + /** \brief Return the universe level for type A. If A is not a type return none. */ + optional get_level_core(expr const & A); + /** \brief Similar to previous method, but throws exception instead */ + level get_level(expr const & A); + /** \brief If \c type is a type class, return its name */ optional is_class(expr const & type); diff --git a/tests/lean/run/blast14.lean b/tests/lean/run/blast14.lean new file mode 100644 index 0000000000..f73fdf78e0 --- /dev/null +++ b/tests/lean/run/blast14.lean @@ -0,0 +1,13 @@ +set_option blast.init_depth 10 +set_option blast.inc_depth 1000 + +definition lemma1 (p q : Prop) : p ∧ q → q ∧ p := +by blast + +definition lemma2 (p q r s : Prop) : r ∧ s → p ∧ q → q ∧ p := +by blast + +print lemma2 -- unnecessary and.rec's are not included + +example (p q : Prop) : p ∧ p ∧ q ∧ q → q ∧ p := +by blast