diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c832122bd051329ff77e31600ea9d9d8ecaf0b4..cd68a8fba8179592f6544468ea76f8729bbc098c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ API-breaking change is listed. Coq 8.10 is no longer supported by this release. +- Add `is_closed_term` tactic for determining whether a term depends on variables bound + in the context. (by Michael Sammler) - Add `list.zip_with_take_both` and `list.zip_with_take_both'` - Specialize `list.zip_with_take_{l,r}`, add generalized lemmas `list.zip_with_take_{l,r}'` - Add `bool_to_Z` that converts true to 1 and false to 0. (by Michael Sammler) diff --git a/tests/is_closed_term.ref b/tests/is_closed_term.ref new file mode 100644 index 0000000000000000000000000000000000000000..3bf0af63c6025d104c7b3e5704f4f8eaae5dceff --- /dev/null +++ b/tests/is_closed_term.ref @@ -0,0 +1,16 @@ +"is_closed_term_test" + : string +The command has indeed failed with message: +Tactic failure: The term x is not closed. +The command has indeed failed with message: +Tactic failure: The term (x + 1) is not closed. +The command has indeed failed with message: +Tactic failure: The term (x + y) is not closed. +The command has indeed failed with message: +Tactic failure: The term section_variable is not closed. +The command has indeed failed with message: +Tactic failure: The term (section_variable + 1) is not closed. +The command has indeed failed with message: +Tactic failure: The term P is not closed. +The command has indeed failed with message: +Tactic failure: The term (P ∧ True) is not closed. diff --git a/tests/is_closed_term.v b/tests/is_closed_term.v new file mode 100644 index 0000000000000000000000000000000000000000..aa9393fa237d92fdd78738876160ac9b94939ba4 --- /dev/null +++ b/tests/is_closed_term.v @@ -0,0 +1,52 @@ +From stdpp Require Import tactics strings. +Unset Mangle Names. + +Section test. + Context (section_variable : nat). + + Axiom axiom : nat. + + Check "is_closed_term_test". + Lemma is_closed_term_test : + ∀ x y (P : Prop), + let a := 10 in + a = a → + let b := (a + 11) in + x + y = y + x. + Proof. + intros x y P a H b. + (* Constructors are closed. *) + is_closed_term 1. + + (* Functions on closed terms are closed. *) + is_closed_term (1 + 1). + + (* Variables bound in the context are not closed. *) + Fail is_closed_term x. + Fail is_closed_term (x + 1). + Fail is_closed_term (x + y). + + (* Section variables are not closed. *) + Fail is_closed_term section_variable. + Fail is_closed_term (section_variable + 1). + + (* Axioms are considered closed. (Arguably this is a bug, but + there is nothing we can do about it.) *) + is_closed_term axiom. + is_closed_term (axiom + 1). + + (* Let-bindings are considered closed. *) + is_closed_term a. + is_closed_term (a + 1). + is_closed_term b. + is_closed_term (b + 1). + + (* is_closed_term also works for propositions. *) + is_closed_term True. + is_closed_term (True ∧ True). + Fail is_closed_term P. + Fail is_closed_term (P ∧ True). + + lia. + Qed. +End test. diff --git a/theories/tactics.v b/theories/tactics.v index ff8306f34dabda515ff46ce4d1dff42345ac5805..744ec4b71494275fee04328618bdeb0e32fc8442 100644 --- a/theories/tactics.v +++ b/theories/tactics.v @@ -599,6 +599,35 @@ Tactic Notation "revert" "select" open_constr(pat) := select pat (fun H => rever Tactic Notation "rename" "select" open_constr(pat) "into" ident(name) := select pat (fun H => rename H into name). +(** The tactic [is_closed_term t] succeeds if [t] is a closed term and fails otherwise. +By closed we mean that [t] does not depend on any variable bound in the context. +axioms are considered closed terms by this tactic (but Section +variables are not). A function application is considered closed if the +function and the argument are closed, without considering the body of +the function (or whether it is opaque or not). This tactic is useful +for example to decide whether to call [vm_compute] on [t]. + +This trick was originally suggested by Jason Gross: +https://coq.zulipchat.com/#narrow/stream/237977-Coq-users/topic/Check.20that.20a.20term.20is.20closed.20in.20Ltac/near/240885618 +*) +Ltac is_closed_term t := + first [ + (** We use the [assert_succeeds] sandbox to be able to freely + change the context. *) + assert_succeeds ( + (** Make sure that the goal only contains [t]. (We use + [const False t] instead of [let x := t in False] as the + let-binding in the latter would be unfolded by the [unfold] + later.) *) + exfalso; change_no_check (const False t); + (** Clear all hypotheses. *) + repeat match goal with H : _ |- _ => try unfold H in *; clear H end; + (** If there are still hypotheses left, [t] is not closed. *) + lazymatch goal with H : _ |- _ => fail | _ => idtac end + ) | + fail 1 "The term" t "is not closed" + ]. + (** Coq's [firstorder] tactic fails or loops on rather small goals already. In particular, on those generated by the tactic [unfold_elem_ofs] which is used to solve propositions on sets. The [naive_solver] tactic implements an