Skip to content

Commit 007f44c

Browse files
hyperpolymathclaude
andcommitted
proof(SafeJson): DISCHARGE anyMatchesTAny (6-arm case-split) + singleKeyPath (with-pattern + Maybe-identity) (proven#90 #107 #119)
Two of the 12 remaining `SafeJson.Proofs` OWEDs turned out to be overly-cautious — empirically dischargeable with the techniques already in proven's toolbox: 1. **anyMatchesTAny** (`matchesType v TAny = True` for all v): OWED comment claimed the `covering` annotation on `matchesType` blocks reduction on an abstract `v`. The comment SUGGESTED a six-arm manual case-split would work — and empirically it does (`/tmp/charrefl/src/TestJson.idr`). Each concrete-constructor arm reduces through the catch-all `matchesType _ TAny = True` clause by Refl. 7 lines (signature + 6 arms). 2. **singleKeyPath** (`getPath [Key k] (JsonObject obj) = lookup k obj`): OWED comment claimed `getPath`/`getSegment` are `covering`. Source inspection shows neither carries the annotation — both are total. The proof reduces to `case lookup k obj of {Nothing => Nothing; Just v => Just v}` which is `lookup k obj` after a `with`-pattern on the lookup result (Maybe-identity, both arms by Refl). Empirically verified at `/tmp/charrefl/src/TestJsonPath.idr`. The remaining 10 SafeJson OWEDs are GENUINELY String-FFI-blocked: - setGetIdentity / setPreservesOther / setHasKey / removeNotHasKey: all thread through `prim__eq_String` on abstract keys. - parseNullCorrect / parseTrueCorrect / parseFalseCorrect / parseEmptyFails / parseEmptyArray / parseEmptyObject: all thread through the String-FFI parser (`unpack`/`strHead`/`strSubstr`). These cannot be discharged without a reflective String-axiom in Idris2 0.8.0 base (or rewriting parser onto `List Char`). Empirical reduction map ref: `reference_idris2_0_8_0_reduction_map.md`. Zero `believe_me`/`postulate`/`idris_crash`. Note on local-vs-CI: local `idris2 --check Proofs.idr` shows a pre-existing error on `appendLengthInc` due to a `Data.List` / `Data.List.Equalities` / `Data.List.Quantifiers` name-resolution quirk in the local contrib install — does NOT reproduce in CI (PR #127 merged green with the same code). My new proofs verify in a minimal isolated context (`/tmp/JsonNew.idr`, exit 0). Refs proven#90 (Phase 3 OWED triage), proven#107 (overly-cautious OWED meta), proven#119 (paths-forward). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 477172b commit 007f44c

1 file changed

Lines changed: 33 additions & 36 deletions

File tree

src/Proven/SafeJson/Proofs.idr

Lines changed: 33 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -216,26 +216,24 @@ public export
216216
emptyPathIdentity : (v : JsonValue) -> getPath [] v = Just v
217217
emptyPathIdentity v = Refl
218218

219-
||| OWED: a single-segment `Key k` path equals direct `lookup`:
219+
||| DISCHARGED: a single-segment `Key k` path equals direct `lookup`:
220220
||| `getPath [Key k] (JsonObject obj) = lookup k obj`.
221-
||| `getPath` is defined in `Proven.SafeJson.Access` and dispatches
222-
||| through `getSegment` (mutually recursive across `PathSegment`
223-
||| constructors). Idris2 0.8.0 marks the family `covering` (not
224-
||| `total`) because the recursion is on `List PathSegment` paired
225-
||| with a `JsonValue` whose `JsonArray`/`JsonObject` arms recurse
226-
||| with new path-tails — same termination-witness shape as the
227-
||| `Proven.SafeJson.Access.deletePath` unreachable-clause warning
228-
||| already in this build. Covering functions do NOT reduce by
229-
||| `Refl` outside their pattern arms in Idris2 0.8.0, so this
230-
||| statement cannot be discharged without an external
231-
||| `assert_total`-style reduction lemma. Held back by the same
232-
||| Access-module covering/total gap that motivated removing
233-
||| `singleIndexPath` (L154 comment). Discharge once `getPath`
234-
||| can be re-stated as `total` via a structurally decreasing
235-
||| metric (path length is the obvious candidate).
236-
public export
237-
0 singleKeyPath : (k : String) -> (obj : List (String, JsonValue)) ->
221+
||| The OWED comment claimed `getPath`/`getSegment` are `covering`
222+
||| (which would block reduction), but inspection of the current
223+
||| `Proven.SafeJson.Access` source shows neither carries the
224+
||| annotation — they are total. Discharge proceeds:
225+
||| getPath [Key k] (JsonObject obj)
226+
||| = case getSegment (Key k) (JsonObject obj) of {Nothing => Nothing; Just v => getPath [] v}
227+
||| = case lookup k obj of {Nothing => Nothing; Just v => Just v}
228+
||| `with`-pattern on `lookup k obj` discharges both arms by `Refl`
229+
||| (Maybe-identity). Empirically verified at
230+
||| `/tmp/charrefl/src/TestJsonPath.idr`.
231+
public export
232+
singleKeyPath : (k : String) -> (obj : List (String, JsonValue)) ->
238233
getPath [Key k] (JsonObject obj) = lookup k obj
234+
singleKeyPath k obj with (lookup k obj)
235+
singleKeyPath k obj | Nothing = Refl
236+
singleKeyPath k obj | Just _ = Refl
239237

240238
-- singleIndexPath: removed — where-clause in type signature is invalid
241239
-- in Idris2 0.8.0 and the index' helper conflicts with stdlib names.
@@ -326,26 +324,25 @@ public export
326324
stringMatchesTString : (s : String) -> matchesType (JsonString s) TString = True
327325
stringMatchesTString s = Refl
328326

329-
||| OWED: every `JsonValue` matches `TAny`:
327+
||| DISCHARGED: every `JsonValue` matches `TAny`:
330328
||| `matchesType v TAny = True` for all `v`.
331329
||| Definitionally `matchesType _ TAny = True` (`SafeJson.idr` L353).
332-
||| The clause is a catch-all on the second argument and reduces by
333-
||| `Refl` for any concrete `v` constructor — but Idris2 0.8.0 will
334-
||| not reduce it for an abstract `v : JsonValue` because the
335-
||| preceding clauses (`JsonArray arr` / `JsonObject pairs` with
336-
||| `TArray`/`TObject`/`TOneOf` on the right) are *covering* (not
337-
||| total): `matchesType` mutually recurses through `all` over the
338-
||| array/object children. Covering definitions do not reduce on
339-
||| open variables in Idris2 0.8.0 — same blocker family as
340-
||| `singleKeyPath` above. The proof would normally close by a
341-
||| six-arm case-split on `v` (`JsonNull`/`JsonBool b`/`JsonNumber n`
342-
||| /`JsonString s`/`JsonArray arr`/`JsonObject pairs`), each with
343-
||| `Refl`. Held back by the covering-vs-total reduction policy.
344-
||| Discharge once `matchesType` is restated as `total` (e.g. by
345-
||| an explicit size metric over `JsonValue` + children), or with
346-
||| a manual six-arm split that elaborates per-arm `Refl`s.
347-
public export
348-
0 anyMatchesTAny : (v : JsonValue) -> matchesType v TAny = True
330+
||| The OWED comment claimed the covering-vs-total policy blocks
331+
||| reduction on an abstract `v`, but in practice the manual
332+
||| six-arm case-split it suggested (`JsonNull` / `JsonBool b` /
333+
||| `JsonNumber n` / `JsonString s` / `JsonArray arr` /
334+
||| `JsonObject pairs`) works: each concrete-constructor arm
335+
||| reduces through the catch-all `matchesType _ TAny = True`
336+
||| clause by `Refl`. Empirically verified at
337+
||| `/tmp/charrefl/src/TestJson.idr`.
338+
public export
339+
anyMatchesTAny : (v : JsonValue) -> matchesType v TAny = True
340+
anyMatchesTAny JsonNull = Refl
341+
anyMatchesTAny (JsonBool _) = Refl
342+
anyMatchesTAny (JsonNumber _) = Refl
343+
anyMatchesTAny (JsonString _) = Refl
344+
anyMatchesTAny (JsonArray _) = Refl
345+
anyMatchesTAny (JsonObject _) = Refl
349346

350347
--------------------------------------------------------------------------------
351348
-- Totality Proofs

0 commit comments

Comments
 (0)