| /- | |
| Copyright (c) 2021 Gabriel Ebner. All rights reserved. | |
| Released under Apache 2.0 license as described in the file LICENSE. | |
| Authors: Gabriel Ebner, Mario Carneiro | |
| -/ | |
| prelude | |
| import Init.TacticsExtra | |
| import Init.RCases | |
| namespace Lean | |
| namespace Parser.Attr | |
| /-- Registers an extensionality theorem. | |
| * When `@[ext]` is applied to a structure, it generates `.ext` and `.ext_iff` theorems and registers | |
| them for the `ext` tactic. | |
| * When `@[ext]` is applied to a theorem, the theorem is registered for the `ext` tactic. | |
| * An optional natural number argument, e.g. `@[ext 9000]`, specifies a priority for the lemma. Higher-priority lemmas are chosen first, and the default is `1000`. | |
| * The flag `@[ext (flat := false)]` causes generated structure extensionality theorems to show inherited fields based on their representation, | |
| rather than flattening the parents' fields into the lemma's equality hypotheses. | |
| structures in the generated extensionality theorems. -/ | |
| syntax (name := ext) "ext" (" (" &"flat" " := " term ")")? (ppSpace prio)? : attr | |
| end Parser.Attr | |
| -- TODO: rename this namespace? | |
| -- Remark: `ext` has scoped syntax, Mathlib may depend on the actual namespace name. | |
| namespace Elab.Tactic.Ext | |
| /-- | |
| Creates the type of the extensionality theorem for the given structure, | |
| elaborating to `x.1 = y.1 → x.2 = y.2 → x = y`, for example. | |
| -/ | |
| scoped syntax (name := extType) "ext_type% " term:max ppSpace ident : term | |
| /-- | |
| Creates the type of the iff-variant of the extensionality theorem for the given structure, | |
| elaborating to `x = y ↔ x.1 = y.1 ∧ x.2 = y.2`, for example. | |
| -/ | |
| scoped syntax (name := extIffType) "ext_iff_type% " term:max ppSpace ident : term | |
| /-- | |
| `declare_ext_theorems_for A` declares the extensionality theorems for the structure `A`. | |
| These theorems state that two expressions with the structure type are equal if their fields are equal. | |
| -/ | |
| syntax (name := declareExtTheoremFor) "declare_ext_theorems_for " ("(" &"flat" " := " term ") ")? ident (ppSpace prio)? : command | |
| macro_rules | `(declare_ext_theorems_for $[(flat := $f)]? $struct:ident $(prio)?) => do | |
| let flat := f.getD (mkIdent `true) | |
| let names ← Macro.resolveGlobalName struct.getId.eraseMacroScopes | |
| let name ← match names.filter (·.2.isEmpty) with | |
| | [] => Macro.throwError s!"unknown constant {struct.getId}" | |
| | [(name, _)] => pure name | |
| | _ => Macro.throwError s!"ambiguous name {struct.getId}" | |
| let extName := mkIdentFrom struct (canonical := true) <| name.mkStr "ext" | |
| let extIffName := mkIdentFrom struct (canonical := true) <| name.mkStr "ext_iff" | |
| `(@[ext $(prio)?] protected theorem $extName:ident : ext_type% $flat $struct:ident := | |
| fun {..} {..} => by intros; subst_eqs; rfl | |
| protected theorem $extIffName:ident : ext_iff_type% $flat $struct:ident := | |
| fun {..} {..} => | |
| ⟨fun h => by cases h; and_intros <;> rfl, | |
| fun _ => by (repeat cases ‹_ ∧ _›); subst_eqs; rfl⟩) | |
| /-- | |
| Applies extensionality lemmas that are registered with the `@[ext]` attribute. | |
| * `ext pat*` applies extensionality theorems as much as possible, | |
| using the patterns `pat*` to introduce the variables in extensionality theorems using `rintro`. | |
| For example, the patterns are used to name the variables introduced by lemmas such as `funext`. | |
| * Without patterns,`ext` applies extensionality lemmas as much | |
| as possible but introduces anonymous hypotheses whenever needed. | |
| * `ext pat* : n` applies ext theorems only up to depth `n`. | |
| The `ext1 pat*` tactic is like `ext pat*` except that it only applies a single extensionality theorem. | |
| Unused patterns will generate warning. | |
| Patterns that don't match the variables will typically result in the introduction of anonymous hypotheses. | |
| -/ | |
| syntax (name := ext) "ext" (colGt ppSpace rintroPat)* (" : " num)? : tactic | |
| /-- Apply a single extensionality theorem to the current goal. -/ | |
| syntax (name := applyExtTheorem) "apply_ext_theorem" : tactic | |
| /-- | |
| `ext1 pat*` is like `ext pat*` except that it only applies a single extensionality theorem rather | |
| than recursively applying as many extensionality theorems as possible. | |
| The `pat*` patterns are processed using the `rintro` tactic. | |
| If no patterns are supplied, then variables are introduced anonymously using the `intros` tactic. | |
| -/ | |
| macro "ext1" xs:(colGt ppSpace rintroPat)* : tactic => | |
| if xs.isEmpty then `(tactic| apply_ext_theorem <;> intros) | |
| else `(tactic| apply_ext_theorem <;> rintro $xs*) | |
| end Elab.Tactic.Ext | |
| end Lean | |
| attribute [ext] funext propext Subtype.eq | |
| @[ext] theorem Prod.ext : {x y : Prod α β} → x.fst = y.fst → x.snd = y.snd → x = y | |
| | ⟨_,_⟩, ⟨_,_⟩, rfl, rfl => rfl | |
| @[ext] theorem PProd.ext : {x y : PProd α β} → x.fst = y.fst → x.snd = y.snd → x = y | |
| | ⟨_,_⟩, ⟨_,_⟩, rfl, rfl => rfl | |
| @[ext] theorem Sigma.ext : {x y : Sigma β} → x.fst = y.fst → HEq x.snd y.snd → x = y | |
| | ⟨_,_⟩, ⟨_,_⟩, rfl, .rfl => rfl | |
| @[ext] theorem PSigma.ext : {x y : PSigma β} → x.fst = y.fst → HEq x.snd y.snd → x = y | |
| | ⟨_,_⟩, ⟨_,_⟩, rfl, .rfl => rfl | |
| @[ext] protected theorem PUnit.ext (x y : PUnit) : x = y := rfl | |
| protected theorem Unit.ext (x y : Unit) : x = y := rfl |