| import Pantograph.Tactic.Prograde |
| import Test.Common |
|
|
| open Lean |
|
|
| namespace Pantograph.Test.Tactic.Prograde |
|
|
| def test_define : TestT Elab.TermElabM Unit := do |
| let expr := "forall (p q : Prop) (h: p), And (Or p q) (Or p q)" |
| let expr β parseSentence expr |
| Meta.forallTelescope expr $ Ξ» _ body => do |
| let e β match Parser.runParserCategory |
| (env := β MonadEnv.getEnv) |
| (catName := `term) |
| (input := "Or.inl h") |
| (fileName := β getFileName) with |
| | .ok syn => pure syn |
| | .error error => throwError "Failed to parse: {error}" |
| -- Apply the tactic |
| let goal β Meta.mkFreshExprSyntheticOpaqueMVar body |
| let target: Expr := mkAnd |
| (mkOr (.fvar β¨uniq 8β©) (.fvar β¨uniq 9β©)) |
| (mkOr (.fvar β¨uniq 8β©) (.fvar β¨uniq 9β©)) |
| let h := .fvar β¨uniq 8β© |
| addTest $ LSpec.test "goals before" ((β toCondensedGoal goal.mvarId!).devolatilize == { |
| context := #[ |
| cdeclOf `p (.sort 0), |
| cdeclOf `q (.sort 0), |
| cdeclOf `h h |
| ], |
| target, |
| }) |
| let tactic := Tactic.evalDefine `h2 e |
| let m := .mvar β¨uniq 13β© |
| let [newGoal] β runTacticOnMVar tactic goal.mvarId! | panic! "Incorrect goal number" |
| addTest $ LSpec.test "goals after" ((β toCondensedGoal newGoal).devolatilize == { |
| context := #[ |
| cdeclOf `p (.sort 0), |
| cdeclOf `q (.sort 0), |
| cdeclOf `h h, |
| { |
| userName := `h2, |
| type := mkOr h m, |
| value? := .some $ mkApp3 (mkConst `Or.inl) h m (.fvar β¨uniq 10β©) |
| } |
| ], |
| target, |
| }) |
| let .some e β getExprMVarAssignment? goal.mvarId! | panic! "Tactic must assign" |
| addTest $ LSpec.test "assign" e.isLet |
|
|
| def test_define_proof : TestT Elab.TermElabM Unit := do |
| let rootExpr β parseSentence "β (p q: Prop), p β ((p β¨ q) β¨ (p β¨ q))" |
| let state0 β GoalState.create rootExpr |
| let tactic := "intro p q h" |
| let state1 β match β state0.tacticOn (goalId := 0) (tactic := tactic) with |
| | .success state _ => pure state |
| | other => do |
| addTest $ assertUnreachable $ other.toString |
| return () |
| checkEq tactic ((β state1.serializeGoals).map (Β·.devolatilize)) |
| #[buildGoal [(`p, "Prop"), (`q, "Prop"), (`h, "p")] "(p β¨ q) β¨ p β¨ q"] |
|
|
| let expr := "Or.inl (Or.inl h)" |
| let state2 β match β state1.tryAssign (state1.get! 0) (expr := expr) with |
| | .success state _ => pure state |
| | other => do |
| addTest $ assertUnreachable $ other.toString |
| return () |
| addTest $ LSpec.check s!":= {expr}" ((β state2.serializeGoals).map (Β·.devolatilize) = |
| #[]) |
|
|
| let evalBind := `y |
| let evalExpr := "Or.inl h" |
| let state2 β match β state1.tryDefine (state1.get! 0) (binderName := evalBind) (expr := evalExpr) with |
| | .success state _ => pure state |
| | other => do |
| addTest $ assertUnreachable $ other.toString |
| return () |
| checkEq s!"eval {evalBind} := {evalExpr}" ((β state2.serializeGoals).map (Β·.devolatilize)) |
| #[{ |
| target := { pp? := .some "(p β¨ q) β¨ p β¨ q"}, |
| vars := #[ |
| { userName := `p, type? := .some { pp? := .some "Prop" } }, |
| { userName := `q, type? := .some { pp? := .some "Prop" } }, |
| { userName := `h, type? := .some { pp? := .some "p" } }, |
| { userName := `y, |
| type? := .some { pp? := .some "p β¨ ?m.9" }, |
| value? := .some { pp? := .some "Or.inl h" }, |
| } |
| ] |
| }] |
|
|
| let expr := "Or.inl y" |
| let state3 β match β state2.tryAssign (state2.get! 0) (expr := expr) with |
| | .success state _ => pure state |
| | other => do |
| addTest $ assertUnreachable $ other.toString |
| return () |
| addTest $ LSpec.check s!":= {expr}" ((β state3.serializeGoals).map (Β·.devolatilize) = |
| #[]) |
|
|
| addTest $ LSpec.check "(3 root)" state3.rootExpr?.isSome |
|
|
| def fun_define_root_expr: β (p: Prop), PProd (Nat β p) Unit β p := by |
| intro p x |
| apply x.fst |
| exact 5 |
|
|
| def test_define_root_expr : TestT Elab.TermElabM Unit := do |
| --let rootExpr β parseSentence "Nat" |
| --let state0 β GoalState.create rootExpr |
| --let .success state1 _ β state0.tacticOn (goalId := 0) "exact 5" | addTest $ assertUnreachable "exact 5" |
| --let .some rootExpr := state1.rootExpr? | addTest $ assertUnreachable "Root expr" |
| --addTest $ LSpec.check "root" ((toString $ β Meta.ppExpr rootExpr) = "5") |
| let rootExpr β parseSentence "β (p: Prop), PProd (Nat β p) Unit β p" |
| let state0 β GoalState.create rootExpr |
| let tactic := "intro p x" |
| let .success state1 _ β state0.tacticOn (goalId := 0) tactic | addTest $ assertUnreachable tactic |
| let binderName := `binder |
| let value := "x.fst" |
| let expr β state1.goals[0]!.withContext $ strToTermSyntax value |
| let tacticM := Tactic.evalDefine binderName expr |
| let .success state2 _ β state1.tryTacticM (state1.get! 0) tacticM | addTest $ assertUnreachable s!"define {binderName} := {value}" |
| let tactic := s!"apply {binderName}" |
| let .success state3 _ β state2.tacticOn (goalId := 0) tactic | addTest $ assertUnreachable tactic |
| let tactic := s!"exact 5" |
| let .success state4 _ β state3.tacticOn (goalId := 0) tactic | addTest $ assertUnreachable tactic |
| let .some rootExpr := state4.rootExpr? | addTest $ assertUnreachable "Root expr" |
| addTest $ LSpec.check "root" ((toString $ β Meta.ppExpr rootExpr) = "fun p x =>\n let binder := x.fst;\n binder 5") |
|
|
| def test_have_proof : TestT Elab.TermElabM Unit := do |
| let rootExpr β parseSentence "β (p q: Prop), p β ((p β¨ q) β¨ (p β¨ q))" |
| let state0 β GoalState.create rootExpr |
| let tactic := "intro p q h" |
| let state1 β match β state0.tacticOn (goalId := 0) (tactic := tactic) with |
| | .success state _ => pure state |
| | other => do |
| addTest $ assertUnreachable $ other.toString |
| return () |
| checkEq tactic ((β state1.serializeGoals).map (Β·.devolatilize)) |
| #[buildGoal [(`p, "Prop"), (`q, "Prop"), (`h, "p")] "(p β¨ q) β¨ p β¨ q"] |
|
|
| let expr := "Or.inl (Or.inl h)" |
| let state2 β match β state1.tryAssign (state1.get! 0) (expr := expr) with |
| | .success state _ => pure state |
| | other => do |
| addTest $ assertUnreachable $ other.toString |
| return () |
| addTest $ LSpec.check s!":= {expr}" ((β state2.serializeGoals).map (Β·.devolatilize) = |
| #[]) |
|
|
| let haveBind := `y |
| let haveType := "p β¨ q" |
| let state2 β match β state1.tryHave (state1.get! 0) (binderName := haveBind) (type := haveType) with |
| | .success state _ => pure state |
| | other => do |
| addTest $ assertUnreachable $ other.toString |
| return () |
| checkEq s!"have {haveBind}: {haveType}" ((β state2.serializeGoals).map (Β·.devolatilize)) |
| #[ |
| buildGoal [(`p, "Prop"), (`q, "Prop"), (`h, "p")] "p β¨ q", |
| buildGoal [(`p, "Prop"), (`q, "Prop"), (`h, "p"), (`y, "p β¨ q")] "(p β¨ q) β¨ p β¨ q" |
| ] |
|
|
| let expr := "Or.inl h" |
| let state3 β match β state2.tryAssign (state2.get! 0) (expr := expr) with |
| | .success state _ => pure state |
| | other => do |
| addTest $ assertUnreachable $ other.toString |
| return () |
| checkEq s!":= {expr}" ((β state3.serializeGoals).map (Β·.devolatilize)) #[] |
|
|
| let state2b β match state3.continue state2 with |
| | .ok state => pure state |
| | .error e => do |
| addTest $ assertUnreachable e |
| return () |
| let expr := "Or.inl y" |
| let state4 β match β state2b.tryAssign (state2b.get! 0) (expr := expr) with |
| | .success state _ => pure state |
| | other => do |
| addTest $ assertUnreachable $ other.toString |
| return () |
| checkEq s!":= {expr}" ((β state4.serializeGoals).map (Β·.devolatilize)) #[] |
|
|
| let .some rootExpr := state4.rootExpr? | fail "Root expr" |
| checkEq "root" (toString $ β Meta.ppExpr rootExpr) "fun p q h y => Or.inl y" |
|
|
| def test_let (specialized: Bool): TestT Elab.TermElabM Unit := do |
| let rootExpr β parseSentence "β (p q: Prop), p β ((p β¨ q) β¨ (p β¨ q))" |
| let state0 β GoalState.create rootExpr |
| let tactic := "intro a p h" |
| let state1 β match β state0.tacticOn (goalId := 0) (tactic := tactic) with |
| | .success state _ => pure state |
| | other => do |
| addTest $ assertUnreachable $ other.toString |
| return () |
| addTest $ LSpec.check tactic ((β state1.serializeGoals).map (Β·.devolatilize) = |
| #[{ |
| target := { pp? := .some mainTarget }, |
| vars := interiorVars, |
| }]) |
|
|
| let letType := "Nat" |
| let expr := s!"let b: {letType} := _; _" |
| let result2 β match specialized with |
| | true => state1.tryLet (state1.get! 0) (binderName := `b) (type := letType) |
| | false => state1.tryAssign (state1.get! 0) (expr := expr) |
| let state2 β match result2 with |
| | .success state _ => pure state |
| | other => do |
| addTest $ assertUnreachable $ other.toString |
| return () |
| let serializedState2 β state2.serializeGoals |
| let letBindName := if specialized then `b else `_1 |
| checkEq expr (serializedState2.map (Β·.devolatilize)) |
| #[{ |
| target := { pp? := .some letType }, |
| vars := interiorVars, |
| userName? := .some letBindName |
| }, |
| { |
| target := { pp? := .some mainTarget }, |
| vars := interiorVars ++ #[{ |
| userName := `b, |
| type? := .some { pp? := .some letType }, |
| value? := .some { pp? := .some s!"?{letBindName}" }, |
| }], |
| userName? := if specialized then .none else .some `_2, |
| }] |
|
|
| let tactic := "exact 1" |
| let state3 β match β state2.tacticOn (goalId := 0) (tactic := tactic) with |
| | .success state _ => pure state |
| | other => do |
| addTest $ assertUnreachable $ other.toString |
| return () |
| addTest $ LSpec.check tactic ((β state3.serializeGoals).map (Β·.devolatilize) = #[]) |
|
|
| let state3r β match state3.continue state2 with |
| | .error msg => do |
| addTest $ assertUnreachable $ msg |
| return () |
| | .ok state => pure state |
| checkEq "(continue)" ((β state3r.serializeGoals).map (Β·.devolatilize)) |
| #[{ |
| target := { pp? := .some mainTarget }, |
| vars := interiorVars ++ #[{ |
| userName := `b, |
| type? := .some { pp? := .some "Nat" }, |
| value? := .some { pp? := .some "1" }, |
| }], |
| userName? := if specialized then .none else `_2, |
| }] |
|
|
| let tactic := "exact h" |
| match β state3r.tacticOn (goalId := 0) (tactic := tactic) with |
| | .failure #[message] => |
| checkEq tactic |
| (β message.toString) |
| s!"{β getFileName}:0:0: error: Type mismatch\n h\nhas type\n a\nbut is expected to have type\n {mainTarget}\n" |
| | other => do |
| fail s!"Should be a failure: {other.toString}" |
|
|
| let tactic := "exact Or.inl (Or.inl h)" |
| let state4 β match β state3r.tacticOn (goalId := 0) (tactic := tactic) with |
| | .success state _ => pure state |
| | other => do |
| addTest $ assertUnreachable $ other.toString |
| return () |
| addTest $ LSpec.test "(4 root)" state4.rootExpr?.isSome |
| where |
| mainTarget := "(a β¨ p) β¨ a β¨ p" |
| interiorVars: Array Protocol.Variable := #[ |
| { userName := `a, type? := .some { pp? := .some "Prop" }, }, |
| { userName := `p, type? := .some { pp? := .some "Prop" }, }, |
| { userName := `h, type? := .some { pp? := .some "a" }, } |
| ] |
|
|
| def suite (env: Environment): List (String Γ IO LSpec.TestSeq) := |
| [ |
| ("define", test_define), |
| ("define proof", test_define_proof), |
| ("define root expr", test_define_root_expr), |
| ("have proof", test_have_proof), |
| ("let via assign", test_let false), |
| ("let via tryLet", test_let true), |
| ] |>.map (Ξ» (name, t) => (name, runTestTermElabM env t)) |
|
|