action_result intros_action(unsigned max) { if (max == 0) return action_result::new_branch(); state & s = curr_state(); expr target = whnf(s.get_target()); if (!is_pi(target)) return action_result::failed(); auto pcell = new intros_proof_step_cell(); s.push_proof_step(pcell); buffer<expr> new_hs; for (unsigned i = 0; i < max; i++) { if (!is_pi(target)) break; expr href; expr htype = head_beta_reduce(binding_domain(target)); if (is_default_var_name(binding_name(target)) && closed(binding_body(target))) { href = s.mk_hypothesis(htype); } else { href = s.mk_hypothesis(binding_name(target), htype); } new_hs.push_back(href); target = whnf(instantiate(binding_body(target), href)); } pcell->m_new_hs = to_list(new_hs); s.set_target(target); trace_action("intros"); return action_result::new_branch(); }
static proof_state_seq apply_tactic_core(environment const & env, io_state const & ios, proof_state const & s, expr const & _e, buffer<constraint> & cs, add_meta_kind add_meta, subgoals_action_kind subgoals_action, optional<unifier_kind> const & uk = optional<unifier_kind>()) { goals const & gs = s.get_goals(); if (empty(gs)) { throw_no_goal_if_enabled(s); return proof_state_seq(); } bool class_inst = get_apply_class_instance(ios.get_options()); name_generator ngen = s.get_ngen(); std::shared_ptr<type_checker> tc(mk_type_checker(env, ngen.mk_child())); goal g = head(gs); goals tail_gs = tail(gs); expr t = g.get_type(); expr e = _e; auto e_t_cs = tc->infer(e); e_t_cs.second.linearize(cs); expr e_t = e_t_cs.first; buffer<expr> metas; local_context ctx; bool initialized_ctx = false; unifier_config cfg(ios.get_options()); if (uk) cfg.m_kind = *uk; if (add_meta != DoNotAdd) { unsigned num_e_t = get_expect_num_args(*tc, e_t); if (add_meta == AddDiff) { unsigned num_t = get_expect_num_args(*tc, t); if (num_t <= num_e_t) num_e_t -= num_t; else num_e_t = 0; } else { lean_assert(add_meta == AddAll); } for (unsigned i = 0; i < num_e_t; i++) { auto e_t_cs = tc->whnf(e_t); e_t_cs.second.linearize(cs); e_t = e_t_cs.first; expr meta; if (class_inst && binding_info(e_t).is_inst_implicit()) { if (!initialized_ctx) { ctx = g.to_local_context(); initialized_ctx = true; } bool use_local_insts = true; bool is_strict = false; auto mc = mk_class_instance_elaborator( env, ios, ctx, ngen.next(), optional<name>(), use_local_insts, is_strict, some_expr(head_beta_reduce(binding_domain(e_t))), e.get_tag(), cfg, nullptr); meta = mc.first; cs.push_back(mc.second); } else { meta = g.mk_meta(ngen.next(), head_beta_reduce(binding_domain(e_t))); } e = mk_app(e, meta); e_t = instantiate(binding_body(e_t), meta); metas.push_back(meta); } } metavar_closure cls(t); cls.mk_constraints(s.get_subst(), justification()); pair<bool, constraint_seq> dcs = tc->is_def_eq(t, e_t); if (!dcs.first) { throw_tactic_exception_if_enabled(s, [=](formatter const & fmt) { format r = format("invalid 'apply' tactic, failed to unify"); r += pp_indent_expr(fmt, t); r += compose(line(), format("with")); r += pp_indent_expr(fmt, e_t); return r; }); return proof_state_seq(); } dcs.second.linearize(cs); unify_result_seq rseq = unify(env, cs.size(), cs.data(), ngen.mk_child(), s.get_subst(), cfg); list<expr> meta_lst = to_list(metas.begin(), metas.end()); return map2<proof_state>(rseq, [=](pair<substitution, constraints> const & p) -> proof_state { substitution const & subst = p.first; constraints const & postponed = p.second; name_generator new_ngen(ngen); substitution new_subst = subst; expr new_e = new_subst.instantiate_all(e); assign(new_subst, g, new_e); goals new_gs = tail_gs; if (subgoals_action != IgnoreSubgoals) { buffer<expr> metas; for (auto m : meta_lst) { if (!new_subst.is_assigned(get_app_fn(m))) metas.push_back(m); } if (subgoals_action == AddRevSubgoals) { for (unsigned i = 0; i < metas.size(); i++) new_gs = cons(goal(metas[i], new_subst.instantiate_all(tc->infer(metas[i]).first)), new_gs); } else { lean_assert(subgoals_action == AddSubgoals || subgoals_action == AddAllSubgoals); if (subgoals_action == AddSubgoals) remove_redundant_metas(metas); unsigned i = metas.size(); while (i > 0) { --i; new_gs = cons(goal(metas[i], new_subst.instantiate_all(tc->infer(metas[i]).first)), new_gs); } } } return proof_state(s, new_gs, new_subst, new_ngen, postponed); }); }
/** \brief Create a "choice" constraint that postpones the resolution of a calc proof step. By delaying it, we can perform quick fixes such as: - adding symmetry - adding ! - adding subst */ constraint mk_calc_proof_cnstr(environment const & env, options const & opts, old_local_context const & _ctx, expr const & m, expr const & _e, constraint_seq const & cs, unifier_config const & cfg, info_manager * im, update_type_info_fn const & fn) { justification j = mk_failed_to_synthesize_jst(env, m); auto choice_fn = [=](expr const & meta, expr const & _meta_type, substitution const & _s) { old_local_context ctx = _ctx; expr e = _e; substitution s = _s; expr meta_type = _meta_type; type_checker_ptr tc = mk_type_checker(env); constraint_seq new_cs = cs; expr e_type = tc->infer(e, new_cs); e_type = s.instantiate(e_type); tag g = e.get_tag(); bool calc_assistant = get_elaborator_calc_assistant(opts); if (calc_assistant) { // add '!' is needed while (is_norm_pi(*tc, e_type, new_cs)) { binder_info bi = binding_info(e_type); if (!bi.is_implicit() && !bi.is_inst_implicit()) { if (!has_free_var(binding_body(e_type), 0)) { // if the rest of the type does not reference argument, // then we also stop consuming arguments break; } } expr imp_arg = ctx.mk_meta(some_expr(binding_domain(e_type)), g); e = mk_app(e, imp_arg, g); e_type = instantiate(binding_body(e_type), imp_arg); } if (im) fn(e); } e_type = head_beta_reduce(e_type); expr const & meta_type_fn = get_app_fn(meta_type); expr const & e_type_fn = get_app_fn(e_type); if (is_constant(meta_type_fn) && (!is_constant(e_type_fn) || const_name(e_type_fn) != const_name(meta_type_fn))) { // try to make sure meta_type and e_type have the same head symbol if (!try_normalize_to_head(env, const_name(meta_type_fn), e_type, new_cs) && is_constant(e_type_fn)) { try_normalize_to_head(env, const_name(e_type_fn), meta_type, new_cs); } } auto try_alternative = [&](expr const & e, expr const & e_type, constraint_seq fcs, bool conservative) { justification new_j = mk_type_mismatch_jst(e, e_type, meta_type); if (!tc->is_def_eq(e_type, meta_type, new_j, fcs)) throw unifier_exception(new_j, s); buffer<constraint> cs_buffer; fcs.linearize(cs_buffer); metavar_closure cls(meta); cls.add(meta_type); cls.mk_constraints(s, j, cs_buffer); unifier_config new_cfg(cfg); new_cfg.m_discard = false; new_cfg.m_kind = conservative ? unifier_kind::Conservative : unifier_kind::Liberal; unify_result_seq seq = unify(env, cs_buffer.size(), cs_buffer.data(), substitution(), new_cfg); auto p = seq.pull(); lean_assert(p); substitution new_s = p->first.first; constraints postponed = map(p->first.second, [&](constraint const & c) { // we erase internal justifications return update_justification(c, j); }); expr new_e = new_s.instantiate(e); if (conservative && has_expr_metavar_relaxed(new_s.instantiate_all(e))) throw_elaborator_exception("solution contains metavariables", e); if (im) im->instantiate(new_s); constraints r = cls.mk_constraints(new_s, j); buffer<expr> locals; expr mvar = get_app_args(meta, locals); expr val = Fun(locals, new_e); r = cons(mk_eq_cnstr(mvar, val, j), r); return append(r, postponed); }; if (!get_elaborator_calc_assistant(opts)) { bool conservative = false; return try_alternative(e, e_type, new_cs, conservative); } else { // TODO(Leo): after we have the simplifier and rewriter tactic, we should revise // this code. It is "abusing" the higher-order unifier. { // Try the following possible intrepretations using a "conservative" unification procedure. // That is, we only unfold definitions marked as reducible. // Assume pr is the proof provided. // 1. pr bool conservative = true; try { return try_alternative(e, e_type, new_cs, conservative); } catch (exception & ex) {} // 2. eq.symm pr constraint_seq symm_cs = new_cs; auto symm = apply_symmetry(env, ctx, tc, e, e_type, symm_cs, g); if (symm) { try { return try_alternative(symm->first, symm->second, symm_cs, conservative); } catch (exception &) {} } // 3. subst pr (eq.refl lhs) constraint_seq subst_cs = new_cs; if (auto subst = apply_subst(env, ctx, tc, e, e_type, meta_type, subst_cs, g)) { try { return try_alternative(subst->first, subst->second, subst_cs, conservative); } catch (exception&) {} } // 4. subst (eq.symm pr) (eq.refl lhs) if (symm) { constraint_seq subst_cs = symm_cs; if (auto subst = apply_subst(env, ctx, tc, symm->first, symm->second, meta_type, subst_cs, g)) { try { return try_alternative(subst->first, subst->second, subst_cs, conservative); } catch (exception&) {} } } } { // Try the following possible insterpretations using the default unification procedure. // 1. pr bool conservative = false; std::unique_ptr<throwable> saved_ex; try { return try_alternative(e, e_type, new_cs, conservative); } catch (exception & ex) { saved_ex.reset(ex.clone()); } // 2. eq.symm pr constraint_seq symm_cs = new_cs; auto symm = apply_symmetry(env, ctx, tc, e, e_type, symm_cs, g); if (symm) { try { return try_alternative(symm->first, symm->second, symm_cs, conservative); } catch (exception &) {} } // We use the exception for the first alternative as the error message saved_ex->rethrow(); lean_unreachable(); } } }; bool owner = false; return mk_choice_cnstr(m, choice_fn, to_delay_factor(cnstr_group::Epilogue), owner, j); }