diff --git a/src/frontends/lean/builtin_exprs.cpp b/src/frontends/lean/builtin_exprs.cpp index a18521af20..b9210a540c 100644 --- a/src/frontends/lean/builtin_exprs.cpp +++ b/src/frontends/lean/builtin_exprs.cpp @@ -43,7 +43,8 @@ bool get_parser_checkpoint_have(options const & opts) { return opts.get_bool(*g_parser_checkpoint_have, LEAN_DEFAULT_PARSER_CHECKPOINT_HAVE); } -namespace notation { +using namespace notation; + static expr parse_Type(parser & p, unsigned, expr const *, pos_info const & pos) { if (p.curr_is_token(get_llevel_curly_tk())) { p.next(); @@ -621,6 +622,32 @@ static expr parse_quoted_name(parser & p, unsigned, expr const *, pos_info const return p.rec_save_pos(e, pos); } +static name * g_anonymous_constructor = nullptr; + +expr mk_anonymous_constructor(expr const & e) { return mk_annotation(*g_anonymous_constructor, e); } + +bool is_anonymous_constructor(expr const & e) { return is_annotation(e, *g_anonymous_constructor); } + +expr const & get_anonymous_constructor_arg(expr const & e) { + lean_assert(is_anonymous_constructor(e)); + return get_annotation_arg(e); +} + +static expr parse_constructor(parser & p, unsigned, expr const *, pos_info const & pos) { + buffer args; + while (!p.curr_is_token(get_rangle_tk())) { + args.push_back(p.parse_expr()); + if (p.curr_is_token(get_comma_tk())) { + p.next(); + } else { + break; + } + } + p.check_token_next(get_rangle_tk(), "invalid constructor, `⟩` expected"); + expr fn = p.save_pos(mk_expr_placeholder(), pos); + return p.save_pos(mk_anonymous_constructor(p.save_pos(mk_app(fn, args), pos)), pos); +} + parse_table init_nud_table() { action Expr(mk_expr_action()); action Skip(mk_skip_action()); @@ -637,6 +664,7 @@ parse_table init_nud_table() { r = r.add({transition("if", mk_ext_action(parse_if_then_else))}, x0); r = r.add({transition("(", Expr), transition(")", mk_ext_action(parse_rparen))}, x0); r = r.add({transition("(", Expr), transition(":", Expr), transition(")", mk_ext_action(parse_typed_expr))}, x0); + r = r.add({transition("⟨", mk_ext_action(parse_constructor))}, x0); r = r.add({transition("?(", Expr), transition(")", mk_ext_action(parse_inaccessible))}, x0); r = r.add({transition("`(", mk_ext_action(parse_quoted_expr))}, x0); r = r.add({transition("`", mk_ext_action(parse_quoted_name))}, x0); @@ -665,7 +693,6 @@ parse_table init_led_table() { r = r.add({transition(" const & fns) { format r("overloads:"); r += space(); @@ -500,7 +506,7 @@ expr elaborator::visit_prenum(expr const & e, optional const & expected_ty level A_lvl = get_level(A, ref); levels ls(A_lvl); if (v.is_neg()) - throw elaborator_exception(ref, format("invalid pre-numeral, it must be a non-negative value")); + throw elaborator_exception(ref, "invalid pre-numeral, it must be a non-negative value"); if (v.is_zero()) { expr has_zero_A = mk_app(mk_constant(get_has_zero_name(), ls), A, e_tag); expr S = mk_instance(has_zero_A, ref); @@ -562,13 +568,16 @@ expr elaborator::visit_const_core(expr const & e) { } expr elaborator::visit_function(expr const & fn, bool has_args, expr const & ref) { + if (is_placeholder(fn)) { + throw elaborator_exception(ref, "placeholders '_' cannot be used where a function is expected"); + } expr r; switch (fn.kind()) { case expr_kind::Var: case expr_kind::Pi: case expr_kind::Meta: case expr_kind::Sort: - throw elaborator_exception(ref, format("invalid application, function expected")); + throw elaborator_exception(ref, "invalid application, function expected"); // The expr_kind::App case can only happen when nary notation is used case expr_kind::App: r = visit(fn, none_expr()); break; case expr_kind::Local: r = fn; break; @@ -749,7 +758,7 @@ expr elaborator::visit_elim_app(expr const & fn, elim_info const & info, buffer< expr motive_arg = new_args[info.m_motive_idx]; if (!is_def_eq(motive_arg, motive)) { - throw elaborator_exception(ref, format("\"eliminator\" elaborator failed to compute the motive")); + throw elaborator_exception(ref, "\"eliminator\" elaborator failed to compute the motive"); } /* Elaborate postponed arguments */ @@ -759,7 +768,7 @@ expr elaborator::visit_elim_app(expr const & fn, elim_info const & info, buffer< expr new_arg_type = instantiate_mvars(infer_type(new_args[i])); expr new_arg = visit(*arg, some_expr(new_arg_type)); if (!is_def_eq(new_args[i], new_arg)) { - throw elaborator_exception(ref, format("\"eliminator\" elaborator failed to assign explicit argument")); + throw elaborator_exception(ref, "\"eliminator\" elaborator failed to assign explicit argument"); } } } @@ -960,8 +969,8 @@ expr elaborator::visit_app_core(expr fn, buffer const & args, optional fns; if (amask != arg_mask::Default) - throw elaborator_exception(ref, format("invalid explicit annotation, symbol is overloaded " - "(solution: use fully qualified names)")); + throw elaborator_exception(ref, "invalid explicit annotation, symbol is overloaded " + "(solution: use fully qualified names)"); for (unsigned i = 0; i < get_num_choices(fn); i++) { expr const & fn_i = get_choice(fn, i); fns.push_back(fn_i); @@ -1023,9 +1032,34 @@ expr elaborator::visit_by(expr const & e, optional const & expected_type) return mvar; } +expr elaborator::visit_anonymous_constructor(expr const & e, optional const & expected_type) { + lean_assert(is_anonymous_constructor(e)); + buffer args; + get_app_args(get_anonymous_constructor_arg(e), args); + if (!expected_type) + throw elaborator_exception(e, "invalid constructor ⟨...⟩, expected type must be known"); + expr I = get_app_fn(m_ctx.relaxed_whnf(instantiate_mvars(*expected_type))); + if (!is_constant(I)) + throw elaborator_exception(e, format("invalid constructor ⟨...⟩, expected type is not an inductive type") + + pp_indent(*expected_type)); + name I_name = const_name(I); + if (!inductive::is_inductive_decl(m_env, I_name)) + throw elaborator_exception(e, sstream() << "invalid constructor ⟨...⟩, '" << I_name << "' is not an inductive type"); + buffer c_names; + get_intro_rule_names(m_env, I_name, c_names); + if (c_names.size() != 1) + throw elaborator_exception(e, sstream() << "invalid constructor ⟨...⟩, '" << I_name << "' must have only one constructor"); + expr new_e = copy_tag(e, mk_app(mk_constant(c_names[0]), args)); + return visit(new_e, expected_type); +} + expr elaborator::visit_macro(expr const & e, optional const & expected_type, bool is_app_fn) { if (is_as_is(e)) { return get_as_is_arg(e); + } else if (is_anonymous_constructor(e)) { + if (is_app_fn) + throw elaborator_exception(e, "invalid constructor ⟨...⟩, function expected"); + return visit_anonymous_constructor(e, expected_type); } else if (is_prenum(e)) { return visit_prenum(e, expected_type); } else if (is_typed_expr(e)) { @@ -1207,7 +1241,7 @@ void elaborator::ensure_numeral_types_assigned(checkpoint const & C) { expr A = instantiate_mvars(head(m_numeral_type_stack)); if (is_metavar(A)) { if (!assign_mvar(A, get_default_numeral_type())) - throw elaborator_exception(A, format("invalid numeral, failed to force numeral to be a nat")); + throw elaborator_exception(A, "invalid numeral, failed to force numeral to be a nat"); } m_numeral_type_stack = tail(m_numeral_type_stack); } @@ -1238,8 +1272,7 @@ void elaborator::synthesize_type_class_instances_core(list const & old_sta if (!has_expr_metavar(inst_type)) { // We must try to synthesize instance using the local context where it was declared if (!is_def_eq(inst, mk_instance_core(mdecl.get_context(), inst_type, ref))) - throw elaborator_exception(mvar, - format("failed to assign type class instance to placeholder")); + throw elaborator_exception(mvar, "failed to assign type class instance to placeholder"); } else { if (force) { auto pp_fn = mk_pp_fn(m_ctx); diff --git a/src/frontends/lean/elaborator.h b/src/frontends/lean/elaborator.h index 4dc4afa5fb..899fcb2e51 100644 --- a/src/frontends/lean/elaborator.h +++ b/src/frontends/lean/elaborator.h @@ -95,6 +95,7 @@ class elaborator { pp_fn mk_pp_fn(type_context & ctx); format pp_indent(pp_fn const & pp_fn, expr const & e); + format pp_indent(expr const & e); format pp_overloads(pp_fn const & pp_fn, buffer const & fns); expr infer_type(expr const & e) { return m_ctx.infer(e); } @@ -148,6 +149,7 @@ class elaborator { expr visit_placeholder(expr const & e, optional const & expected_type); expr visit_have_expr(expr const & e, optional const & expected_type); expr visit_by(expr const & e, optional const & expected_type); + expr visit_anonymous_constructor(expr const & e, optional const & expected_type); expr visit_sort(expr const & e); expr visit_const_core(expr const & e); diff --git a/src/frontends/lean/elaborator_exception.h b/src/frontends/lean/elaborator_exception.h index e74c19af91..230f03ad79 100644 --- a/src/frontends/lean/elaborator_exception.h +++ b/src/frontends/lean/elaborator_exception.h @@ -12,5 +12,6 @@ class elaborator_exception : public formatted_exception { public: elaborator_exception(expr const & e, format const & fmt):formatted_exception(e, fmt) {} elaborator_exception(expr const & e, sstream const & strm):formatted_exception(e, format(strm.str())) {} + elaborator_exception(expr const & e, char const * msg):formatted_exception(e, format(msg)) {} }; } diff --git a/tests/lean/anc1.lean b/tests/lean/anc1.lean new file mode 100644 index 0000000000..064624f3f4 --- /dev/null +++ b/tests/lean/anc1.lean @@ -0,0 +1,18 @@ +check (⟨1, 2⟩ : nat × nat) + +check (⟨trivial, trivial⟩ : true ∧ true) + +example : true := sorry + +check (⟨1, sorry⟩ : Σ x : nat, x > 0) + +open tactic + +check show true, from ⟨⟩ + +check (⟨1, by intro1 >> contradiction⟩ : ∃ x : nat, 1 ≠ 0) + +check λ (A B C : Prop), + assume (Ha : A) (Hb : B) (Hc : C), + show B ∧ A, from + ⟨Hb, Ha⟩ diff --git a/tests/lean/anc1.lean.expected.out b/tests/lean/anc1.lean.expected.out new file mode 100644 index 0000000000..b47c4a5a7b --- /dev/null +++ b/tests/lean/anc1.lean.expected.out @@ -0,0 +1,6 @@ +(1, 2) : ℕ × ℕ +and.intro trivial trivial : true ∧ true +sigma.mk 1 sorry : Σ x, x > 0 +show true, from true.intro : true +Exists.intro 1 (λ a, nat.no_confusion a) : ∃ x, 1 ≠ 0 +λ A B C Ha Hb Hc, show B ∧ A, from and.intro Hb Ha : ∀ A B C, A → B → C → B ∧ A