diff --git a/src/frontends/lean/frontend.cpp b/src/frontends/lean/frontend.cpp index b531c58af..76fee8daf 100644 --- a/src/frontends/lean/frontend.cpp +++ b/src/frontends/lean/frontend.cpp @@ -504,6 +504,12 @@ bool frontend::has_implicit_arguments(name const & n) const { std::vector const & frontend::get_implicit_arguments(name const & n) const { return to_ext(m_env).get_implicit_arguments(n); } +std::vector const & frontend::get_implicit_arguments(expr const & n) const { + if (is_constant(n)) + return get_implicit_arguments(const_name(n)); + else + return g_empty_vector; +} name const & frontend::get_explicit_version(name const & n) const { return to_ext(m_env).get_explicit_version(n); } diff --git a/src/frontends/lean/frontend.h b/src/frontends/lean/frontend.h index 4c3765ef0..03718e773 100644 --- a/src/frontends/lean/frontend.h +++ b/src/frontends/lean/frontend.h @@ -129,6 +129,11 @@ public: bool has_implicit_arguments(name const & n) const; /** \brief Return the position of the arguments that are implicit. */ std::vector const & get_implicit_arguments(name const & n) const; + /** + \brief Return the position of the arguments that are implicit. + \remark If \c n is not a constant, then return the empty vector. + */ + std::vector const & get_implicit_arguments(expr const & n) const; /** \brief This frontend associates an definition with each definition (or postulate) that has implicit arguments. The diff --git a/src/frontends/lean/frontend_elaborator.cpp b/src/frontends/lean/frontend_elaborator.cpp index 766982723..735304cf1 100644 --- a/src/frontends/lean/frontend_elaborator.cpp +++ b/src/frontends/lean/frontend_elaborator.cpp @@ -15,6 +15,7 @@ Author: Leonardo de Moura #include "kernel/replace_visitor.h" #include "kernel/unification_constraint.h" #include "kernel/instantiate.h" +#include "kernel/builtin.h" #include "library/type_inferer.h" #include "library/placeholder.h" #include "library/elaborator/elaborator.h" @@ -23,29 +24,52 @@ Author: Leonardo de Moura namespace lean { static name g_x_name("x"); -static name g_choice_name = name::mk_internal_unique_name(); -static expr g_choice = mk_constant(g_choice_name); static format g_assignment_fmt = format(":="); static format g_unification_u_fmt = format("\u2248"); static format g_unification_fmt = format("=?="); +/** + \brief Internal value used to store choices for the elaborator. + This is a transient value that is only used to setup a problem + for the elaborator. +*/ +struct choice_value : public value { + std::vector m_choices; + choice_value(unsigned num_fs, expr const * fs):m_choices(fs, fs + num_fs) {} + virtual ~choice_value() {} + virtual expr get_type() const { lean_unreachable(); } // LCOV_EXCL_LINE + virtual name get_name() const { return name("Choice"); } + virtual void display(std::ostream & out) const { + out << "(Choice"; + for (auto c : m_choices) { + out << " (" << c << ")"; + } + out << ")"; + } + // Remark: we don't implement the pp methods because the lean::pp_fn formatter + // object has support for formatting choice internal values. +}; + expr mk_choice(unsigned num_fs, expr const * fs) { lean_assert(num_fs >= 2); - return mk_eq(g_choice, mk_app(num_fs, fs)); + return mk_value(*(new choice_value(num_fs, fs))); } bool is_choice(expr const & e) { - return is_eq(e) && eq_lhs(e) == g_choice; + return is_value(e) && dynamic_cast(&to_value(e)) != nullptr; +} + +choice_value const & to_choice_value(expr const & e) { + lean_assert(is_choice(e)); + return static_cast(to_value(e)); } unsigned get_num_choices(expr const & e) { - lean_assert(is_choice(e)); - return num_args(eq_rhs(e)); + return to_choice_value(e).m_choices.size(); } expr const & get_choice(expr const & e, unsigned i) { - lean_assert(is_choice(e)); - return arg(eq_rhs(e), i); + return to_choice_value(e).m_choices[i]; } class coercion_justification_cell : public justification_cell { @@ -251,6 +275,7 @@ class frontend_elaborator::imp { } else { buffer to_keep; buffer> to_keep_types; + std::sort(matched.begin(), matched.end()); // we must preserve the original order for (unsigned i : matched) { to_keep.push_back(f_choices[i]); to_keep_types.push_back(f_choice_types[i]); diff --git a/src/frontends/lean/parser.cpp b/src/frontends/lean/parser.cpp index 1fb7785b9..b3af7ecbe 100644 --- a/src/frontends/lean/parser.cpp +++ b/src/frontends/lean/parser.cpp @@ -17,6 +17,7 @@ Author: Leonardo de Moura #include #include #include +#include #include "util/scoped_map.h" #include "util/exception.h" #include "util/sstream.h" @@ -579,6 +580,41 @@ class parser::imp { */ /*@{*/ + /** + \brief Return the size of the implicit vector associated with the given denotation. + */ + unsigned get_implicit_vector_size(expr const & d) { + return m_frontend.get_implicit_arguments(d).size(); + } + + /** + \brief Return a vector \c v that is the implicit vector for some \c d in \c ds, + and v.size() == min{get_implicit_vector_size(d) | d in ds} + */ + std::vector const & get_smallest_implicit_vector(list const & ds) { + std::vector const * r = nullptr; + unsigned m = std::numeric_limits::max(); + for (expr const & d : ds) { + std::vector const & v = m_frontend.get_implicit_arguments(d); + unsigned s = v.size(); + if (s == 0) { + return v; + } else if (s < m) { + r = &v; + m = s; + } + } + lean_assert(r); + return *r; + } + + /** + \brief Return min{get_implicit_vector_size(d) | d in ds} + */ + unsigned get_smallest_implicit_vector_size(list const & ds) { + return get_smallest_implicit_vector(ds).size(); + } + /** \brief Return the function associated with the given operator. If the operator has been overloaded, it returns a choice expression @@ -586,7 +622,7 @@ class parser::imp { After we finish parsing, the elaborator resolve/decide which f_i should be used. */ - expr mk_fun(operator_info const & op) { + expr mk_fun(operator_info const & op, pos_info const & pos) { list const & fs = op.get_denotations(); lean_assert(!is_nil(fs)); auto it = fs.begin(); @@ -595,10 +631,24 @@ class parser::imp { if (it == fs.end()) { return r; } else { + unsigned min_sz = get_smallest_implicit_vector_size(fs); buffer alternatives; - alternatives.push_back(r); + auto add_alternative = [&](expr const & d) { + unsigned sz = get_implicit_vector_size(d); + if (sz > min_sz) { + // must fill the difference with placeholders + buffer aux; + aux.push_back(d); + for (unsigned i = min_sz; i < sz; i++) + aux.push_back(save(mk_placeholder(), pos)); + alternatives.push_back(mk_app(aux)); + } else { + alternatives.push_back(d); + } + }; + add_alternative(r); for (; it != fs.end(); ++it) - alternatives.push_back(*it); + add_alternative(*it); return mk_choice(alternatives.size(), alternatives.data()); } } @@ -609,29 +659,23 @@ class parser::imp { */ expr mk_application(operator_info const & op, pos_info const & pos, unsigned num_args, expr const * args) { buffer new_args; - expr f = save(mk_fun(op), pos); + expr f = save(mk_fun(op, pos), pos); new_args.push_back(f); - // I'm using the fact that all denotations are compatible. // See lean_frontend.cpp for the definition of compatible denotations. - expr const & d = head(op.get_denotations()); - if (is_constant(d) && m_frontend.has_implicit_arguments(const_name(d))) { - std::vector const & imp_args = m_frontend.get_implicit_arguments(const_name(d)); - unsigned i = 0; - for (unsigned j = 0; j < imp_args.size(); j++) { - if (imp_args[j]) { - new_args.push_back(save(mk_placeholder(), pos)); - } else { - if (i >= num_args) - throw parser_error(sstream() << "unexpected number of arguments for denotation with implicit arguments, it expects " << num_args << " explicit argument(s)", pos); - new_args.push_back(args[i]); - i++; - } - } - for (; i < num_args; i++) + auto imp_args = get_smallest_implicit_vector(op.get_denotations()); + unsigned i = 0; + for (unsigned j = 0; j < imp_args.size(); j++) { + if (imp_args[j]) { + new_args.push_back(save(mk_placeholder(), pos)); + } else { + if (i >= num_args) + throw parser_error(sstream() << "unexpected number of arguments for denotation with implicit arguments, it expects " << num_args << " explicit argument(s)", pos); new_args.push_back(args[i]); - } else { - new_args.append(num_args, args); + i++; + } } + for (; i < num_args; i++) + new_args.push_back(args[i]); return save(mk_app(new_args), pos); } expr mk_application(operator_info const & op, pos_info const & pos, std::initializer_list const & l) { diff --git a/src/frontends/lean/pp.cpp b/src/frontends/lean/pp.cpp index ce49fa2f3..4463b9108 100644 --- a/src/frontends/lean/pp.cpp +++ b/src/frontends/lean/pp.cpp @@ -200,8 +200,10 @@ class pp_fn { */ bool is_atomic(expr const & e) { switch (e.kind()) { - case expr_kind::Var: case expr_kind::Constant: case expr_kind::Value: + case expr_kind::Var: case expr_kind::Constant: return true; + case expr_kind::Value: + return !is_choice(e); case expr_kind::Type: return e == Type(); case expr_kind::MetaVar: @@ -700,6 +702,8 @@ class pp_fn { result p; if (is_constant(f)) p = mk_result(format(const_name(f)), 1); + else if (is_choice(f)) + p = pp_child(f, depth); else if (is_value(f)) p = mk_result(to_value(f).pp(m_unicode, m_coercion), 1); else diff --git a/tests/lean/implicit6.lean b/tests/lean/implicit6.lean new file mode 100644 index 000000000..871aa7605 --- /dev/null +++ b/tests/lean/implicit6.lean @@ -0,0 +1,10 @@ +Variable f {A : Type} : A -> A -> A +Infixl 65 + : f +Show true + false +Show 10 + 20 +Show 10 + (- 20) +Set pp::notation false +Set pp::coercion true +Show true + false +Show 10 + 20 +Show 10 + (- 20) diff --git a/tests/lean/implicit6.lean.expected.out b/tests/lean/implicit6.lean.expected.out new file mode 100644 index 000000000..f83b05b27 --- /dev/null +++ b/tests/lean/implicit6.lean.expected.out @@ -0,0 +1,11 @@ + Set: pp::colors + Set: pp::unicode + Assumed: f +⊤ + ⊥ +10 + 20 +10 + - 20 + Set: lean::pp::notation + Set: lean::pp::coercion +f ⊤ ⊥ +Nat::add 10 20 +Int::add (nat_to_int 10) (Nat::neg 20) diff --git a/tests/lean/implicit7.lean b/tests/lean/implicit7.lean new file mode 100644 index 000000000..ebefa0930 --- /dev/null +++ b/tests/lean/implicit7.lean @@ -0,0 +1,18 @@ +(* + TODO(Leo): + Improve elaborator's performance on this example. + The main problem is that we don't have any indexing technique + for the elaborator. +*) +Variable f {A : Type} (a : A) {B : Type} : A -> B -> A +Variable g {A B : Type} (a : A) {C : Type} : B -> C -> C +Notation 100 _ ; _ ; _ : f +Notation 100 _ ; _ ; _ : g +Check 10 ; true ; false +Check 10 ; 10 ; true +Set pp::notation false +Check 10 ; true ; false +Check 10 ; 10 ; true +Set pp::implicit true +Check 10 ; true ; false +Check 10 ; 10 ; true diff --git a/tests/lean/implicit7.lean.expected.out b/tests/lean/implicit7.lean.expected.out new file mode 100644 index 000000000..03a533123 --- /dev/null +++ b/tests/lean/implicit7.lean.expected.out @@ -0,0 +1,12 @@ + Set: pp::colors + Set: pp::unicode + Assumed: f + Assumed: g +10 ; ⊤ ; ⊥ : Bool +10 ; 10 ; ⊤ : ℕ + Set: lean::pp::notation +g 10 ⊤ ⊥ : Bool +f 10 10 ⊤ : ℕ + Set: lean::pp::implicit +g::explicit ℕ Bool 10 Bool ⊤ ⊥ : Bool +f::explicit ℕ 10 Bool 10 ⊤ : ℕ