|
| 1 | +/- |
| 2 | +Copyright (c) 2021-2023 by the authors listed in the file AUTHORS and their |
| 3 | +institutional affiliations. All rights reserved. |
| 4 | +Released under Apache 2.0 license as described in the file LICENSE. |
| 5 | +Authors: Harun Khan |
| 6 | +-/ |
| 7 | + |
| 8 | +import Smt.Reconstruct |
| 9 | + |
| 10 | +namespace Smt.Reconstruct.Datatype |
| 11 | + |
| 12 | +open Lean Meta Qq |
| 13 | + |
| 14 | +/-- Names of types that already have dedicated sort/term/proof reconstructors |
| 15 | +and should not be handled by the generic datatype reconstructor. -/ |
| 16 | +private def builtinTypeNames : Std.HashSet Name := |
| 17 | + Std.HashSet.ofList [ |
| 18 | + ``Bool, `Prop, ``True, ``False, |
| 19 | + ``Or, ``And, ``Iff, ``Exists, |
| 20 | + ``Nat, ``Int, `Rat, `Real, |
| 21 | + ``String, ``BitVec |
| 22 | + ] |
| 23 | + |
| 24 | +/-- Return `true` when the name (or its prefix for a constructor) belongs to a |
| 25 | +type with dedicated built-in reconstruction support. -/ |
| 26 | +private def isBuiltinName (n : Name) : Bool := |
| 27 | + builtinTypeNames.contains n || |
| 28 | + -- Also catch constructor names like `Bool.true`, `Nat.zero`, etc. |
| 29 | + match n with |
| 30 | + | .str p _ => builtinTypeNames.contains p |
| 31 | + | _ => false |
| 32 | + |
| 33 | +-- Strip SMT-LIB2 pipe quoting (|name|) from a symbol if present. |
| 34 | +private def stripSMTPipes (s : String) : String := |
| 35 | + if s.startsWith "|" && s.endsWith "|" then (s.drop 1 |>.dropEnd 1).toString else s |
| 36 | + |
| 37 | +private def getFVarOrConstExpr! (n : String) : ReconstructM Expr := do |
| 38 | + match (← read).userNames[n]? with |
| 39 | + | some e => return e |
| 40 | + | none => match (← getLCtx).findFromUserName? n.toName with |
| 41 | + | some d => return d.toExpr |
| 42 | + | none => |
| 43 | + let c ← getConstInfo n.toName |
| 44 | + return .const c.name (c.numLevelParams.repeat (.zero :: ·) []) |
| 45 | + |
| 46 | +@[smt_sort_reconstruct] def reconstructDatatypeSort : SortReconstructor := fun s => do |
| 47 | + match s.getKind! with |
| 48 | + | .DATATYPE_SORT => |
| 49 | + let name := s.getDatatype!.getName! |
| 50 | + if isBuiltinName name.toName then return none |
| 51 | + getFVarOrConstExpr! name |
| 52 | + | _ => return none |
| 53 | + |
| 54 | +@[smt_term_reconstruct] def reconstructDatatypeTerm : TermReconstructor := fun t => do |
| 55 | + match t.getKind! with |
| 56 | + | .APPLY_CONSTRUCTOR => |
| 57 | + -- t[0]! is the constructor symbol. It has INTERNAL_KIND in cvc5 (even for non-zero-arity), |
| 58 | + -- so we look it up by name instead of calling reconstructTerm recursively. |
| 59 | + -- toString may include SMT-LIB pipe escaping (e.g., "|mynat'.succ|"), so strip it. |
| 60 | + let ctorName := stripSMTPipes t[0]!.toString |
| 61 | + if isBuiltinName ctorName.toName then return none |
| 62 | + let mut curr ← getFVarOrConstExpr! ctorName |
| 63 | + for i in [1:t.getNumChildren] do |
| 64 | + curr := .app curr (← reconstructTerm t[i]!) |
| 65 | + return curr |
| 66 | + | _ => return none |
| 67 | + |
| 68 | +/-- Build a Lean proof of `(t = s) = False` where `t` and `s` are |
| 69 | +distinct constructors of the same inductive type. Uses the `noConfusion` |
| 70 | +principle that Lean auto-generates for every inductive type. -/ |
| 71 | +private def proveConsClash (t s : Expr) : MetaM Expr := do |
| 72 | + let α ← inferType t |
| 73 | + let .const αName _ := α.getAppFn |
| 74 | + | throwError "Smt.Reconstruct.Datatype: expected inductive type, got {α}" |
| 75 | + let ncName := αName ++ `noConfusion |
| 76 | + let heq ← mkEq t s |
| 77 | + -- Forward: (t = s) → False via noConfusion with motive False |
| 78 | + let fwd ← withLocalDeclD `h heq fun h => do |
| 79 | + let nc ← mkAppOptM ncName #[some (mkConst ``False), some t, some s, some h] |
| 80 | + mkLambdaFVars #[h] nc |
| 81 | + -- Backward: False → (t = s) via False.elim |
| 82 | + let bwd ← withLocalDeclD `h (mkConst ``False) fun h => do |
| 83 | + let fe ← mkAppOptM ``False.elim #[some heq, some h] |
| 84 | + mkLambdaFVars #[h] fe |
| 85 | + let iff_pf ← mkAppM ``Iff.intro #[fwd, bwd] |
| 86 | + mkAppM ``propext #[iff_pf] |
| 87 | + |
| 88 | +def reconstructRewrite (pf : cvc5.Proof) : ReconstructM (Option Expr) := do |
| 89 | + match pf.getRewriteRule! with |
| 90 | + | .MACRO_DT_CONS_EQ |
| 91 | + | .DT_CONS_EQ_CLASH => |
| 92 | + let result := pf.getResult |
| 93 | + -- Only handle the "clash" case: (t = s) = false (distinct constructors) |
| 94 | + if result[1]!.getKind! == .CONST_BOOLEAN && !result[1]!.getBooleanValue! then |
| 95 | + let sort := result[0]![0]!.getSort! |
| 96 | + if sort.getKind! == .DATATYPE_SORT && isBuiltinName sort.getDatatype!.getName!.toName then |
| 97 | + return none |
| 98 | + let (u, (α : Q(Sort u))) ← reconstructSortLevelAndSort sort |
| 99 | + let t : Q($α) ← reconstructTerm result[0]![0]! |
| 100 | + let s : Q($α) ← reconstructTerm result[0]![1]! |
| 101 | + addTac q(($t = $s) = False) fun mv => do |
| 102 | + let proof ← mv.withContext (proveConsClash t s) |
| 103 | + mv.assign proof |
| 104 | + else |
| 105 | + return none |
| 106 | + | _ => return none |
| 107 | + |
| 108 | +/-- Build a Lean proof of `¬(t = s)` where `t` and `s` are distinct constructors. |
| 109 | +Uses the `noConfusion` principle: `fun h : t = s => noConfusion False t s h`. -/ |
| 110 | +private def proveDistinctValues (t s : Expr) : MetaM Expr := do |
| 111 | + let α ← inferType t |
| 112 | + let .const αName _ := α.getAppFn |
| 113 | + | throwError "Smt.Reconstruct.Datatype: expected inductive type, got {α}" |
| 114 | + let ncName := αName ++ `noConfusion |
| 115 | + let heq ← mkEq t s |
| 116 | + withLocalDeclD `h heq fun h => do |
| 117 | + let nc ← mkAppOptM ncName #[some (mkConst ``False), some t, some s, some h] |
| 118 | + mkLambdaFVars #[h] nc |
| 119 | + |
| 120 | +@[smt_proof_reconstruct] def reconstructDatatypeProof : ProofReconstructor := fun pf => do |
| 121 | + match pf.getRule with |
| 122 | + | .DSL_REWRITE |
| 123 | + | .THEORY_REWRITE => reconstructRewrite pf |
| 124 | + | .DISTINCT_VALUES => |
| 125 | + let sort := pf.getArguments[0]!.getSort! |
| 126 | + if sort.getKind! == .DATATYPE_SORT && isBuiltinName sort.getDatatype!.getName!.toName then |
| 127 | + return none |
| 128 | + let (u, (α : Q(Sort u))) ← reconstructSortLevelAndSort sort |
| 129 | + let t : Q($α) ← reconstructTerm pf.getArguments[0]! |
| 130 | + let s : Q($α) ← reconstructTerm pf.getArguments[1]! |
| 131 | + addTac q($t ≠ $s) fun mv => do |
| 132 | + let proof ← mv.withContext (proveDistinctValues t s) |
| 133 | + mv.assign proof |
| 134 | + | _ => return none |
| 135 | + |
| 136 | +end Smt.Reconstruct.Datatype |
0 commit comments