diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ddf92e204636385e0de378543274400f97279d6..de0d390c2cb216b420f0e832d7b74d4711a97836 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,12 @@ lemma. * Rename `Build_loc` constructor for `loc` type to `Loc`. +**Changes in `proof_mode`:** + +* Add support for pure names `%H` in intro patterns. This is now natively + supported whereas the previous experimental support required installing + https://gitlab.mpi-sws.org/iris/string-ident. + The following `sed` script helps adjust your code to the renaming (on macOS, replace `sed` by `gsed`, installed via e.g. `brew install gnu-sed`). Note that the script is not idempotent, do not run it twice. diff --git a/_CoqProject b/_CoqProject index fcb68112827ff88a4bcf50c0391cdb5337a0a747..4737f7f4ceb4c9ec344ef56727f765f9631efa55 100644 --- a/_CoqProject +++ b/_CoqProject @@ -117,6 +117,7 @@ iris/program_logic/total_ectx_lifting.v iris/program_logic/atomic.v iris/proofmode/base.v iris/proofmode/ident_name.v +iris/proofmode/string_ident.v iris/proofmode/tokens.v iris/proofmode/coq_tactics.v iris/proofmode/ltac_tactics.v diff --git a/iris/proofmode/ltac_tactics.v b/iris/proofmode/ltac_tactics.v index ad529cf61b68d08729e07a6827e328182e3237a8..180eeee01dcc7ba69bbd0b8cee422b9fffafcadb 100644 --- a/iris/proofmode/ltac_tactics.v +++ b/iris/proofmode/ltac_tactics.v @@ -1,7 +1,8 @@ From stdpp Require Import namespaces hlist pretty. From iris.bi Require Export bi telescopes. From iris.proofmode Require Import base intro_patterns spec_patterns - sel_patterns coq_tactics reduction. + sel_patterns coq_tactics reduction + string_ident. From iris.proofmode Require Export classes notation. From iris.prelude Require Import options. Export ident. @@ -1421,30 +1422,6 @@ Tactic Notation "iModCore" constr(H) := |iSolveSideCondition |pm_reduce; pm_prettify(* subgoal *)]. -(* This tactic should take a string [s] and solve the goal with [exact (λ -(s:unit), tt)], where the name of the binder is the string as an identifier. -We use this API (rather than simply returning the identifier) since it works -correctly when replaced with [fail]. - -One way to implement such a function is to use Ltac2 on Coq 8.11+. Another -option is https://github.com/ppedrot/coq-string-ident for Coq 8.10. *) -Ltac string_to_ident_hook := fun s => fail 100 "string_to_ident is unavailable in this version of Coq". - -(* Turn a string_to_ident that produces an ident value into one that solves the -goal with a [unit → unit] function instead, as expected for -[string_to_ident_hook]. *) -Ltac make_string_to_ident_hook string_to_ident := - fun s => let x := string_to_ident s in - exact (λ (x:unit), tt). - -(* [string_to_ident] uses [string_to_ident_hook] to turn [s] into an identifier -and return it. *) -Local Ltac string_to_ident s := - let ident_fun := constr:(ltac:(string_to_ident_hook s)) in - lazymatch ident_fun with - | λ (x:_), _ => x - end. - (** * Basic destruct tactic *) (** [pat0] is the unparsed pattern, and is only used in error messages *) diff --git a/iris/proofmode/string_ident.v b/iris/proofmode/string_ident.v new file mode 100644 index 0000000000000000000000000000000000000000..1241e3928880fe1c7dcb469fa245a99022adad7c --- /dev/null +++ b/iris/proofmode/string_ident.v @@ -0,0 +1,98 @@ +From Ltac2 Require Import Ltac2. +From Coq Require Import Strings.String. +From Coq Require Import Init.Byte. +From iris.prelude Require Import options. + +Import List.ListNotations. +Local Open Scope list. + +Ltac2 Type exn ::= [ NotStringLiteral(constr) | InvalidIdent(string) ]. + +Module StringToIdent. + + Ltac2 coq_byte_to_int b := + (match! b with + (* generate this line with python3 -c 'print(" ".join([\'| x%02x => %d\' % (x,x) for x in range(256)]))' *) + | x00 => 0 | x01 => 1 | x02 => 2 | x03 => 3 | x04 => 4 | x05 => 5 | x06 => 6 | x07 => 7 | x08 => 8 | x09 => 9 | x0a => 10 | x0b => 11 | x0c => 12 | x0d => 13 | x0e => 14 | x0f => 15 | x10 => 16 | x11 => 17 | x12 => 18 | x13 => 19 | x14 => 20 | x15 => 21 | x16 => 22 | x17 => 23 | x18 => 24 | x19 => 25 | x1a => 26 | x1b => 27 | x1c => 28 | x1d => 29 | x1e => 30 | x1f => 31 | x20 => 32 | x21 => 33 | x22 => 34 | x23 => 35 | x24 => 36 | x25 => 37 | x26 => 38 | x27 => 39 | x28 => 40 | x29 => 41 | x2a => 42 | x2b => 43 | x2c => 44 | x2d => 45 | x2e => 46 | x2f => 47 | x30 => 48 | x31 => 49 | x32 => 50 | x33 => 51 | x34 => 52 | x35 => 53 | x36 => 54 | x37 => 55 | x38 => 56 | x39 => 57 | x3a => 58 | x3b => 59 | x3c => 60 | x3d => 61 | x3e => 62 | x3f => 63 | x40 => 64 | x41 => 65 | x42 => 66 | x43 => 67 | x44 => 68 | x45 => 69 | x46 => 70 | x47 => 71 | x48 => 72 | x49 => 73 | x4a => 74 | x4b => 75 | x4c => 76 | x4d => 77 | x4e => 78 | x4f => 79 | x50 => 80 | x51 => 81 | x52 => 82 | x53 => 83 | x54 => 84 | x55 => 85 | x56 => 86 | x57 => 87 | x58 => 88 | x59 => 89 | x5a => 90 | x5b => 91 | x5c => 92 | x5d => 93 | x5e => 94 | x5f => 95 | x60 => 96 | x61 => 97 | x62 => 98 | x63 => 99 | x64 => 100 | x65 => 101 | x66 => 102 | x67 => 103 | x68 => 104 | x69 => 105 | x6a => 106 | x6b => 107 | x6c => 108 | x6d => 109 | x6e => 110 | x6f => 111 | x70 => 112 | x71 => 113 | x72 => 114 | x73 => 115 | x74 => 116 | x75 => 117 | x76 => 118 | x77 => 119 | x78 => 120 | x79 => 121 | x7a => 122 | x7b => 123 | x7c => 124 | x7d => 125 | x7e => 126 | x7f => 127 | x80 => 128 | x81 => 129 | x82 => 130 | x83 => 131 | x84 => 132 | x85 => 133 | x86 => 134 | x87 => 135 | x88 => 136 | x89 => 137 | x8a => 138 | x8b => 139 | x8c => 140 | x8d => 141 | x8e => 142 | x8f => 143 | x90 => 144 | x91 => 145 | x92 => 146 | x93 => 147 | x94 => 148 | x95 => 149 | x96 => 150 | x97 => 151 | x98 => 152 | x99 => 153 | x9a => 154 | x9b => 155 | x9c => 156 | x9d => 157 | x9e => 158 | x9f => 159 | xa0 => 160 | xa1 => 161 | xa2 => 162 | xa3 => 163 | xa4 => 164 | xa5 => 165 | xa6 => 166 | xa7 => 167 | xa8 => 168 | xa9 => 169 | xaa => 170 | xab => 171 | xac => 172 | xad => 173 | xae => 174 | xaf => 175 | xb0 => 176 | xb1 => 177 | xb2 => 178 | xb3 => 179 | xb4 => 180 | xb5 => 181 | xb6 => 182 | xb7 => 183 | xb8 => 184 | xb9 => 185 | xba => 186 | xbb => 187 | xbc => 188 | xbd => 189 | xbe => 190 | xbf => 191 | xc0 => 192 | xc1 => 193 | xc2 => 194 | xc3 => 195 | xc4 => 196 | xc5 => 197 | xc6 => 198 | xc7 => 199 | xc8 => 200 | xc9 => 201 | xca => 202 | xcb => 203 | xcc => 204 | xcd => 205 | xce => 206 | xcf => 207 | xd0 => 208 | xd1 => 209 | xd2 => 210 | xd3 => 211 | xd4 => 212 | xd5 => 213 | xd6 => 214 | xd7 => 215 | xd8 => 216 | xd9 => 217 | xda => 218 | xdb => 219 | xdc => 220 | xdd => 221 | xde => 222 | xdf => 223 | xe0 => 224 | xe1 => 225 | xe2 => 226 | xe3 => 227 | xe4 => 228 | xe5 => 229 | xe6 => 230 | xe7 => 231 | xe8 => 232 | xe9 => 233 | xea => 234 | xeb => 235 | xec => 236 | xed => 237 | xee => 238 | xef => 239 | xf0 => 240 | xf1 => 241 | xf2 => 242 | xf3 => 243 | xf4 => 244 | xf5 => 245 | xf6 => 246 | xf7 => 247 | xf8 => 248 | xf9 => 249 | xfa => 250 | xfb => 251 | xfc => 252 | xfd => 253 | xfe => 254 | xff => 255 + end). + + Ltac2 coq_byte_to_char b := + Char.of_int (coq_byte_to_int b). + + Fixpoint coq_string_to_list_byte (s : string): list byte := + match s with + | EmptyString => [] + | String c s => Ascii.byte_of_ascii c :: coq_string_to_list_byte s + end. + + (** copy a list of Coq byte constrs into a string (already of the right length) *) + Ltac2 rec coq_byte_list_blit_list (pos : int) (ls : constr) (str : string) := + match! ls with + | nil => () + | ?c :: ?ls => + let b := coq_byte_to_char c in + String.set str pos b; coq_byte_list_blit_list (Int.add pos 1) ls str + end. + + Ltac2 rec coq_string_length (s : constr) := + match! s with + | EmptyString => 0 + | String _ ?s' => Int.add 1 (coq_string_length s') + | _ => Control.throw (NotStringLiteral(s)) + end. + + Ltac2 compute c := + Std.eval_vm None c. + + (** [coq_string_to_string] converts a Gallina string in a constr to an Ltac2 + native string *) + Ltac2 coq_string_to_string (s : constr) := + let l := coq_string_length s in + let str := String.make l (Char.of_int 0) in + let bytes := compute constr:(coq_string_to_list_byte $s) in + let _ := coq_byte_list_blit_list 0 bytes str in + str. + + (* to convert the string to an identifier we have to use the tools from Fresh; + note we pass Fresh.fresh and empty set of identifiers to avoid, so this + isn't necessarily fresh in the context, but if that's desired we can always + do it later with Ltac1's fresh. *) + Ltac2 ident_from_string (s : string) := + match Ident.of_string s with + | Some id => Fresh.fresh (Fresh.Free.of_ids []) id + | None => Control.throw (InvalidIdent s) + end. + + (** [coq_string_to_ident] implements the ident to string conversion in Ltac2 *) + Ltac2 coq_string_to_ident (s : constr) := ident_from_string (coq_string_to_string s). + + (** We want to wrap this in an Ltac1 API, which requires passing a string to + Ltac2 and then returning the resulting identifier. + + The only FFI from Ltac1 to Ltac2 is to call an Ltac2 expression that solves + a goal. We'll solve that goal with [fun (x:unit) => tt] where the name [x] is + the desired identifier - Coq remembers the identifier and we can extract it + with Ltac1 pattern matching. *) + + Ltac2 get_opt o := match o with None => Control.throw Not_found | Some x => x end. + + (** solve a goal of the form [unit -> unit] with a function that has the + appropriate name *) + Ltac coq_string_to_ident_lambda := + ltac2:(s1 |- let s := get_opt (Ltac1.to_constr s1) in + let ident := coq_string_to_ident s in + clear; (* needed since ident might already be in scope where + this is called *) + intros $ident; + exact tt). +End StringToIdent. + +(** Finally we wrap everything up by running [coq_string_to_ident_lambda] in a new +context using a tactic-in-terms, extracting just the identifier from the +produced lambda, and returning it as an Ltac1 value. *) +Ltac string_to_ident s := + let s := (eval cbv in s) in + let x := constr:(ltac:(StringToIdent.coq_string_to_ident_lambda s) : unit -> unit) in + match x with + | (fun (name:_) => _) => name + end. diff --git a/tests/proofmode.v b/tests/proofmode.v index bf5c427a0950f187e06067b529f81c7300109278..9940220a322df3876e16151375d5b30367928fa0 100644 --- a/tests/proofmode.v +++ b/tests/proofmode.v @@ -1356,14 +1356,6 @@ End error_tests_bi. Section pure_name_tests. Context {PROP : bi}. Implicit Types P Q R : PROP. -(* mock string_to_ident for just these tests *) -Ltac ltac_tactics.string_to_ident_hook ::= - make_string_to_ident_hook ltac:(fun s => lazymatch s with - | "HP2" => ident:(HP2) - | "H" => ident:(H) - | "y" => ident:(y) - | _ => fail 100 s - end). Check "test_pure_name". Lemma test_pure_name P (φ1 φ2 : Prop) (Himpl : φ1 → φ2) : diff --git a/tests/string_ident.ref b/tests/string_ident.ref new file mode 100644 index 0000000000000000000000000000000000000000..f42f19c8c7f259392d3c40e7d35cfdb8e0efce9c --- /dev/null +++ b/tests/string_ident.ref @@ -0,0 +1,12 @@ +"test_invalid_ident" + : string +The command has indeed failed with message: +Uncaught Ltac2 exception: InvalidIdent ("has a space") +"test_not_string" + : string +The command has indeed failed with message: +Uncaught Ltac2 exception: NotStringLiteral (constr:(4)) +"test_not_literal" + : string +The command has indeed failed with message: +Uncaught Ltac2 exception: NotStringLiteral (constr:(s)) diff --git a/tests/string_ident.v b/tests/string_ident.v new file mode 100644 index 0000000000000000000000000000000000000000..379ba301107b4aad130f07b58821dd0d7a1bcf3e --- /dev/null +++ b/tests/string_ident.v @@ -0,0 +1,41 @@ +From iris.proofmode Require Import string_ident. +From Coq Require Import Strings.String. +From stdpp Require Import base. +Open Scope string. + +(* these tests should test name generation directly, which Mangle Names +interferes with *) +Unset Mangle Names. + +Lemma test_basic_ident : ∀ (n:nat), n = n. +Proof. + let x := string_to_ident "n" in + intros x. + exact (eq_refl n). +Qed. + +Lemma test_in_scope (n:nat) : n = n. +Proof. + let x := string_to_ident "n" in exact (eq_refl n). +Qed. + +Check "test_invalid_ident". +Lemma test_invalid_ident : True. +Proof. + Fail let x := string_to_ident "has a space" in + idtac x. +Abort. + +Check "test_not_string". +Lemma test_not_string : True. +Proof. + Fail let x := string_to_ident 4 in + idtac x. +Abort. + +Check "test_not_literal". +Lemma test_not_literal (s:string) : True. +Proof. + Fail let x := string_to_ident s in + idtac x. +Abort.