From 1a010f06b37f8cef1f391049434a4f6f5e5d662d Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 1 Nov 2026 14:58:49 +0000 Subject: [PATCH 1/7] feat(rules)!: regroup, demote and retire rules after the audit The audit of every rule but style, against public Bend 2 code, sorted each rule into keep, guard, move, demote or retire. This applies the moves: - perf, a new group at warn: `strings` and `chars`, whose cost is real but which never cause a failure (out of correctness). - pedantic, off by default: `foreign` (0 of 30 sampled hits were defects), `fuel` (23% precise), `index` (38%), `strict` (its fix is 1.56x slower on JS) and `rewalk` (its fix is 45-60% slower on native). - opt-in: `coverage` and `closed`, a project's policy rather than a defect; only their own setting turns them on. - retired, codes never reused: `table` (U008) and `hoist` (U009), with no public hits; `ring` (U010), 1 of 4 real and no mechanical fix; `unit` (U012), 0 of 7; `scan` (U013), 50% precise with a false positive tokens cannot tell apart; `unsafe` (L003), which bend reports itself since 2.0.32. A code names the rule, not its group, so moved rules keep their codes, and each module moves to the directory of its new group. `index` still leaves a get on a fixed table alone: that test is src/rules/table.bend now. The import-closure laws coverage's proof leans on, which lived in unsafe's law file, are their own guarantee (laws/ImportReachIsClosed.bend), and the digest no longer carries each file's unsafe defs. The rule criteria gain a guard exception for rare shapes whose miss is silently wrong (`arms`, `escape`, `setting`). This repo's bolt.bend turns perf, pedantic (but `tail`), coverage and closed on, so its gate grades as before. BREAKING CHANGE: `table`, `hoist`, `ring`, `unit`, `scan` and `unsafe` are removed; a bolt.bend that still sets one gets a `setting` finding (C011). `strings`, `chars` and `foreign` are no longer errors by default, `strict`, `fuel`, `index`, `rewalk` and `foreign` are off unless `pedantic` (or the rule) is set, and `coverage` and `closed` are off unless set by name: a project that relied on them must add `def coverage()` / `def closed()`. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_0186KEnw5sg7heUQZTJJU89N --- AGENTS.md | 5 +- LAWS.bend | 13 +- PROOF.bend | 13 +- README.md | 6 +- bolt.bend | 26 +- laws/CharsReportManyCharArms.bend | 2 +- laws/CodeRulesIgnoreCommentsAndStrings.bend | 114 +- laws/ExemptPathsYieldNoFindings.bend | 69 +- laws/ForeignReportsMissingBackend.bend | 2 +- laws/FuelReportsLiteralBudgets.bend | 2 +- laws/HoistReportsTablesInLoops.bend | 154 - laws/ImportReachIsClosed.bend | 36 + laws/IndexReportsGetsInRecursion.bend | 21 +- laws/LawNamesEveryItem.bend | 15 +- laws/NoqaReportsStaleComments.bend | 6 +- laws/OutputOrderIsFixed.bend | 4 +- laws/PackagesScoped.bend | 3 +- laws/RewalkReportsRepeatedWalks.bend | 2 +- laws/RingReportsDropAppendRotation.bend | 88 - laws/RoleHoist.bend | 21 - laws/RoleIndex.bend | 2 +- laws/RoleRewalk.bend | 2 +- laws/RoleRing.bend | 21 - laws/RoleScan.bend | 20 - laws/RoleScanExempt.bend | 21 - laws/RoleStrict.bend | 2 +- laws/RoleTable.bend | 21 - laws/RoleUnit.bend | 21 - laws/ScanReportsCarriedArgumentWalks.bend | 170 - laws/ScopeExemptionsFollowPaths.bend | 18 +- laws/ScopeLawsGovernPackage.bend | 4 +- laws/ScopePackageRootsFound.bend | 3 +- laws/ScopeProjectRulesRunOnRoot.bend | 13 +- laws/ScopeRulesSeeTheirInputs.bend | 3 +- laws/SpaceReportsBlanksAndWidth.bend | 1 - laws/StrictReportsEagerSelfCalls.bend | 2 +- laws/StringsReportLongLiteralArms.bend | 2 +- laws/TableReportsFixedTableAccess.bend | 85 - laws/UnitReportsMultiplyByOne.bend | 158 - laws/UnsafeReportsReachableForeignDefs.bend | 107 - laws/defs/ScopeWalkFiles.bend | 10 +- laws/defs/Unsafe.bend | 14 +- proofs/CharsReportManyCharArms.bend | 2 +- proofs/CodeRulesIgnoreCommentsAndStrings.bend | 8528 ++--------------- proofs/ExemptPathsYieldNoFindings.bend | 7 - proofs/ForeignReportsMissingBackend.bend | 2 +- proofs/FuelReportsLiteralBudgets.bend | 2 +- proofs/HoistReportsTablesInLoops.bend | 398 - proofs/ImportReachIsClosed.bend | 4 + proofs/IndexReportsGetsInRecursion.bend | 8 +- proofs/LawNamesEveryItem.bend | 16 +- proofs/NoqaReportsStaleComments.bend | 8 +- proofs/RewalkReportsRepeatedWalks.bend | 2 +- proofs/RingReportsDropAppendRotation.bend | 157 - proofs/RoleHoist.bend | 11 - proofs/RoleIndex.bend | 2 +- proofs/RoleRewalk.bend | 2 +- proofs/RoleRing.bend | 11 - proofs/RoleScan.bend | 17 - proofs/RoleScanExempt.bend | 10 - proofs/RoleStrict.bend | 2 +- proofs/RoleTable.bend | 11 - proofs/RoleUnit.bend | 11 - proofs/ScanReportsCarriedArgumentWalks.bend | 773 -- proofs/ScopeExemptionsFollowPaths.bend | 66 +- proofs/ScopeLawsGovernPackage.bend | 4 +- proofs/ScopeProjectRulesRunOnRoot.bend | 11 +- proofs/StrictReportsEagerSelfCalls.bend | 2 +- proofs/StringsReportLongLiteralArms.bend | 2 +- proofs/TableReportsFixedTableAccess.bend | 123 - proofs/UnitReportsMultiplyByOne.bend | 715 -- proofs/UnsafeReportsReachableForeignDefs.bend | 121 - proofs/lemmas/CarriedSearch.bend | 46 - proofs/lemmas/ImportClosure.bend | 11 +- proofs/lemmas/LawFileExemptions.bend | 103 +- proofs/lemmas/PackageRootLookup.bend | 17 +- src/README.md | 153 +- src/codes.bend | 28 +- src/config.bend | 5 +- src/rules.bend | 47 +- src/rules/digest.bend | 78 +- src/rules/imports.bend | 40 - src/rules/laws/law.bend | 8 +- src/rules/laws/unsafe.bend | 138 - .../{correctness => pedantic}/foreign.bend | 0 src/rules/{suspicious => pedantic}/fuel.bend | 0 src/rules/{suspicious => pedantic}/index.bend | 8 +- .../{suspicious => pedantic}/rewalk.bend | 0 .../{suspicious => pedantic}/strict.bend | 0 src/rules/{correctness => perf}/chars.bend | 0 src/rules/{correctness => perf}/strings.bend | 0 src/rules/style/noqa.bend | 4 +- src/rules/suspicious/hoist.bend | 653 -- src/rules/suspicious/ring.bend | 313 - src/rules/suspicious/scan.bend | 347 - src/rules/suspicious/unit.bend | 227 - src/rules/{suspicious => }/table.bend | 66 +- src/syntax/tree.bend | 11 - 98 files changed, 1251 insertions(+), 13382 deletions(-) delete mode 100644 laws/HoistReportsTablesInLoops.bend create mode 100644 laws/ImportReachIsClosed.bend delete mode 100644 laws/RingReportsDropAppendRotation.bend delete mode 100644 laws/RoleHoist.bend delete mode 100644 laws/RoleRing.bend delete mode 100644 laws/RoleScan.bend delete mode 100644 laws/RoleScanExempt.bend delete mode 100644 laws/RoleTable.bend delete mode 100644 laws/RoleUnit.bend delete mode 100644 laws/ScanReportsCarriedArgumentWalks.bend delete mode 100644 laws/TableReportsFixedTableAccess.bend delete mode 100644 laws/UnitReportsMultiplyByOne.bend delete mode 100644 laws/UnsafeReportsReachableForeignDefs.bend delete mode 100644 proofs/HoistReportsTablesInLoops.bend create mode 100644 proofs/ImportReachIsClosed.bend delete mode 100644 proofs/RingReportsDropAppendRotation.bend delete mode 100644 proofs/RoleHoist.bend delete mode 100644 proofs/RoleRing.bend delete mode 100644 proofs/RoleScan.bend delete mode 100644 proofs/RoleScanExempt.bend delete mode 100644 proofs/RoleTable.bend delete mode 100644 proofs/RoleUnit.bend delete mode 100644 proofs/ScanReportsCarriedArgumentWalks.bend delete mode 100644 proofs/TableReportsFixedTableAccess.bend delete mode 100644 proofs/UnitReportsMultiplyByOne.bend delete mode 100644 proofs/UnsafeReportsReachableForeignDefs.bend delete mode 100644 src/rules/laws/unsafe.bend rename src/rules/{correctness => pedantic}/foreign.bend (100%) rename src/rules/{suspicious => pedantic}/fuel.bend (100%) rename src/rules/{suspicious => pedantic}/index.bend (94%) rename src/rules/{suspicious => pedantic}/rewalk.bend (100%) rename src/rules/{suspicious => pedantic}/strict.bend (100%) rename src/rules/{correctness => perf}/chars.bend (100%) rename src/rules/{correctness => perf}/strings.bend (100%) delete mode 100644 src/rules/suspicious/hoist.bend delete mode 100644 src/rules/suspicious/ring.bend delete mode 100644 src/rules/suspicious/scan.bend delete mode 100644 src/rules/suspicious/unit.bend rename src/rules/{suspicious => }/table.bend (77%) diff --git a/AGENTS.md b/AGENTS.md index ee5c69a..dfa5944 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -9,7 +9,8 @@ writing Bend. Then this. If Bend can state it as a quantified law (a `for` or `exs` binder, IO values included), it goes in a law file under `laws/` and its proof under `proofs/`. A law with no -binder is a unit test the checker runs, not a guarantee: `closed` reports +binder is a unit test the checker runs, not a guarantee: `closed` (opt-in, +on in this repo's bolt.bend) reports it, and it does not go in at all (docs/rfc/bolt-spec.md). `tests/*.bend` and `#|` exist only for claims Bend cannot prove: @@ -174,7 +175,7 @@ Design specs and plans are not kept in this repo; they live under it) stay right for expensive work that does not recurse. The same goes for any expensive expression in a branch: a scan of the whole token list inside a per-token pick runs for every token (bind's notes were 20 s that way, - 20 ms as one pass). `bolt`'s `strict` rule catches the Bool.and/or shape. + 20 ms as one pass). `bolt`'s `strict` rule (pedantic) catches the Bool.and/or shape. - A destructure or a `match` needs a variable, never a call: `Out{a, b} = f(x)` is "a match cannot scrutinize a computed value"; bind the call first, or take it apart in a helper that receives it as a parameter. diff --git a/LAWS.bend b/LAWS.bend index 688cad8..21ccfe8 100644 --- a/LAWS.bend +++ b/LAWS.bend @@ -76,19 +76,18 @@ import ./laws/ForeignReportsMissingBackend.bend as ForeignReportsMissingBackend import ./laws/FromrevExempt.bend as FromrevExempt import ./laws/FromrevReportsReversedStringBuild.bend as FromrevReportsReversedStringBuild import ./laws/FuelReportsLiteralBudgets.bend as FuelReportsLiteralBudgets -import ./laws/HoistReportsTablesInLoops.bend as HoistReportsTablesInLoops import ./laws/HoleReportsEachTodo.bend as HoleReportsEachTodo import ./laws/IndexReportsGetsInRecursion.bend as IndexReportsGetsInRecursion import ./laws/InertConcatCont.bend as InertConcatCont import ./laws/InertFromrev.bend as InertFromrev import ./laws/InertTranscong.bend as InertTranscong +import ./laws/ImportReachIsClosed.bend as ImportReachIsClosed import ./laws/LawNamesEveryItem.bend as LawNamesEveryItem import ./laws/ParamReportsShortNames.bend as ParamReportsShortNames import ./laws/PickReportsSelfCalls.bend as PickReportsSelfCalls import ./laws/PickReportsBoundSearches.bend as PickReportsBoundSearches import ./laws/PutReportsMapPutCalls.bend as PutReportsMapPutCalls import ./laws/RewalkReportsRepeatedWalks.bend as RewalkReportsRepeatedWalks -import ./laws/RingReportsDropAppendRotation.bend as RingReportsDropAppendRotation import ./laws/RoleClosed.bend as RoleClosed import ./laws/RoleClosedExempt.bend as RoleClosedExempt import ./laws/RoleConcat.bend as RoleConcat @@ -97,7 +96,6 @@ import ./laws/RoleDocExempt.bend as RoleDocExempt import ./laws/RoleEager.bend as RoleEager import ./laws/RoleFromrev.bend as RoleFromrev import ./laws/RoleFromrevExempt.bend as RoleFromrevExempt -import ./laws/RoleHoist.bend as RoleHoist import ./laws/RoleHole.bend as RoleHole import ./laws/RoleHoleExempt.bend as RoleHoleExempt import ./laws/RoleIndex.bend as RoleIndex @@ -110,27 +108,18 @@ import ./laws/RolePick.bend as RolePick import ./laws/RoleProofNamed.bend as RoleProofNamed import ./laws/RoleReached.bend as RoleReached import ./laws/RoleRewalk.bend as RoleRewalk -import ./laws/RoleRing.bend as RoleRing -import ./laws/RoleScan.bend as RoleScan -import ./laws/RoleScanExempt.bend as RoleScanExempt import ./laws/RoleStrict.bend as RoleStrict -import ./laws/RoleTable.bend as RoleTable import ./laws/RoleTail.bend as RoleTail import ./laws/RoleThunk.bend as RoleThunk -import ./laws/RoleUnit.bend as RoleUnit -import ./laws/ScanReportsCarriedArgumentWalks.bend as ScanReportsCarriedArgumentWalks import ./laws/ScopeConcatCont.bend as ScopeConcatCont import ./laws/ScopeExemptionsFollowPaths.bend as ScopeExemptionsFollowPaths import ./laws/SpaceReportsBlanksAndWidth.bend as SpaceReportsBlanksAndWidth import ./laws/StrictReportsEagerSelfCalls.bend as StrictReportsEagerSelfCalls import ./laws/StringsReportLongLiteralArms.bend as StringsReportLongLiteralArms -import ./laws/TableReportsFixedTableAccess.bend as TableReportsFixedTableAccess import ./laws/TailReportsNonTailCalls.bend as TailReportsNonTailCalls import ./laws/ThunkReportsIgnoredSelfCallLambdas.bend as ThunkReportsIgnoredSelfCallLambdas import ./laws/TranscongReportsTransCongChains.bend as TranscongReportsTransCongChains import ./laws/TwiceReportsRepeatedListLiteral.bend as TwiceReportsRepeatedListLiteral -import ./laws/UnitReportsMultiplyByOne.bend as UnitReportsMultiplyByOne -import ./laws/UnsafeReportsReachableForeignDefs.bend as UnsafeReportsReachableForeignDefs import ./laws/UnusedReportsDeadBinders.bend as UnusedReportsDeadBinders import ./laws/WrapReportsBrokenHeaderShape.bend as WrapReportsBrokenHeaderShape import ./laws/LspAnswersEachRequestOnce.bend as LspAnswersEachRequestOnce diff --git a/PROOF.bend b/PROOF.bend index 90d84bb..881c2d4 100644 --- a/PROOF.bend +++ b/PROOF.bend @@ -61,19 +61,18 @@ import ./proofs/ForeignReportsMissingBackend.bend as PForeignReportsMissingBacke import ./proofs/FromrevExempt.bend as PFromrevExempt import ./proofs/FromrevReportsReversedStringBuild.bend as PFromrevReportsReversedStringBuild import ./proofs/FuelReportsLiteralBudgets.bend as PFuelReportsLiteralBudgets -import ./proofs/HoistReportsTablesInLoops.bend as PHoistReportsTablesInLoops import ./proofs/HoleReportsEachTodo.bend as PHoleReportsEachTodo import ./proofs/IndexReportsGetsInRecursion.bend as PIndexReportsGetsInRecursion import ./proofs/InertConcatCont.bend as PInertConcatCont import ./proofs/InertFromrev.bend as PInertFromrev import ./proofs/InertTranscong.bend as PInertTranscong +import ./proofs/ImportReachIsClosed.bend as PImportReachIsClosed import ./proofs/LawNamesEveryItem.bend as PLawNamesEveryItem import ./proofs/ParamReportsShortNames.bend as PParamReportsShortNames import ./proofs/PickReportsSelfCalls.bend as PPickReportsSelfCalls import ./proofs/PickReportsBoundSearches.bend as PPickReportsBoundSearches import ./proofs/PutReportsMapPutCalls.bend as PPutReportsMapPutCalls import ./proofs/RewalkReportsRepeatedWalks.bend as PRewalkReportsRepeatedWalks -import ./proofs/RingReportsDropAppendRotation.bend as PRingReportsDropAppendRotation import ./proofs/RoleClosed.bend as PRoleClosed import ./proofs/RoleClosedExempt.bend as PRoleClosedExempt import ./proofs/RoleConcat.bend as PRoleConcat @@ -82,7 +81,6 @@ import ./proofs/RoleDocExempt.bend as PRoleDocExempt import ./proofs/RoleEager.bend as PRoleEager import ./proofs/RoleFromrev.bend as PRoleFromrev import ./proofs/RoleFromrevExempt.bend as PRoleFromrevExempt -import ./proofs/RoleHoist.bend as PRoleHoist import ./proofs/RoleHole.bend as PRoleHole import ./proofs/RoleHoleExempt.bend as PRoleHoleExempt import ./proofs/RoleIndex.bend as PRoleIndex @@ -95,27 +93,18 @@ import ./proofs/RolePick.bend as PRolePick import ./proofs/RoleProofNamed.bend as PRoleProofNamed import ./proofs/RoleReached.bend as PRoleReached import ./proofs/RoleRewalk.bend as PRoleRewalk -import ./proofs/RoleRing.bend as PRoleRing -import ./proofs/RoleScan.bend as PRoleScan -import ./proofs/RoleScanExempt.bend as PRoleScanExempt import ./proofs/RoleStrict.bend as PRoleStrict -import ./proofs/RoleTable.bend as PRoleTable import ./proofs/RoleTail.bend as PRoleTail import ./proofs/RoleThunk.bend as PRoleThunk -import ./proofs/RoleUnit.bend as PRoleUnit -import ./proofs/ScanReportsCarriedArgumentWalks.bend as PScanReportsCarriedArgumentWalks import ./proofs/ScopeConcatCont.bend as PScopeConcatCont import ./proofs/ScopeExemptionsFollowPaths.bend as PScopeExemptionsFollowPaths import ./proofs/SpaceReportsBlanksAndWidth.bend as PSpaceReportsBlanksAndWidth import ./proofs/StrictReportsEagerSelfCalls.bend as PStrictReportsEagerSelfCalls import ./proofs/StringsReportLongLiteralArms.bend as PStringsReportLongLiteralArms -import ./proofs/TableReportsFixedTableAccess.bend as PTableReportsFixedTableAccess import ./proofs/TailReportsNonTailCalls.bend as PTailReportsNonTailCalls import ./proofs/ThunkReportsIgnoredSelfCallLambdas.bend as PThunkReportsIgnoredSelfCallLambdas import ./proofs/TranscongReportsTransCongChains.bend as PTranscongReportsTransCongChains import ./proofs/TwiceReportsRepeatedListLiteral.bend as PTwiceReportsRepeatedListLiteral -import ./proofs/UnitReportsMultiplyByOne.bend as PUnitReportsMultiplyByOne -import ./proofs/UnsafeReportsReachableForeignDefs.bend as PUnsafeReportsReachableForeignDefs import ./proofs/UnusedReportsDeadBinders.bend as PUnusedReportsDeadBinders import ./proofs/WrapReportsBrokenHeaderShape.bend as PWrapReportsBrokenHeaderShape import ./proofs/LspAnswersEachRequestOnce.bend as PLspAnswersEachRequestOnce diff --git a/README.md b/README.md index 87ca8b6..37bb4a2 100644 --- a/README.md +++ b/README.md @@ -154,8 +154,10 @@ def space() -> String: ``` The groups are `correctness` (`hole`, `pick`, `put`; error by default), -`suspicious` (`unused`), `style` (`doc`, `space`, `wrap`, `param`) and `laws` (`coverage`), the -rest warn by default. Every rule, and the config in full, is in +`suspicious` (`unused`), `perf` (`strings`, `chars`), `style` (`doc`, `space`, +`wrap`, `param`) and `laws`, the rest warn by default, and `pedantic` (`tail`, +`strict`, `index`), off by default. The laws rules, `coverage` and `closed`, +are opt-in: a project that keeps laws turns each on by name. Every rule, and the config in full, is in [src/README.md](src/README.md). ## Develop diff --git a/bolt.bend b/bolt.bend index 3fbfa79..63358d6 100644 --- a/bolt.bend +++ b/bolt.bend @@ -1,5 +1,6 @@ # bolt.bend: how bolt lints this repo. Every rule is an error except `coverage`, -# which warns until the laws exist: the gate must see no errors. See +# which warns until the laws exist: the gate must see no errors. The opt-in +# and pedantic rules are turned on by name or group below. See # src/config.bend for the groups and levels. import Base @@ -11,20 +12,29 @@ def correctness() -> String: def suspicious() -> String: "error" +# what is slow but right +def perf() -> String: + "error" + # comments and whitespace def style() -> String: "error" +# advice that is noisy on idiomatic code, held here all the same: foreign, +# strict, fuel, index and rewalk were errors here before they moved +def pedantic() -> String: + "error" + +# but not a tail call on every step +def tail() -> String: + "off" + # a def named by no law, IO included: advice while guarantees are pending, # since deleting the closed laws left defs no law names. That rule -# is `coverage` (L001), which `def coverage()` could also set on its own. -def laws() -> String: +# is `coverage` (L001), opt-in, so only its own setting turns it on. +def coverage() -> String: "warn" -# a law with no binder +# a law with no binder (opt-in) def closed() -> String: "error" - -# an @unsafe def a law file reaches -def unsafe() -> String: - "error" diff --git a/laws/CharsReportManyCharArms.bend b/laws/CharsReportManyCharArms.bend index 204fa94..2b061d2 100644 --- a/laws/CharsReportManyCharArms.bend +++ b/laws/CharsReportManyCharArms.bend @@ -7,7 +7,7 @@ import ../src/syntax/bind.bend as Bind import ../src/src.bend as Src import ../src/syntax/tree.bend as Tree import ../src/syntax/outline.bend as Outline -import ../src/rules/correctness/chars.bend as Chars +import ../src/rules/perf/chars.bend as Chars import ./defs/MatchArmRules.bend as D1 # one when a case statement's first match column opens with a char literal, diff --git a/laws/CodeRulesIgnoreCommentsAndStrings.bend b/laws/CodeRulesIgnoreCommentsAndStrings.bend index 5bb81be..af9d7c6 100644 --- a/laws/CodeRulesIgnoreCommentsAndStrings.bend +++ b/laws/CodeRulesIgnoreCommentsAndStrings.bend @@ -20,18 +20,13 @@ import ../src/rules/correctness/pick.bend as Pick import ../src/rules/pedantic/tail.bend as Tail import ../src/rules/suspicious/concat.bend as Concat import ../src/rules/suspicious/eager.bend as Eager -import ../src/rules/suspicious/strict.bend as Strict -import ../src/rules/suspicious/hoist.bend as Hoist -import ../src/rules/suspicious/index.bend as Index -import ../src/rules/suspicious/ring.bend as Ring -import ../src/rules/suspicious/table.bend as Table -import ../src/rules/suspicious/unit.bend as UnitRule +import ../src/rules/pedantic/strict.bend as Strict +import ../src/rules/pedantic/index.bend as Index import ../src/rules/suspicious/thunk.bend as Thunk -import ../src/rules/suspicious/scan.bend as Scan import ../src/rules/tokens.bend as T import ../src/rules/correctness/arms.bend as Arms import ../src/rules/suspicious/unused.bend as Unused -import ../src/rules/suspicious/fuel.bend as FuelRule +import ../src/rules/pedantic/fuel.bend as FuelRule import ../src/rules/style/wrap.bend as Wrap import ./defs/InertText.bend as D1 @@ -220,27 +215,6 @@ law inert_param: for e: {inert.binds(b1) == inert.binds(b2) : List<&2, Bind.Bind>} {Param.check.on(b1, path) == Param.check.on(b2, path) : List<&2, F.Finding>} -# LAW: table reads what no string says: two sources as the lexer makes them -# whose trees read the same with strings cut have the same findings (a -# string's text is only ever compared with a call's or a table's name) -law inert_table: - for +path: String - for text: String - for +tree: Tree.Node - for toks: List<&2, Lex.Tok> - for bound: Bind.Bound - for items: List<&2, Outline.Item> - for text2: String - for +tree2: Tree.Node - for toks2: List<&2, Lex.Tok> - for bound2: Bind.Bound - for items2: List<&2, Outline.Item> - for +ok: {D1.inert.ok(tree) == True{} : Bool} - for +ok2: {D1.inert.ok(tree2) == True{} : Bool} - for e: {D1.inert.node(tree) == D1.inert.node(tree2) : Tree.Node} - {Table.check(Src.Src{path, text, toks, tree, bound, items}) - == Table.check(Src.Src{path, text2, toks2, tree2, bound2, items2}) : List<&2, F.Finding>} - # LAW: index reads what no string says: two sources as the lexer makes them # whose trees read the same with strings cut have the same findings law inert_index: @@ -281,26 +255,6 @@ law inert_concat: {Concat.check(Src.Src{path, text, toks, tree, bound, items}) == Concat.check(Src.Src{path, text2, toks2, tree2, bound2, items2}) : List<&2, F.Finding>} -# LAW: unit reads what no string says: two sources as the lexer makes them -# whose trees read the same with strings cut have the same findings -law inert_unit: - for +path: String - for text: String - for +tree: Tree.Node - for toks: List<&2, Lex.Tok> - for bound: Bind.Bound - for items: List<&2, Outline.Item> - for text2: String - for +tree2: Tree.Node - for toks2: List<&2, Lex.Tok> - for bound2: Bind.Bound - for items2: List<&2, Outline.Item> - for +ok: {D1.inert.ok(tree) == True{} : Bool} - for +ok2: {D1.inert.ok(tree2) == True{} : Bool} - for e: {D1.inert.node(tree) == D1.inert.node(tree2) : Tree.Node} - {UnitRule.check(Src.Src{path, text, toks, tree, bound, items}) - == UnitRule.check(Src.Src{path, text2, toks2, tree2, bound2, items2}) : List<&2, F.Finding>} - # LAW: thunk reads what no string says: two sources as the lexer makes them # whose trees read the same with strings cut have the same findings law inert_thunk: @@ -361,26 +315,6 @@ law inert_fuel: {FuelRule.check(Src.Src{path, text, toks, tree, bound, items}) == FuelRule.check(Src.Src{path, text2, toks2, tree2, bound2, items2}) : List<&2, F.Finding>} -# LAW: ring reads what no string says: two sources as the lexer makes them -# whose trees read the same with strings cut have the same findings -law inert_ring: - for +path: String - for text: String - for +tree: Tree.Node - for toks: List<&2, Lex.Tok> - for bound: Bind.Bound - for items: List<&2, Outline.Item> - for text2: String - for +tree2: Tree.Node - for toks2: List<&2, Lex.Tok> - for bound2: Bind.Bound - for items2: List<&2, Outline.Item> - for +ok: {D1.inert.ok(tree) == True{} : Bool} - for +ok2: {D1.inert.ok(tree2) == True{} : Bool} - for e: {D1.inert.node(tree) == D1.inert.node(tree2) : Tree.Node} - {Ring.check(Src.Src{path, text, toks, tree, bound, items}) - == Ring.check(Src.Src{path, text2, toks2, tree2, bound2, items2}) : List<&2, F.Finding>} - # LAW: arms reads what no string says: two sources as the lexer makes them # whose trees read the same with strings cut have the same findings law inert_arms: @@ -440,48 +374,6 @@ law inert_unused: == Unused.check(Src.Src{path, text2, toks2, tree2, Bind.Bound{binds2, uses, scopes2}, items2}) : List<&2, F.Finding>} -# LAW: hoist reads what no string says: two sources as the lexer makes them -# whose trees read the same with strings cut have the same findings (a -# string's text is only ever compared with a name, a keyword or a mark) -law inert_hoist: - for +path: String - for text: String - for +tree: Tree.Node - for toks: List<&2, Lex.Tok> - for bound: Bind.Bound - for items: List<&2, Outline.Item> - for text2: String - for +tree2: Tree.Node - for toks2: List<&2, Lex.Tok> - for bound2: Bind.Bound - for items2: List<&2, Outline.Item> - for +ok: {D1.inert.ok(tree) == True{} : Bool} - for +ok2: {D1.inert.ok(tree2) == True{} : Bool} - for e: {D1.inert.node(tree) == D1.inert.node(tree2) : Tree.Node} - {Hoist.check(Src.Src{path, text, toks, tree, bound, items}) - == Hoist.check(Src.Src{path, text2, toks2, tree2, bound2, items2}) : List<&2, F.Finding>} - -# LAW: scan reads what no comment or string says: two sources as the lexer -# makes them whose trees read the same with comments and strings cut have -# the same findings -law inert_scan: - for +path: String - for text: String - for +tree: Tree.Node - for toks: List<&2, Lex.Tok> - for bound: Bind.Bound - for items: List<&2, Outline.Item> - for text2: String - for +tree2: Tree.Node - for toks2: List<&2, Lex.Tok> - for bound2: Bind.Bound - for items2: List<&2, Outline.Item> - for +ok: {D1.inert.ok(tree) == True{} : Bool} - for +ok2: {D1.inert.ok(tree2) == True{} : Bool} - for e: {D1.inert.node(tree) == D1.inert.node(tree2) : Tree.Node} - {Scan.check(Src.Src{path, text, toks, tree, bound, items}) - == Scan.check(Src.Src{path, text2, toks2, tree2, bound2, items2}) : List<&2, F.Finding>} - # the tokens of each item's header, with comments and strings cut def inert.sigs(its: List<&2, Outline.Item>) -> List<&2, List<&2, Lex.Tok>>: match its: diff --git a/laws/ExemptPathsYieldNoFindings.bend b/laws/ExemptPathsYieldNoFindings.bend index d238fad..f1f91ef 100644 --- a/laws/ExemptPathsYieldNoFindings.bend +++ b/laws/ExemptPathsYieldNoFindings.bend @@ -1,7 +1,6 @@ # For every text, a per-file rule's check on a path its header exempts returns # no findings. The cost rules (`pick`, `strict`, `eager`, `tail`, `concat`, -# `index`, `table`, `hoist`, `ring`, `rewalk`, `unit`, `scan`, `thunk`) also -# skip, wherever it is, every def that is a proof: one whose signature returns +# `index`, `rewalk`, `thunk`) also skip, wherever it is, every def that is a proof: one whose signature returns # a proof (`-> {a == b : T}`), or one written with no type at all (no `:` # among its parameters and no `->`, as `def f(x, y):`), which is how Bend # fills the law named f; their "reports exactly" rows are read with that skip. @@ -22,15 +21,10 @@ import ../src/rules/correctness/pick.bend as Pick import ../src/rules/pedantic/tail.bend as Tail import ../src/rules/suspicious/concat.bend as Concat import ../src/rules/suspicious/eager.bend as Eager -import ../src/rules/suspicious/rewalk.bend as Rewalk -import ../src/rules/suspicious/strict.bend as Strict -import ../src/rules/suspicious/hoist.bend as Hoist -import ../src/rules/suspicious/index.bend as Index -import ../src/rules/suspicious/ring.bend as Ring -import ../src/rules/suspicious/table.bend as Table -import ../src/rules/suspicious/unit.bend as UnitRule +import ../src/rules/pedantic/rewalk.bend as Rewalk +import ../src/rules/pedantic/strict.bend as Strict +import ../src/rules/pedantic/index.bend as Index import ../src/rules/suspicious/thunk.bend as Thunk -import ../src/rules/suspicious/scan.bend as Scan import ../src/rules/calls.bend as Calls # The header exemptions. A per-file rule whose header exempts a path (a law @@ -138,17 +132,6 @@ law strict_exempt: for e: {Paths.is_law_file(path) == True{} : Bool} {Strict.check(Src.Src{path, text, toks, tree, bound, items}) == Nil{} : List<&2, F.Finding>} -# LAW: hoist reports nothing in a LAWS.bend or a PROOF.bend, which never run, whatever the source holds -law hoist_exempt: - for +path: String - for text: String - for toks: List<&2, Lex.Tok> - for tree: Tree.Node - for bound: Bind.Bound - for items: List<&2, Outline.Item> - for e: {Paths.is_law_file(path) == True{} : Bool} - {Hoist.check(Src.Src{path, text, toks, tree, bound, items}) == Nil{} : List<&2, F.Finding>} - # LAW: index reports nothing in a LAWS.bend or a PROOF.bend, which never run, whatever the source holds law index_exempt: for +path: String @@ -160,39 +143,6 @@ law index_exempt: for e: {Paths.is_law_file(path) == True{} : Bool} {Index.check(Src.Src{path, text, toks, tree, bound, items}) == Nil{} : List<&2, F.Finding>} -# LAW: ring reports nothing in a LAWS.bend or a PROOF.bend, which never run, whatever the source holds -law ring_exempt: - for +path: String - for text: String - for toks: List<&2, Lex.Tok> - for tree: Tree.Node - for bound: Bind.Bound - for items: List<&2, Outline.Item> - for e: {Paths.is_law_file(path) == True{} : Bool} - {Ring.check(Src.Src{path, text, toks, tree, bound, items}) == Nil{} : List<&2, F.Finding>} - -# LAW: table reports nothing in a LAWS.bend or a PROOF.bend, which never run, whatever the source holds -law table_exempt: - for +path: String - for text: String - for toks: List<&2, Lex.Tok> - for tree: Tree.Node - for bound: Bind.Bound - for items: List<&2, Outline.Item> - for e: {Paths.is_law_file(path) == True{} : Bool} - {Table.check(Src.Src{path, text, toks, tree, bound, items}) == Nil{} : List<&2, F.Finding>} - -# LAW: unit reports nothing in a LAWS.bend or a PROOF.bend, which never run, whatever the source holds -law unit_exempt: - for +path: String - for text: String - for toks: List<&2, Lex.Tok> - for tree: Tree.Node - for bound: Bind.Bound - for items: List<&2, Outline.Item> - for e: {Paths.is_law_file(path) == True{} : Bool} - {UnitRule.check(Src.Src{path, text, toks, tree, bound, items}) == Nil{} : List<&2, F.Finding>} - # LAW: thunk reports nothing in a LAWS.bend or a PROOF.bend, which never run, whatever the source holds law thunk_exempt: for +path: String @@ -204,17 +154,6 @@ law thunk_exempt: for e: {Paths.is_law_file(path) == True{} : Bool} {Thunk.check(Src.Src{path, text, toks, tree, bound, items}) == Nil{} : List<&2, F.Finding>} -# LAW: scan reports nothing in a LAWS.bend or a PROOF.bend, which never run, whatever the source holds -law scan_exempt: - for +path: String - for text: String - for toks: List<&2, Lex.Tok> - for tree: Tree.Node - for bound: Bind.Bound - for items: List<&2, Outline.Item> - for e: {Paths.is_law_file(path) == True{} : Bool} - {Scan.check(Src.Src{path, text, toks, tree, bound, items}) == Nil{} : List<&2, F.Finding>} - # LAW: a def written with no type (no `:` among its parameters, no `->`) fills the law of its name, so it is a # proof: every cost rule that reads Calls.exempt skips it wherever it is, as it skips one that returns a proof law untyped_exempt: diff --git a/laws/ForeignReportsMissingBackend.bend b/laws/ForeignReportsMissingBackend.bend index ad60b25..6863e71 100644 --- a/laws/ForeignReportsMissingBackend.bend +++ b/laws/ForeignReportsMissingBackend.bend @@ -8,7 +8,7 @@ import ../src/syntax/bind.bend as Bind import ../src/src.bend as Src import ../src/syntax/tree.bend as Tree import ../src/syntax/outline.bend as Outline -import ../src/rules/correctness/foreign.bend as Foreign +import ../src/rules/pedantic/foreign.bend as Foreign # foreign (laws/ForeignReportsMissingBackend.bend). Over the file's top-level statements the rule # reports each def whose body is one import or more and nothing else (a diff --git a/laws/FuelReportsLiteralBudgets.bend b/laws/FuelReportsLiteralBudgets.bend index 42cbe3c..aea8a5d 100644 --- a/laws/FuelReportsLiteralBudgets.bend +++ b/laws/FuelReportsLiteralBudgets.bend @@ -14,7 +14,7 @@ import ../src/syntax/tree.bend as Tree import ../src/syntax/outline.bend as Outline import ../src/rules/calls.bend as Calls import ../src/rules/tokens.bend as T -import ../src/rules/suspicious/fuel.bend as FuelRule +import ../src/rules/pedantic/fuel.bend as FuelRule # fuel (laws/FuelReportsLiteralBudgets.bend). The rule lists the file's fuel parameters: for each # def of the file (Calls.defs) that returns no effect, each parameter diff --git a/laws/HoistReportsTablesInLoops.bend b/laws/HoistReportsTablesInLoops.bend deleted file mode 100644 index 6970ed8..0000000 --- a/laws/HoistReportsTablesInLoops.bend +++ /dev/null @@ -1,154 +0,0 @@ -# `hoist` reports exactly, in a def that calls itself and is not a law or a -# proof, outside a lambda's body (the rest of a chain after `=>`), a case -# pattern, and a case arm that does not call the def: a `List.get`, -# `List.set`, `String.get`, `Array.get` or `Array.set` call whose collection -# is a table, and a let of one plain name to a table that a later get, set or -# `name[..]` in its block or the statements after it indexes; where a table is -# a list literal with eight or more top-level commas, an array literal of more -# than eight slots (`[v : T*n]` with n not a literal 0 to 8, `[v : T^d]` with -# d not a literal 0 to 3), a `List.replicate`, `Array.new` or `List.range` -# whose count is not a literal 0 to 8, a `List.map` or `Array.map`, or a call -# to a def of the file, other than an undotted self-call, whose body is a -# single statement that is a list literal, or an array literal, -# `List.replicate`, `Array.new` or `List.range` sized by a number literal, of -# more than eight cells by those measures; and every value name in the table -# (a callee or a `~` template aside) is a parameter that each self-call passes -# back unchanged in its own position. -import Base -import ../src/syntax/lex.bend as Lex -import ../src/finding.bend as F -import ../src/syntax/bind.bend as Bind -import ../src/src.bend as Src -import ../src/syntax/tree.bend as Tree -import ../src/syntax/outline.bend as Outline -import ../src/rules/suspicious/hoist.bend as Hoist -import ../src/rules/calls.bend as Calls -import ../src/rules/tokens.bend as T - -# The recursion rule, hoist. It reads each def of the file whose body calls -# its own name and that is not a law or a proof, and walks that body as -# chains: a `=>` token makes the rest of its chain cold, a case's pattern is -# cold, and a case arm's body stays hot only when it calls the def; the rest -# keeps the heat it came in with, and the def's body starts hot. On a hot -# path it reports each call, a name token then a `(` group, that is a -# `List.get` / `List.set` (collection argument 2), `String.get` (0) or -# `Array.get` / `Array.set` (1) whose collection is a table, and each -# statement that is not a case, whose tokens before its first `=` hold exactly -# one lowercase name and no constructor or group, whose right side is a -# table, and whose name is indexed (`name[..]`, or a get or set's collection, -# bare or in parentheses) anywhere in its own block or the statements after -# it. A table is the rule's own recognizer (`is_table`): a list literal with -# eight or more top-level commas, an array literal `[v : T*n]` or `[v : T^d]` -# whose size is not a literal 0 to 8 after `*` or 0 to 3 after `^` (2^d -# slots), a `List.replicate` / `Array.new` / `List.range` whose count is not -# such a literal, a `List.map` / `Array.map`, or a call to a def of the file -# (not an undotted self-call) whose body is one constant-size table of that -# width (`wide_defs`); in every case -# with each value name under it (callees, `~` templates, dotted names and -# types aside) a parameter that every self-call passes back as that same lone -# name in its own position. - -# does a get or set's collection argument, at its slot, hold a table? -def hoist.slot( - ss: Hoist.Slot, - +as: List<&2, Tree.Node>, - +self: String, - +wides: List<&2, String>, - +carried: List<&2, String> -) -> Bool: - match ss: - case Hoist.Slot{on, at}: - Bool.and(on, Hoist.is_table(Calls.arg(as, at), self, wides, carried)) - -# does a call site report: hot, a `(` group, and a get or set on a table -def hoist.call( - +hot: Bool, - +open: Bool, - +tt: String, - kids: Tree.Node, - +self: String, - +wides: List<&2, String>, - +carried: List<&2, String> -) -> Bool: - Bool.and(hot, Bool.and(open, hoist.slot(Hoist.slot_of(tt), Calls.args(kids), self, wides, carried))) - -# a plain let's name and right side: a table, and the name indexed afterwards -def hoist.got( - mm: Maybe<&2, Hoist.Got>, - +body: Tree.Node, - +rest: Tree.Node, - +self: String, - +wides: List<&2, String>, - +carried: List<&2, String> -) -> Bool: - match mm: - case None{}: - False{} - case Some{Hoist.Got{+nm, +expr}}: - Bool.and(Hoist.is_table(expr, self, wides, carried), Hoist.uses_or(body, rest, nm)) - -# does a statement report: hot, and a plain let of a table indexed afterwards -def hoist.let( - +hot: Bool, - kids: Tree.Node, - +body: Tree.Node, - +rest: Tree.Node, - +self: String, - +wides: List<&2, String>, - +carried: List<&2, String> -) -> Bool: - Bool.and(hot, hoist.got(Hoist.eat(kids, False{}, False{}, ""), body, rest, self, wides, carried)) - -# how many calls and lets hoist reports under the node, entered hot or cold -def hoist.count( - nn: Tree.Node, - +self: String, - +wides: List<&2, String>, - +carried: List<&2, String>, - +hot: Bool -) -> Nat: - match nn: - case Tree.NCons{Tree.Leaf{Lex.Tok{k, +t, l, c}}, Tree.NCons{Tree.Group{Lex.Tok{_, +o, _, _}, +kids, _}, rest}}: - +heat = Hoist.warm(hot, k) - Nat.add(Bool.pick(Nat, hoist.call(heat, String.eq(o, "("), t, kids, self, wides, carried), 1n, 0n), - Nat.add(hoist.count(kids, self, wides, carried, heat), hoist.count(rest, self, wides, carried, heat))) - case Tree.NCons{Tree.Leaf{Lex.Tok{k, t, l, c}}, rest}: - hoist.count(rest, self, wides, carried, Hoist.warm(hot, k)) - case Tree.NCons{Tree.Group{o, kids, cl}, rest}: - Nat.add(hoist.count(kids, self, wides, carried, hot), hoist.count(rest, self, wides, carried, hot)) - case Tree.NCons{Tree.Stmt{Tree.SCase{}, kids, +body}, rest}: - Nat.add(hoist.count(kids, self, wides, carried, False{}), - Nat.add(hoist.count(body, self, wides, carried, Bool.and(hot, Calls.calls(body, self))), - hoist.count(rest, self, wides, carried, hot))) - case Tree.NCons{Tree.Stmt{kind, +kids, +body}, +rest}: - Nat.add(Bool.pick(Nat, hoist.let(hot, kids, body, rest, self, wides, carried), 1n, 0n), - Nat.add(hoist.count(kids, self, wides, carried, hot), - Nat.add(hoist.count(body, self, wides, carried, hot), hoist.count(rest, self, wides, carried, hot)))) - case Tree.NCons{h, rest}: - hoist.count(rest, self, wides, carried, hot) - case other: - 0n - -# how many hoist reports over the defs: those in each def that calls itself -# and is not a law or a proof, walked hot, with its carried parameters -def hoist.total(ds: List<&2, Calls.Def>, +wides: List<&2, String>, +path: String) -> Nat: - match ds: - case Nil{}: - 0n - case Con{Calls.Def{+name, +sig, +body}, rest}: - Nat.add(Bool.pick(Nat, Bool.and(Calls.calls(body, name), Bool.not(Calls.exempt(path, sig))), - hoist.count(body, name, wides, Hoist.carried(sig, body, name), True{}), 0n), hoist.total(rest, wides, path)) - -# LAW: hoist reports one finding for each call and each let the walk above -# counts, in each def that calls itself and is not a law or a proof, with -# the file's defs whose body is one wide constant-size table as builders; -# none for anything else -law hoist_counts: - for +path: String - for text: String - for toks: List<&2, Lex.Tok> - for +tree: Tree.Node - for bound: Bind.Bound - for items: List<&2, Outline.Item> - {List.length(&2, F.Finding, Hoist.check(Src.Src{path, text, toks, tree, bound, items})) - == hoist.total(Calls.defs(tree), Hoist.wide_defs(Calls.defs(tree), []), path) : Nat} diff --git a/laws/ImportReachIsClosed.bend b/laws/ImportReachIsClosed.bend new file mode 100644 index 0000000..6ec5d33 --- /dev/null +++ b/laws/ImportReachIsClosed.bend @@ -0,0 +1,36 @@ +# What a file reaches by relative imports through the run, the walk the +# coverage rule takes over the laws' graph (laws/LawNamesEveryItem.bend), +# holds the file, is shut under imports, and is the least list that is: the +# walk's fuel never cuts it short, and it reaches only files a chain of +# relative imports leads to. +import Base +import ../src/rules/digest.bend as Digest +import ../src/rules/imports.bend as Imports +import ./defs/Unsafe.bend as D1 + +# LAW: over every list of digests, what the file at from reaches holds it +law unsafe_from: + for +ds: List<&2, Digest.Digest> + for +from: String + {Imports.has(D1.unsafe.closure(ds, from), from) == True{} : Bool} + +# LAW: over every list of digests, what a file reaches is shut: every file in +# the run that one of its files imports is in it. So the walk's fuel never +# cuts it short +law unsafe_shut: + for +ds: List<&2, Digest.Digest> + for +from: String + {D1.unsafe.shut(ds, D1.unsafe.closure(ds, from), D1.unsafe.closure(ds, from)) == True{} : Bool} + +# LAW: and it is the least such list: every list that holds from and is shut +# holds each file it reaches, so it reaches only files a chain of relative +# imports through the run leads to +law unsafe_least: + for +ds: List<&2, Digest.Digest> + for +from: String + for +ss: List<&2, String> + for +pp: String + for hf: {Imports.has(ss, from) == True{} : Bool} + for hs: {D1.unsafe.shut(ds, ss, ss) == True{} : Bool} + for hp: {Imports.has(D1.unsafe.closure(ds, from), pp) == True{} : Bool} + {Imports.has(ss, pp) == True{} : Bool} diff --git a/laws/IndexReportsGetsInRecursion.bend b/laws/IndexReportsGetsInRecursion.bend index 652af80..4b5c8a8 100644 --- a/laws/IndexReportsGetsInRecursion.bend +++ b/laws/IndexReportsGetsInRecursion.bend @@ -1,6 +1,6 @@ # `index` reports exactly a `List.get` or `String.get` at a non-literal index -# anywhere in a recursive def, except a `List.get` on a fixed table that -# `table` reports and a get that is the def's own self-call (the def is named +# anywhere in a recursive def, except a `List.get` on a fixed table +# (src/rules/table.bend) and a get that is the def's own self-call (the def is named # `List.get` or `String.get`). import Base import ../src/syntax/lex.bend as Lex @@ -9,16 +9,16 @@ import ../src/syntax/bind.bend as Bind import ../src/src.bend as Src import ../src/syntax/tree.bend as Tree import ../src/syntax/outline.bend as Outline -import ../src/rules/suspicious/index.bend as Index -import ../src/rules/suspicious/table.bend as Table +import ../src/rules/pedantic/index.bend as Index +import ../src/rules/table.bend as Table import ../src/rules/calls.bend as Calls # The recursion rule, index. It reads each def of the file whose body calls # its own name and that is not a law or a proof, and every call in that body # at any depth: a name token, then a group. A `String.get(..)` whose index # (argument 1) is not one number token is a finding. So is a `List.get(..)` -# whose index (argument 3) is not one number token, unless `table` reports -# that same call: its fixed-table test, over the tables it finds in that def. +# whose index (argument 3) is not one number token, unless src/rules/table.bend +# calls it a get on a fixed table, over the tables it finds in that def. # A call whose name is the def's own (a def named `List.get` or `String.get` # stepping to itself) is neither. @@ -32,7 +32,8 @@ def index.lit(nn: Tree.Node) -> Bool: # does a call report, given whether its name is List.get (list) or # String.get (sget), whether its group opens with `(` (ob), its arguments, -# and what `table` reports for it? +# and what src/rules/table.bend makes of it (a finding when it is a fixed +# table's)? def index.fires(list: Bool, +ob: Bool, +sget: Bool, +as: List<&2, Tree.Node>, table: List<&2, F.Finding>) -> Bool: match list: case True{}: @@ -61,7 +62,7 @@ def index.site( 0n # how many calls at any depth of the node index reports, fixed the tables -# `table` sees, in the def named self +# src/rules/table.bend sees, in the def named self def index.count(nn: Tree.Node, +path: String, +fixed: List<&2, String>, +self: String) -> Nat: match nn: case Tree.NCons{Tree.Leaf{Lex.Tok{k, +t, +l, +c}}, +rest}: @@ -77,7 +78,7 @@ def index.count(nn: Tree.Node, +path: String, +fixed: List<&2, String>, +self: S 0n # how many calls index reports over the defs: those in each def that calls -# itself and is not a law or a proof, with the fixed tables `table` sees in it +# itself and is not a law or a proof, with the fixed tables it holds def index.total(ds: List<&2, Calls.Def>, +mods: List<&2, String>, +path: String) -> Nat: match ds: case Nil{}: @@ -89,7 +90,7 @@ def index.total(ds: List<&2, Calls.Def>, +mods: List<&2, String>, +path: String) # LAW: index reports one finding for each `List.get(..)` or `String.get(..)` # at an index that is not one number token, anywhere in a def that calls # itself (a base arm included) and is not a law or a proof, except a -# `List.get` that `table` reports as a get on a fixed table and a get that is +# `List.get` on a fixed table (src/rules/table.bend) and a get that is # the def's own self-call (in a def named `List.get` or `String.get`); none # for anything else law index_counts: diff --git a/laws/LawNamesEveryItem.bend b/laws/LawNamesEveryItem.bend index 46dcda6..423a849 100644 --- a/laws/LawNamesEveryItem.bend +++ b/laws/LawNamesEveryItem.bend @@ -24,7 +24,6 @@ import ../src/syntax/outline.bend as Outline import ../src/rules/calls.bend as Calls import ../src/rules/laws/closed.bend as Closed import ../src/rules/laws/law.bend as Coverage -import ../src/rules/laws/unsafe.bend as Unsafe import ../src/rules/imports.bend as Imports import ./defs/Unsafe.bend as D1 @@ -39,7 +38,7 @@ def coverage.lawful(ds: List<&2, Digest.Digest>) -> Bool: match ds: case Nil{}: False{} - case Con{Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, unsafes, laws}, rest}: + case Con{Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, laws}, rest}: Bool.or(is_laws, coverage.lawful(rest)) # coverage's reading of a file (laws/LawNamesEveryItem.bend). reach_digest and reach_binder, @@ -133,7 +132,7 @@ type CoverageView is Data: # a digest as the coverage rule reads it def coverage.view(dd: Digest.Digest) -> CoverageView: match dd: - case Digest.Digest{_path, norm, _dir, is_laws, _is_law_file, exempt, tops, says, _deps, _unsafes, _laws}: + case Digest.Digest{_path, norm, _dir, is_laws, _is_law_file, exempt, tops, says, _deps, _laws}: CoverageView{norm, is_laws, exempt, tops, says} # coverage by reach (laws/LawNamesEveryItem.bend). A def is covered when a quantified law of @@ -141,7 +140,7 @@ def coverage.view(dd: Digest.Digest) -> CoverageView: # items that holds what the laws name and every call of each def in it. A # type is covered when that set holds it or one of its constructors: a law or # a covered def names it, and a type calls nothing. The set is stated over a -# graph written as digests, so the unsafe laws' spec of a closure (the least +# graph written as digests, so the closure laws' spec of a closure (the least # list that holds its start and is shut, unsafe.shut) is the same spec here: # each node is a digest whose path is an item's key and whose imports are # what the item leads to. An item's key is its name, a space and its file's @@ -165,7 +164,7 @@ def reach.keys(ms: List<&2, Digest.Mention>) -> List<&2, String>: # a node: a digest with only a key and what it leads to def reach.node(+kk: String, to: List<&2, String>) -> Digest.Digest: - Digest.Digest{kk, kk, "", False{}, False{}, True{}, [], [], to, [], []} + Digest.Digest{kk, kk, "", False{}, False{}, True{}, [], [], to, []} # each constructor as a node that leads nowhere, onto the others def reach.ctors(cs: List<&2, String>, +pp: String, more: List<&2, Digest.Digest>) -> List<&2, Digest.Digest>: @@ -193,7 +192,7 @@ def reach.items(ds: List<&2, Digest.Digest>) -> List<&2, Digest.Digest>: match ds: case Nil{}: [] - case Con{Digest.Digest{_path, norm, _dir, _is_laws, _is_law_file, _exempt, tops, _says, _deps, _unsafes, _laws}, + case Con{Digest.Digest{_path, norm, _dir, _is_laws, _is_law_file, _exempt, tops, _says, _deps, _laws}, rest}: reach.nodes(tops, norm, reach.items(rest)) @@ -202,7 +201,7 @@ def reach.named(ds: List<&2, Digest.Digest>) -> List<&2, Digest.Mention>: match ds: case Nil{}: [] - case Con{Digest.Digest{_path, _norm, _dir, is_laws, _is_law_file, _exempt, _tops, says, _deps, _unsafes, _laws}, + case Con{Digest.Digest{_path, _norm, _dir, is_laws, _is_law_file, _exempt, _tops, says, _deps, _laws}, rest}: +more = reach.named(rest) Bool.pick(List<&2, Digest.Mention>, is_laws, List.append(&2, Digest.Mention, says, more), more) @@ -248,7 +247,7 @@ def reach.files(fs: List<&2, Digest.Digest>, +cl: List<&2, String>) -> List<&2, match fs: case Nil{}: [] - case Con{Digest.Digest{path, norm, _dir, _is_laws, _is_law_file, exempt, tops, _says, _deps, _unsafes, _laws}, + case Con{Digest.Digest{path, norm, _dir, _is_laws, _is_law_file, exempt, tops, _says, _deps, _laws}, rest}: match exempt: case True{}: diff --git a/laws/NoqaReportsStaleComments.bend b/laws/NoqaReportsStaleComments.bend index c36ce03..83fe7f1 100644 --- a/laws/NoqaReportsStaleComments.bend +++ b/laws/NoqaReportsStaleComments.bend @@ -2,7 +2,7 @@ # one finding for a bare one (`#`, any spaces, then `noqa` at the comment's # end or before a space) and one for each code it names that no graded finding # of its file on its line has, a code that is no rule's included, where a -# project rule's code (`coverage`, `unsafe`) is judged only when the +# project rule's code (`coverage`'s) is judged only when the # run is the whole tree and never in the editor, and nothing else. It runs # after the filter, over the findings of every other rule, so its findings # come last (laws/OutputOrderIsFixed.bend) and no noqa comment silences them: a `# noqa: S005` @@ -24,9 +24,9 @@ def nq.used(gs: List<&2, F.Graded>, +path: String, +line: U32, +code: String) -> Bool.or(Bool.and(String.eq(p, path), Bool.and(U32.is_eq(l, line), String.eq(Codes.code(rule), code))), nq.used(rest, path, line, code)) -# a project rule's code: `coverage` or `unsafe`'s +# a project rule's code: `coverage`'s def nq.project(+code: String) -> Bool: - Bool.or(String.eq(code, "L001"), String.eq(code, "L003")) + String.eq(code, "L001") # how many of a mark's codes the run judges (any over the whole tree, no # project rule's otherwise) and silence nothing on its line diff --git a/laws/OutputOrderIsFixed.bend b/laws/OutputOrderIsFixed.bend index 6a8f8c4..fb7de46 100644 --- a/laws/OutputOrderIsFixed.bend +++ b/laws/OutputOrderIsFixed.bend @@ -1,6 +1,6 @@ # Output order is read failures, then per-file findings in file-list order and -# `Rules.on` order, then `setting`'s findings (C011), then `coverage` -# and `unsafe`, less the findings a noqa comment silences +# `Rules.on` order, then `setting`'s findings (C011), then `coverage`'s, +# less the findings a noqa comment silences # (laws/NoqaSilencesNamedFindings.bend), then `noqa`'s findings (S005) in file-list order. # # also rests on: laws/LinesInOrder.bend diff --git a/laws/PackagesScoped.bend b/laws/PackagesScoped.bend index c6f4b14..76a7321 100644 --- a/laws/PackagesScoped.bend +++ b/laws/PackagesScoped.bend @@ -4,8 +4,7 @@ import ../src/rules.bend as Rules import ../src/src.bend as Src import ./defs/ScopeWalkFiles.bend as D1 -# LAW: `coverage` sees the digests of one package's files at a time, and -# `unsafe` sees every file's +# LAW: `coverage` sees the digests of one package's files at a time law packages_scoped: for +srcs: List<&2, Src.Src> for +rs: List<&2, String> diff --git a/laws/RewalkReportsRepeatedWalks.bend b/laws/RewalkReportsRepeatedWalks.bend index a337c35..2c280c0 100644 --- a/laws/RewalkReportsRepeatedWalks.bend +++ b/laws/RewalkReportsRepeatedWalks.bend @@ -15,7 +15,7 @@ import ../src/syntax/bind.bend as Bind import ../src/src.bend as Src import ../src/syntax/tree.bend as Tree import ../src/syntax/outline.bend as Outline -import ../src/rules/suspicious/rewalk.bend as Rewalk +import ../src/rules/pedantic/rewalk.bend as Rewalk import ../src/rules/calls.bend as Calls # The recursion rule, rewalk (laws/RewalkReportsRepeatedWalks.bend). It reads each def of the file diff --git a/laws/RingReportsDropAppendRotation.bend b/laws/RingReportsDropAppendRotation.bend deleted file mode 100644 index 0390d52..0000000 --- a/laws/RingReportsDropAppendRotation.bend +++ /dev/null @@ -1,88 +0,0 @@ -# `ring` reports exactly, in a def that calls itself, a self-call argument -# that appends onto a drop by a number literal, or a tail, of the parameter in -# its own position, when no `match` of the def has that parameter as a -# scrutinee. -import Base -import ../src/syntax/lex.bend as Lex -import ../src/finding.bend as F -import ../src/syntax/bind.bend as Bind -import ../src/src.bend as Src -import ../src/syntax/tree.bend as Tree -import ../src/syntax/outline.bend as Outline -import ../src/rules/suspicious/ring.bend as Ring -import ../src/rules/calls.bend as Calls - -# ring: one when the scan found a drop, else zero -def ring.hit(mm: Maybe<&2, Lex.Tok>) -> Nat: - match mm: - case None{}: - 0n - case Some{t}: - 1n - -# how many self-call arguments slide the window of their own slot: argument -# i is read against parameter i only (Ring.slot_wins: that parameter, when -# it is a window), and slides when Ring.plus_scan finds, over it, a -# `List.drop` / `String.drop` of the window by a number literal or a -# `List.tail` / `String.tail` of it, that is then `++`-appended onto or is -# the receiver of `List.append` / `String.append`. What is appended is not -# measured: the drop is the fixed part. -def ring.args(as: List<&2, Tree.Node>, +params: List<&2, String>, +wins: List<&2, String>) -> Nat: - match as: - case Nil{}: - 0n - case Con{h, rest}: - +slot = Maybe.default(&2, String, List.head(&2, String, params), "") - Nat.add(ring.hit(Ring.plus_scan(h, None{}, Ring.slot_wins(slot, wins))), - ring.args(rest, List.tail(&2, String, params), wins)) - -# how many sliding arguments the self-calls `self(..)` under the node hold, -# at any depth -def ring.count(nn: Tree.Node, +self: String, +params: List<&2, String>, +wins: List<&2, String>) -> Nat: - match nn: - case Tree.NCons{Tree.Leaf{Lex.Tok{k, +t, l, c}}, Tree.NCons{Tree.Group{Lex.Tok{_k, +o, _l, _c}, +kids, _e}, rest}}: - Nat.add(Bool.pick(Nat, Bool.and(String.eq(o, "("), String.eq(t, self)), ring.args(Calls.args(kids), params, wins), - 0n), Nat.add(ring.count(kids, self, params, wins), ring.count(rest, self, params, wins))) - case Tree.NCons{Tree.Group{open, kids, close}, rest}: - Nat.add(ring.count(kids, self, params, wins), ring.count(rest, self, params, wins)) - case Tree.NCons{Tree.Stmt{kind, kids, body}, rest}: - Nat.add(ring.count(kids, self, params, wins), - Nat.add(ring.count(body, self, params, wins), ring.count(rest, self, params, wins))) - case Tree.NCons{h, rest}: - ring.count(rest, self, params, wins) - case other: - 0n - -# LAW: over any chain, ring reports one finding for each argument of a -# self-call that drops and appends the window in its own parameter slot, and -# none for a window moved to another parameter's slot or any other call -law ring_walk_counts: - for nn: Tree.Node - for +self: String - for +params: List<&2, String> - for +path: String - for +wins: List<&2, String> - {List.length(&2, F.Finding, Ring.walk(nn, self, params, path, wins)) == ring.count(nn, self, params, wins) : Nat} - -# the sliding arguments of each def of the list that calls itself and is not -# exempt; its windows are its parameters not named as any scrutinee of a -# `match` in its body (Ring.windows) -def ring.defs(ds: List<&2, Calls.Def>, +path: String) -> Nat: - match ds: - case Nil{}: - 0n - case Con{Calls.Def{+name, +sig, +body}, rest}: - Nat.add(Bool.pick(Nat, Bool.and(Calls.calls(body, name), Bool.not(Calls.exempt(path, sig))), - ring.count(body, name, Calls.names(sig), Ring.windows(sig, body)), 0n), ring.defs(rest, path)) - -# LAW: ring reports exactly those arguments, def by def, over the defs of the -# tree that call themselves, and none in a def that does not -law ring_counts: - for +path: String - for text: String - for toks: List<&2, Lex.Tok> - for tree: Tree.Node - for bound: Bind.Bound - for items: List<&2, Outline.Item> - {List.length(&2, F.Finding, Ring.check(Src.Src{path, text, toks, tree, bound, items})) - == ring.defs(Calls.defs(tree), path) : Nat} diff --git a/laws/RoleHoist.bend b/laws/RoleHoist.bend deleted file mode 100644 index e48ca76..0000000 --- a/laws/RoleHoist.bend +++ /dev/null @@ -1,21 +0,0 @@ -import Base -import ../src/syntax/lex.bend as Lex -import ../src/finding.bend as F -import ../src/syntax/bind.bend as Bind -import ../src/role.bend as Role -import ../src/src.bend as Src -import ../src/syntax/tree.bend as Tree -import ../src/syntax/outline.bend as Outline -import ../src/rules/suspicious/hoist.bend as Hoist - -# LAW: hoist, which checks nothing in a law or proof file, told a file's role -# by its name finds what hoist finds -law role_hoist: - for +path: String - for +text: String - for +toks: List<&2, Lex.Tok> - for +tree: Tree.Node - for +bound: Bind.Bound - for +items: List<&2, Outline.Item> - {Role.skip(Role.of_path(path), _u => Hoist.check(Src.Src{path, text, toks, tree, bound, items})) - == Hoist.check(Src.Src{path, text, toks, tree, bound, items}) : List<&2, F.Finding>} diff --git a/laws/RoleIndex.bend b/laws/RoleIndex.bend index b2f9d1d..65dacfa 100644 --- a/laws/RoleIndex.bend +++ b/laws/RoleIndex.bend @@ -6,7 +6,7 @@ import ../src/role.bend as Role import ../src/src.bend as Src import ../src/syntax/tree.bend as Tree import ../src/syntax/outline.bend as Outline -import ../src/rules/suspicious/index.bend as Index +import ../src/rules/pedantic/index.bend as Index # LAW: index, which checks nothing in a law or proof file, told a file's role # by its name finds what index finds diff --git a/laws/RoleRewalk.bend b/laws/RoleRewalk.bend index 17d57fd..502727e 100644 --- a/laws/RoleRewalk.bend +++ b/laws/RoleRewalk.bend @@ -6,7 +6,7 @@ import ../src/role.bend as Role import ../src/src.bend as Src import ../src/syntax/tree.bend as Tree import ../src/syntax/outline.bend as Outline -import ../src/rules/suspicious/rewalk.bend as Rewalk +import ../src/rules/pedantic/rewalk.bend as Rewalk # LAW: rewalk, which checks nothing in a law or proof file, told a file's role # by its name finds what rewalk finds diff --git a/laws/RoleRing.bend b/laws/RoleRing.bend deleted file mode 100644 index cdc20ee..0000000 --- a/laws/RoleRing.bend +++ /dev/null @@ -1,21 +0,0 @@ -import Base -import ../src/syntax/lex.bend as Lex -import ../src/finding.bend as F -import ../src/syntax/bind.bend as Bind -import ../src/role.bend as Role -import ../src/src.bend as Src -import ../src/syntax/tree.bend as Tree -import ../src/syntax/outline.bend as Outline -import ../src/rules/suspicious/ring.bend as Ring - -# LAW: ring, which checks nothing in a law or proof file, told a file's role -# by its name finds what ring finds -law role_ring: - for +path: String - for +text: String - for +toks: List<&2, Lex.Tok> - for +tree: Tree.Node - for +bound: Bind.Bound - for +items: List<&2, Outline.Item> - {Role.skip(Role.of_path(path), _u => Ring.check(Src.Src{path, text, toks, tree, bound, items})) - == Ring.check(Src.Src{path, text, toks, tree, bound, items}) : List<&2, F.Finding>} diff --git a/laws/RoleScan.bend b/laws/RoleScan.bend deleted file mode 100644 index 4c5508e..0000000 --- a/laws/RoleScan.bend +++ /dev/null @@ -1,20 +0,0 @@ -import Base -import ../src/syntax/lex.bend as Lex -import ../src/finding.bend as F -import ../src/syntax/bind.bend as Bind -import ../src/role.bend as Role -import ../src/src.bend as Src -import ../src/syntax/tree.bend as Tree -import ../src/syntax/outline.bend as Outline -import ../src/rules/suspicious/scan.bend as Scan - -# LAW: scan told a file's role by its name finds what scan finds -law role_scan: - for +path: String - for +text: String - for +toks: List<&2, Lex.Tok> - for +tree: Tree.Node - for +bound: Bind.Bound - for +items: List<&2, Outline.Item> - {Scan.check.role(Role.of_path(path), Src.Src{path, text, toks, tree, bound, items}) - == Scan.check(Src.Src{path, text, toks, tree, bound, items}) : List<&2, F.Finding>} diff --git a/laws/RoleScanExempt.bend b/laws/RoleScanExempt.bend deleted file mode 100644 index 16df7ba..0000000 --- a/laws/RoleScanExempt.bend +++ /dev/null @@ -1,21 +0,0 @@ -import Base -import ../src/syntax/lex.bend as Lex -import ../src/finding.bend as F -import ../src/syntax/bind.bend as Bind -import ../src/role.bend as Role -import ../src/src.bend as Src -import ../src/syntax/tree.bend as Tree -import ../src/syntax/outline.bend as Outline -import ../src/rules/suspicious/scan.bend as Scan - -# LAW: scan reports nothing in a law or proof file, whatever the source holds -law role_scan_exempt: - for rr: Role.Role - for e: {Role.is_law_file(rr) == True{} : Bool} - for +path: String - for +text: String - for +toks: List<&2, Lex.Tok> - for +tree: Tree.Node - for +bound: Bind.Bound - for +items: List<&2, Outline.Item> - {Scan.check.role(rr, Src.Src{path, text, toks, tree, bound, items}) == Nil{} : List<&2, F.Finding>} diff --git a/laws/RoleStrict.bend b/laws/RoleStrict.bend index 6f6778b..e6fd861 100644 --- a/laws/RoleStrict.bend +++ b/laws/RoleStrict.bend @@ -6,7 +6,7 @@ import ../src/role.bend as Role import ../src/src.bend as Src import ../src/syntax/tree.bend as Tree import ../src/syntax/outline.bend as Outline -import ../src/rules/suspicious/strict.bend as Strict +import ../src/rules/pedantic/strict.bend as Strict # LAW: strict, which checks nothing in a law or proof file, told a file's role # by its name finds what strict finds diff --git a/laws/RoleTable.bend b/laws/RoleTable.bend deleted file mode 100644 index a881cbd..0000000 --- a/laws/RoleTable.bend +++ /dev/null @@ -1,21 +0,0 @@ -import Base -import ../src/syntax/lex.bend as Lex -import ../src/finding.bend as F -import ../src/syntax/bind.bend as Bind -import ../src/role.bend as Role -import ../src/src.bend as Src -import ../src/syntax/tree.bend as Tree -import ../src/syntax/outline.bend as Outline -import ../src/rules/suspicious/table.bend as Table - -# LAW: table, which checks nothing in a law or proof file, told a file's role -# by its name finds what table finds -law role_table: - for +path: String - for +text: String - for +toks: List<&2, Lex.Tok> - for +tree: Tree.Node - for +bound: Bind.Bound - for +items: List<&2, Outline.Item> - {Role.skip(Role.of_path(path), _u => Table.check(Src.Src{path, text, toks, tree, bound, items})) - == Table.check(Src.Src{path, text, toks, tree, bound, items}) : List<&2, F.Finding>} diff --git a/laws/RoleUnit.bend b/laws/RoleUnit.bend deleted file mode 100644 index 1e516f7..0000000 --- a/laws/RoleUnit.bend +++ /dev/null @@ -1,21 +0,0 @@ -import Base -import ../src/syntax/lex.bend as Lex -import ../src/finding.bend as F -import ../src/syntax/bind.bend as Bind -import ../src/role.bend as Role -import ../src/src.bend as Src -import ../src/syntax/tree.bend as Tree -import ../src/syntax/outline.bend as Outline -import ../src/rules/suspicious/unit.bend as UnitRule - -# LAW: unit, which checks nothing in a law or proof file, told a file's role -# by its name finds what unit finds -law role_unit: - for +path: String - for +text: String - for +toks: List<&2, Lex.Tok> - for +tree: Tree.Node - for +bound: Bind.Bound - for +items: List<&2, Outline.Item> - {Role.skip(Role.of_path(path), _u => UnitRule.check(Src.Src{path, text, toks, tree, bound, items})) - == UnitRule.check(Src.Src{path, text, toks, tree, bound, items}) : List<&2, F.Finding>} diff --git a/laws/ScanReportsCarriedArgumentWalks.bend b/laws/ScanReportsCarriedArgumentWalks.bend deleted file mode 100644 index 4464cb4..0000000 --- a/laws/ScanReportsCarriedArgumentWalks.bend +++ /dev/null @@ -1,170 +0,0 @@ -# `scan` reports exactly, outside a LAWS.bend or a PROOF.bend, in a def that -# calls itself and is not a proof, outside a lambda's body (the rest of a -# chain after `=>`), a case pattern, and a case arm that does not call the -# def: each call, a plain or dotted name token then a `(` group, of a callee -# other than the def, one of whose walked arguments is exactly the lone name -# of a parameter that every self-call passes back as that same lone name in -# its own position (carried). A callee walks argument 2 of `List.contains`, -# `List.find`, `List.filter` and `List.length`, 3 of `List.any` and -# `List.all`, and 4 of `List.foldl` and `List.foldr` (the Base list searches); -# a def of the file walks, when it calls itself, its first live parameter that -# some self-call does not pass back unchanged, unless that parameter's type is -# `Nat` or `String`, and each parameter its body passes, anywhere, as the lone -# name of an argument a call of a Base search or of a def listed before it -# walks. One finding per call. -import Base -import ../src/syntax/lex.bend as Lex -import ../src/finding.bend as F -import ../src/syntax/bind.bend as Bind -import ../src/paths.bend as Paths -import ../src/src.bend as Src -import ../src/syntax/tree.bend as Tree -import ../src/syntax/outline.bend as Outline -import ../src/rules/suspicious/hoist.bend as Hoist -import ../src/rules/suspicious/scan.bend as Scan -import ../src/rules/calls.bend as Calls - -# scan (laws/ScanReportsCarriedArgumentWalks.bend). Outside a law file the rule walks each def of the -# file that calls itself (Calls.calls) and is not a proof, its body hot, as -# hoist does: a `=>` token makes the rest of its chain cold, a case's pattern -# is cold, and a case arm's body stays hot only when it calls the def. In a -# hot region it reports each call, a plain or dotted name token then a `(` -# group, of a callee other than the def, when one of the arguments the -# callee walks (Scan.slots, over the file's walks, Scan.walks) is exactly the -# lone name of a carried parameter (Hoist.carried). - -# a callee is a plain or a dotted name -def scan.callee(kk: Lex.TokKind) -> Bool: - match kk: - case Lex.TName{}: - True{} - case Lex.TDotted{}: - True{} - case other: - False{} - -# is the token a plain name the list holds? -def scan.named(tok: Lex.Tok, +carried: List<&2, String>) -> Bool: - match tok: - case Lex.Tok{Lex.TName{}, +t, l, c}: - List.contains(~String, ~String.eq, carried, t) - case other: - False{} - -# is the argument exactly the lone name of a carried parameter? -def scan.lone(nn: Tree.Node, +carried: List<&2, String>) -> Bool: - match nn: - case Tree.NCons{Tree.Leaf{tok}, Tree.NNil{}}: - scan.named(tok, carried) - case other: - False{} - -# does some walked argument hold a carried parameter's lone name? -def scan.held(ss: List<&2, Nat>, +as: List<&2, Tree.Node>, +carried: List<&2, String>) -> Bool: - match ss: - case Nil{}: - False{} - case Con{at, rest}: - Bool.or(scan.lone(Calls.arg(as, at), carried), scan.held(rest, as, carried)) - -# does a call site report: a named callee, hot, a `(` group, not the def, -# and a walked argument carried -def scan.call( - kk: Lex.TokKind, - +hot: Bool, - +open: Bool, - +tt: String, - kids: Tree.Node, - +self: String, - +ws: List<&2, Scan.Walk>, - +carried: List<&2, String> -) -> Bool: - Bool.and(scan.callee(kk), Bool.and(hot, Bool.and(open, Bool.and(Bool.not(String.eq(tt, self)), - scan.held(Scan.slots(tt, ws), Calls.args(kids), carried))))) - -# how many calls scan reports under the node, entered hot or cold -def scan.count( - nn: Tree.Node, - +self: String, - +ws: List<&2, Scan.Walk>, - +carried: List<&2, String>, - +hot: Bool -) -> Nat: - match nn: - case Tree.NCons{Tree.Leaf{Lex.Tok{+k, +t, l, c}}, Tree.NCons{Tree.Group{Lex.Tok{_, +o, _, _}, +kids, _}, rest}}: - +heat = Hoist.warm(hot, k) - Nat.add(Bool.pick(Nat, scan.call(k, heat, String.eq(o, "("), t, kids, self, ws, carried), 1n, 0n), - Nat.add(scan.count(kids, self, ws, carried, heat), scan.count(rest, self, ws, carried, heat))) - case Tree.NCons{Tree.Leaf{Lex.Tok{k, t, l, c}}, rest}: - scan.count(rest, self, ws, carried, Hoist.warm(hot, k)) - case Tree.NCons{Tree.Group{o, kids, cl}, rest}: - Nat.add(scan.count(kids, self, ws, carried, hot), scan.count(rest, self, ws, carried, hot)) - case Tree.NCons{Tree.Stmt{Tree.SCase{}, kids, +body}, rest}: - Nat.add(scan.count(kids, self, ws, carried, False{}), - Nat.add(scan.count(body, self, ws, carried, Bool.and(hot, Calls.calls(body, self))), - scan.count(rest, self, ws, carried, hot))) - case Tree.NCons{Tree.Stmt{kind, kids, body}, rest}: - Nat.add(scan.count(kids, self, ws, carried, hot), - Nat.add(scan.count(body, self, ws, carried, hot), scan.count(rest, self, ws, carried, hot))) - case Tree.NCons{h, rest}: - scan.count(rest, self, ws, carried, hot) - case other: - 0n - -# how many scan reports over the defs: those in each def that calls itself -# and is not a proof, walked hot, with its carried parameters -def scan.total(ds: List<&2, Calls.Def>, +ws: List<&2, Scan.Walk>, +path: String) -> Nat: - match ds: - case Nil{}: - 0n - case Con{Calls.Def{+name, +sig, +body}, rest}: - Nat.add(Bool.pick(Nat, Bool.and(Calls.calls(body, name), Bool.not(Calls.exempt(path, sig))), - scan.count(body, name, ws, Hoist.carried(sig, body, name), True{}), 0n), scan.total(rest, ws, path)) - -# LAW: outside a law file, scan reports one finding for each call the walk -# above counts, in each def that calls itself and is not a proof, with the -# file's walks; none for anything else -law scan_counts: - for +path: String - for text: String - for toks: List<&2, Lex.Tok> - for +tree: Tree.Node - for bound: Bind.Bound - for items: List<&2, Outline.Item> - for e: {Paths.is_law_file(path) == False{} : Bool} - {List.length(&2, F.Finding, Scan.check(Src.Src{path, text, toks, tree, bound, items})) - == scan.total(Calls.defs(tree), Scan.walks(Calls.defs(tree)), path) : Nat} - -# LAW: a walk whose def carries no parameter reports nothing, whatever the -# tree, the file's walks and the heat -law scan_quiet: - for nn: Tree.Node - for +self: String - for +ws: List<&2, Scan.Walk> - for +lead: String - for +path: String - for +hot: Bool - {Scan.walk(nn, self, ws, [], lead, path, hot) == Nil{} : List<&2, F.Finding>} - -# the list arguments of the Base list searches: 2, 3 or 4, counted from 0 -def scan.base(+tt: String) -> List<&2, Nat>: - Bool.pick(List<&2, Nat>, List.contains(~String, ~String.eq, ["List.contains", "List.find", "List.filter", - "List.length"], tt), [2n], - Bool.pick(List<&2, Nat>, List.contains(~String, ~String.eq, ["List.any", "List.all"], tt), [3n], - Bool.pick(List<&2, Nat>, List.contains(~String, ~String.eq, ["List.foldl", "List.foldr"], tt), [4n], []))) - -# the arguments the file's walks record for a name, in order -def scan.file(ws: List<&2, Scan.Walk>, +tt: String) -> List<&2, Nat>: - match ws: - case Nil{}: - Nil{} - case Con{Scan.Walk{+nm, at}, rest}: - +more = scan.file(rest, tt) - Bool.pick(List<&2, Nat>, String.eq(nm, tt), at <> more, more) - -# LAW: a callee walks the Base search's list argument, then each argument a -# walk of the file of that name records -law scan_slots: - for +tt: String - for ws: List<&2, Scan.Walk> - {Scan.slots(tt, ws) == List.append(&2, Nat, scan.base(tt), scan.file(ws, tt)) : List<&2, Nat>} diff --git a/laws/ScopeExemptionsFollowPaths.bend b/laws/ScopeExemptionsFollowPaths.bend index 2e9ef00..4f39282 100644 --- a/laws/ScopeExemptionsFollowPaths.bend +++ b/laws/ScopeExemptionsFollowPaths.bend @@ -22,13 +22,9 @@ import ../src/rules/correctness/pick.bend as Pick import ../src/rules/pedantic/tail.bend as Tail import ../src/rules/suspicious/concat.bend as Concat import ../src/rules/suspicious/eager.bend as Eager -import ../src/rules/suspicious/rewalk.bend as Rewalk -import ../src/rules/suspicious/strict.bend as Strict -import ../src/rules/suspicious/hoist.bend as Hoist -import ../src/rules/suspicious/index.bend as Index -import ../src/rules/suspicious/ring.bend as Ring -import ../src/rules/suspicious/table.bend as Table -import ../src/rules/suspicious/unit.bend as UnitRule +import ../src/rules/pedantic/rewalk.bend as Rewalk +import ../src/rules/pedantic/strict.bend as Strict +import ../src/rules/pedantic/index.bend as Index import ../src/rules/suspicious/thunk.bend as Thunk import ../src/rules/laws/closed.bend as Closed @@ -45,7 +41,7 @@ import ../src/rules/laws/closed.bend as Closed # whether the coverage rule lets a digest's file off def scope.exempt(dd: Digest.Digest) -> Bool: match dd: - case Digest.Digest{_path, _norm, _dir, _is_laws, _is_law_file, exempt, _tops, _says, _deps, _unsafes, _laws}: + case Digest.Digest{_path, _norm, _dir, _is_laws, _is_law_file, exempt, _tops, _says, _deps, _laws}: exempt # LAW: two files at the same path are exempt from `law` alike, whatever the @@ -122,11 +118,11 @@ law scope_param: # the findings of each rule a LAWS.bend or a PROOF.bend exempts, rule by rule def scope.law_file(+ss: Src.Src) -> List<&2, List<&2, F.Finding>>: [Pick.check(ss), Tail.check(ss), Concat.check(ss), Eager.check(ss), Rewalk.check(ss), Strict.check(ss), - Hoist.check(ss), Index.check(ss), Ring.check(ss), Table.check(ss), UnitRule.check(ss), Thunk.check(ss)] + Index.check(ss), Thunk.check(ss)] # LAW: on a LAWS.bend or a PROOF.bend, the findings of each rule its header -# exempts there (pick, tail, concat, eager, rewalk, strict, hoist, index, -# ring, table, unit, thunk) are the same for any two sources at that path +# exempts there (pick, tail, concat, eager, rewalk, strict, index, thunk) +# are the same for any two sources at that path law scope_law_file: for +path: String for +text: String diff --git a/laws/ScopeLawsGovernPackage.bend b/laws/ScopeLawsGovernPackage.bend index 2a07832..82d31ca 100644 --- a/laws/ScopeLawsGovernPackage.bend +++ b/laws/ScopeLawsGovernPackage.bend @@ -16,7 +16,7 @@ def has_laws(ds: List<&2, Digest.Digest>) -> Bool: match ds: case Nil{}: False{} - case Con{Digest.Digest{_path, _norm, _dir, is_laws, _is_law_file, _exempt, _tops, _says, _deps, _unsafes, _laws}, + case Con{Digest.Digest{_path, _norm, _dir, is_laws, _is_law_file, _exempt, _tops, _says, _deps, _laws}, rest}: Bool.or(is_laws, has_laws(rest)) @@ -41,7 +41,7 @@ def under_law(ds: List<&2, Digest.Digest>, +cl: List<&2, String>) -> List<&2, F. match ds: case Nil{}: [] - case Con{Digest.Digest{path, norm, _dir, _is_laws, _is_law_file, exempt, tops, _says, _deps, _unsafes, _laws}, + case Con{Digest.Digest{path, norm, _dir, _is_laws, _is_law_file, exempt, tops, _says, _deps, _laws}, rest}: List.append(&2, F.Finding, graded_file(exempt, tops, cl, norm, path), under_law(rest, cl)) diff --git a/laws/ScopePackageRootsFound.bend b/laws/ScopePackageRootsFound.bend index d61c8c3..1e3b8bb 100644 --- a/laws/ScopePackageRootsFound.bend +++ b/laws/ScopePackageRootsFound.bend @@ -12,8 +12,7 @@ import ./defs/ScopeWalkFiles.bend as D1 # Packages (laws/ScopePackageRootsFound.bend, laws/ScopeProjectRulesRunOnRoot.bend). A package root other than `.` is a # directory the walk reads whose listing names a main.bend; `coverage` runs -# on each package's files alone, `unsafe` on every file. A -# directory is named as Digest.dir names one: normalized, with its slash, and +# on each package's files alone. A directory is named as Digest.dir names one: normalized, with its slash, and # `` for `.`. # whether a listing names a main.bend diff --git a/laws/ScopeProjectRulesRunOnRoot.bend b/laws/ScopeProjectRulesRunOnRoot.bend index 756121c..e594778 100644 --- a/laws/ScopeProjectRulesRunOnRoot.bend +++ b/laws/ScopeProjectRulesRunOnRoot.bend @@ -1,7 +1,6 @@ # When `.` is the only package root and the run holds a file, the project -# rules' findings are those of `coverage` and `unsafe` over the digests of -# every file in the run, in that order: -# a repo with one package is linted as it was before packages. +# rules' findings are those of `coverage` over the digests of every file in +# the run: a repo with one package is linted as it was before packages. import Base import ../src/finding.bend as F import ../src/rules.bend as Rules @@ -9,15 +8,13 @@ import ../src/src.bend as Src import ../src/role.bend as Role import ../src/rules/digest.bend as Digest import ../src/rules/laws/law.bend as Law -import ../src/rules/laws/unsafe.bend as Unsafe # LAW: when `.` is the only package root and the run holds a file, the -# project findings are coverage and unsafe over every file's digest: a repo -# with one package is linted as before +# project findings are coverage over every file's digest: a repo with one +# package is linted as before law one_package: for +s: Src.Src for +ss: List<&2, Src.Src> {Rules.project(s <> ss, []) - == List.concat(&2, F.Finding, [Law.check(Digest.all.role(Role.marks(s <> ss), s <> ss)), - Unsafe.check(Digest.all.role(Role.marks(s <> ss), s <> ss))]) + == List.concat(&2, F.Finding, [Law.check(Digest.all.role(Role.marks(s <> ss), s <> ss)), []]) : List<&2, F.Finding>} diff --git a/laws/ScopeRulesSeeTheirInputs.bend b/laws/ScopeRulesSeeTheirInputs.bend index b56e1d0..293bfe3 100644 --- a/laws/ScopeRulesSeeTheirInputs.bend +++ b/laws/ScopeRulesSeeTheirInputs.bend @@ -1,6 +1,5 @@ # A per-file rule sees one file's `Src`; `coverage` sees the digests of one -# package's files (laws/ScopePackageRootsFound.bend) at a time; `unsafe` sees -# the digests of every file in the run; `setting` sees the text of each +# package's files (laws/ScopePackageRootsFound.bend) at a time; `setting` sees the text of each # bolt.bend grading reads; and nothing else. # # also rests on: laws/PackagesScoped.bend diff --git a/laws/SpaceReportsBlanksAndWidth.bend b/laws/SpaceReportsBlanksAndWidth.bend index 383fc44..829e0dc 100644 --- a/laws/SpaceReportsBlanksAndWidth.bend +++ b/laws/SpaceReportsBlanksAndWidth.bend @@ -10,7 +10,6 @@ import ../src/rules/style/space.bend as Space import ../src/src.bend as Src import ../src/syntax/tree.bend as Tree import ../src/syntax/outline.bend as Outline -import ../src/rules/laws/unsafe.bend as Unsafe import ./defs/Unsafe.bend as D1 # True when the line's last char is whitespace diff --git a/laws/StrictReportsEagerSelfCalls.bend b/laws/StrictReportsEagerSelfCalls.bend index a51061e..ce110d8 100644 --- a/laws/StrictReportsEagerSelfCalls.bend +++ b/laws/StrictReportsEagerSelfCalls.bend @@ -9,7 +9,7 @@ import ../src/syntax/bind.bend as Bind import ../src/src.bend as Src import ../src/syntax/tree.bend as Tree import ../src/syntax/outline.bend as Outline -import ../src/rules/suspicious/strict.bend as Strict +import ../src/rules/pedantic/strict.bend as Strict import ../src/rules/calls.bend as Calls # does the chain, up to its first top-level comma or `=>`, hold a `&&` or diff --git a/laws/StringsReportLongLiteralArms.bend b/laws/StringsReportLongLiteralArms.bend index 00f1d15..4038551 100644 --- a/laws/StringsReportLongLiteralArms.bend +++ b/laws/StringsReportLongLiteralArms.bend @@ -7,7 +7,7 @@ import ../src/syntax/bind.bend as Bind import ../src/src.bend as Src import ../src/syntax/tree.bend as Tree import ../src/syntax/outline.bend as Outline -import ../src/rules/correctness/strings.bend as Strings +import ../src/rules/perf/strings.bend as Strings import ./defs/MatchArmRules.bend as D1 # the chars between the quotes of the string a case statement's first match diff --git a/laws/TableReportsFixedTableAccess.bend b/laws/TableReportsFixedTableAccess.bend deleted file mode 100644 index 128ac8f..0000000 --- a/laws/TableReportsFixedTableAccess.bend +++ /dev/null @@ -1,85 +0,0 @@ -# `table` reports exactly, in a def that calls itself, a `List.get` or -# `List.set` call whose index is not one number token and whose list is a -# fixed table: a list literal, a number-sized array, or `List.replicate` / -# `Array.new` / `List.range` with a number count, written inline, as a table -# def of the file, or held by the let of that name in scope. -import Base -import ../src/syntax/lex.bend as Lex -import ../src/finding.bend as F -import ../src/syntax/bind.bend as Bind -import ../src/src.bend as Src -import ../src/syntax/tree.bend as Tree -import ../src/syntax/outline.bend as Outline -import ../src/rules/suspicious/table.bend as Table -import ../src/rules/calls.bend as Calls -import ../src/rules/tokens.bend as T - -# The table rules: table (laws/TableReportsFixedTableAccess.bend) and ring (laws/RingReportsDropAppendRotation.bend). -# Each -# reads each def of the file whose body calls it, `name(..)` (Calls.calls), -# and is not exempt, and walks that body at any depth: a call site is a name -# token then a group. - -# table: is the call `List.get(..)` or `List.set(..)` whose index (argument -# 3, counting `&2` and the type) is not one number token (Table.literal) and -# whose list (argument 2) is a fixed table (Table.owns): a list literal with -# a top-level comma, a `[v : T*n]` or `[v : T^n]` array sized by a number, or -# `List.replicate` / `Array.new` / `List.range` with a number count, written -# inline, as `name()` or `name` for a def of the file whose body is one such -# table (Table.modules), or as a use of a name whose let in scope binds one -# (Table.scope). A let reaches the statements after it and their blocks, not -# an enclosing block or a sibling case arm, and a later let of the name to -# anything else ends it; a case pattern, a lambda or a parameter binding the -# same name does not end it. -def table.site(+tt: String, +op: String, kids: Tree.Node, +fixed: List<&2, String>) -> Bool: - +as = Calls.args(kids) - Bool.and(Bool.and(String.eq(op, "("), Bool.or(String.eq(tt, "List.get"), String.eq(tt, "List.set"))), - Bool.and(Bool.not(Table.literal(Calls.arg(as, 3n))), Table.owns(Calls.arg(as, 2n), fixed))) - -# how many call sites under the node, at any depth, are a fixed-table get or -# set at a computed index -def table.count(nn: Tree.Node, +fixed: List<&2, String>) -> Nat: - match nn: - case Tree.NCons{Tree.Leaf{Lex.Tok{k, +t, l, c}}, Tree.NCons{Tree.Group{Lex.Tok{_k, +o, _l, _c}, +kids, _e}, rest}}: - Nat.add(Bool.pick(Nat, table.site(t, o, kids, fixed), 1n, 0n), - Nat.add(table.count(kids, fixed), table.count(rest, fixed))) - case Tree.NCons{Tree.Group{open, kids, close}, rest}: - Nat.add(table.count(kids, fixed), table.count(rest, fixed)) - case Tree.NCons{Tree.Stmt{kind, kids, body}, rest}: - Nat.add(table.count(kids, fixed), Nat.add(table.count(body, fixed), table.count(rest, fixed))) - case Tree.NCons{h, rest}: - table.count(rest, fixed) - case other: - 0n - -# LAW: over any chain, table reports one finding for each fixed-table -# `List.get(..)` or `List.set(..)` at an index that is not one number token, -# and none for any other call: a literal index (0 and 1 included), a list -# that is not a fixed table, `List.head` and `List.tail` stay quiet -law table_walk_counts: - for nn: Tree.Node - for +path: String - for +fixed: List<&2, String> - {List.length(&2, F.Finding, Table.walk(nn, path, fixed)) == table.count(nn, fixed) : Nat} - -# the fixed-table gets and sets of each def of the list that calls itself -# and is not exempt, the tables in scope of its body -def table.defs(ds: List<&2, Calls.Def>, +mods: List<&2, String>, +path: String) -> Nat: - match ds: - case Nil{}: - 0n - case Con{Calls.Def{+name, +sig, +body}, rest}: - Nat.add(Bool.pick(Nat, Bool.and(Calls.calls(body, name), Bool.not(Calls.exempt(path, sig))), - table.count(body, Table.scope(body, mods)), 0n), table.defs(rest, mods, path)) - -# LAW: table reports exactly those gets and sets, def by def, over the defs -# of the tree that call themselves, and none in a def that does not -law table_counts: - for +path: String - for text: String - for toks: List<&2, Lex.Tok> - for +tree: Tree.Node - for bound: Bind.Bound - for items: List<&2, Outline.Item> - {List.length(&2, F.Finding, Table.check(Src.Src{path, text, toks, tree, bound, items})) - == table.defs(Calls.defs(tree), Table.modules(Calls.defs(tree), []), path) : Nat} diff --git a/laws/UnitReportsMultiplyByOne.bend b/laws/UnitReportsMultiplyByOne.bend deleted file mode 100644 index cd5b082..0000000 --- a/laws/UnitReportsMultiplyByOne.bend +++ /dev/null @@ -1,158 +0,0 @@ -# `unit` reports exactly a multiply or divide by one on a recursive step. -import Base -import ../src/syntax/lex.bend as Lex -import ../src/finding.bend as F -import ../src/syntax/bind.bend as Bind -import ../src/src.bend as Src -import ../src/syntax/tree.bend as Tree -import ../src/syntax/outline.bend as Outline -import ../src/rules/suspicious/unit.bend as UnitRule -import ../src/rules/calls.bend as Calls - -# unit (laws/UnitReportsMultiplyByOne.bend). The rule walks each def of the file that calls -# itself (Calls.calls) and is not exempt (a law file, a proof file, a def -# returning a proof), its body hot: the recursive step. A case arm's body is -# hot only when it calls the def too (a base case is not), a case's own -# pattern tokens never; anything else, lambda bodies included, is as hot as -# what holds it. In a hot region it reports one finding for each operator -# `*` with the literal one (a number token `1`, `1n` or `1.0`) right after or -# right before it, or `/` with it right after (the divisor); and one for each -# call, a leaf then a `(` group, of `Nat.mul`, `U32.mul` or `F32.mul` with -# the literal one alone as some argument, or of `Nat.div`, `U32.div` or -# `F32.div` with it alone as the last. Right before an operator is the leaf -# just before it in its chain, none past a group, a statement or another -# operator; right after is the node just after it, when a leaf. - -# does the maybe hold the literal one: a number token `1`, `1n` or `1.0`? -def unit.one(mm: Maybe<&2, Lex.Tok>) -> Bool: - match mm: - case Some{Lex.Tok{Lex.TNum{}, +t, l, c}}: - Bool.or(String.eq(t, "1"), Bool.or(String.eq(t, "1n"), String.eq(t, "1.0"))) - case other: - False{} - -# is the operator's text `*` with the literal one after or before it, or `/` -# with it after? -def unit.by_one(+op: String, prev: Maybe<&2, Lex.Tok>, +nxt: Maybe<&2, Lex.Tok>) -> Bool: - Bool.pick(Bool, String.eq(op, "*"), Bool.or(unit.one(nxt), unit.one(prev)), - Bool.and(String.eq(op, "/"), unit.one(nxt))) - -# is the token an operator by one, between prev and nxt? -def unit.hit(tok: Lex.Tok, prev: Maybe<&2, Lex.Tok>, nxt: Maybe<&2, Lex.Tok>) -> Bool: - match tok: - case Lex.Tok{Lex.TOp{}, t, l, c}: - unit.by_one(t, prev, nxt) - case other: - False{} - -# what an operator after this leaf sees before it: the leaf, none when it is -# an operator -def unit.next(tok: Lex.Tok) -> Maybe<&2, Lex.Tok>: - match tok: - case Lex.Tok{Lex.TOp{}, t, l, c}: - None{} - case other: - Some{other} - -# the node right after, when it is a leaf -def unit.first(nn: Tree.Node) -> Maybe<&2, Lex.Tok>: - match nn: - case Tree.NCons{Tree.Leaf{tok}, rest}: - Some{tok} - case other: - None{} - -# the name a group right after the leaf calls, "" after no leaf -def unit.name(mm: Maybe<&2, Lex.Tok>) -> String: - match mm: - case None{}: - "" - case Some{Lex.Tok{k, t, l, c}}: - t - -# is the argument the literal one alone? -def unit.lone(aa: Tree.Node) -> Bool: - match aa: - case Tree.NCons{Tree.Leaf{tok}, Tree.NNil{}}: - unit.one(Some{tok}) - case other: - False{} - -# is some argument the literal one alone? -def unit.any(as: List<&2, Tree.Node>) -> Bool: - match as: - case Nil{}: - False{} - case Con{h, rest}: - Bool.or(unit.lone(h), unit.any(rest)) - -# is the last argument the literal one alone? -def unit.last(as: List<&2, Tree.Node>) -> Bool: - match as: - case Nil{}: - False{} - case Con{h, Nil{}}: - unit.lone(h) - case Con{h, rest}: - unit.last(rest) - -# is the call a multiply with some argument one, else a divide by one? -def unit.call(+tt: String, +as: List<&2, Tree.Node>) -> Bool: - Bool.pick(Bool, List.contains(~String, ~String.eq, ["Nat.mul", "U32.mul", "F32.mul"], tt), - unit.any(as), - Bool.and(List.contains(~String, ~String.eq, ["Nat.div", "U32.div", "F32.div"], tt), - unit.last(as))) - -# how many multiplies and divides by one the chain holds, at any depth, from -# a region as hot as hot, the leaf before it prev -def unit.count(nn: Tree.Node, +prev: Maybe<&2, Lex.Tok>, +self: String, +hot: Bool) -> Nat: - match nn: - case Tree.NCons{Tree.Leaf{+tok}, +rest}: - +m = unit.count(rest, unit.next(tok), self, hot) - Bool.pick(Nat, Bool.and(hot, unit.hit(tok, prev, unit.first(rest))), 1n+m, m) - case Tree.NCons{Tree.Group{Lex.Tok{ok, +o, ol, oc}, +kids, close}, rest}: - +own = Bool.and(String.eq(o, "("), Bool.and(hot, unit.call(unit.name(prev), Calls.args(kids)))) - Nat.add(unit.count(kids, None{}, self, hot), - Nat.add(Bool.pick(Nat, own, 1n, 0n), unit.count(rest, None{}, self, hot))) - case Tree.NCons{Tree.Stmt{Tree.SCase{}, kids, +body}, rest}: - Nat.add(unit.count(kids, None{}, self, False{}), - Nat.add(unit.count(body, None{}, self, Bool.and(hot, Calls.calls(body, self))), unit.count(rest, None{}, self, - hot))) - case Tree.NCons{Tree.Stmt{kind, kids, body}, rest}: - Nat.add(unit.count(kids, None{}, self, hot), - Nat.add(unit.count(body, None{}, self, hot), unit.count(rest, None{}, self, hot))) - case Tree.NCons{h, rest}: - unit.count(rest, prev, self, hot) - case other: - 0n - -# LAW: over any chain, unit reports one finding for each multiply or divide -# by one in a hot region, and none for anything else -law unit_walk_counts: - for nn: Tree.Node - for +prev: Maybe<&2, Lex.Tok> - for +self: String - for +path: String - for +hot: Bool - {List.length(&2, F.Finding, UnitRule.walk(nn, prev, self, path, hot)) == unit.count(nn, prev, self, hot) : Nat} - -# the multiplies and divides by one of each def of the list that calls itself -# and is not exempt, its body hot -def unit.defs(ds: List<&2, Calls.Def>, +path: String) -> Nat: - match ds: - case Nil{}: - 0n - case Con{Calls.Def{+name, +sig, +body}, rest}: - Nat.add(Bool.pick(Nat, Bool.and(Calls.calls(body, name), Bool.not(Calls.exempt(path, sig))), - unit.count(body, None{}, name, True{}), 0n), unit.defs(rest, path)) - -# LAW: unit reports exactly those, def by def, over the defs of the tree -law unit_counts: - for +path: String - for text: String - for toks: List<&2, Lex.Tok> - for tree: Tree.Node - for bound: Bind.Bound - for items: List<&2, Outline.Item> - {List.length(&2, F.Finding, UnitRule.check(Src.Src{path, text, toks, tree, bound, items})) - == unit.defs(Calls.defs(tree), path) : Nat} diff --git a/laws/UnsafeReportsReachableForeignDefs.bend b/laws/UnsafeReportsReachableForeignDefs.bend deleted file mode 100644 index f840602..0000000 --- a/laws/UnsafeReportsReachableForeignDefs.bend +++ /dev/null @@ -1,107 +0,0 @@ -# An `@unsafe def` or a foreign def (a def whose body is only imports of `.c` -# or `.js` files) reachable by relative imports from a law file in the run is -# a finding, at the def, that names the first such law file and the import -# path from it to the def's file. -import Base -import ../src/finding.bend as F -import ../src/rules/digest.bend as Digest -import ../src/rules/laws/unsafe.bend as Unsafe -import ../src/rules/imports.bend as Imports -import ./defs/Unsafe.bend as D1 - -# the import path from the file at from to the file at to, over the digests, -# as the finding spells it: `a -> b -> c` -def unsafe.via(+all: List<&2, Digest.Digest>, +from: String, +to: String) -> String: - String.join(Imports.trail(Digest.edges(all), Imports.paths(Digest.edges(all)), List.length(&2, Digest.Digest, all), - from, to), " -> ") - -# the path of the first law file among ls (a LAWS.bend or a PROOF.bend) -# whose closure over all holds the path pp -def unsafe.origin(ls: List<&2, Digest.Digest>, +all: List<&2, Digest.Digest>, +pp: String) -> Maybe<&2, String>: - match ls: - case Nil{}: - None{} - case Con{Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, unsafes, laws}, rest}: - +more = unsafe.origin(rest, all, pp) - Bool.pick(Maybe<&2, String>, Bool.and(is_law_file, Imports.has(D1.unsafe.closure(all, norm), pp)), Some{path}, - more) - -# what the finding says of a foreign or an `@unsafe` def, reached from the -# law file at origin along the import path via -def unsafe.says(foreign: Bool, +name: String, +origin: String, +via: String) -> String: - match foreign: - case True{}: - "foreign def " ++ name ++ " is reachable from " ++ origin ++ " (" ++ via - ++ "), so bend fails that proof: SOME PROOFS FAIL, defs rely on unsafe or foreign code; move the effect into a sibling module no law file imports, and pass it in as a service." - case False{}: - "@unsafe def " ++ name ++ " is reachable from " ++ origin ++ " (" ++ via - ++ "), so bend fails that proof: SOME PROOFS FAIL, defs rely on unsafe or foreign code; recurse on a shrinking argument or on fuel and drop @unsafe, or move it into a sibling module no law file imports." - -# one finding for each def, at its `@` (7 wide) or, a foreign def, its -# header's start (3 wide), naming the law file and the import path -def unsafe.each(us: List<&2, Digest.Unsafe>, +origin: String, +via: String, +path: String) -> List<&2, F.Finding>: - match us: - case Nil{}: - Nil{} - case Con{Digest.Unsafe{n, l, c, +fo}, rest}: - F.Finding{path, l, c, Bool.pick(U32, fo, 3, 7), "unsafe", unsafe.says(fo, n, origin, via)} - <> unsafe.each(rest, origin, via, path) - -# a file's unsafe and foreign defs when a law file reaches it, else nothing; -# the path runs from that law file to the file at norm -def unsafe.at( - mm: Maybe<&2, String>, - us: List<&2, Digest.Unsafe>, - +path: String, - +norm: String, - +all: List<&2, Digest.Digest> -) -> List<&2, F.Finding>: - match mm: - case None{}: - Nil{} - case Some{+origin}: - unsafe.each(us, origin, unsafe.via(all, Imports.norm(origin), norm), path) - -# the findings of every file of fs, in order, against the law files of all -def unsafe.files(fs: List<&2, Digest.Digest>, +all: List<&2, Digest.Digest>) -> List<&2, F.Finding>: - match fs: - case Nil{}: - Nil{} - case Con{Digest.Digest{path, +norm, dir, is_laws, is_law_file, exempt, tops, says, deps, unsafes, laws}, rest}: - List.append(&2, F.Finding, unsafe.at(unsafe.origin(all, all, norm), unsafes, path, norm, all), - unsafe.files(rest, all)) - -# LAW: over every list of digests, unsafe reports exactly each `@unsafe def` -# and each foreign def of each file that the closure of some law file holds, -# in file order, and names the first such law file and the import path from -# it to the file -law unsafe_reports: - for +ds: List<&2, Digest.Digest> - {Unsafe.check(ds) == unsafe.files(ds, ds) : List<&2, F.Finding>} - -# LAW: over every list of digests, what the file at from reaches holds it -law unsafe_from: - for +ds: List<&2, Digest.Digest> - for +from: String - {Imports.has(D1.unsafe.closure(ds, from), from) == True{} : Bool} - -# LAW: over every list of digests, what a file reaches is shut: every file in -# the run that one of its files imports is in it. So the walk's fuel never -# cuts it short -law unsafe_shut: - for +ds: List<&2, Digest.Digest> - for +from: String - {D1.unsafe.shut(ds, D1.unsafe.closure(ds, from), D1.unsafe.closure(ds, from)) == True{} : Bool} - -# LAW: and it is the least such list: every list that holds from and is shut -# holds each file it reaches, so it reaches only files a chain of relative -# imports through the run leads to -law unsafe_least: - for +ds: List<&2, Digest.Digest> - for +from: String - for +ss: List<&2, String> - for +pp: String - for hf: {Imports.has(ss, from) == True{} : Bool} - for hs: {D1.unsafe.shut(ds, ss, ss) == True{} : Bool} - for hp: {Imports.has(D1.unsafe.closure(ds, from), pp) == True{} : Bool} - {Imports.has(ss, pp) == True{} : Bool} diff --git a/laws/defs/ScopeWalkFiles.bend b/laws/defs/ScopeWalkFiles.bend index fd7c884..e83b794 100644 --- a/laws/defs/ScopeWalkFiles.bend +++ b/laws/defs/ScopeWalkFiles.bend @@ -6,7 +6,6 @@ import ../../src/src.bend as Src import ../../src/role.bend as Role import ../../src/rules/digest.bend as Digest import ../../src/rules/laws/law.bend as Law -import ../../src/rules/laws/unsafe.bend as Unsafe import ../../src/lint/world.bend as W # whether a directory is among the roots @@ -31,10 +30,10 @@ def part(ds: List<&2, Digest.Digest>, +rs: List<&2, String>, +pkg: String) -> Li match ds: case Nil{}: [] - case Con{Digest.Digest{path, norm, +dir, is_laws, is_law_file, exempt, tops, says, deps, unsafes, laws}, rest}: + case Con{Digest.Digest{path, norm, +dir, is_laws, is_law_file, exempt, tops, says, deps, laws}, rest}: +more = part(rest, rs, pkg) Bool.pick(List<&2, Digest.Digest>, String.eq(nearest(Rules.ups(dir), rs), pkg), - Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, unsafes, laws} <> more, more) + Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, laws} <> more, more) # `coverage` on the files of each package but `.` def covered(ps: List<&2, String>, +ds: List<&2, Digest.Digest>, +rs: List<&2, String>) -> List<&2, F.Finding>: @@ -44,11 +43,10 @@ def covered(ps: List<&2, String>, +ds: List<&2, Digest.Digest>, +rs: List<&2, St case Con{+dir, rest}: List.append(&2, F.Finding, Law.check(part(ds, rs, dir)), covered(rest, ds, rs)) -# the project rules' findings: `coverage` on each package's files, `.` first, -# then `unsafe` on every file +# the project rules' findings: `coverage` on each package's files, `.` first def packed(+srcs: List<&2, Src.Src>, +rs: List<&2, String>) -> List<&2, F.Finding>: +ds = Digest.all.role(Role.marks(srcs), srcs) - List.concat(&2, F.Finding, [Law.check(part(ds, rs, "")), covered(rs, ds, rs), Unsafe.check(ds)]) + List.concat(&2, F.Finding, [Law.check(part(ds, rs, "")), covered(rs, ds, rs)]) # The walk (laws/ScopeWalksWholeTree.bend). With no paths named, the planner lints what a # breadth-first search over the World's listings reaches from `.`. The spec diff --git a/laws/defs/Unsafe.bend b/laws/defs/Unsafe.bend index 4e10e1c..ffce175 100644 --- a/laws/defs/Unsafe.bend +++ b/laws/defs/Unsafe.bend @@ -19,13 +19,11 @@ def share(kk: Lex.TokKind, +tt: String) -> U32: def over(+width: U32, +more: Nat) -> Nat: Bool.pick(Nat, U32.is_gt(width, 120), 1n+more, more) -# unsafe (laws/UnsafeReportsReachableForeignDefs.bend). Over every list of digests, as coverage's laws are. -# A digest records a file's relative imports (normalized, `deps`) and its -# `@unsafe def`s and foreign defs (a def whose body is only imports of `.c` -# or `.js` files); the import graph is theirs, and the closure a law file +# Reach (laws/ImportReachIsClosed.bend), over every list of digests, as +# coverage's laws are. A digest records a file's relative imports +# (normalized, `deps`); the import graph is theirs, and the closure a file # reaches over it is Imports.closure.go's, the walk every project rule -# shares. A finding names the import path from the law file to the def's -# file, a shortest one, as Imports.trail walks it. +# shares. # every file of the digests the file at from reaches by relative imports, # itself first, as the closure walk takes it @@ -46,7 +44,7 @@ def unsafe.imports(ds: List<&2, Digest.Digest>, +pp: String) -> List<&2, String> match ds: case Nil{}: Nil{} - case Con{Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, unsafes, laws}, rest}: + case Con{Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, laws}, rest}: +more = unsafe.imports(rest, pp) Bool.pick(List<&2, String>, String.eq(norm, pp), deps, more) @@ -55,7 +53,7 @@ def unsafe.read(ds: List<&2, Digest.Digest>, +pp: String) -> Bool: match ds: case Nil{}: False{} - case Con{Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, unsafes, laws}, rest}: + case Con{Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, laws}, rest}: +more = unsafe.read(rest, pp) Bool.or(String.eq(norm, pp), more) diff --git a/proofs/CharsReportManyCharArms.bend b/proofs/CharsReportManyCharArms.bend index a0f0416..563afed 100644 --- a/proofs/CharsReportManyCharArms.bend +++ b/proofs/CharsReportManyCharArms.bend @@ -5,7 +5,7 @@ import ../src/finding.bend as F import ../src/rules/tokens.bend as T import ../src/syntax/tree.bend as Tree import ../src/rules/correctness/pick.bend as Pick -import ../src/rules/correctness/chars.bend as Chars +import ../src/rules/perf/chars.bend as Chars import ../laws/CharsReportManyCharArms.bend as Laws import ../laws/defs/MatchArmRules.bend as D1 import ./lemmas/MatchStatementCounts.bend as M1 diff --git a/proofs/CodeRulesIgnoreCommentsAndStrings.bend b/proofs/CodeRulesIgnoreCommentsAndStrings.bend index 47e0545..8a0d2b9 100644 --- a/proofs/CodeRulesIgnoreCommentsAndStrings.bend +++ b/proofs/CodeRulesIgnoreCommentsAndStrings.bend @@ -17,16 +17,12 @@ import ../src/rules/correctness/pick.bend as Pick import ../src/rules/pedantic/tail.bend as Tail import ../src/rules/suspicious/concat.bend as Concat import ../src/rules/suspicious/eager.bend as Eager -import ../src/rules/suspicious/strict.bend as Strict -import ../src/rules/suspicious/hoist.bend as Hoist -import ../src/rules/suspicious/index.bend as Index -import ../src/rules/suspicious/ring.bend as Ring -import ../src/rules/suspicious/table.bend as Table -import ../src/rules/suspicious/unit.bend as UnitRule -import ../src/rules/suspicious/scan.bend as Scan +import ../src/rules/pedantic/strict.bend as Strict +import ../src/rules/pedantic/index.bend as Index +import ../src/rules/table.bend as Table import ../src/rules/correctness/arms.bend as Arms import ../src/rules/suspicious/unused.bend as Unused -import ../src/rules/suspicious/fuel.bend as FuelRule +import ../src/rules/pedantic/fuel.bend as FuelRule import ../src/rules/style/wrap.bend as Wrap import ../src/rules/suspicious/thunk.bend as Thunk import ../laws/CodeRulesIgnoreCommentsAndStrings.bend as Laws @@ -2857,56 +2853,6 @@ def inert.contains_keep(xs, b, t, ef, ep): # table # ----- -# a chain read that way is one number as it was -law inert.table.literal: - for nn: Tree.Node - {Table.literal(D1.inert.node(nn)) == Table.literal(nn) : Bool} - -def inert.table.literal(nn): - match nn: - case Tree.NCons{h, +r}: - match h: - case Tree.Leaf{tok}: - match tok: - case Lex.Tok{k, t, l, c}: - match k: - case Lex.TName{}: {==} - case Lex.TUpper{}: {==} - case Lex.TDotted{}: {==} - case Lex.TWild{}: {==} - case Lex.TKey{}: {==} - case Lex.TNum{}: - match r: - case Tree.NNil{}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NCons{x, y}: {==} - case Lex.TStr{}: {==} - case Lex.TChar{}: {==} - case Lex.TComment{}: {==} - case Lex.TSpace{}: {==} - case Lex.TNewline{}: {==} - case Lex.TOp{}: {==} - case Lex.TColon{}: {==} - case Lex.TEq{}: {==} - case Lex.TBind{}: {==} - case Lex.TArrow{}: {==} - case Lex.TLam{}: {==} - case Lex.TAll{}: {==} - case Lex.TAmp{}: {==} - case Lex.TOpen{}: {==} - case Lex.TClose{}: {==} - case Lex.TComma{}: {==} - case Tree.Group{o, gk, cl}: {==} - case Tree.Stmt{sk, sks, sb}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{tok}: {==} - case Tree.Group{o, gk, cl}: {==} - case Tree.Stmt{sk, sks, sb}: {==} - case Tree.NNil{}: {==} - # a chain read that way is one number as it was law inert.table.lone_num: for nn: Tree.Node @@ -3782,248 +3728,6 @@ def inert.table.modules(ds, acc, e): inert.table.modules(rest, Table.mod_one(D1.inert.node(body), name, acc), inert.dok.rest(name, sig, body, rest, e)) -# what hit reads of a call: its name against get and set, its index and list -def inert.table.hq( - +open: Bool, - +get: Bool, - set: Bool, - lit: Bool, - own: Bool, - +tt: String, - +path: String, - +line: U32, - +col: U32 -) -> List<&2, F.Finding>: - Bool.pick(List<&2, F.Finding>, Bool.and(Bool.and(open, Bool.or(get, set)), Bool.and(Bool.not(lit), own)), - [Table.cite(get, path, line, col, U32.from_nat(String.length(tt)))], []) - -# a call named neither get nor set is no hit -law inert.table.hq_off: - for open: Bool - for lit: Bool - for own: Bool - for +tt: String - for +path: String - for +line: U32 - for +col: U32 - {inert.table.hq(open, False{}, False{}, lit, own, tt, path, line, col) == [] : List<&2, F.Finding>} - -def inert.table.hq_off(open, _lit, _own, _tt, _path, _line, _col): - match open: - case True{}: {==} - case False{}: {==} - -# a call whose name is no get and no set, as hit reads it, is no hit -law inert.table.hit_off: - for +open: Bool - for +tt: String - for +kids: Tree.Node - for +fixed: List<&2, String> - for +path: String - for +line: U32 - for +col: U32 - for eg: {String.eq(tt, "List.get") == False{} : Bool} - for es: {String.eq(tt, "List.set") == False{} : Bool} - {Table.hit(open, tt, kids, fixed, path, line, col) == [] : List<&2, F.Finding>} - -def inert.table.hit_off(open, tt, kids, fixed, path, line, col, eg, es): - +aa = Calls.args(kids) - +lit = Table.literal(Calls.arg(aa, 3n)) - +own = Table.owns(Calls.arg(aa, 2n), fixed) - %Equal.sym(Bool, String.eq(tt, "List.get"), False{}, eg) : {inert.table.hq(open, _, String.eq(tt, "List.set"), lit, - own, tt, path, line, col) == [] : List<&2, F.Finding>} - %Equal.sym(Bool, String.eq(tt, "List.set"), False{}, es) : {inert.table.hq(open, False{}, _, lit, own, tt, path, - line, col) == [] : List<&2, F.Finding>} - inert.table.hq_off(open, lit, own, tt, path, line, col) - -# a call read that way is the hit it was: a string is named neither get nor -# set, and a name is read as it was -law inert.table.hit: - for +open: Bool - for b: Bool - for +t: String - for +kids: Tree.Node - for +fixed: List<&2, String> - for +path: String - for +line: U32 - for +col: U32 - for +ef: {D1.inert.fits.go(b, t) == True{} : Bool} - for +ek: {D1.inert.ok(kids) == True{} : Bool} - {Table.hit(open, D1.inert.keep(b, t), D1.inert.node(kids), fixed, path, line, col) - == Table.hit(open, t, kids, fixed, path, line, col) : List<&2, F.Finding>} - -def inert.table.hit(open, b, t, kids, fixed, path, line, col, ef, ek): - match b: - case True{}: - Equal.trans(List<&2, F.Finding>, Table.hit(open, D1.inert.cut(t), D1.inert.node(kids), fixed, path, line, - col), [], Table.hit(open, t, kids, fixed, path, line, col), - inert.table.hit_off(open, D1.inert.cut(t), D1.inert.node(kids), fixed, path, line, col, - M1.inert.neq(D1.inert.cut(t), "List.get", M1.inert.cut_marked(t, ef), {==}), - M1.inert.neq(D1.inert.cut(t), "List.set", M1.inert.cut_marked(t, ef), {==})), - Equal.sym(List<&2, F.Finding>, Table.hit(open, t, kids, fixed, path, line, col), [], - inert.table.hit_off(open, t, kids, fixed, path, line, col, M1.inert.neq(t, "List.get", ef, {==}), - M1.inert.neq(t, "List.set", ef, {==})))) - case False{}: - +aa = Calls.args(kids) - +lhs = Table.hit(open, t, D1.inert.node(kids), fixed, path, line, col) - %inert.table.literal(Calls.arg(aa, 3n)) : {lhs == inert.table.hq(open, String.eq(t, "List.get"), - String.eq(t, "List.set"), _, Table.owns(Calls.arg(aa, 2n), fixed), t, path, line, col) : List<&2, F.Finding>} - %inert.table.owns(Calls.arg(aa, 2n), fixed, inert.ok.arg(kids, 2n, ek)) : {lhs == inert.table.hq(open, - String.eq(t, "List.get"), String.eq(t, "List.set"), Table.literal(D1.inert.node(Calls.arg(aa, 3n))), _, t, - path, line, col) : List<&2, F.Finding>} - %inert.arg(aa, 3n) : {lhs == inert.table.hq(open, String.eq(t, "List.get"), String.eq(t, "List.set"), - Table.literal(_), Table.owns(D1.inert.node(Calls.arg(aa, 2n)), fixed), t, path, line, col) - : List<&2, F.Finding>} - %inert.arg(aa, 2n) : {lhs == inert.table.hq(open, String.eq(t, "List.get"), String.eq(t, "List.set"), - Table.literal(Calls.arg(inert.nodes(aa), 3n)), Table.owns(_, fixed), t, path, line, col) : List<&2, F.Finding>} - %inert.args(kids) : {lhs == inert.table.hq(open, String.eq(t, "List.get"), String.eq(t, "List.set"), - Table.literal(Calls.arg(_, 3n)), Table.owns(Calls.arg(_, 2n), fixed), t, path, line, col) - : List<&2, F.Finding>} - {==} - -# a name then a group: the call's hit, the group and the rest walked as they were -law inert.table.lg: - for +kk: Lex.TokKind - for +t: String - for +o: String - for +l: U32 - for +c: U32 - for +path: String - for +fixed: List<&2, String> - for +kids: Tree.Node - for +r2: Tree.Node - for +ef: {D1.inert.fits.go(D1.inert.blanks(kk), t) == True{} : Bool} - for +ek: {D1.inert.ok(kids) == True{} : Bool} - for ik: {Table.walk(D1.inert.node(kids), path, fixed) == Table.walk(kids, path, fixed) : List<&2, F.Finding>} - for ir: {Table.walk(D1.inert.node(r2), path, fixed) == Table.walk(r2, path, fixed) : List<&2, F.Finding>} - {List.concat(&2, F.Finding, [Table.hit(String.eq(o, "("), D1.inert.keep(D1.inert.blanks(kk), t), - D1.inert.node(kids), fixed, path, l, c), Table.walk(D1.inert.node(kids), path, fixed), - Table.walk(D1.inert.node(r2), path, fixed)]) - == List.concat(&2, F.Finding, [Table.hit(String.eq(o, "("), t, kids, fixed, path, l, c), Table.walk(kids, path, - fixed), Table.walk(r2, path, fixed)]) : List<&2, F.Finding>} - -def inert.table.lg(kk, t, o, l, c, path, fixed, kids, r2, ef, ek, ik, ir): - inert.cat3(Table.hit(String.eq(o, "("), D1.inert.keep(D1.inert.blanks(kk), t), D1.inert.node(kids), fixed, path, - l, c), Table.hit(String.eq(o, "("), t, kids, fixed, path, l, c), Table.walk(D1.inert.node(kids), path, fixed), - Table.walk(kids, path, fixed), Table.walk(D1.inert.node(r2), path, fixed), Table.walk(r2, path, fixed), - inert.table.hit(String.eq(o, "("), D1.inert.blanks(kk), t, kids, fixed, path, l, c, ef, ek), ik, ir) - -# table's walk reads no string: a leaf's text is compared with `List.get` -# and `List.set`, which no string equals -law inert.table.walk: - for nn: Tree.Node - for +path: String - for +fixed: List<&2, String> - for +e: {D1.inert.ok(nn) == True{} : Bool} - {Table.walk(D1.inert.node(nn), path, fixed) == Table.walk(nn, path, fixed) : List<&2, F.Finding>} - -def inert.table.walk(nn, path, fixed, e): - match nn: - case Tree.NCons{h, +rest}: - match h: - case Tree.Leaf{tok}: - match tok: - case Lex.Tok{+k, +t, +l, +c}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{+o, +kids, +cl}: - match o: - case Lex.Tok{+ok, +ot, +ol, +oc}: - +lf = {Tree.Leaf{Lex.Tok{k, t, l, c}} : Tree.Node} - +eg = inert.ok_t(lf, Tree.NCons{Tree.Group{Lex.Tok{ok, ot, ol, oc}, kids, cl}, r2}, e) - +ec = inert.ok_h(Tree.Group{Lex.Tok{ok, ot, ol, oc}, kids, cl}, r2, eg) - inert.table.lg(k, t, ot, l, c, path, fixed, kids, r2, - inert.ok_h(lf, Tree.NCons{Tree.Group{Lex.Tok{ok, ot, ol, oc}, kids, cl}, r2}, e), ec, - inert.table.walk(kids, path, fixed, ec), - inert.table.walk(r2, path, fixed, inert.ok_t(Tree.Group{Lex.Tok{ok, ot, ol, oc}, kids, - cl}, r2, eg))) - case Tree.Leaf{x}: - inert.table.walk(rest, path, fixed, inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.Stmt{x, y, z}: - inert.table.walk(rest, path, fixed, inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.NNil{}: - inert.table.walk(rest, path, fixed, inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.NCons{x, y}: - inert.table.walk(rest, path, fixed, inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.Group{+o, +kids, +cl}: - inert.cat2(Table.walk(D1.inert.node(kids), path, fixed), Table.walk(kids, path, fixed), - Table.walk(D1.inert.node(rest), path, fixed), Table.walk(rest, path, fixed), - inert.table.walk(kids, path, fixed, inert.ok_h(Tree.Group{o, kids, cl}, rest, e)), - inert.table.walk(rest, path, fixed, inert.ok_t(Tree.Group{o, kids, cl}, rest, e))) - case Tree.Stmt{+sk, +kids, +body}: - +es = inert.ok_h(Tree.Stmt{sk, kids, body}, rest, e) - inert.cat3(Table.walk(D1.inert.node(kids), path, fixed), Table.walk(kids, path, fixed), - Table.walk(D1.inert.node(body), path, fixed), Table.walk(body, path, fixed), - Table.walk(D1.inert.node(rest), path, fixed), Table.walk(rest, path, fixed), - inert.table.walk(kids, path, fixed, M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(body), es)), - inert.table.walk(body, path, fixed, M1.inert.and_r(D1.inert.ok(kids), D1.inert.ok(body), es)), - inert.table.walk(rest, path, fixed, inert.ok_t(Tree.Stmt{sk, kids, body}, rest, e))) - case Tree.NNil{}: inert.table.walk(rest, path, fixed, e) - case Tree.NCons{x, y}: inert.table.walk(rest, path, fixed, inert.ok_t(Tree.NCons{x, y}, rest, e)) - case Tree.Leaf{tok}: {==} - case Tree.Group{o, gk, cl}: {==} - case Tree.Stmt{sk, sks, sb}: {==} - case Tree.NNil{}: {==} - -# table over the defs read that way reports what it reports over the defs -law inert.table.go: - for ds: List<&2, Calls.Def> - for +mods: List<&2, String> - for +path: String - for +acc: List<&2, List<&2, F.Finding>> - for +e: {inert.dok(ds) == True{} : Bool} - {Table.check.go(inert.defs(ds), mods, path, acc) == Table.check.go(ds, mods, path, acc) : List<&2, F.Finding>} - -def inert.table.go(ds, mods, path, acc, e): - match ds: - case Nil{}: {==} - case Con{Calls.Def{+name, +sig, +body}, +rest}: - +en = inert.dok.head(name, sig, body, rest, e) - +eb = inert.dok.body(name, sig, body, rest, e) - +ib = D1.inert.node(body) - +lst = Lazy.stop(List<&2, F.Finding>, Bool.not(Bool.and(Calls.calls(ib, name), Bool.not(Calls.exempt(path, - D1.inert.node(sig))))), [], _u => Table.walk(ib, path, Table.scope(ib, mods))) - %inert.table.scope(body, mods, eb) : {Table.check.go(inert.defs(rest), mods, path, lst <> acc) - == Table.check.go(rest, mods, path, Lazy.stop(List<&2, F.Finding>, Bool.not(Bool.and(Calls.calls(body, name), - Bool.not(Calls.exempt(path, sig)))), [], _u => Table.walk(body, path, _)) <> acc) : List<&2, F.Finding>} - %inert.stop(Bool.not(Bool.and(Calls.calls(ib, name), Bool.not(Calls.exempt(path, D1.inert.node(sig))))), - Bool.not(Bool.and(Calls.calls(body, name), Bool.not(Calls.exempt(path, sig)))), - Table.walk(ib, path, Table.scope(ib, mods)), Table.walk(body, path, Table.scope(ib, mods)), - inert.gate(path, name, sig, body, en, eb), inert.table.walk(body, path, Table.scope(ib, mods), eb)) : - {Table.check.go(inert.defs(rest), mods, path, lst <> acc) == Table.check.go(rest, mods, path, _ <> acc) - : List<&2, F.Finding>} - inert.table.go(rest, mods, path, lst <> acc, inert.dok.rest(name, sig, body, rest, e)) - -# table over the defs read that way, their table defs read that way too -law inert.table.all: - for +ds: List<&2, Calls.Def> - for +path: String - for +e: {inert.dok(ds) == True{} : Bool} - {Table.check.go(inert.defs(ds), Table.modules(inert.defs(ds), []), path, []) - == Table.check.go(ds, Table.modules(ds, []), path, []) : List<&2, F.Finding>} - -def inert.table.all(ds, path, e): - %inert.table.modules(ds, [], e) : {Table.check.go(inert.defs(ds), Table.modules(inert.defs(ds), []), path, []) - == Table.check.go(ds, _, path, []) : List<&2, F.Finding>} - inert.table.go(ds, Table.modules(inert.defs(ds), []), path, [], e) - -def Laws.inert_table(path, _text, tree, _toks, _bound, _items, _text2, tree2, _toks2, _bound2, _items2, ok, ok2, e): - M1.inert.via(Tree.Node, z => Table.check.go(Calls.defs(z), Table.modules(Calls.defs(z), []), path, []), tree, tree2, - D1.inert.node(tree), D1.inert.node(tree2), - inert.lift(z => Table.check.go(z, Table.modules(z, []), path, []), tree, inert.table.all(Calls.defs(tree), path, - inert.dok.of(tree, ok))), - inert.lift(z => Table.check.go(z, Table.modules(z, []), path, []), tree2, inert.table.all(Calls.defs(tree2), path, - inert.dok.of(tree2, ok2))), - e) - -# index -# ----- - # a chain read that way is one number as it was law inert.index.literal: for nn: Tree.Node @@ -5024,123 +4728,6 @@ def inert.mfits(mm: Maybe<&2, Lex.Tok>) -> Bool: case None{}: True{} case Some{tok}: D1.inert.fits(tok) -# a token read that way is the literal one it was: a number is never cut -law inert.unit.one_of: - for mm: Maybe<&2, Lex.Tok> - {UnitRule.one_of(inert.mtok(mm)) == UnitRule.one_of(mm) : Maybe<&2, Lex.Tok>} - -def inert.unit.one_of(mm): - match mm: - case None{}: {==} - case Some{tok}: - match tok: - case Lex.Tok{k, t, l, c}: - match k: - case Lex.TName{}: {==} - case Lex.TUpper{}: {==} - case Lex.TDotted{}: {==} - case Lex.TWild{}: {==} - case Lex.TKey{}: {==} - case Lex.TNum{}: {==} - case Lex.TStr{}: {==} - case Lex.TChar{}: {==} - case Lex.TComment{}: {==} - case Lex.TSpace{}: {==} - case Lex.TNewline{}: {==} - case Lex.TOp{}: {==} - case Lex.TColon{}: {==} - case Lex.TEq{}: {==} - case Lex.TBind{}: {==} - case Lex.TArrow{}: {==} - case Lex.TLam{}: {==} - case Lex.TAll{}: {==} - case Lex.TAmp{}: {==} - case Lex.TOpen{}: {==} - case Lex.TClose{}: {==} - case Lex.TComma{}: {==} - -# which literal an operator's finding points at, from the two literals -def inert.unit.whichp(+op: String, +right: Maybe<&2, Lex.Tok>, left: Maybe<&2, Lex.Tok>) -> Maybe<&2, Lex.Tok>: - Bool.pick(Maybe<&2, Lex.Tok>, String.eq(op, "*"), UnitRule.either(right, left), - Bool.pick(Maybe<&2, Lex.Tok>, String.eq(op, "/"), right, None{})) - -# the literal an operator points at, its neighbours read that way -law inert.unit.which: - for +op: String - for +prev: Maybe<&2, Lex.Tok> - for +nxt: Maybe<&2, Lex.Tok> - {UnitRule.which(op, inert.mtok(prev), inert.mtok(nxt)) == UnitRule.which(op, prev, nxt) : Maybe<&2, Lex.Tok>} - -def inert.unit.which(op, prev, nxt): - +lhs = UnitRule.which(op, inert.mtok(prev), inert.mtok(nxt)) - %inert.unit.one_of(nxt) : {lhs == inert.unit.whichp(op, _, UnitRule.one_of(prev)) : Maybe<&2, Lex.Tok>} - %inert.unit.one_of(prev) : {lhs == inert.unit.whichp(op, UnitRule.one_of(inert.mtok(nxt)), _) : Maybe<&2, Lex.Tok>} - {==} - -# a leaf read that way hits as it did: only an operator's text is read -law inert.unit.op_hit: - for hot: Bool - for tok: Lex.Tok - for +prev: Maybe<&2, Lex.Tok> - for +nxt: Maybe<&2, Lex.Tok> - for +path: String - {UnitRule.op_hit(D1.inert.tok(tok), inert.mtok(prev), inert.mtok(nxt), path, hot) - == UnitRule.op_hit(tok, prev, nxt, path, hot) : List<&2, F.Finding>} - -def inert.unit.op_hit(hot, tok, prev, nxt, path): - match hot: - case False{}: - match tok: - case Lex.Tok{k, t, l, c}: {==} - case True{}: - match tok: - case Lex.Tok{k, +t, l, c}: - match k: - case Lex.TName{}: {==} - case Lex.TUpper{}: {==} - case Lex.TDotted{}: {==} - case Lex.TWild{}: {==} - case Lex.TKey{}: {==} - case Lex.TNum{}: {==} - case Lex.TStr{}: {==} - case Lex.TChar{}: {==} - case Lex.TComment{}: {==} - case Lex.TSpace{}: {==} - case Lex.TNewline{}: {==} - case Lex.TOp{}: - Equal.cong(Maybe<&2, Lex.Tok>, List<&2, F.Finding>, z => UnitRule.cite(z, path), - UnitRule.which(t, inert.mtok(prev), inert.mtok(nxt)), UnitRule.which(t, prev, nxt), - inert.unit.which(t, prev, nxt)) - case Lex.TColon{}: {==} - case Lex.TEq{}: {==} - case Lex.TBind{}: {==} - case Lex.TArrow{}: {==} - case Lex.TLam{}: {==} - case Lex.TAll{}: {==} - case Lex.TAmp{}: {==} - case Lex.TOpen{}: {==} - case Lex.TClose{}: {==} - case Lex.TComma{}: {==} - -# the first leaf of a chain read that way is the leaf it was, read that way -law inert.unit.first_tok: - for nn: Tree.Node - {UnitRule.first_tok(D1.inert.node(nn)) == inert.mtok(UnitRule.first_tok(nn)) : Maybe<&2, Lex.Tok>} - -def inert.unit.first_tok(nn): - match nn: - case Tree.NCons{h, r}: - match h: - case Tree.Leaf{tok}: {==} - case Tree.Group{o, gk, cl}: {==} - case Tree.Stmt{sk, sks, sb}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{tok}: {==} - case Tree.Group{o, gk, cl}: {==} - case Tree.Stmt{sk, sks, sb}: {==} - case Tree.NNil{}: {==} - # a token dropped or kept, read that way law inert.mt_pick: for c: Bool @@ -5165,366 +4752,10 @@ def inert.mf_pick(c, _tok, e): case True{}: {==} case False{}: e -# what a leaf leaves behind, read that way -law inert.unit.after: - for tok: Lex.Tok - {UnitRule.after(D1.inert.tok(tok)) == inert.mtok(UnitRule.after(tok)) : Maybe<&2, Lex.Tok>} - -def inert.unit.after(tok): - match tok: - case Lex.Tok{+k, +t, +l, +c}: inert.mt_pick(UnitRule.op_kind(k), Lex.Tok{k, t, l, c}) - -# what a leaf leaves behind as the lexer makes it -law inert.unit.mf_after: - for tok: Lex.Tok - for +e: {D1.inert.fits(tok) == True{} : Bool} - {inert.mfits(UnitRule.after(tok)) == True{} : Bool} - -def inert.unit.mf_after(tok, e): - match tok: - case Lex.Tok{+k, +t, +l, +c}: inert.mf_pick(UnitRule.op_kind(k), Lex.Tok{k, t, l, c}, e) - -# an argument read that way is the literal one it was -law inert.unit.arg_one: - for aa: Tree.Node - {UnitRule.arg_one(D1.inert.node(aa)) == UnitRule.arg_one(aa) : Maybe<&2, Lex.Tok>} - -def inert.unit.arg_one(aa): - match aa: - case Tree.NCons{h, +r}: - match h: - case Tree.Leaf{tok}: - match r: - case Tree.NNil{}: inert.unit.one_of(Some{tok}) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Group{o, gk, cl}: {==} - case Tree.Stmt{sk, sks, sb}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{tok}: {==} - case Tree.Group{o, gk, cl}: {==} - case Tree.Stmt{sk, sks, sb}: {==} - case Tree.NNil{}: {==} - -# the last argument read that way is the literal one it was -law inert.unit.last_one: - for as: List<&2, Tree.Node> - {UnitRule.last_one(inert.nodes(as)) == UnitRule.last_one(as) : Maybe<&2, Lex.Tok>} - -def inert.unit.last_one(as): - match as: - case Nil{}: {==} - case Con{h, +rest}: - match rest: - case Nil{}: inert.unit.arg_one(h) - case Con{x, y}: inert.unit.last_one(rest) - -# the last argument that is the literal one, the arguments read that way -law inert.unit.any_one: - for as: List<&2, Tree.Node> - {UnitRule.any_one(inert.nodes(as)) == UnitRule.any_one(as) : Maybe<&2, Lex.Tok>} - -def inert.unit.any_one(as): - match as: - case Nil{}: {==} - case Con{+h, +rest}: - %inert.unit.any_one(rest) : {UnitRule.any_one(inert.nodes(Con{h, rest})) == UnitRule.either(_, - UnitRule.arg_one(h)) : Maybe<&2, Lex.Tok>} - %inert.unit.arg_one(h) : {UnitRule.any_one(inert.nodes(Con{h, rest})) == UnitRule.either( - UnitRule.any_one(inert.nodes(rest)), _) : Maybe<&2, Lex.Tok>} - {==} - -# a divide's divisor, the arguments read that way -law inert.unit.on_div_is: - for d: Bool - for +as: List<&2, Tree.Node> - for +path: String - {UnitRule.on_div_is(d, inert.nodes(as), path) == UnitRule.on_div_is(d, as, path) : List<&2, F.Finding>} - -def inert.unit.on_div_is(d, as, path): - match d: - case False{}: {==} - case True{}: - Equal.cong(Maybe<&2, Lex.Tok>, List<&2, F.Finding>, z => UnitRule.cite(z, path), - UnitRule.last_one(inert.nodes(as)), UnitRule.last_one(as), inert.unit.last_one(as)) - -# the three divides -def inert.unit.divs() -> List<&2, String>: - ["Nat.div", "U32.div", "F32.div"] - -# the three multiplies -def inert.unit.muls() -> List<&2, String>: - ["Nat.mul", "U32.mul", "F32.mul"] - -# a multiply or a divide, the name and arguments read that way -law inert.unit.on_op_is: - for m: Bool - for +b: Bool - for +t: String - for +as: List<&2, Tree.Node> - for +path: String - for +ef: {D1.inert.fits.go(b, t) == True{} : Bool} - {UnitRule.on_op_is(m, D1.inert.keep(b, t), inert.nodes(as), path) == UnitRule.on_op_is(m, t, as, path) - : List<&2, F.Finding>} - -def inert.unit.on_op_is(m, b, t, as, path, ef): - match m: - case True{}: - Equal.cong(Maybe<&2, Lex.Tok>, List<&2, F.Finding>, z => UnitRule.cite(z, path), - UnitRule.any_one(inert.nodes(as)), UnitRule.any_one(as), inert.unit.any_one(as)) - case False{}: - +cd = List.contains(~String, ~String.eq, inert.unit.divs(), D1.inert.keep(b, t)) - %inert.contains_keep(inert.unit.divs(), b, t, ef, {==}) : {UnitRule.on_div(D1.inert.keep(b, t), - inert.nodes(as), path) == UnitRule.on_div_is(_, as, path) : List<&2, F.Finding>} - inert.unit.on_div_is(cd, as, path) - -# a call's identity operands, the name and arguments read that way -law inert.unit.on_op: - for +b: Bool - for +t: String - for +as: List<&2, Tree.Node> - for +path: String - for +ef: {D1.inert.fits.go(b, t) == True{} : Bool} - {UnitRule.on_op(D1.inert.keep(b, t), inert.nodes(as), path) == UnitRule.on_op(t, as, path) : List<&2, F.Finding>} - -def inert.unit.on_op(b, t, as, path, ef): - +kt = D1.inert.keep(b, t) - %inert.contains_keep(inert.unit.muls(), b, t, ef, {==}) : {UnitRule.on_op(kt, inert.nodes(as), path) - == UnitRule.on_op_is(_, t, as, path) : List<&2, F.Finding>} - inert.unit.on_op_is(List.contains(~String, ~String.eq, inert.unit.muls(), kt), b, t, as, path, ef) - -# an application's findings, the name and the group read that way -law inert.unit.own: - for call: Bool - for hot: Bool - for +b: Bool - for +t: String - for +kids: Tree.Node - for +path: String - for +ef: {D1.inert.fits.go(b, t) == True{} : Bool} - {UnitRule.own_of(call, D1.inert.keep(b, t), D1.inert.node(kids), path, hot) - == UnitRule.own_of(call, t, kids, path, hot) : List<&2, F.Finding>} - -def inert.unit.own(call, hot, b, t, kids, path, ef): - match call: - case False{}: {==} - case True{}: - match hot: - case False{}: {==} - case True{}: - %Equal.sym(List<&2, Tree.Node>, Calls.args(D1.inert.node(kids)), inert.nodes(Calls.args(kids)), - inert.args(kids)) : {UnitRule.on_op(D1.inert.keep(b, t), _, path) == UnitRule.on_op(t, Calls.args(kids), - path) : List<&2, F.Finding>} - inert.unit.on_op(b, t, Calls.args(kids), path, ef) - -# an application's findings, the callee and the group read that way -law inert.unit.callee: - for +call: Bool - for +hot: Bool - for prev: Maybe<&2, Lex.Tok> - for +kids: Tree.Node - for +path: String - for +ep: {inert.mfits(prev) == True{} : Bool} - {UnitRule.own_of(call, UnitRule.callee(inert.mtok(prev)), D1.inert.node(kids), path, hot) - == UnitRule.own_of(call, UnitRule.callee(prev), kids, path, hot) : List<&2, F.Finding>} - -def inert.unit.callee(call, hot, prev, kids, path, ep): - match prev: - case None{}: inert.unit.own(call, hot, False{}, "", kids, path, {==}) - case Some{tok}: - match tok: - case Lex.Tok{+k, +t, l, c}: inert.unit.own(call, hot, D1.inert.blanks(k), t, kids, path, ep) - -# a statement, read that way: its tokens, its body and the rest walked as -# they were, a case arm hot by its self-call as it was -law inert.unit.stmt: - for sk: Tree.StmtKind - for +kids: Tree.Node - for +body: Tree.Node - for +rest: Tree.Node - for +prev: Maybe<&2, Lex.Tok> - for +self: String - for +path: String - for +hot: Bool - for +es: {D1.inert.marked(self) == False{} : Bool} - for +ek: {D1.inert.ok(kids) == True{} : Bool} - for +eb: {D1.inert.ok(body) == True{} : Bool} - for i1: {UnitRule.walk(D1.inert.node(kids), None{}, self, path, False{}) == UnitRule.walk(kids, None{}, self, - path, False{}) : List<&2, F.Finding>} - for i2: {UnitRule.walk(D1.inert.node(kids), None{}, self, path, hot) == UnitRule.walk(kids, None{}, self, path, - hot) : List<&2, F.Finding>} - for i3: {UnitRule.walk(D1.inert.node(body), None{}, self, path, Bool.and(hot, Calls.calls(body, self))) - == UnitRule.walk(body, None{}, self, path, Bool.and(hot, Calls.calls(body, self))) : List<&2, F.Finding>} - for i4: {UnitRule.walk(D1.inert.node(body), None{}, self, path, hot) == UnitRule.walk(body, None{}, self, path, - hot) : List<&2, F.Finding>} - for i5: {UnitRule.walk(D1.inert.node(rest), None{}, self, path, hot) == UnitRule.walk(rest, None{}, self, path, - hot) : List<&2, F.Finding>} - {UnitRule.walk(Tree.NCons{Tree.Stmt{sk, D1.inert.node(kids), D1.inert.node(body)}, D1.inert.node(rest)}, - inert.mtok(prev), self, path, hot) - == UnitRule.walk(Tree.NCons{Tree.Stmt{sk, kids, body}, rest}, prev, self, path, hot) : List<&2, F.Finding>} - -def inert.unit.stmt(sk, kids, body, rest, _prev, self, path, hot, es, _ek, eb, i1, i2, i3, i4, i5): - match sk: - case Tree.SDef{}: - inert.cat3(UnitRule.walk(D1.inert.node(kids), None{}, self, path, hot), - UnitRule.walk(kids, None{}, self, path, hot), UnitRule.walk(D1.inert.node(body), None{}, self, path, hot), - UnitRule.walk(body, None{}, self, path, hot), UnitRule.walk(D1.inert.node(rest), None{}, self, path, hot), - UnitRule.walk(rest, None{}, self, path, hot), i2, i4, i5) - case Tree.SType{}: - inert.cat3(UnitRule.walk(D1.inert.node(kids), None{}, self, path, hot), - UnitRule.walk(kids, None{}, self, path, hot), UnitRule.walk(D1.inert.node(body), None{}, self, path, hot), - UnitRule.walk(body, None{}, self, path, hot), UnitRule.walk(D1.inert.node(rest), None{}, self, path, hot), - UnitRule.walk(rest, None{}, self, path, hot), i2, i4, i5) - case Tree.SLaw{}: - inert.cat3(UnitRule.walk(D1.inert.node(kids), None{}, self, path, hot), - UnitRule.walk(kids, None{}, self, path, hot), UnitRule.walk(D1.inert.node(body), None{}, self, path, hot), - UnitRule.walk(body, None{}, self, path, hot), UnitRule.walk(D1.inert.node(rest), None{}, self, path, hot), - UnitRule.walk(rest, None{}, self, path, hot), i2, i4, i5) - case Tree.SImport{}: - inert.cat3(UnitRule.walk(D1.inert.node(kids), None{}, self, path, hot), - UnitRule.walk(kids, None{}, self, path, hot), UnitRule.walk(D1.inert.node(body), None{}, self, path, hot), - UnitRule.walk(body, None{}, self, path, hot), UnitRule.walk(D1.inert.node(rest), None{}, self, path, hot), - UnitRule.walk(rest, None{}, self, path, hot), i2, i4, i5) - case Tree.SCase{}: - +ch = Bool.and(hot, Calls.calls(body, self)) - +ci = Bool.and(hot, Calls.calls(D1.inert.node(body), self)) - inert.cat3(UnitRule.walk(D1.inert.node(kids), None{}, self, path, False{}), - UnitRule.walk(kids, None{}, self, path, False{}), UnitRule.walk(D1.inert.node(body), None{}, self, path, ci), - UnitRule.walk(body, None{}, self, path, ch), UnitRule.walk(D1.inert.node(rest), None{}, self, path, hot), - UnitRule.walk(rest, None{}, self, path, hot), i1, - Equal.trans(List<&2, F.Finding>, UnitRule.walk(D1.inert.node(body), None{}, self, path, ci), - UnitRule.walk(D1.inert.node(body), None{}, self, path, ch), UnitRule.walk(body, None{}, self, path, ch), - Equal.cong(Bool, List<&2, F.Finding>, z => UnitRule.walk(D1.inert.node(body), None{}, self, path, - Bool.and(hot, z)), Calls.calls(D1.inert.node(body), self), Calls.calls(body, self), - inert.calls(body, self, es, eb)), i3), i5) - case Tree.SFor{}: - inert.cat3(UnitRule.walk(D1.inert.node(kids), None{}, self, path, hot), - UnitRule.walk(kids, None{}, self, path, hot), UnitRule.walk(D1.inert.node(body), None{}, self, path, hot), - UnitRule.walk(body, None{}, self, path, hot), UnitRule.walk(D1.inert.node(rest), None{}, self, path, hot), - UnitRule.walk(rest, None{}, self, path, hot), i2, i4, i5) - case Tree.SLet{}: - inert.cat3(UnitRule.walk(D1.inert.node(kids), None{}, self, path, hot), - UnitRule.walk(kids, None{}, self, path, hot), UnitRule.walk(D1.inert.node(body), None{}, self, path, hot), - UnitRule.walk(body, None{}, self, path, hot), UnitRule.walk(D1.inert.node(rest), None{}, self, path, hot), - UnitRule.walk(rest, None{}, self, path, hot), i2, i4, i5) - case Tree.STerm{}: - inert.cat3(UnitRule.walk(D1.inert.node(kids), None{}, self, path, hot), - UnitRule.walk(kids, None{}, self, path, hot), UnitRule.walk(D1.inert.node(body), None{}, self, path, hot), - UnitRule.walk(body, None{}, self, path, hot), UnitRule.walk(D1.inert.node(rest), None{}, self, path, hot), - UnitRule.walk(rest, None{}, self, path, hot), i2, i4, i5) - -# unit's walk reads no string: an operator's text, a callee's against the -# multiplies and divides, and a number's against the one -law inert.unit.walk: - for nn: Tree.Node - for +prev: Maybe<&2, Lex.Tok> - for +self: String - for +path: String - for +hot: Bool - for +es: {D1.inert.marked(self) == False{} : Bool} - for +ep: {inert.mfits(prev) == True{} : Bool} - for +e: {D1.inert.ok(nn) == True{} : Bool} - {UnitRule.walk(D1.inert.node(nn), inert.mtok(prev), self, path, hot) == UnitRule.walk(nn, prev, self, path, hot) - : List<&2, F.Finding>} - -def inert.unit.walk(nn, prev, self, path, hot, es, ep, e): - match nn: - case Tree.NCons{h, +rest}: - match h: - case Tree.Leaf{tok}: - match tok: - case Lex.Tok{+k, +t, +l, +c}: - +tk = {Lex.Tok{k, t, l, c} : Lex.Tok} - +ir = D1.inert.node(rest) - +lhs = UnitRule.walk(D1.inert.node(Tree.NCons{Tree.Leaf{tk}, rest}), inert.mtok(prev), self, path, hot) - %inert.unit.walk(rest, UnitRule.after(tk), self, path, hot, es, inert.unit.mf_after(tk, - inert.ok_h(Tree.Leaf{tk}, rest, e)), inert.ok_t(Tree.Leaf{tk}, rest, e)) : {lhs - == List.append(&2, F.Finding, UnitRule.op_hit(tk, prev, UnitRule.first_tok(rest), path, hot), _) - : List<&2, F.Finding>} - %inert.unit.after(tk) : {lhs == List.append(&2, F.Finding, UnitRule.op_hit(tk, prev, - UnitRule.first_tok(rest), path, hot), UnitRule.walk(ir, _, self, path, hot)) : List<&2, F.Finding>} - %inert.unit.op_hit(hot, tk, prev, UnitRule.first_tok(rest), path) : {lhs == List.append(&2, F.Finding, _, - UnitRule.walk(ir, UnitRule.after(D1.inert.tok(tk)), self, path, hot)) : List<&2, F.Finding>} - %inert.unit.first_tok(rest) : {lhs == List.append(&2, F.Finding, UnitRule.op_hit(D1.inert.tok(tk), - inert.mtok(prev), _, path, hot), UnitRule.walk(ir, UnitRule.after(D1.inert.tok(tk)), self, path, hot)) - : List<&2, F.Finding>} - {==} - case Tree.Group{o, +kids, +cl}: - match o: - case Lex.Tok{+ok, +ot, +ol, +oc}: - +gp = {Tree.Group{Lex.Tok{ok, ot, ol, oc}, kids, cl} : Tree.Node} - inert.cat3(UnitRule.walk(D1.inert.node(kids), None{}, self, path, hot), UnitRule.walk(kids, None{}, - self, path, hot), - UnitRule.own_of(String.eq(ot, "("), UnitRule.callee(inert.mtok(prev)), D1.inert.node(kids), path, - hot), UnitRule.own_of(String.eq(ot, "("), UnitRule.callee(prev), kids, path, hot), - UnitRule.walk(D1.inert.node(rest), None{}, self, path, hot), UnitRule.walk(rest, None{}, self, path, - hot), - inert.unit.walk(kids, None{}, self, path, hot, es, {==}, inert.ok_h(gp, rest, e)), - inert.unit.callee(String.eq(ot, "("), hot, prev, kids, path, ep), - inert.unit.walk(rest, None{}, self, path, hot, es, {==}, inert.ok_t(gp, rest, e))) - case Tree.Stmt{+sk, +kids, +body}: - +es2 = inert.ok_h(Tree.Stmt{sk, kids, body}, rest, e) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(body), es2) - +eb = M1.inert.and_r(D1.inert.ok(kids), D1.inert.ok(body), es2) - +er = inert.ok_t(Tree.Stmt{sk, kids, body}, rest, e) - inert.unit.stmt(sk, kids, body, rest, prev, self, path, hot, es, ek, eb, - inert.unit.walk(kids, None{}, self, path, False{}, es, {==}, ek), - inert.unit.walk(kids, None{}, self, path, hot, es, {==}, ek), - inert.unit.walk(body, None{}, self, path, Bool.and(hot, Calls.calls(body, self)), es, {==}, eb), - inert.unit.walk(body, None{}, self, path, hot, es, {==}, eb), - inert.unit.walk(rest, None{}, self, path, hot, es, {==}, er)) - case Tree.NNil{}: inert.unit.walk(rest, prev, self, path, hot, es, ep, e) - case Tree.NCons{x, y}: - inert.unit.walk(rest, prev, self, path, hot, es, ep, inert.ok_t(Tree.NCons{x, y}, rest, e)) - case Tree.Leaf{tok}: {==} - case Tree.Group{o, gk, cl}: {==} - case Tree.Stmt{sk, sks, sb}: {==} - case Tree.NNil{}: {==} - -# unit over the defs read that way reports what it reports over the defs -law inert.unit.go: - for ds: List<&2, Calls.Def> - for +path: String - for +acc: List<&2, List<&2, F.Finding>> - for +e: {inert.dok(ds) == True{} : Bool} - {UnitRule.check.go(inert.defs(ds), path, acc) == UnitRule.check.go(ds, path, acc) : List<&2, F.Finding>} - -def inert.unit.go(ds, path, acc, e): - match ds: - case Nil{}: {==} - case Con{Calls.Def{+name, +sig, +body}, +rest}: - +en = inert.dok.head(name, sig, body, rest, e) - +eb = inert.dok.body(name, sig, body, rest, e) - +ib = D1.inert.node(body) - +lst = Lazy.stop(List<&2, F.Finding>, Bool.not(Bool.and(Calls.calls(ib, name), Bool.not(Calls.exempt(path, - D1.inert.node(sig))))), [], _u => UnitRule.walk(ib, None{}, name, path, True{})) - %inert.stop(Bool.not(Bool.and(Calls.calls(ib, name), Bool.not(Calls.exempt(path, D1.inert.node(sig))))), - Bool.not(Bool.and(Calls.calls(body, name), Bool.not(Calls.exempt(path, sig)))), - UnitRule.walk(ib, None{}, name, path, True{}), UnitRule.walk(body, None{}, name, path, True{}), - inert.gate(path, name, sig, body, en, eb), inert.unit.walk(body, None{}, name, path, True{}, en, {==}, eb)) : - {UnitRule.check.go(inert.defs(rest), path, lst <> acc) == UnitRule.check.go(rest, path, _ <> acc) - : List<&2, F.Finding>} - inert.unit.go(rest, path, lst <> acc, inert.dok.rest(name, sig, body, rest, e)) - -def Laws.inert_unit(path, _text, tree, _toks, _bound, _items, _text2, tree2, _toks2, _bound2, _items2, ok, ok2, e): - M1.inert.via(Tree.Node, z => UnitRule.check.go(Calls.defs(z), path, []), tree, tree2, D1.inert.node(tree), - D1.inert.node(tree2), - inert.lift(z => UnitRule.check.go(z, path, []), tree, inert.unit.go(Calls.defs(tree), path, [], inert.dok.of(tree, - ok))), - inert.lift(z => UnitRule.check.go(z, path, []), tree2, inert.unit.go(Calls.defs(tree2), path, [], - inert.dok.of(tree2, ok2))), - e) - -# thunk -# ----- - -# a name is never cut, read in the order thunk tests it -law inert.thunk.name_code: - for k: Lex.TokKind - {Bool.and(Lex.is_name(k), D1.inert.blanks(k)) == False{} : Bool} +# a name is never cut, read in the order thunk tests it +law inert.thunk.name_code: + for k: Lex.TokKind + {Bool.and(Lex.is_name(k), D1.inert.blanks(k)) == False{} : Bool} def inert.thunk.name_code(k): match k: @@ -7097,81 +6328,236 @@ def Laws.inert_fuel(path, _text, tree, _toks, _bound, _items, _text2, tree2, _to # ring # ---- -# a chain read that way is one number as it was -law inert.ring.is_num: - for nn: Tree.Node - {Ring.is_num(D1.inert.node(nn)) == Ring.is_num(nn) : Bool} +# a leaf, a group and the rest as the lexer makes them: each fits +law inert.ok3: + for +a: Tree.Node + for +g: Tree.Node + for +r: Tree.Node + for e: {D1.inert.ok(Tree.NCons{a, Tree.NCons{g, r}}) == True{} : Bool} + {Bool.and(D1.inert.ok(a), Bool.and(D1.inert.ok(g), D1.inert.ok(r))) == True{} : Bool} -def inert.ring.is_num(nn): - match nn: +def inert.ok3(_a, _g, _r, e): + e + +# a case statement's pattern read that way is the pattern it had, read that way +law inert.pattern: + for kids: Tree.Node + {T.pattern(D1.inert.node(kids)) == D1.inert.node(T.pattern(kids)) : Tree.Node} + +def inert.pattern(kids): + match kids: + case Tree.NCons{h, rest}: {==} + case Tree.Leaf{tok}: {==} + case Tree.Group{o, gk, cl}: {==} + case Tree.Stmt{sk, sks, sb}: {==} + case Tree.NNil{}: {==} + +# a from-k-up arm whose binder is read that way: a binder is never cut +law inert.arms.bpick: + for x: Bool + for b: Lex.TokKind + for +p: String + for -f: String -> Arms.Arm + {Bool.pick(Arms.Arm, Bool.and(x, Arms.binder(b)), f(D1.inert.keep(D1.inert.blanks(b), p)), Arms.AOther{}) + == Bool.pick(Arms.Arm, Bool.and(x, Arms.binder(b)), f(p), Arms.AOther{}) : Arms.Arm} + +def inert.arms.bpick(x, b, _p, _f): + match x: + case False{}: {==} + case True{}: + match b: + case Lex.TName{}: {==} + case Lex.TUpper{}: {==} + case Lex.TDotted{}: {==} + case Lex.TWild{}: {==} + case Lex.TKey{}: {==} + case Lex.TNum{}: {==} + case Lex.TStr{}: {==} + case Lex.TChar{}: {==} + case Lex.TComment{}: {==} + case Lex.TSpace{}: {==} + case Lex.TNewline{}: {==} + case Lex.TOp{}: {==} + case Lex.TColon{}: {==} + case Lex.TEq{}: {==} + case Lex.TBind{}: {==} + case Lex.TArrow{}: {==} + case Lex.TLam{}: {==} + case Lex.TAll{}: {==} + case Lex.TAmp{}: {==} + case Lex.TOpen{}: {==} + case Lex.TClose{}: {==} + case Lex.TComma{}: {==} + +# what follows a Nat literal read that way makes the arm it made +law inert.arms.shape: + for rest: Tree.Node + for +kk: U32 + for +tt: String + for +line: U32 + for +col: U32 + {Arms.lit.shape(D1.inert.node(rest), kk, tt, line, col) == Arms.lit.shape(rest, kk, tt, line, col) : Arms.Arm} + +def inert.arms.shape(rest, kk, tt, line, col): + match rest: case Tree.NCons{h, +r}: match h: case Tree.Leaf{tok}: match tok: - case Lex.Tok{k, t, l, c}: + case Lex.Tok{k, +o, l, c}: match k: case Lex.TName{}: {==} case Lex.TUpper{}: {==} case Lex.TDotted{}: {==} case Lex.TWild{}: {==} case Lex.TKey{}: {==} - case Lex.TNum{}: - match r: - case Tree.NNil{}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NCons{x, y}: {==} + case Lex.TNum{}: {==} case Lex.TStr{}: {==} case Lex.TChar{}: {==} case Lex.TComment{}: {==} case Lex.TSpace{}: {==} case Lex.TNewline{}: {==} - case Lex.TOp{}: {==} - case Lex.TColon{}: {==} - case Lex.TEq{}: {==} - case Lex.TBind{}: {==} - case Lex.TArrow{}: {==} - case Lex.TLam{}: {==} - case Lex.TAll{}: {==} - case Lex.TAmp{}: {==} - case Lex.TOpen{}: {==} - case Lex.TClose{}: {==} - case Lex.TComma{}: {==} - case Tree.Group{o, gk, cl}: {==} - case Tree.Stmt{sk, sks, sb}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{tok}: {==} - case Tree.Group{o, gk, cl}: {==} - case Tree.Stmt{sk, sks, sb}: {==} - case Tree.NNil{}: {==} - -# a chain read that way is the one name it was -law inert.ring.name_text: - for nn: Tree.Node - {Ring.name_text(D1.inert.node(nn)) == Ring.name_text(nn) : String} - -def inert.ring.name_text(nn): - match nn: - case Tree.NCons{h, +r}: + case Lex.TOp{}: + match r: + case Tree.NCons{h2, +r2}: + match h2: + case Tree.Leaf{tok2}: + match tok2: + case Lex.Tok{+b, +p, l2, c2}: + match r2: + case Tree.NCons{h3, r3}: + match h3: + case Tree.Leaf{tok3}: + match tok3: + case Lex.Tok{k3, s, l3, c3}: + match k3: + case Lex.TName{}: {==} + case Lex.TUpper{}: {==} + case Lex.TDotted{}: {==} + case Lex.TWild{}: {==} + case Lex.TKey{}: {==} + case Lex.TNum{}: {==} + case Lex.TStr{}: {==} + case Lex.TChar{}: {==} + case Lex.TComment{}: {==} + case Lex.TSpace{}: {==} + case Lex.TNewline{}: {==} + case Lex.TOp{}: {==} + case Lex.TColon{}: + inert.arms.bpick(String.eq(o, "+"), b, p, z => Arms.APlus{kk, + tt ++ "+" ++ z, line, col, T.width(tt)}) + case Lex.TEq{}: {==} + case Lex.TBind{}: {==} + case Lex.TArrow{}: {==} + case Lex.TLam{}: {==} + case Lex.TAll{}: {==} + case Lex.TAmp{}: {==} + case Lex.TOpen{}: {==} + case Lex.TClose{}: {==} + case Lex.TComma{}: {==} + case Tree.Group{x, y, z}: {==} + case Tree.Stmt{x, y, z}: {==} + case Tree.NNil{}: {==} + case Tree.NCons{x, y}: {==} + case Tree.Leaf{x}: {==} + case Tree.Group{x, y, z}: {==} + case Tree.Stmt{x, y, z}: {==} + case Tree.NNil{}: {==} + case Tree.Group{x, y, z}: {==} + case Tree.Stmt{x, y, z}: {==} + case Tree.NNil{}: {==} + case Tree.NCons{x, y}: {==} + case Tree.Leaf{x}: {==} + case Tree.Group{x, y, z}: {==} + case Tree.Stmt{x, y, z}: {==} + case Tree.NNil{}: {==} + case Lex.TColon{}: {==} + case Lex.TEq{}: {==} + case Lex.TBind{}: {==} + case Lex.TArrow{}: {==} + case Lex.TLam{}: {==} + case Lex.TAll{}: {==} + case Lex.TAmp{}: {==} + case Lex.TOpen{}: {==} + case Lex.TClose{}: {==} + case Lex.TComma{}: {==} + case Tree.Group{x, y, z}: {==} + case Tree.Stmt{x, y, z}: {==} + case Tree.NNil{}: {==} + case Tree.NCons{x, y}: {==} + case Tree.Leaf{x}: {==} + case Tree.Group{x, y, z}: {==} + case Tree.Stmt{x, y, z}: {==} + case Tree.NNil{}: {==} + +# a Nat literal's arm, what follows it read that way +law inert.arms.lit: + for mm: Maybe<&2, U32> + for +tt: String + for +rest: Tree.Node + for +line: U32 + for +col: U32 + {Arms.lit.of(mm, tt, D1.inert.node(rest), line, col) == Arms.lit.of(mm, tt, rest, line, col) : Arms.Arm} + +def inert.arms.lit(mm, tt, rest, line, col): + match mm: + case None{}: {==} + case Some{+k}: inert.arms.shape(rest, k, tt, line, col) + +# a case pattern read that way makes the arm it made: a Nat literal, a capital +# name and a binder are never cut +law inert.arms.arm: + for pat: Tree.Node + {Arms.arm(D1.inert.node(pat)) == Arms.arm(pat) : Arms.Arm} + +def inert.arms.arm(pat): + match pat: + case Tree.NCons{h, +rest}: match h: case Tree.Leaf{tok}: match tok: - case Lex.Tok{k, t, l, c}: + case Lex.Tok{k, +t, +l, +c}: match k: - case Lex.TName{}: - match r: - case Tree.NNil{}: {==} + case Lex.TName{}: {==} + case Lex.TUpper{}: + match rest: + case Tree.NCons{g, more}: + match g: + case Tree.Group{op, gk, cl}: + match gk: + case Tree.NCons{h3, +r3}: + match h3: + case Tree.Leaf{tok3}: + match tok3: + case Lex.Tok{+k3, +p, l2, c2}: + match r3: + case Tree.NNil{}: + inert.arms.bpick(String.eq(t, "Succ"), k3, p, z => Arms.APlus{1, + "Succ{" ++ z ++ "}", l, c, 4}) + case Tree.Leaf{x}: {==} + case Tree.Group{x, y, z}: {==} + case Tree.Stmt{x, y, z}: {==} + case Tree.NCons{x, y}: {==} + case Tree.Group{x, y, z}: {==} + case Tree.Stmt{x, y, z}: {==} + case Tree.NNil{}: {==} + case Tree.NCons{x, y}: {==} + case Tree.Leaf{x}: {==} + case Tree.Group{x, y, z}: {==} + case Tree.Stmt{x, y, z}: {==} + case Tree.NNil{}: {==} + case Tree.Leaf{x}: {==} + case Tree.Stmt{x, y, z}: {==} + case Tree.NNil{}: {==} + case Tree.NCons{x, y}: {==} case Tree.Leaf{x}: {==} case Tree.Group{x, y, z}: {==} case Tree.Stmt{x, y, z}: {==} - case Tree.NCons{x, y}: {==} - case Lex.TUpper{}: {==} + case Tree.NNil{}: {==} case Lex.TDotted{}: {==} case Lex.TWild{}: {==} case Lex.TKey{}: {==} - case Lex.TNum{}: {==} + case Lex.TNum{}: inert.arms.lit(T.nat_value(t), t, rest, l, c) case Lex.TStr{}: {==} case Lex.TChar{}: {==} case Lex.TComment{}: {==} @@ -7188,6227 +6574,609 @@ def inert.ring.name_text(nn): case Lex.TOpen{}: {==} case Lex.TClose{}: {==} case Lex.TComma{}: {==} - case Tree.Group{o, gk, cl}: {==} - case Tree.Stmt{sk, sks, sb}: {==} + case Tree.Group{x, y, z}: {==} + case Tree.Stmt{x, y, z}: {==} case Tree.NNil{}: {==} case Tree.NCons{x, y}: {==} - case Tree.Leaf{tok}: {==} - case Tree.Group{o, gk, cl}: {==} - case Tree.Stmt{sk, sks, sb}: {==} + case Tree.Leaf{x}: {==} + case Tree.Group{x, y, z}: {==} + case Tree.Stmt{x, y, z}: {==} case Tree.NNil{}: {==} -# arguments read that way name a window as they did -law inert.ring.has_name: - for as: List<&2, Tree.Node> - for +wins: List<&2, String> - {Ring.has_name(inert.nodes(as), wins) == Ring.has_name(as, wins) : Bool} - -def inert.ring.has_name(as, wins): - match as: - case Nil{}: {==} - case Con{+h, +rest}: - +lhs = Ring.has_name(inert.nodes(Con{h, rest}), wins) - %inert.ring.has_name(rest, wins) : {lhs == Bool.or(Ring.win_name(h, wins), _) : Bool} - %inert.ring.name_text(h) : {lhs == Bool.or(Bool.and(Bool.not(String.is_empty(_)), List.contains(~String, - ~String.eq, wins, _)), Ring.has_name(inert.nodes(rest), wins)) : Bool} - {==} - -# arguments read that way end with a number as they did -law inert.ring.last_num: - for as: List<&2, Tree.Node> - {Ring.last_num(inert.nodes(as)) == Ring.last_num(as) : Bool} - -def inert.ring.last_num(as): - match as: - case Nil{}: {==} - case Con{h, +rest}: - match rest: - case Nil{}: inert.ring.is_num(h) - case Con{x, y}: inert.ring.last_num(rest) - -# what drop_ok reads: the name among the drops and tails, the window named, -# the name a tail, the count a number -def inert.ring.dparts(kd: Bool, hn: Bool, +tl: Bool, ln: Bool) -> Bool: - Bool.and(kd, Bool.and(hn, Bool.or(tl, ln))) - -# the drops and tails -def inert.ring.drops() -> List<&2, String>: - ["List.drop", "String.drop", "List.tail", "String.tail"] - -# a call's name and arguments read that way are the fixed drop they were -law inert.ring.drop_ok: - for +b: Bool - for +t: String - for +as: List<&2, Tree.Node> - for +wins: List<&2, String> - for +ef: {D1.inert.fits.go(b, t) == True{} : Bool} - {Ring.drop_ok(D1.inert.keep(b, t), inert.nodes(as), wins) == Ring.drop_ok(t, as, wins) : Bool} - -def inert.ring.drop_ok(b, t, as, wins, ef): - +kt = D1.inert.keep(b, t) - +lhs = Ring.drop_ok(kt, inert.nodes(as), wins) - %inert.contains_keep(inert.ring.drops(), b, t, ef, {==}) : {lhs == inert.ring.dparts(_, Ring.has_name(as, wins), - Bool.or(String.eq(t, "List.tail"), String.eq(t, "String.tail")), Ring.last_num(as)) : Bool} - %M1.inert.eq_keep(b, t, "List.tail", ef, {==}) : {lhs == inert.ring.dparts(List.contains(~String, ~String.eq, - inert.ring.drops(), kt), Ring.has_name(as, wins), Bool.or(_, String.eq(t, "String.tail")), Ring.last_num(as)) - : Bool} - %M1.inert.eq_keep(b, t, "String.tail", ef, {==}) : {lhs == inert.ring.dparts(List.contains(~String, ~String.eq, - inert.ring.drops(), kt), Ring.has_name(as, wins), Bool.or(String.eq(kt, "List.tail"), _), Ring.last_num(as)) - : Bool} - %inert.ring.has_name(as, wins) : {lhs == inert.ring.dparts(List.contains(~String, ~String.eq, inert.ring.drops(), - kt), _, Bool.or(String.eq(kt, "List.tail"), String.eq(kt, "String.tail")), Ring.last_num(as)) : Bool} - %inert.ring.last_num(as) : {lhs == inert.ring.dparts(List.contains(~String, ~String.eq, inert.ring.drops(), kt), - Ring.has_name(inert.nodes(as), wins), Bool.or(String.eq(kt, "List.tail"), String.eq(kt, "String.tail")), _) - : Bool} - {==} - -# a drop's token is kept only for a drop's name, which is never cut -law inert.ring.drop_pick: - for b: Bool - for +t: String - for +k: Lex.TokKind - for +l: U32 - for +c: U32 - for +as: List<&2, Tree.Node> - for +wins: List<&2, String> - for +ef: {D1.inert.fits.go(b, t) == True{} : Bool} - {Bool.pick(Maybe<&2, Lex.Tok>, Ring.drop_ok(t, as, wins), Some{Lex.Tok{k, D1.inert.keep(b, t), l, c}}, None{}) - == Bool.pick(Maybe<&2, Lex.Tok>, Ring.drop_ok(t, as, wins), Some{Lex.Tok{k, t, l, c}}, None{}) - : Maybe<&2, Lex.Tok>} - -def inert.ring.drop_pick(b, t, k, l, c, as, wins, ef): - match b: - case False{}: {==} - case True{}: - +tl = Bool.or(String.eq(t, "List.tail"), String.eq(t, "String.tail")) - %Equal.sym(Bool, List.contains(~String, ~String.eq, inert.ring.drops(), t), False{}, - inert.contains_marked(inert.ring.drops(), t, {==}, ef)) : {Bool.pick(Maybe<&2, Lex.Tok>, - inert.ring.dparts(_, Ring.has_name(as, wins), tl, Ring.last_num(as)), Some{Lex.Tok{k, D1.inert.cut(t), l, - c}}, None{}) == Bool.pick(Maybe<&2, Lex.Tok>, inert.ring.dparts(_, Ring.has_name(as, wins), tl, - Ring.last_num(as)), Some{Lex.Tok{k, t, l, c}}, None{}) : Maybe<&2, Lex.Tok>} - {==} - -# a call read that way is the fixed drop it was -law inert.ring.drop_tok: - for +k: Lex.TokKind - for +t: String - for +l: U32 - for +c: U32 - for +kids: Tree.Node - for +wins: List<&2, String> - for +ef: {D1.inert.fits.go(D1.inert.blanks(k), t) == True{} : Bool} - {Ring.drop_tok(k, D1.inert.keep(D1.inert.blanks(k), t), l, c, D1.inert.node(kids), wins) - == Ring.drop_tok(k, t, l, c, kids, wins) : Maybe<&2, Lex.Tok>} - -def inert.ring.drop_tok(k, t, l, c, kids, wins, ef): - +b = D1.inert.blanks(k) - +kt = D1.inert.keep(b, t) - +aa = Calls.args(kids) - +lhs = Ring.drop_tok(k, kt, l, c, D1.inert.node(kids), wins) - %inert.ring.drop_pick(b, t, k, l, c, aa, wins, ef) : {lhs == _ : Maybe<&2, Lex.Tok>} - %inert.ring.drop_ok(b, t, aa, wins, ef) : {lhs == Bool.pick(Maybe<&2, Lex.Tok>, _, Some{Lex.Tok{k, kt, l, c}}, - None{}) : Maybe<&2, Lex.Tok>} - %inert.args(kids) : {lhs == Bool.pick(Maybe<&2, Lex.Tok>, Ring.drop_ok(kt, _, wins), Some{Lex.Tok{k, kt, l, c}}, - None{}) : Maybe<&2, Lex.Tok>} - {==} - -# an application read that way is the drop it was -law inert.ring.here_of: - for open: Bool - for +k: Lex.TokKind - for +t: String - for +l: U32 - for +c: U32 - for +kids: Tree.Node - for +wins: List<&2, String> - for +ef: {D1.inert.fits.go(D1.inert.blanks(k), t) == True{} : Bool} - {Ring.here_of(open, k, D1.inert.keep(D1.inert.blanks(k), t), l, c, D1.inert.node(kids), wins) - == Ring.here_of(open, k, t, l, c, kids, wins) : Maybe<&2, Lex.Tok>} - -def inert.ring.here_of(open, k, t, l, c, kids, wins, ef): - match open: - case False{}: {==} - case True{}: inert.ring.drop_tok(k, t, l, c, kids, wins, ef) - -# two maybes read as they did give the first of them as it did -law inert.ring.or2: - for -a1: Maybe<&2, Lex.Tok> - for -a2: Maybe<&2, Lex.Tok> - for -b1: Maybe<&2, Lex.Tok> - for -b2: Maybe<&2, Lex.Tok> - for ea: {a1 == a2 : Maybe<&2, Lex.Tok>} - for eb: {b1 == b2 : Maybe<&2, Lex.Tok>} - {Ring.or_tok(a1, b1) == Ring.or_tok(a2, b2) : Maybe<&2, Lex.Tok>} - -def inert.ring.or2(a1, _a2, b1, b2, ea, eb): - %ea : {Ring.or_tok(a1, b1) == Ring.or_tok(_, b2) : Maybe<&2, Lex.Tok>} - %eb : {Ring.or_tok(a1, b1) == Ring.or_tok(a1, _) : Maybe<&2, Lex.Tok>} - {==} - -# a drop anywhere under a node read that way is the drop it was -law inert.ring.drop_in: - for nn: Tree.Node - for +wins: List<&2, String> - for +e: {D1.inert.ok(nn) == True{} : Bool} - {Ring.drop_in(D1.inert.node(nn), wins) == Ring.drop_in(nn, wins) : Maybe<&2, Lex.Tok>} +# a match statement read that way has one scrutinee as it had: only a +# keyword's text is read +law inert.arms.single: + for kids: Tree.Node + {Arms.single(D1.inert.node(kids)) == Arms.single(kids) : Bool} -def inert.ring.drop_in(nn, wins, e): - match nn: - case Tree.NCons{h, +rest}: +def inert.arms.single(kids): + match kids: + case Tree.NCons{h, +r}: match h: case Tree.Leaf{tok}: match tok: - case Lex.Tok{+k, +t, +l, +c}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{o, +kids, +cl}: - match o: - case Lex.Tok{+ok, +ot, +ol, +oc}: - +lf = {Tree.Leaf{Lex.Tok{k, t, l, c}} : Tree.Node} - +gp = {Tree.Group{Lex.Tok{ok, ot, ol, oc}, kids, cl} : Tree.Node} - +eg = inert.ok_t(lf, Tree.NCons{gp, r2}, e) - inert.ring.or2(Ring.here_of(String.eq(ot, "("), k, D1.inert.keep(D1.inert.blanks(k), t), l, - c, D1.inert.node(kids), wins), Ring.here_of(String.eq(ot, "("), k, t, l, c, kids, wins), - Ring.or_tok(Ring.drop_in(D1.inert.node(kids), wins), Ring.drop_in(D1.inert.node(r2), - wins)), Ring.or_tok(Ring.drop_in(kids, wins), Ring.drop_in(r2, wins)), - inert.ring.here_of(String.eq(ot, "("), k, t, l, c, kids, wins, inert.ok_h(lf, - Tree.NCons{gp, r2}, e)), - inert.ring.or2(Ring.drop_in(D1.inert.node(kids), wins), Ring.drop_in(kids, wins), - Ring.drop_in(D1.inert.node(r2), wins), Ring.drop_in(r2, wins), - inert.ring.drop_in(kids, wins, inert.ok_h(gp, r2, eg)), - inert.ring.drop_in(r2, wins, inert.ok_t(gp, r2, eg)))) - case Tree.Leaf{x}: - inert.ring.drop_in(rest, wins, inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.Stmt{x, y, z}: - inert.ring.drop_in(rest, wins, inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.NNil{}: - inert.ring.drop_in(rest, wins, inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.NCons{x, y}: - inert.ring.drop_in(rest, wins, inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.Group{+o, +kids, +cl}: - inert.ring.or2(Ring.drop_in(D1.inert.node(kids), wins), Ring.drop_in(kids, wins), - Ring.drop_in(D1.inert.node(rest), wins), Ring.drop_in(rest, wins), - inert.ring.drop_in(kids, wins, inert.ok_h(Tree.Group{o, kids, cl}, rest, e)), - inert.ring.drop_in(rest, wins, inert.ok_t(Tree.Group{o, kids, cl}, rest, e))) - case Tree.Stmt{+sk, +kids, +body}: - +es = inert.ok_h(Tree.Stmt{sk, kids, body}, rest, e) - inert.ring.or2(Ring.drop_in(D1.inert.node(kids), wins), Ring.drop_in(kids, wins), - Ring.or_tok(Ring.drop_in(D1.inert.node(body), wins), Ring.drop_in(D1.inert.node(rest), wins)), - Ring.or_tok(Ring.drop_in(body, wins), Ring.drop_in(rest, wins)), - inert.ring.drop_in(kids, wins, M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(body), es)), - inert.ring.or2(Ring.drop_in(D1.inert.node(body), wins), Ring.drop_in(body, wins), - Ring.drop_in(D1.inert.node(rest), wins), Ring.drop_in(rest, wins), - inert.ring.drop_in(body, wins, M1.inert.and_r(D1.inert.ok(kids), D1.inert.ok(body), es)), - inert.ring.drop_in(rest, wins, inert.ok_t(Tree.Stmt{sk, kids, body}, rest, e)))) - case Tree.NNil{}: inert.ring.drop_in(rest, wins, e) - case Tree.NCons{x, y}: inert.ring.drop_in(rest, wins, inert.ok_t(Tree.NCons{x, y}, rest, e)) - case Tree.Leaf{tok}: {==} - case Tree.Group{o, gk, cl}: {==} - case Tree.Stmt{sk, sks, sb}: {==} - case Tree.NNil{}: {==} - -# the receiver of an append, its arguments read that way -law inert.ring.receiver_is: - for list: Bool - for str: Bool - for +as: List<&2, Tree.Node> - for +wins: List<&2, String> - for +ea: {inert.noks(as) == True{} : Bool} - {Ring.receiver_is(list, str, inert.nodes(as), wins) == Ring.receiver_is(list, str, as, wins) : Maybe<&2, Lex.Tok>} - -def inert.ring.receiver_is(list, str, as, wins, ea): - match list str: - case True{} s: - %Equal.sym(Tree.Node, Calls.arg(inert.nodes(as), 2n), D1.inert.node(Calls.arg(as, 2n)), inert.arg(as, 2n)) : - {Ring.drop_in(_, wins) == Ring.drop_in(Calls.arg(as, 2n), wins) : Maybe<&2, Lex.Tok>} - inert.ring.drop_in(Calls.arg(as, 2n), wins, inert.ok.nth(as, 2n, ea)) - case False{} True{}: - %Equal.sym(Tree.Node, Calls.arg(inert.nodes(as), 0n), D1.inert.node(Calls.arg(as, 0n)), inert.arg(as, 0n)) : - {Ring.drop_in(_, wins) == Ring.drop_in(Calls.arg(as, 0n), wins) : Maybe<&2, Lex.Tok>} - inert.ring.drop_in(Calls.arg(as, 0n), wins, inert.ok.nth(as, 0n, ea)) - case False{} False{}: {==} - -# the receiver of an append, the call's name and arguments read that way -law inert.ring.receiver: - for +b: Bool - for +t: String - for +kids: Tree.Node - for +wins: List<&2, String> - for +ef: {D1.inert.fits.go(b, t) == True{} : Bool} - for +ek: {D1.inert.ok(kids) == True{} : Bool} - {Ring.receiver(D1.inert.keep(b, t), D1.inert.node(kids), wins) == Ring.receiver(t, kids, wins) - : Maybe<&2, Lex.Tok>} - -def inert.ring.receiver(b, t, kids, wins, ef, ek): - +kt = D1.inert.keep(b, t) - +aa = Calls.args(kids) - +lhs = Ring.receiver(kt, D1.inert.node(kids), wins) - %inert.ring.receiver_is(String.eq(t, "List.append"), String.eq(t, "String.append"), aa, wins, - inert.ok.args.go(kids, Tree.NNil{}, [], ek, {==}, {==})) : {lhs == _ : Maybe<&2, Lex.Tok>} - %M1.inert.eq_keep(b, t, "List.append", ef, {==}) : {lhs == Ring.receiver_is(_, String.eq(t, "String.append"), - inert.nodes(aa), wins) : Maybe<&2, Lex.Tok>} - %M1.inert.eq_keep(b, t, "String.append", ef, {==}) : {lhs == Ring.receiver_is(String.eq(kt, "List.append"), _, - inert.nodes(aa), wins) : Maybe<&2, Lex.Tok>} - %inert.args(kids) : {lhs == Ring.receiver_is(String.eq(kt, "List.append"), String.eq(kt, "String.append"), _, wins) - : Maybe<&2, Lex.Tok>} - {==} + case Lex.Tok{k, t, l, c}: + match k: + case Lex.TName{}: {==} + case Lex.TUpper{}: {==} + case Lex.TDotted{}: {==} + case Lex.TWild{}: {==} + case Lex.TKey{}: + match r: + case Tree.NCons{x, +r2}: + match r2: + case Tree.NCons{h3, +r3}: + match h3: + case Tree.Leaf{tok3}: + match tok3: + case Lex.Tok{k3, s, l3, c3}: + match k3: + case Lex.TName{}: {==} + case Lex.TUpper{}: {==} + case Lex.TDotted{}: {==} + case Lex.TWild{}: {==} + case Lex.TKey{}: {==} + case Lex.TNum{}: {==} + case Lex.TStr{}: {==} + case Lex.TChar{}: {==} + case Lex.TComment{}: {==} + case Lex.TSpace{}: {==} + case Lex.TNewline{}: {==} + case Lex.TOp{}: {==} + case Lex.TColon{}: + match r3: + case Tree.NNil{}: {==} + case Tree.Leaf{y}: {==} + case Tree.Group{y, z, w}: {==} + case Tree.Stmt{y, z, w}: {==} + case Tree.NCons{y, z}: {==} + case Lex.TEq{}: {==} + case Lex.TBind{}: {==} + case Lex.TArrow{}: {==} + case Lex.TLam{}: {==} + case Lex.TAll{}: {==} + case Lex.TAmp{}: {==} + case Lex.TOpen{}: {==} + case Lex.TClose{}: {==} + case Lex.TComma{}: {==} + case Tree.Group{y, z, w}: {==} + case Tree.Stmt{y, z, w}: {==} + case Tree.NNil{}: {==} + case Tree.NCons{y, z}: {==} + case Tree.Leaf{y}: {==} + case Tree.Group{y, z, w}: {==} + case Tree.Stmt{y, z, w}: {==} + case Tree.NNil{}: {==} + case Tree.Leaf{y}: {==} + case Tree.Group{y, z, w}: {==} + case Tree.Stmt{y, z, w}: {==} + case Tree.NNil{}: {==} + case Lex.TNum{}: {==} + case Lex.TStr{}: {==} + case Lex.TChar{}: {==} + case Lex.TComment{}: {==} + case Lex.TSpace{}: {==} + case Lex.TNewline{}: {==} + case Lex.TOp{}: {==} + case Lex.TColon{}: {==} + case Lex.TEq{}: {==} + case Lex.TBind{}: {==} + case Lex.TArrow{}: {==} + case Lex.TLam{}: {==} + case Lex.TAll{}: {==} + case Lex.TAmp{}: {==} + case Lex.TOpen{}: {==} + case Lex.TClose{}: {==} + case Lex.TComma{}: {==} + case Tree.Group{x, y, z}: {==} + case Tree.Stmt{x, y, z}: {==} + case Tree.NNil{}: {==} + case Tree.NCons{x, y}: {==} + case Tree.Leaf{x}: {==} + case Tree.Group{x, y, z}: {==} + case Tree.Stmt{x, y, z}: {==} + case Tree.NNil{}: {==} -# an application's receiver, read that way -law inert.ring.call_recv: - for open: Bool - for +b: Bool - for +t: String +# a case's arm, its tokens read that way +law inert.arms.case: for +kids: Tree.Node - for +wins: List<&2, String> - for +ef: {D1.inert.fits.go(b, t) == True{} : Bool} - for +ek: {D1.inert.ok(kids) == True{} : Bool} - {Ring.call_recv(open, D1.inert.keep(b, t), D1.inert.node(kids), wins) == Ring.call_recv(open, t, kids, wins) - : Maybe<&2, Lex.Tok>} + {Arms.arm(T.pattern(D1.inert.node(kids))) == Arms.arm(T.pattern(kids)) : Arms.Arm} -def inert.ring.call_recv(open, b, t, kids, wins, ef, ek): - match open: - case False{}: {==} - case True{}: inert.ring.receiver(b, t, kids, wins, ef, ek) +def inert.arms.case(kids): + Equal.trans(Arms.Arm, Arms.arm(T.pattern(D1.inert.node(kids))), Arms.arm(D1.inert.node(T.pattern(kids))), + Arms.arm(T.pattern(kids)), Equal.cong(Tree.Node, Arms.Arm, z => Arms.arm(z), T.pattern(D1.inert.node(kids)), + D1.inert.node(T.pattern(kids)), inert.pattern(kids)), inert.arms.arm(T.pattern(kids))) -# the drop a call or a bracket is, read that way -law inert.ring.call_drop: - for open: Bool - for +k: Lex.TokKind - for +t: String - for +l: U32 - for +c: U32 - for +kids: Tree.Node - for +wins: List<&2, String> - for +ef: {D1.inert.fits.go(D1.inert.blanks(k), t) == True{} : Bool} - for +ek: {D1.inert.ok(kids) == True{} : Bool} - {Ring.call_drop(open, k, D1.inert.keep(D1.inert.blanks(k), t), l, c, D1.inert.node(kids), wins) - == Ring.call_drop(open, k, t, l, c, kids, wins) : Maybe<&2, Lex.Tok>} +# a match's arms read that way are judged as they were +law inert.arms.arms: + for body: Tree.Node + for +wide: Maybe<&2, Arms.Wide> + for +path: String + {Arms.arms(D1.inert.node(body), wide, path) == Arms.arms(body, wide, path) : List<&2, F.Finding>} + +def inert.arms.arms(body, wide, path): + match body: + case Tree.NCons{h, +rest}: + match h: + case Tree.Stmt{sk, +kids, b}: + match sk: + case Tree.SCase{}: + +ao = Arms.arm(T.pattern(kids)) + +ai = Arms.arm(T.pattern(D1.inert.node(kids))) + +lhs = Arms.verdict(wide, ai, path, Arms.arms(D1.inert.node(rest), Arms.widen(wide, ai), path)) + %inert.arms.arms(rest, Arms.widen(wide, ao), path) : {lhs == Arms.verdict(wide, ao, path, _) + : List<&2, F.Finding>} + %inert.arms.case(kids) : {lhs == Arms.verdict(wide, _, path, Arms.arms(D1.inert.node(rest), + Arms.widen(wide, _), path)) : List<&2, F.Finding>} + {==} + case Tree.SDef{}: inert.arms.arms(rest, wide, path) + case Tree.SType{}: inert.arms.arms(rest, wide, path) + case Tree.SLaw{}: inert.arms.arms(rest, wide, path) + case Tree.SImport{}: inert.arms.arms(rest, wide, path) + case Tree.SFor{}: inert.arms.arms(rest, wide, path) + case Tree.SLet{}: inert.arms.arms(rest, wide, path) + case Tree.STerm{}: inert.arms.arms(rest, wide, path) + case Tree.Leaf{x}: inert.arms.arms(rest, wide, path) + case Tree.Group{x, y, z}: inert.arms.arms(rest, wide, path) + case Tree.NNil{}: inert.arms.arms(rest, wide, path) + case Tree.NCons{x, y}: inert.arms.arms(rest, wide, path) + case Tree.Leaf{x}: {==} + case Tree.Group{x, y, z}: {==} + case Tree.Stmt{x, y, z}: {==} + case Tree.NNil{}: {==} -def inert.ring.call_drop(open, k, t, l, c, kids, wins, ef, ek): - match open: - case False{}: inert.ring.drop_in(kids, wins, ek) - case True{}: inert.ring.drop_tok(k, t, l, c, kids, wins, ef) +# arms reads no string: a Nat literal, `Succ`, `match` and `+` are read by +# their kind, and a binder by its +law inert.arms.walk: + for nn: Tree.Node + for +path: String + {Arms.walk(D1.inert.node(nn), path) == Arms.walk(nn, path) : List<&2, F.Finding>} -# a leaf, a group and the rest as the lexer makes them: each fits -law inert.ok3: - for +a: Tree.Node - for +g: Tree.Node - for +r: Tree.Node - for e: {D1.inert.ok(Tree.NCons{a, Tree.NCons{g, r}}) == True{} : Bool} - {Bool.and(D1.inert.ok(a), Bool.and(D1.inert.ok(g), D1.inert.ok(r))) == True{} : Bool} +def inert.arms.walk(nn, path): + match nn: + case Tree.NCons{h, +rest}: + match h: + case Tree.Stmt{sk, +kids, +body}: + inert.cat3(Lazy.stop(List<&2, F.Finding>, Bool.not(Arms.single(D1.inert.node(kids))), [], + _u => Arms.arms(D1.inert.node(body), None{}, path)), Lazy.stop(List<&2, F.Finding>, + Bool.not(Arms.single(kids)), [], _u => Arms.arms(body, None{}, path)), + Arms.walk(D1.inert.node(body), path), Arms.walk(body, path), Arms.walk(D1.inert.node(rest), path), + Arms.walk(rest, path), + inert.stop(Bool.not(Arms.single(D1.inert.node(kids))), Bool.not(Arms.single(kids)), + Arms.arms(D1.inert.node(body), None{}, path), Arms.arms(body, None{}, path), + Equal.cong(Bool, Bool, z => Bool.not(z), Arms.single(D1.inert.node(kids)), Arms.single(kids), + inert.arms.single(kids)), inert.arms.arms(body, None{}, path)), + inert.arms.walk(body, path), inert.arms.walk(rest, path)) + case Tree.Leaf{x}: inert.arms.walk(rest, path) + case Tree.Group{x, y, z}: inert.arms.walk(rest, path) + case Tree.NNil{}: inert.arms.walk(rest, path) + case Tree.NCons{x, y}: inert.arms.walk(rest, path) + case Tree.Leaf{x}: {==} + case Tree.Group{x, y, z}: {==} + case Tree.Stmt{x, y, z}: {==} + case Tree.NNil{}: {==} -def inert.ok3(_a, _g, _r, e): - e +def Laws.inert_arms(path, _text, tree, _toks, _bound, _items, _text2, tree2, _toks2, _bound2, _items2, _ok, _ok2, e): + M1.inert.via(Tree.Node, z => Arms.walk(z, path), tree, tree2, D1.inert.node(tree), D1.inert.node(tree2), + inert.arms.walk(tree, path), inert.arms.walk(tree2, path), e) -# a name then a group, read that way: the scan finds the drop it found -law inert.ring.ps_lg: - for +k: Lex.TokKind - for +t: String - for +l: U32 - for +c: U32 - for +o: String - for +kids: Tree.Node - for +r2: Tree.Node - for +seen: Maybe<&2, Lex.Tok> - for +wins: List<&2, String> - for +ef: {D1.inert.fits.go(D1.inert.blanks(k), t) == True{} : Bool} - for +ek: {D1.inert.ok(kids) == True{} : Bool} - for ik: {Ring.plus_scan(D1.inert.node(kids), None{}, wins) == Ring.plus_scan(kids, None{}, wins) - : Maybe<&2, Lex.Tok>} - for ir: {Ring.plus_scan(D1.inert.node(r2), Ring.or_tok(seen, Ring.call_drop(String.eq(o, "("), k, t, l, c, kids, - wins)), wins) == Ring.plus_scan(r2, Ring.or_tok(seen, Ring.call_drop(String.eq(o, "("), k, t, l, c, kids, wins)), - wins) : Maybe<&2, Lex.Tok>} - {Ring.or_tok(Ring.call_recv(String.eq(o, "("), D1.inert.keep(D1.inert.blanks(k), t), D1.inert.node(kids), - wins), Ring.or_tok(Ring.plus_scan(D1.inert.node(kids), None{}, wins), Ring.plus_scan(D1.inert.node(r2), - Ring.or_tok(seen, Ring.call_drop(String.eq(o, "("), k, D1.inert.keep(D1.inert.blanks(k), t), l, c, - D1.inert.node(kids), wins)), wins))) - == Ring.or_tok(Ring.call_recv(String.eq(o, "("), t, kids, wins), Ring.or_tok(Ring.plus_scan(kids, None{}, wins), - Ring.plus_scan(r2, Ring.or_tok(seen, Ring.call_drop(String.eq(o, "("), k, t, l, c, kids, wins)), wins))) - : Maybe<&2, Lex.Tok>} +# unused +# ------ -def inert.ring.ps_lg(k, t, l, c, o, kids, r2, seen, wins, ef, ek, ik, ir): - +op = String.eq(o, "(") - +kt = D1.inert.keep(D1.inert.blanks(k), t) - +ki = D1.inert.node(kids) - +di = Ring.call_drop(op, k, kt, l, c, ki, wins) - +lhs = Ring.or_tok(Ring.call_recv(op, kt, ki, wins), Ring.or_tok(Ring.plus_scan(ki, None{}, wins), - Ring.plus_scan(D1.inert.node(r2), Ring.or_tok(seen, di), wins))) - %ir : {lhs == Ring.or_tok(Ring.call_recv(op, t, kids, wins), Ring.or_tok(Ring.plus_scan(kids, None{}, wins), _)) - : Maybe<&2, Lex.Tok>} - %inert.ring.call_drop(op, k, t, l, c, kids, wins, ef, ek) : {lhs == Ring.or_tok(Ring.call_recv(op, t, kids, wins), - Ring.or_tok(Ring.plus_scan(kids, None{}, wins), Ring.plus_scan(D1.inert.node(r2), Ring.or_tok(seen, _), wins))) - : Maybe<&2, Lex.Tok>} - %ik : {lhs == Ring.or_tok(Ring.call_recv(op, t, kids, wins), Ring.or_tok(_, Ring.plus_scan(D1.inert.node(r2), - Ring.or_tok(seen, di), wins))) : Maybe<&2, Lex.Tok>} - %inert.ring.call_recv(op, D1.inert.blanks(k), t, kids, wins, ef, ek) : {lhs == Ring.or_tok(_, Ring.or_tok( - Ring.plus_scan(ki, None{}, wins), Ring.plus_scan(D1.inert.node(r2), Ring.or_tok(seen, di), wins))) - : Maybe<&2, Lex.Tok>} - {==} +# a token by where it stands only +def inert.canon(tok: Lex.Tok) -> Lex.Tok: + match tok: + case Lex.Tok{k, t, l, c}: Lex.Tok{Lex.TSpace{}, "", l, c} -# an operator: the scan past it, read that way, finds what it found -law inert.ring.ps_op: - for +t: String - for +seen: Maybe<&2, Lex.Tok> - for +rest: Tree.Node - for +wins: List<&2, String> - for ih: {Ring.plus_scan(D1.inert.node(rest), Ring.pp_seen(String.eq(t, "++"), seen), wins) - == Ring.plus_scan(rest, Ring.pp_seen(String.eq(t, "++"), seen), wins) : Maybe<&2, Lex.Tok>} - {Ring.or_tok(Ring.pp_found(String.eq(t, "++"), seen), Ring.plus_scan(D1.inert.node(rest), - Ring.pp_seen(String.eq(t, "++"), seen), wins)) - == Ring.or_tok(Ring.pp_found(String.eq(t, "++"), seen), Ring.plus_scan(rest, Ring.pp_seen(String.eq(t, "++"), - seen), wins)) : Maybe<&2, Lex.Tok>} - -def inert.ring.ps_op(t, seen, rest, wins, ih): - Equal.cong(Maybe<&2, Lex.Tok>, Maybe<&2, Lex.Tok>, z => Ring.or_tok(Ring.pp_found(String.eq(t, "++"), seen), z), - Ring.plus_scan(D1.inert.node(rest), Ring.pp_seen(String.eq(t, "++"), seen), wins), - Ring.plus_scan(rest, Ring.pp_seen(String.eq(t, "++"), seen), wins), ih) - -# ring's scan reads no string: an operator's text, a drop's or an append's -# name against the few it knows, a window's name -law inert.ring.plus_scan: +# the tokens, each by where it stands only +def inert.canons(ts: List<&2, Lex.Tok>) -> List<&2, Lex.Tok>: + match ts: + case Nil{}: Nil{} + case Con{h, t}: inert.canon(h) <> inert.canons(t) + +# a token read that way stands where it stood +law inert.canon_tok: + for tok: Lex.Tok + {inert.canon(D1.inert.tok(tok)) == inert.canon(tok) : Lex.Tok} + +def inert.canon_tok(tok): + match tok: + case Lex.Tok{k, t, l, c}: {==} + +# a group's close onto tokens that stand alike +law inert.canon_close: + for close: Maybe<&2, Lex.Tok> + for -x: List<&2, Lex.Tok> + for -y: List<&2, Lex.Tok> + for e: {inert.canons(x) == inert.canons(y) : List<&2, Lex.Tok>} + {inert.canons(Tree.leaf_close(close, x)) == inert.canons(Tree.leaf_close(close, y)) : List<&2, Lex.Tok>} + +def inert.canon_close(close, x, y, e): + match close: + case None{}: e + case Some{+tok}: + Equal.cong(List<&2, Lex.Tok>, List<&2, Lex.Tok>, z => inert.canon(tok) <> z, inert.canons(x), inert.canons(y), + e) + +# the leaves of a node read that way stand where they stood +law inert.canon_leaves: for nn: Tree.Node - for +seen: Maybe<&2, Lex.Tok> - for +wins: List<&2, String> - for +e: {D1.inert.ok(nn) == True{} : Bool} - {Ring.plus_scan(D1.inert.node(nn), seen, wins) == Ring.plus_scan(nn, seen, wins) : Maybe<&2, Lex.Tok>} + for -a: List<&2, Lex.Tok> + for -b: List<&2, Lex.Tok> + for e: {inert.canons(a) == inert.canons(b) : List<&2, Lex.Tok>} + {inert.canons(Tree.leaves.go(D1.inert.node(nn), a)) == inert.canons(Tree.leaves.go(nn, b)) : List<&2, Lex.Tok>} -def inert.ring.plus_scan(nn, seen, wins, e): +def inert.canon_leaves(nn, a, b, e): match nn: - case Tree.NCons{h, +rest}: - match h: - case Tree.Leaf{tok}: - match tok: - case Lex.Tok{k, +t, +l, +c}: - match k: - case Lex.TName{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{o, +kids, +cl}: - match o: - case Lex.Tok{+a, +ot, +b, +d}: - +e3 = inert.ok3(Tree.Leaf{Lex.Tok{Lex.TName{}, t, l, c}}, Tree.Group{Lex.Tok{a, ot, b, - d}, kids, cl}, r2, e) - +ea = M1.inert.and_l(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TName{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +eg = M1.inert.and_r(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TName{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(r2), eg) - inert.ring.ps_lg(Lex.TName{}, t, l, c, ot, kids, r2, seen, wins, ea, ek, - inert.ring.plus_scan(kids, None{}, wins, ek), - inert.ring.plus_scan(r2, Ring.or_tok(seen, Ring.call_drop(String.eq(ot, "("), - Lex.TName{}, t, l, c, kids, wins)), wins, M1.inert.and_r(D1.inert.ok(kids), - D1.inert.ok(r2), eg))) - case Tree.Leaf{x}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TName{}, t, l, c}}, - rest, e)) - case Tree.Stmt{x, y, z}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TName{}, t, l, c}}, - rest, e)) - case Tree.NNil{}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TName{}, t, l, c}}, - rest, e)) - case Tree.NCons{x, y}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TName{}, t, l, c}}, - rest, e)) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TUpper{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{o, +kids, +cl}: - match o: - case Lex.Tok{+a, +ot, +b, +d}: - +e3 = inert.ok3(Tree.Leaf{Lex.Tok{Lex.TUpper{}, t, l, c}}, Tree.Group{Lex.Tok{a, ot, b, - d}, kids, cl}, r2, e) - +ea = M1.inert.and_l(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TUpper{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +eg = M1.inert.and_r(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TUpper{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(r2), eg) - inert.ring.ps_lg(Lex.TUpper{}, t, l, c, ot, kids, r2, seen, wins, ea, ek, - inert.ring.plus_scan(kids, None{}, wins, ek), - inert.ring.plus_scan(r2, Ring.or_tok(seen, Ring.call_drop(String.eq(ot, "("), - Lex.TUpper{}, t, l, c, kids, wins)), wins, M1.inert.and_r(D1.inert.ok(kids), - D1.inert.ok(r2), eg))) - case Tree.Leaf{x}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TUpper{}, t, l, c}}, - rest, e)) - case Tree.Stmt{x, y, z}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TUpper{}, t, l, c}}, - rest, e)) - case Tree.NNil{}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TUpper{}, t, l, c}}, - rest, e)) - case Tree.NCons{x, y}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TUpper{}, t, l, c}}, - rest, e)) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TDotted{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{o, +kids, +cl}: - match o: - case Lex.Tok{+a, +ot, +b, +d}: - +e3 = inert.ok3(Tree.Leaf{Lex.Tok{Lex.TDotted{}, t, l, c}}, Tree.Group{Lex.Tok{a, ot, b, - d}, kids, cl}, r2, e) - +ea = M1.inert.and_l(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TDotted{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +eg = M1.inert.and_r(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TDotted{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(r2), eg) - inert.ring.ps_lg(Lex.TDotted{}, t, l, c, ot, kids, r2, seen, wins, ea, ek, - inert.ring.plus_scan(kids, None{}, wins, ek), - inert.ring.plus_scan(r2, Ring.or_tok(seen, Ring.call_drop(String.eq(ot, "("), - Lex.TDotted{}, t, l, c, kids, wins)), wins, M1.inert.and_r(D1.inert.ok(kids), - D1.inert.ok(r2), eg))) - case Tree.Leaf{x}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TDotted{}, t, l, c}}, - rest, e)) - case Tree.Stmt{x, y, z}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TDotted{}, t, l, c}}, - rest, e)) - case Tree.NNil{}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TDotted{}, t, l, c}}, - rest, e)) - case Tree.NCons{x, y}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TDotted{}, t, l, c}}, - rest, e)) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TWild{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{o, +kids, +cl}: - match o: - case Lex.Tok{+a, +ot, +b, +d}: - +e3 = inert.ok3(Tree.Leaf{Lex.Tok{Lex.TWild{}, t, l, c}}, Tree.Group{Lex.Tok{a, ot, b, - d}, kids, cl}, r2, e) - +ea = M1.inert.and_l(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TWild{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +eg = M1.inert.and_r(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TWild{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(r2), eg) - inert.ring.ps_lg(Lex.TWild{}, t, l, c, ot, kids, r2, seen, wins, ea, ek, - inert.ring.plus_scan(kids, None{}, wins, ek), - inert.ring.plus_scan(r2, Ring.or_tok(seen, Ring.call_drop(String.eq(ot, "("), - Lex.TWild{}, t, l, c, kids, wins)), wins, M1.inert.and_r(D1.inert.ok(kids), - D1.inert.ok(r2), eg))) - case Tree.Leaf{x}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TWild{}, t, l, c}}, - rest, e)) - case Tree.Stmt{x, y, z}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TWild{}, t, l, c}}, - rest, e)) - case Tree.NNil{}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TWild{}, t, l, c}}, - rest, e)) - case Tree.NCons{x, y}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TWild{}, t, l, c}}, - rest, e)) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TKey{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{o, +kids, +cl}: - match o: - case Lex.Tok{+a, +ot, +b, +d}: - +e3 = inert.ok3(Tree.Leaf{Lex.Tok{Lex.TKey{}, t, l, c}}, Tree.Group{Lex.Tok{a, ot, b, - d}, kids, cl}, r2, e) - +ea = M1.inert.and_l(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TKey{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +eg = M1.inert.and_r(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TKey{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(r2), eg) - inert.ring.ps_lg(Lex.TKey{}, t, l, c, ot, kids, r2, seen, wins, ea, ek, - inert.ring.plus_scan(kids, None{}, wins, ek), - inert.ring.plus_scan(r2, Ring.or_tok(seen, Ring.call_drop(String.eq(ot, "("), - Lex.TKey{}, t, l, c, kids, wins)), wins, M1.inert.and_r(D1.inert.ok(kids), - D1.inert.ok(r2), eg))) - case Tree.Leaf{x}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TKey{}, t, l, c}}, - rest, e)) - case Tree.Stmt{x, y, z}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TKey{}, t, l, c}}, - rest, e)) - case Tree.NNil{}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TKey{}, t, l, c}}, - rest, e)) - case Tree.NCons{x, y}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TKey{}, t, l, c}}, - rest, e)) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TNum{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{o, +kids, +cl}: - match o: - case Lex.Tok{+a, +ot, +b, +d}: - +e3 = inert.ok3(Tree.Leaf{Lex.Tok{Lex.TNum{}, t, l, c}}, Tree.Group{Lex.Tok{a, ot, b, - d}, kids, cl}, r2, e) - +ea = M1.inert.and_l(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TNum{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +eg = M1.inert.and_r(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TNum{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(r2), eg) - inert.ring.ps_lg(Lex.TNum{}, t, l, c, ot, kids, r2, seen, wins, ea, ek, - inert.ring.plus_scan(kids, None{}, wins, ek), - inert.ring.plus_scan(r2, Ring.or_tok(seen, Ring.call_drop(String.eq(ot, "("), - Lex.TNum{}, t, l, c, kids, wins)), wins, M1.inert.and_r(D1.inert.ok(kids), - D1.inert.ok(r2), eg))) - case Tree.Leaf{x}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TNum{}, t, l, c}}, - rest, e)) - case Tree.Stmt{x, y, z}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TNum{}, t, l, c}}, - rest, e)) - case Tree.NNil{}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TNum{}, t, l, c}}, - rest, e)) - case Tree.NCons{x, y}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TNum{}, t, l, c}}, - rest, e)) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TStr{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{o, +kids, +cl}: - match o: - case Lex.Tok{+a, +ot, +b, +d}: - +e3 = inert.ok3(Tree.Leaf{Lex.Tok{Lex.TStr{}, t, l, c}}, Tree.Group{Lex.Tok{a, ot, b, - d}, kids, cl}, r2, e) - +ea = M1.inert.and_l(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TStr{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +eg = M1.inert.and_r(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TStr{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(r2), eg) - inert.ring.ps_lg(Lex.TStr{}, t, l, c, ot, kids, r2, seen, wins, ea, ek, - inert.ring.plus_scan(kids, None{}, wins, ek), - inert.ring.plus_scan(r2, Ring.or_tok(seen, Ring.call_drop(String.eq(ot, "("), - Lex.TStr{}, t, l, c, kids, wins)), wins, M1.inert.and_r(D1.inert.ok(kids), - D1.inert.ok(r2), eg))) - case Tree.Leaf{x}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TStr{}, t, l, c}}, - rest, e)) - case Tree.Stmt{x, y, z}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TStr{}, t, l, c}}, - rest, e)) - case Tree.NNil{}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TStr{}, t, l, c}}, - rest, e)) - case Tree.NCons{x, y}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TStr{}, t, l, c}}, - rest, e)) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TChar{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{o, +kids, +cl}: - match o: - case Lex.Tok{+a, +ot, +b, +d}: - +e3 = inert.ok3(Tree.Leaf{Lex.Tok{Lex.TChar{}, t, l, c}}, Tree.Group{Lex.Tok{a, ot, b, - d}, kids, cl}, r2, e) - +ea = M1.inert.and_l(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TChar{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +eg = M1.inert.and_r(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TChar{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(r2), eg) - inert.ring.ps_lg(Lex.TChar{}, t, l, c, ot, kids, r2, seen, wins, ea, ek, - inert.ring.plus_scan(kids, None{}, wins, ek), - inert.ring.plus_scan(r2, Ring.or_tok(seen, Ring.call_drop(String.eq(ot, "("), - Lex.TChar{}, t, l, c, kids, wins)), wins, M1.inert.and_r(D1.inert.ok(kids), - D1.inert.ok(r2), eg))) - case Tree.Leaf{x}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TChar{}, t, l, c}}, - rest, e)) - case Tree.Stmt{x, y, z}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TChar{}, t, l, c}}, - rest, e)) - case Tree.NNil{}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TChar{}, t, l, c}}, - rest, e)) - case Tree.NCons{x, y}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TChar{}, t, l, c}}, - rest, e)) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TComment{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{o, +kids, +cl}: - match o: - case Lex.Tok{+a, +ot, +b, +d}: - +e3 = inert.ok3(Tree.Leaf{Lex.Tok{Lex.TComment{}, t, l, c}}, Tree.Group{Lex.Tok{a, ot, b, - d}, kids, cl}, r2, e) - +ea = M1.inert.and_l(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TComment{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +eg = M1.inert.and_r(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TComment{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(r2), eg) - inert.ring.ps_lg(Lex.TComment{}, t, l, c, ot, kids, r2, seen, wins, ea, ek, - inert.ring.plus_scan(kids, None{}, wins, ek), - inert.ring.plus_scan(r2, Ring.or_tok(seen, Ring.call_drop(String.eq(ot, "("), - Lex.TComment{}, t, l, c, kids, wins)), wins, M1.inert.and_r(D1.inert.ok(kids), - D1.inert.ok(r2), eg))) - case Tree.Leaf{x}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TComment{}, t, l, c}}, - rest, e)) - case Tree.Stmt{x, y, z}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TComment{}, t, l, c}}, - rest, e)) - case Tree.NNil{}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TComment{}, t, l, c}}, - rest, e)) - case Tree.NCons{x, y}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TComment{}, t, l, c}}, - rest, e)) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TSpace{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{o, +kids, +cl}: - match o: - case Lex.Tok{+a, +ot, +b, +d}: - +e3 = inert.ok3(Tree.Leaf{Lex.Tok{Lex.TSpace{}, t, l, c}}, Tree.Group{Lex.Tok{a, ot, b, - d}, kids, cl}, r2, e) - +ea = M1.inert.and_l(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TSpace{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +eg = M1.inert.and_r(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TSpace{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(r2), eg) - inert.ring.ps_lg(Lex.TSpace{}, t, l, c, ot, kids, r2, seen, wins, ea, ek, - inert.ring.plus_scan(kids, None{}, wins, ek), - inert.ring.plus_scan(r2, Ring.or_tok(seen, Ring.call_drop(String.eq(ot, "("), - Lex.TSpace{}, t, l, c, kids, wins)), wins, M1.inert.and_r(D1.inert.ok(kids), - D1.inert.ok(r2), eg))) - case Tree.Leaf{x}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TSpace{}, t, l, c}}, - rest, e)) - case Tree.Stmt{x, y, z}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TSpace{}, t, l, c}}, - rest, e)) - case Tree.NNil{}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TSpace{}, t, l, c}}, - rest, e)) - case Tree.NCons{x, y}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TSpace{}, t, l, c}}, - rest, e)) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TNewline{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{o, +kids, +cl}: - match o: - case Lex.Tok{+a, +ot, +b, +d}: - +e3 = inert.ok3(Tree.Leaf{Lex.Tok{Lex.TNewline{}, t, l, c}}, Tree.Group{Lex.Tok{a, ot, b, - d}, kids, cl}, r2, e) - +ea = M1.inert.and_l(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TNewline{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +eg = M1.inert.and_r(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TNewline{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(r2), eg) - inert.ring.ps_lg(Lex.TNewline{}, t, l, c, ot, kids, r2, seen, wins, ea, ek, - inert.ring.plus_scan(kids, None{}, wins, ek), - inert.ring.plus_scan(r2, Ring.or_tok(seen, Ring.call_drop(String.eq(ot, "("), - Lex.TNewline{}, t, l, c, kids, wins)), wins, M1.inert.and_r(D1.inert.ok(kids), - D1.inert.ok(r2), eg))) - case Tree.Leaf{x}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TNewline{}, t, l, c}}, - rest, e)) - case Tree.Stmt{x, y, z}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TNewline{}, t, l, c}}, - rest, e)) - case Tree.NNil{}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TNewline{}, t, l, c}}, - rest, e)) - case Tree.NCons{x, y}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TNewline{}, t, l, c}}, - rest, e)) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TOp{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Leaf{x}: - inert.ring.ps_op(t, seen, rest, wins, inert.ring.plus_scan(rest, - Ring.pp_seen(String.eq(t, "++"), seen), wins, - inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TOp{}, t, l, c}}, rest, e))) - case Tree.Stmt{x, y, z}: - inert.ring.ps_op(t, seen, rest, wins, inert.ring.plus_scan(rest, - Ring.pp_seen(String.eq(t, "++"), seen), wins, - inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TOp{}, t, l, c}}, rest, e))) - case Tree.NNil{}: - inert.ring.ps_op(t, seen, rest, wins, inert.ring.plus_scan(rest, - Ring.pp_seen(String.eq(t, "++"), seen), wins, - inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TOp{}, t, l, c}}, rest, e))) - case Tree.NCons{x, y}: - inert.ring.ps_op(t, seen, rest, wins, inert.ring.plus_scan(rest, - Ring.pp_seen(String.eq(t, "++"), seen), wins, - inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TOp{}, t, l, c}}, rest, e))) - case Tree.Group{x, y, z}: - match x: - case Lex.Tok{a1, a2, a3, a4}: - inert.ring.ps_op(t, seen, rest, wins, inert.ring.plus_scan(rest, - Ring.pp_seen(String.eq(t, "++"), seen), wins, - inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TOp{}, t, l, c}}, rest, e))) - case Tree.Leaf{x}: - inert.ring.ps_op(t, seen, rest, wins, inert.ring.plus_scan(rest, - Ring.pp_seen(String.eq(t, "++"), seen), wins, - inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TOp{}, t, l, c}}, rest, e))) - case Tree.Group{x, y, z}: - inert.ring.ps_op(t, seen, rest, wins, inert.ring.plus_scan(rest, - Ring.pp_seen(String.eq(t, "++"), seen), wins, - inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TOp{}, t, l, c}}, rest, e))) - case Tree.Stmt{x, y, z}: - inert.ring.ps_op(t, seen, rest, wins, inert.ring.plus_scan(rest, - Ring.pp_seen(String.eq(t, "++"), seen), wins, - inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TOp{}, t, l, c}}, rest, e))) - case Tree.NNil{}: - inert.ring.ps_op(t, seen, rest, wins, inert.ring.plus_scan(rest, - Ring.pp_seen(String.eq(t, "++"), seen), wins, - inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TOp{}, t, l, c}}, rest, e))) - case Lex.TColon{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{o, +kids, +cl}: - match o: - case Lex.Tok{+a, +ot, +b, +d}: - +e3 = inert.ok3(Tree.Leaf{Lex.Tok{Lex.TColon{}, t, l, c}}, Tree.Group{Lex.Tok{a, ot, b, - d}, kids, cl}, r2, e) - +ea = M1.inert.and_l(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TColon{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +eg = M1.inert.and_r(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TColon{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(r2), eg) - inert.ring.ps_lg(Lex.TColon{}, t, l, c, ot, kids, r2, seen, wins, ea, ek, - inert.ring.plus_scan(kids, None{}, wins, ek), - inert.ring.plus_scan(r2, Ring.or_tok(seen, Ring.call_drop(String.eq(ot, "("), - Lex.TColon{}, t, l, c, kids, wins)), wins, M1.inert.and_r(D1.inert.ok(kids), - D1.inert.ok(r2), eg))) - case Tree.Leaf{x}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TColon{}, t, l, c}}, - rest, e)) - case Tree.Stmt{x, y, z}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TColon{}, t, l, c}}, - rest, e)) - case Tree.NNil{}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TColon{}, t, l, c}}, - rest, e)) - case Tree.NCons{x, y}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TColon{}, t, l, c}}, - rest, e)) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TEq{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{o, +kids, +cl}: - match o: - case Lex.Tok{+a, +ot, +b, +d}: - +e3 = inert.ok3(Tree.Leaf{Lex.Tok{Lex.TEq{}, t, l, c}}, Tree.Group{Lex.Tok{a, ot, b, - d}, kids, cl}, r2, e) - +ea = M1.inert.and_l(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TEq{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +eg = M1.inert.and_r(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TEq{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(r2), eg) - inert.ring.ps_lg(Lex.TEq{}, t, l, c, ot, kids, r2, seen, wins, ea, ek, - inert.ring.plus_scan(kids, None{}, wins, ek), - inert.ring.plus_scan(r2, Ring.or_tok(seen, Ring.call_drop(String.eq(ot, "("), - Lex.TEq{}, t, l, c, kids, wins)), wins, M1.inert.and_r(D1.inert.ok(kids), - D1.inert.ok(r2), eg))) - case Tree.Leaf{x}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TEq{}, t, l, c}}, - rest, e)) - case Tree.Stmt{x, y, z}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TEq{}, t, l, c}}, - rest, e)) - case Tree.NNil{}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TEq{}, t, l, c}}, - rest, e)) - case Tree.NCons{x, y}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TEq{}, t, l, c}}, - rest, e)) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TBind{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{o, +kids, +cl}: - match o: - case Lex.Tok{+a, +ot, +b, +d}: - +e3 = inert.ok3(Tree.Leaf{Lex.Tok{Lex.TBind{}, t, l, c}}, Tree.Group{Lex.Tok{a, ot, b, - d}, kids, cl}, r2, e) - +ea = M1.inert.and_l(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TBind{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +eg = M1.inert.and_r(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TBind{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(r2), eg) - inert.ring.ps_lg(Lex.TBind{}, t, l, c, ot, kids, r2, seen, wins, ea, ek, - inert.ring.plus_scan(kids, None{}, wins, ek), - inert.ring.plus_scan(r2, Ring.or_tok(seen, Ring.call_drop(String.eq(ot, "("), - Lex.TBind{}, t, l, c, kids, wins)), wins, M1.inert.and_r(D1.inert.ok(kids), - D1.inert.ok(r2), eg))) - case Tree.Leaf{x}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TBind{}, t, l, c}}, - rest, e)) - case Tree.Stmt{x, y, z}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TBind{}, t, l, c}}, - rest, e)) - case Tree.NNil{}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TBind{}, t, l, c}}, - rest, e)) - case Tree.NCons{x, y}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TBind{}, t, l, c}}, - rest, e)) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TArrow{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{o, +kids, +cl}: - match o: - case Lex.Tok{+a, +ot, +b, +d}: - +e3 = inert.ok3(Tree.Leaf{Lex.Tok{Lex.TArrow{}, t, l, c}}, Tree.Group{Lex.Tok{a, ot, b, - d}, kids, cl}, r2, e) - +ea = M1.inert.and_l(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TArrow{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +eg = M1.inert.and_r(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TArrow{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(r2), eg) - inert.ring.ps_lg(Lex.TArrow{}, t, l, c, ot, kids, r2, seen, wins, ea, ek, - inert.ring.plus_scan(kids, None{}, wins, ek), - inert.ring.plus_scan(r2, Ring.or_tok(seen, Ring.call_drop(String.eq(ot, "("), - Lex.TArrow{}, t, l, c, kids, wins)), wins, M1.inert.and_r(D1.inert.ok(kids), - D1.inert.ok(r2), eg))) - case Tree.Leaf{x}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TArrow{}, t, l, c}}, - rest, e)) - case Tree.Stmt{x, y, z}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TArrow{}, t, l, c}}, - rest, e)) - case Tree.NNil{}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TArrow{}, t, l, c}}, - rest, e)) - case Tree.NCons{x, y}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TArrow{}, t, l, c}}, - rest, e)) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TLam{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{o, +kids, +cl}: - match o: - case Lex.Tok{+a, +ot, +b, +d}: - +e3 = inert.ok3(Tree.Leaf{Lex.Tok{Lex.TLam{}, t, l, c}}, Tree.Group{Lex.Tok{a, ot, b, - d}, kids, cl}, r2, e) - +ea = M1.inert.and_l(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TLam{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +eg = M1.inert.and_r(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TLam{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(r2), eg) - inert.ring.ps_lg(Lex.TLam{}, t, l, c, ot, kids, r2, seen, wins, ea, ek, - inert.ring.plus_scan(kids, None{}, wins, ek), - inert.ring.plus_scan(r2, Ring.or_tok(seen, Ring.call_drop(String.eq(ot, "("), - Lex.TLam{}, t, l, c, kids, wins)), wins, M1.inert.and_r(D1.inert.ok(kids), - D1.inert.ok(r2), eg))) - case Tree.Leaf{x}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TLam{}, t, l, c}}, - rest, e)) - case Tree.Stmt{x, y, z}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TLam{}, t, l, c}}, - rest, e)) - case Tree.NNil{}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TLam{}, t, l, c}}, - rest, e)) - case Tree.NCons{x, y}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TLam{}, t, l, c}}, - rest, e)) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TAll{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{o, +kids, +cl}: - match o: - case Lex.Tok{+a, +ot, +b, +d}: - +e3 = inert.ok3(Tree.Leaf{Lex.Tok{Lex.TAll{}, t, l, c}}, Tree.Group{Lex.Tok{a, ot, b, - d}, kids, cl}, r2, e) - +ea = M1.inert.and_l(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TAll{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +eg = M1.inert.and_r(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TAll{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(r2), eg) - inert.ring.ps_lg(Lex.TAll{}, t, l, c, ot, kids, r2, seen, wins, ea, ek, - inert.ring.plus_scan(kids, None{}, wins, ek), - inert.ring.plus_scan(r2, Ring.or_tok(seen, Ring.call_drop(String.eq(ot, "("), - Lex.TAll{}, t, l, c, kids, wins)), wins, M1.inert.and_r(D1.inert.ok(kids), - D1.inert.ok(r2), eg))) - case Tree.Leaf{x}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TAll{}, t, l, c}}, - rest, e)) - case Tree.Stmt{x, y, z}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TAll{}, t, l, c}}, - rest, e)) - case Tree.NNil{}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TAll{}, t, l, c}}, - rest, e)) - case Tree.NCons{x, y}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TAll{}, t, l, c}}, - rest, e)) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TAmp{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{o, +kids, +cl}: - match o: - case Lex.Tok{+a, +ot, +b, +d}: - +e3 = inert.ok3(Tree.Leaf{Lex.Tok{Lex.TAmp{}, t, l, c}}, Tree.Group{Lex.Tok{a, ot, b, - d}, kids, cl}, r2, e) - +ea = M1.inert.and_l(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TAmp{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +eg = M1.inert.and_r(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TAmp{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(r2), eg) - inert.ring.ps_lg(Lex.TAmp{}, t, l, c, ot, kids, r2, seen, wins, ea, ek, - inert.ring.plus_scan(kids, None{}, wins, ek), - inert.ring.plus_scan(r2, Ring.or_tok(seen, Ring.call_drop(String.eq(ot, "("), - Lex.TAmp{}, t, l, c, kids, wins)), wins, M1.inert.and_r(D1.inert.ok(kids), - D1.inert.ok(r2), eg))) - case Tree.Leaf{x}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TAmp{}, t, l, c}}, - rest, e)) - case Tree.Stmt{x, y, z}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TAmp{}, t, l, c}}, - rest, e)) - case Tree.NNil{}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TAmp{}, t, l, c}}, - rest, e)) - case Tree.NCons{x, y}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TAmp{}, t, l, c}}, - rest, e)) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TOpen{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{o, +kids, +cl}: - match o: - case Lex.Tok{+a, +ot, +b, +d}: - +e3 = inert.ok3(Tree.Leaf{Lex.Tok{Lex.TOpen{}, t, l, c}}, Tree.Group{Lex.Tok{a, ot, b, - d}, kids, cl}, r2, e) - +ea = M1.inert.and_l(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TOpen{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +eg = M1.inert.and_r(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TOpen{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(r2), eg) - inert.ring.ps_lg(Lex.TOpen{}, t, l, c, ot, kids, r2, seen, wins, ea, ek, - inert.ring.plus_scan(kids, None{}, wins, ek), - inert.ring.plus_scan(r2, Ring.or_tok(seen, Ring.call_drop(String.eq(ot, "("), - Lex.TOpen{}, t, l, c, kids, wins)), wins, M1.inert.and_r(D1.inert.ok(kids), - D1.inert.ok(r2), eg))) - case Tree.Leaf{x}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TOpen{}, t, l, c}}, - rest, e)) - case Tree.Stmt{x, y, z}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TOpen{}, t, l, c}}, - rest, e)) - case Tree.NNil{}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TOpen{}, t, l, c}}, - rest, e)) - case Tree.NCons{x, y}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TOpen{}, t, l, c}}, - rest, e)) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TClose{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{o, +kids, +cl}: - match o: - case Lex.Tok{+a, +ot, +b, +d}: - +e3 = inert.ok3(Tree.Leaf{Lex.Tok{Lex.TClose{}, t, l, c}}, Tree.Group{Lex.Tok{a, ot, b, - d}, kids, cl}, r2, e) - +ea = M1.inert.and_l(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TClose{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +eg = M1.inert.and_r(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TClose{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(r2), eg) - inert.ring.ps_lg(Lex.TClose{}, t, l, c, ot, kids, r2, seen, wins, ea, ek, - inert.ring.plus_scan(kids, None{}, wins, ek), - inert.ring.plus_scan(r2, Ring.or_tok(seen, Ring.call_drop(String.eq(ot, "("), - Lex.TClose{}, t, l, c, kids, wins)), wins, M1.inert.and_r(D1.inert.ok(kids), - D1.inert.ok(r2), eg))) - case Tree.Leaf{x}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TClose{}, t, l, c}}, - rest, e)) - case Tree.Stmt{x, y, z}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TClose{}, t, l, c}}, - rest, e)) - case Tree.NNil{}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TClose{}, t, l, c}}, - rest, e)) - case Tree.NCons{x, y}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TClose{}, t, l, c}}, - rest, e)) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TComma{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{o, +kids, +cl}: - match o: - case Lex.Tok{+a, +ot, +b, +d}: - +e3 = inert.ok3(Tree.Leaf{Lex.Tok{Lex.TComma{}, t, l, c}}, Tree.Group{Lex.Tok{a, ot, b, - d}, kids, cl}, r2, e) - +ea = M1.inert.and_l(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TComma{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +eg = M1.inert.and_r(D1.inert.ok(Tree.Leaf{Lex.Tok{Lex.TComma{}, t, l, c}}), - Bool.and(D1.inert.ok(kids), D1.inert.ok(r2)), e3) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(r2), eg) - inert.ring.ps_lg(Lex.TComma{}, t, l, c, ot, kids, r2, seen, wins, ea, ek, - inert.ring.plus_scan(kids, None{}, wins, ek), - inert.ring.plus_scan(r2, Ring.or_tok(seen, Ring.call_drop(String.eq(ot, "("), - Lex.TComma{}, t, l, c, kids, wins)), wins, M1.inert.and_r(D1.inert.ok(kids), - D1.inert.ok(r2), eg))) - case Tree.Leaf{x}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TComma{}, t, l, c}}, - rest, e)) - case Tree.Stmt{x, y, z}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TComma{}, t, l, c}}, - rest, e)) - case Tree.NNil{}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TComma{}, t, l, c}}, - rest, e)) - case Tree.NCons{x, y}: - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TComma{}, t, l, c}}, - rest, e)) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.Group{+o, +kids, +cl}: - inert.ring.or2(Ring.plus_scan(D1.inert.node(kids), None{}, wins), Ring.plus_scan(kids, None{}, wins), - Ring.plus_scan(D1.inert.node(rest), Ring.or_tok(seen, Ring.drop_in(D1.inert.node(kids), wins)), wins), - Ring.plus_scan(rest, Ring.or_tok(seen, Ring.drop_in(kids, wins)), wins), - inert.ring.plus_scan(kids, None{}, wins, inert.ok_h(Tree.Group{o, kids, cl}, rest, e)), - Equal.trans(Maybe<&2, Lex.Tok>, Ring.plus_scan(D1.inert.node(rest), Ring.or_tok(seen, - Ring.drop_in(D1.inert.node(kids), wins)), wins), Ring.plus_scan(D1.inert.node(rest), - Ring.or_tok(seen, Ring.drop_in(kids, wins)), wins), Ring.plus_scan(rest, Ring.or_tok(seen, - Ring.drop_in(kids, wins)), wins), - Equal.cong(Maybe<&2, Lex.Tok>, Maybe<&2, Lex.Tok>, z => Ring.plus_scan(D1.inert.node(rest), - Ring.or_tok(seen, z), wins), Ring.drop_in(D1.inert.node(kids), wins), Ring.drop_in(kids, wins), - inert.ring.drop_in(kids, wins, inert.ok_h(Tree.Group{o, kids, cl}, rest, e))), - inert.ring.plus_scan(rest, Ring.or_tok(seen, Ring.drop_in(kids, wins)), wins, - inert.ok_t(Tree.Group{o, kids, cl}, rest, e)))) - case Tree.Stmt{+sk, +kids, +body}: - +es = inert.ok_h(Tree.Stmt{sk, kids, body}, rest, e) - inert.ring.or2(Ring.plus_scan(D1.inert.node(kids), None{}, wins), Ring.plus_scan(kids, None{}, wins), - Ring.or_tok(Ring.plus_scan(D1.inert.node(body), None{}, wins), Ring.plus_scan(D1.inert.node(rest), - seen, wins)), Ring.or_tok(Ring.plus_scan(body, None{}, wins), Ring.plus_scan(rest, seen, wins)), - inert.ring.plus_scan(kids, None{}, wins, M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(body), es)), - inert.ring.or2(Ring.plus_scan(D1.inert.node(body), None{}, wins), Ring.plus_scan(body, None{}, wins), - Ring.plus_scan(D1.inert.node(rest), seen, wins), Ring.plus_scan(rest, seen, wins), - inert.ring.plus_scan(body, None{}, wins, M1.inert.and_r(D1.inert.ok(kids), D1.inert.ok(body), es)), - inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.Stmt{sk, kids, body}, rest, e)))) - case Tree.NNil{}: inert.ring.plus_scan(rest, seen, wins, e) - case Tree.NCons{x, y}: inert.ring.plus_scan(rest, seen, wins, inert.ok_t(Tree.NCons{x, y}, rest, e)) - case Tree.Leaf{tok}: {==} - case Tree.Group{o, gk, cl}: {==} - case Tree.Stmt{sk, sks, sb}: {==} - case Tree.NNil{}: {==} - -# every self-call argument that slides a window, the arguments read that way -law inert.ring.slides: - for as: List<&2, Tree.Node> - for +params: List<&2, String> - for +path: String - for +wins: List<&2, String> - for +e: {inert.noks(as) == True{} : Bool} - {Ring.slides(inert.nodes(as), params, path, wins) == Ring.slides(as, params, path, wins) : List<&2, F.Finding>} - -def inert.ring.slides(as, params, path, wins, e): - match as: - case Nil{}: {==} - case Con{+h, +rest}: - +sw = Ring.slot_wins(Maybe.default(&2, String, List.head(&2, String, params), ""), wins) - +pt = List.tail(&2, String, params) - +lhs = Ring.slides(inert.nodes(Con{h, rest}), params, path, wins) - %inert.ring.slides(rest, pt, path, wins, M1.inert.and_r(D1.inert.ok(h), inert.noks(rest), e)) : {lhs - == List.append(&2, F.Finding, Ring.slide(h, path, sw), _) : List<&2, F.Finding>} - %inert.ring.plus_scan(h, None{}, sw, M1.inert.and_l(D1.inert.ok(h), inert.noks(rest), e)) : {lhs - == List.append(&2, F.Finding, Ring.slide_at(_, path), Ring.slides(inert.nodes(rest), pt, path, wins)) - : List<&2, F.Finding>} - {==} - -# a name then a group: a self-call's sliding arguments, the group and the rest -# walked as they were -law inert.ring.lg: - for +b: Bool - for +t: String - for +o: String - for +self: String - for +params: List<&2, String> - for +path: String - for +wins: List<&2, String> - for +kids: Tree.Node - for +r2: Tree.Node - for +ef: {D1.inert.fits.go(b, t) == True{} : Bool} - for +es: {D1.inert.marked(self) == False{} : Bool} - for +ek: {D1.inert.ok(kids) == True{} : Bool} - for ik: {Ring.walk(D1.inert.node(kids), self, params, path, wins) == Ring.walk(kids, self, params, path, wins) - : List<&2, F.Finding>} - for ir: {Ring.walk(D1.inert.node(r2), self, params, path, wins) == Ring.walk(r2, self, params, path, wins) - : List<&2, F.Finding>} - {List.concat(&2, F.Finding, [Lazy.stop(List<&2, F.Finding>, Bool.not(Bool.and(String.eq(o, "("), - String.eq(D1.inert.keep(b, t), self))), [], _u => Ring.slides(Calls.args(D1.inert.node(kids)), params, path, - wins)), Ring.walk(D1.inert.node(kids), self, params, path, wins), Ring.walk(D1.inert.node(r2), self, params, - path, wins)]) - == List.concat(&2, F.Finding, [Lazy.stop(List<&2, F.Finding>, Bool.not(Bool.and(String.eq(o, "("), - String.eq(t, self))), [], _u => Ring.slides(Calls.args(kids), params, path, wins)), Ring.walk(kids, self, params, - path, wins), Ring.walk(r2, self, params, path, wins)]) : List<&2, F.Finding>} - -def inert.ring.lg(b, t, o, self, params, path, wins, kids, r2, ef, es, ek, ik, ir): - +aa = Calls.args(kids) - +hi = Ring.slides(Calls.args(D1.inert.node(kids)), params, path, wins) - +eh = Equal.trans(List<&2, F.Finding>, hi, Ring.slides(inert.nodes(aa), params, path, wins), Ring.slides(aa, params, - path, wins), Equal.cong(List<&2, Tree.Node>, List<&2, F.Finding>, z => Ring.slides(z, params, path, wins), - Calls.args(D1.inert.node(kids)), inert.nodes(aa), inert.args(kids)), - inert.ring.slides(aa, params, path, wins, inert.ok.args.go(kids, Tree.NNil{}, [], ek, {==}, {==}))) - inert.cat3(Lazy.stop(List<&2, F.Finding>, Bool.not(Bool.and(String.eq(o, "("), String.eq(D1.inert.keep(b, t), - self))), [], _u => hi), Lazy.stop(List<&2, F.Finding>, Bool.not(Bool.and(String.eq(o, "("), String.eq(t, self))), - [], _u => Ring.slides(aa, params, path, wins)), - Ring.walk(D1.inert.node(kids), self, params, path, wins), Ring.walk(kids, self, params, path, wins), - Ring.walk(D1.inert.node(r2), self, params, path, wins), Ring.walk(r2, self, params, path, wins), - inert.stop(Bool.not(Bool.and(String.eq(o, "("), String.eq(D1.inert.keep(b, t), self))), Bool.not(Bool.and( - String.eq(o, "("), String.eq(t, self))), hi, Ring.slides(aa, params, path, wins), - Equal.cong(Bool, Bool, z => Bool.not(Bool.and(String.eq(o, "("), z)), String.eq(D1.inert.keep(b, t), self), - String.eq(t, self), M1.inert.eq_keep(b, t, self, ef, es)), eh), - ik, ir) - -# ring's walk reads no string: a leaf's text is compared with the def's name, -# which no string equals -law inert.ring.walk: - for nn: Tree.Node - for +self: String - for +params: List<&2, String> - for +path: String - for +wins: List<&2, String> - for +es: {D1.inert.marked(self) == False{} : Bool} - for +e: {D1.inert.ok(nn) == True{} : Bool} - {Ring.walk(D1.inert.node(nn), self, params, path, wins) == Ring.walk(nn, self, params, path, wins) - : List<&2, F.Finding>} - -def inert.ring.walk(nn, self, params, path, wins, es, e): - match nn: - case Tree.NCons{h, +rest}: - match h: - case Tree.Leaf{tok}: - match tok: - case Lex.Tok{+k, +t, +l, +c}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{o, +kids, +cl}: - match o: - case Lex.Tok{+ok, +ot, +ol, +oc}: - +lf = {Tree.Leaf{Lex.Tok{k, t, l, c}} : Tree.Node} - +gp = {Tree.Group{Lex.Tok{ok, ot, ol, oc}, kids, cl} : Tree.Node} - +eg = inert.ok_t(lf, Tree.NCons{gp, r2}, e) - +ec = inert.ok_h(gp, r2, eg) - inert.ring.lg(D1.inert.blanks(k), t, ot, self, params, path, wins, kids, r2, - inert.ok_h(lf, Tree.NCons{gp, r2}, e), es, ec, - inert.ring.walk(kids, self, params, path, wins, es, ec), - inert.ring.walk(r2, self, params, path, wins, es, inert.ok_t(gp, r2, eg))) - case Tree.Leaf{x}: - inert.ring.walk(rest, self, params, path, wins, es, inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, - rest, e)) - case Tree.Stmt{x, y, z}: - inert.ring.walk(rest, self, params, path, wins, es, inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, - rest, e)) - case Tree.NNil{}: - inert.ring.walk(rest, self, params, path, wins, es, inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, - rest, e)) - case Tree.NCons{x, y}: - inert.ring.walk(rest, self, params, path, wins, es, inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, - rest, e)) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.Group{+o, +kids, +cl}: - inert.cat2(Ring.walk(D1.inert.node(kids), self, params, path, wins), Ring.walk(kids, self, params, path, - wins), Ring.walk(D1.inert.node(rest), self, params, path, wins), Ring.walk(rest, self, params, path, - wins), - inert.ring.walk(kids, self, params, path, wins, es, inert.ok_h(Tree.Group{o, kids, cl}, rest, e)), - inert.ring.walk(rest, self, params, path, wins, es, inert.ok_t(Tree.Group{o, kids, cl}, rest, e))) - case Tree.Stmt{+sk, +kids, +body}: - +es2 = inert.ok_h(Tree.Stmt{sk, kids, body}, rest, e) - inert.cat3(Ring.walk(D1.inert.node(kids), self, params, path, wins), Ring.walk(kids, self, params, path, - wins), Ring.walk(D1.inert.node(body), self, params, path, wins), Ring.walk(body, self, params, path, - wins), Ring.walk(D1.inert.node(rest), self, params, path, wins), Ring.walk(rest, self, params, path, - wins), - inert.ring.walk(kids, self, params, path, wins, es, M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(body), - es2)), - inert.ring.walk(body, self, params, path, wins, es, M1.inert.and_r(D1.inert.ok(kids), D1.inert.ok(body), - es2)), - inert.ring.walk(rest, self, params, path, wins, es, inert.ok_t(Tree.Stmt{sk, kids, body}, rest, e))) - case Tree.NNil{}: inert.ring.walk(rest, self, params, path, wins, es, e) - case Tree.NCons{x, y}: - inert.ring.walk(rest, self, params, path, wins, es, inert.ok_t(Tree.NCons{x, y}, rest, e)) - case Tree.Leaf{tok}: {==} - case Tree.Group{o, gk, cl}: {==} - case Tree.Stmt{sk, sks, sb}: {==} - case Tree.NNil{}: {==} - -# the top-level names of a chain read that way are the ones it had -law inert.ring.names_in: - for nn: Tree.Node - for +acc: List<&2, String> - {Ring.names_in(D1.inert.node(nn), acc) == Ring.names_in(nn, acc) : List<&2, String>} - -def inert.ring.names_in(nn, acc): - match nn: - case Tree.NCons{h, +rest}: - match h: - case Tree.Leaf{tok}: - match tok: - case Lex.Tok{k, +t, l, c}: - match k: - case Lex.TName{}: - Equal.cong(List<&2, String>, List<&2, String>, z => t <> z, Ring.names_in(D1.inert.node(rest), - acc), Ring.names_in(rest, acc), inert.ring.names_in(rest, acc)) - case Lex.TUpper{}: inert.ring.names_in(rest, acc) - case Lex.TDotted{}: inert.ring.names_in(rest, acc) - case Lex.TWild{}: inert.ring.names_in(rest, acc) - case Lex.TKey{}: inert.ring.names_in(rest, acc) - case Lex.TNum{}: inert.ring.names_in(rest, acc) - case Lex.TStr{}: inert.ring.names_in(rest, acc) - case Lex.TChar{}: inert.ring.names_in(rest, acc) - case Lex.TComment{}: inert.ring.names_in(rest, acc) - case Lex.TSpace{}: inert.ring.names_in(rest, acc) - case Lex.TNewline{}: inert.ring.names_in(rest, acc) - case Lex.TOp{}: inert.ring.names_in(rest, acc) - case Lex.TColon{}: inert.ring.names_in(rest, acc) - case Lex.TEq{}: inert.ring.names_in(rest, acc) - case Lex.TBind{}: inert.ring.names_in(rest, acc) - case Lex.TArrow{}: inert.ring.names_in(rest, acc) - case Lex.TLam{}: inert.ring.names_in(rest, acc) - case Lex.TAll{}: inert.ring.names_in(rest, acc) - case Lex.TAmp{}: inert.ring.names_in(rest, acc) - case Lex.TOpen{}: inert.ring.names_in(rest, acc) - case Lex.TClose{}: inert.ring.names_in(rest, acc) - case Lex.TComma{}: inert.ring.names_in(rest, acc) - case Tree.Group{o, gk, cl}: inert.ring.names_in(rest, acc) - case Tree.Stmt{sk, sks, sb}: inert.ring.names_in(rest, acc) - case Tree.NNil{}: inert.ring.names_in(rest, acc) - case Tree.NCons{x, y}: inert.ring.names_in(rest, acc) - case Tree.Leaf{tok}: {==} - case Tree.Group{o, gk, cl}: {==} - case Tree.Stmt{sk, sks, sb}: {==} - case Tree.NNil{}: {==} - -# the scrutinees of a statement read that way are the ones it had: only a -# keyword's text is read -law inert.ring.match_names: - for kids: Tree.Node - for +acc: List<&2, String> - {Ring.match_names(D1.inert.node(kids), acc) == Ring.match_names(kids, acc) : List<&2, String>} - -def inert.ring.match_names(kids, acc): - match kids: - case Tree.NCons{h, +rest}: - match h: - case Tree.Leaf{tok}: - match tok: - case Lex.Tok{k, +kw, l, c}: - match k: - case Lex.TName{}: {==} - case Lex.TUpper{}: {==} - case Lex.TDotted{}: {==} - case Lex.TWild{}: {==} - case Lex.TKey{}: - Equal.cong(List<&2, String>, List<&2, String>, z => Lazy.stop(List<&2, String>, - Bool.not(String.eq(kw, "match")), acc, _u => z), Ring.names_in(D1.inert.node(rest), acc), - Ring.names_in(rest, acc), inert.ring.names_in(rest, acc)) - case Lex.TNum{}: {==} - case Lex.TStr{}: {==} - case Lex.TChar{}: {==} - case Lex.TComment{}: {==} - case Lex.TSpace{}: {==} - case Lex.TNewline{}: {==} - case Lex.TOp{}: {==} - case Lex.TColon{}: {==} - case Lex.TEq{}: {==} - case Lex.TBind{}: {==} - case Lex.TArrow{}: {==} - case Lex.TLam{}: {==} - case Lex.TAll{}: {==} - case Lex.TAmp{}: {==} - case Lex.TOpen{}: {==} - case Lex.TClose{}: {==} - case Lex.TComma{}: {==} - case Tree.Group{o, gk, cl}: {==} - case Tree.Stmt{sk, sks, sb}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{tok}: {==} - case Tree.Group{o, gk, cl}: {==} - case Tree.Stmt{sk, sks, sb}: {==} - case Tree.NNil{}: {==} - -# three chains matched in turn, each read as it did -law inert.ring.m3: - for +kids: Tree.Node - for +body: Tree.Node - for +rest: Tree.Node - for +a0: List<&2, String> - for ik: {Ring.matched(D1.inert.node(kids), a0) == Ring.matched(kids, a0) : List<&2, String>} - for ib: {Ring.matched(D1.inert.node(body), Ring.matched(kids, a0)) == Ring.matched(body, Ring.matched(kids, a0)) - : List<&2, String>} - for ir: {Ring.matched(D1.inert.node(rest), Ring.matched(body, Ring.matched(kids, a0))) == Ring.matched(rest, - Ring.matched(body, Ring.matched(kids, a0))) : List<&2, String>} - {Ring.matched(D1.inert.node(rest), Ring.matched(D1.inert.node(body), Ring.matched(D1.inert.node(kids), a0))) - == Ring.matched(rest, Ring.matched(body, Ring.matched(kids, a0))) : List<&2, String>} - -def inert.ring.m3(kids, body, rest, a0, ik, ib, ir): - +lhs = Ring.matched(D1.inert.node(rest), Ring.matched(D1.inert.node(body), Ring.matched(D1.inert.node(kids), - a0))) - %ir : {lhs == _ : List<&2, String>} - %ib : {lhs == Ring.matched(D1.inert.node(rest), _) : List<&2, String>} - %ik : {lhs == Ring.matched(D1.inert.node(rest), Ring.matched(D1.inert.node(body), _)) : List<&2, String>} - {==} - -# the names a def matches, its body read that way, are the ones it matched -law inert.ring.matched: - for nn: Tree.Node - for +acc: List<&2, String> - {Ring.matched(D1.inert.node(nn), acc) == Ring.matched(nn, acc) : List<&2, String>} - -def inert.ring.matched(nn, acc): - match nn: - case Tree.NCons{h, +rest}: - match h: - case Tree.Stmt{sk, +kids, +body}: - match sk: - case Tree.STerm{}: - %inert.ring.match_names(kids, acc) : {Ring.matched(D1.inert.node(Tree.NCons{Tree.Stmt{Tree.STerm{}, - kids, body}, rest}), acc) == Ring.matched(rest, Ring.matched(body, Ring.matched(kids, _))) - : List<&2, String>} - inert.ring.m3(kids, body, rest, Ring.match_names(D1.inert.node(kids), acc), - inert.ring.matched(kids, Ring.match_names(D1.inert.node(kids), acc)), - inert.ring.matched(body, Ring.matched(kids, Ring.match_names(D1.inert.node(kids), acc))), - inert.ring.matched(rest, Ring.matched(body, Ring.matched(kids, Ring.match_names(D1.inert.node(kids), - acc))))) - case Tree.SDef{}: inert.ring.m3(kids, body, rest, acc, inert.ring.matched(kids, acc), - inert.ring.matched(body, Ring.matched(kids, acc)), inert.ring.matched(rest, Ring.matched(body, - Ring.matched(kids, acc)))) - case Tree.SType{}: inert.ring.m3(kids, body, rest, acc, inert.ring.matched(kids, acc), - inert.ring.matched(body, Ring.matched(kids, acc)), inert.ring.matched(rest, Ring.matched(body, - Ring.matched(kids, acc)))) - case Tree.SLaw{}: inert.ring.m3(kids, body, rest, acc, inert.ring.matched(kids, acc), - inert.ring.matched(body, Ring.matched(kids, acc)), inert.ring.matched(rest, Ring.matched(body, - Ring.matched(kids, acc)))) - case Tree.SImport{}: inert.ring.m3(kids, body, rest, acc, inert.ring.matched(kids, acc), - inert.ring.matched(body, Ring.matched(kids, acc)), inert.ring.matched(rest, Ring.matched(body, - Ring.matched(kids, acc)))) - case Tree.SCase{}: inert.ring.m3(kids, body, rest, acc, inert.ring.matched(kids, acc), - inert.ring.matched(body, Ring.matched(kids, acc)), inert.ring.matched(rest, Ring.matched(body, - Ring.matched(kids, acc)))) - case Tree.SFor{}: inert.ring.m3(kids, body, rest, acc, inert.ring.matched(kids, acc), - inert.ring.matched(body, Ring.matched(kids, acc)), inert.ring.matched(rest, Ring.matched(body, - Ring.matched(kids, acc)))) - case Tree.SLet{}: inert.ring.m3(kids, body, rest, acc, inert.ring.matched(kids, acc), - inert.ring.matched(body, Ring.matched(kids, acc)), inert.ring.matched(rest, Ring.matched(body, - Ring.matched(kids, acc)))) - case Tree.Group{o, +kids, cl}: - %inert.ring.matched(rest, Ring.matched(kids, acc)) : {Ring.matched(D1.inert.node(rest), - Ring.matched(D1.inert.node(kids), acc)) == _ : List<&2, String>} - %inert.ring.matched(kids, acc) : {Ring.matched(D1.inert.node(rest), Ring.matched(D1.inert.node(kids), - acc)) == Ring.matched(D1.inert.node(rest), _) : List<&2, String>} - {==} - case Tree.Leaf{tok}: inert.ring.matched(rest, acc) - case Tree.NNil{}: inert.ring.matched(rest, acc) - case Tree.NCons{x, y}: inert.ring.matched(rest, acc) - case Tree.Leaf{tok}: {==} - case Tree.Group{o, gk, cl}: {==} - case Tree.Stmt{sk, sks, sb}: {==} - case Tree.NNil{}: {==} - -# the windows of a def read that way are the ones they were -law inert.ring.windows: - for +sig: Tree.Node - for +body: Tree.Node - {Ring.windows(D1.inert.node(sig), D1.inert.node(body)) == Ring.windows(sig, body) : List<&2, String>} - -def inert.ring.windows(sig, body): - +lhs = Ring.windows(D1.inert.node(sig), D1.inert.node(body)) - %inert.names(sig) : {lhs == Ring.windows.go(_, Ring.matched(body, []), []) : List<&2, String>} - %inert.ring.matched(body, []) : {lhs == Ring.windows.go(Calls.names(D1.inert.node(sig)), _, []) : List<&2, String>} - {==} - -# ring over the defs read that way reports what it reports over the defs -law inert.ring.go: - for ds: List<&2, Calls.Def> - for +path: String - for +acc: List<&2, List<&2, F.Finding>> - for +e: {inert.dok(ds) == True{} : Bool} - {Ring.check.go(inert.defs(ds), path, acc) == Ring.check.go(ds, path, acc) : List<&2, F.Finding>} - -def inert.ring.go(ds, path, acc, e): - match ds: - case Nil{}: {==} - case Con{Calls.Def{+name, +sig, +body}, +rest}: - +en = inert.dok.head(name, sig, body, rest, e) - +eb = inert.dok.body(name, sig, body, rest, e) - +ib = D1.inert.node(body) - +isg = D1.inert.node(sig) - +wi = Ring.walk(ib, name, Calls.names(isg), path, Ring.windows(isg, ib)) - +wo = Ring.walk(body, name, Calls.names(sig), path, Ring.windows(sig, body)) - +lst = Lazy.stop(List<&2, F.Finding>, Bool.not(Bool.and(Calls.calls(ib, name), Bool.not(Calls.exempt(path, - isg)))), [], _u => wi) - +ew = Equal.trans(List<&2, F.Finding>, wi, Ring.walk(ib, name, Calls.names(sig), path, Ring.windows(sig, body)), - wo, Equal.trans(List<&2, F.Finding>, wi, Ring.walk(ib, name, Calls.names(sig), path, Ring.windows(isg, ib)), - Ring.walk(ib, name, Calls.names(sig), path, Ring.windows(sig, body)), - Equal.cong(List<&2, String>, List<&2, F.Finding>, z => Ring.walk(ib, name, z, path, Ring.windows(isg, ib)), - Calls.names(isg), Calls.names(sig), inert.names(sig)), - Equal.cong(List<&2, String>, List<&2, F.Finding>, z => Ring.walk(ib, name, Calls.names(sig), path, z), - Ring.windows(isg, ib), Ring.windows(sig, body), inert.ring.windows(sig, body))), - inert.ring.walk(body, name, Calls.names(sig), path, Ring.windows(sig, body), en, eb)) - %inert.stop(Bool.not(Bool.and(Calls.calls(ib, name), Bool.not(Calls.exempt(path, isg)))), - Bool.not(Bool.and(Calls.calls(body, name), Bool.not(Calls.exempt(path, sig)))), wi, wo, - inert.gate(path, name, sig, body, en, eb), ew) : - {Ring.check.go(inert.defs(rest), path, lst <> acc) == Ring.check.go(rest, path, _ <> acc) - : List<&2, F.Finding>} - inert.ring.go(rest, path, lst <> acc, inert.dok.rest(name, sig, body, rest, e)) - -def Laws.inert_ring(path, _text, tree, _toks, _bound, _items, _text2, tree2, _toks2, _bound2, _items2, ok, ok2, e): - M1.inert.via(Tree.Node, z => Ring.check.go(Calls.defs(z), path, []), tree, tree2, D1.inert.node(tree), - D1.inert.node(tree2), - inert.lift(z => Ring.check.go(z, path, []), tree, inert.ring.go(Calls.defs(tree), path, [], inert.dok.of(tree, - ok))), - inert.lift(z => Ring.check.go(z, path, []), tree2, inert.ring.go(Calls.defs(tree2), path, [], - inert.dok.of(tree2, ok2))), - e) - -# Inert text, arms and on (laws/CodeRulesIgnoreCommentsAndStrings.bend): the tree rules not over a -# file's defs, on the lemmas above. - -# arms -# ---- - -# a case statement's pattern read that way is the pattern it had, read that way -law inert.pattern: - for kids: Tree.Node - {T.pattern(D1.inert.node(kids)) == D1.inert.node(T.pattern(kids)) : Tree.Node} - -def inert.pattern(kids): - match kids: - case Tree.NCons{h, rest}: {==} - case Tree.Leaf{tok}: {==} - case Tree.Group{o, gk, cl}: {==} - case Tree.Stmt{sk, sks, sb}: {==} - case Tree.NNil{}: {==} - -# a from-k-up arm whose binder is read that way: a binder is never cut -law inert.arms.bpick: - for x: Bool - for b: Lex.TokKind - for +p: String - for -f: String -> Arms.Arm - {Bool.pick(Arms.Arm, Bool.and(x, Arms.binder(b)), f(D1.inert.keep(D1.inert.blanks(b), p)), Arms.AOther{}) - == Bool.pick(Arms.Arm, Bool.and(x, Arms.binder(b)), f(p), Arms.AOther{}) : Arms.Arm} - -def inert.arms.bpick(x, b, _p, _f): - match x: - case False{}: {==} - case True{}: - match b: - case Lex.TName{}: {==} - case Lex.TUpper{}: {==} - case Lex.TDotted{}: {==} - case Lex.TWild{}: {==} - case Lex.TKey{}: {==} - case Lex.TNum{}: {==} - case Lex.TStr{}: {==} - case Lex.TChar{}: {==} - case Lex.TComment{}: {==} - case Lex.TSpace{}: {==} - case Lex.TNewline{}: {==} - case Lex.TOp{}: {==} - case Lex.TColon{}: {==} - case Lex.TEq{}: {==} - case Lex.TBind{}: {==} - case Lex.TArrow{}: {==} - case Lex.TLam{}: {==} - case Lex.TAll{}: {==} - case Lex.TAmp{}: {==} - case Lex.TOpen{}: {==} - case Lex.TClose{}: {==} - case Lex.TComma{}: {==} - -# what follows a Nat literal read that way makes the arm it made -law inert.arms.shape: - for rest: Tree.Node - for +kk: U32 - for +tt: String - for +line: U32 - for +col: U32 - {Arms.lit.shape(D1.inert.node(rest), kk, tt, line, col) == Arms.lit.shape(rest, kk, tt, line, col) : Arms.Arm} - -def inert.arms.shape(rest, kk, tt, line, col): - match rest: - case Tree.NCons{h, +r}: - match h: - case Tree.Leaf{tok}: - match tok: - case Lex.Tok{k, +o, l, c}: - match k: - case Lex.TName{}: {==} - case Lex.TUpper{}: {==} - case Lex.TDotted{}: {==} - case Lex.TWild{}: {==} - case Lex.TKey{}: {==} - case Lex.TNum{}: {==} - case Lex.TStr{}: {==} - case Lex.TChar{}: {==} - case Lex.TComment{}: {==} - case Lex.TSpace{}: {==} - case Lex.TNewline{}: {==} - case Lex.TOp{}: - match r: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Leaf{tok2}: - match tok2: - case Lex.Tok{+b, +p, l2, c2}: - match r2: - case Tree.NCons{h3, r3}: - match h3: - case Tree.Leaf{tok3}: - match tok3: - case Lex.Tok{k3, s, l3, c3}: - match k3: - case Lex.TName{}: {==} - case Lex.TUpper{}: {==} - case Lex.TDotted{}: {==} - case Lex.TWild{}: {==} - case Lex.TKey{}: {==} - case Lex.TNum{}: {==} - case Lex.TStr{}: {==} - case Lex.TChar{}: {==} - case Lex.TComment{}: {==} - case Lex.TSpace{}: {==} - case Lex.TNewline{}: {==} - case Lex.TOp{}: {==} - case Lex.TColon{}: - inert.arms.bpick(String.eq(o, "+"), b, p, z => Arms.APlus{kk, - tt ++ "+" ++ z, line, col, T.width(tt)}) - case Lex.TEq{}: {==} - case Lex.TBind{}: {==} - case Lex.TArrow{}: {==} - case Lex.TLam{}: {==} - case Lex.TAll{}: {==} - case Lex.TAmp{}: {==} - case Lex.TOpen{}: {==} - case Lex.TClose{}: {==} - case Lex.TComma{}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TColon{}: {==} - case Lex.TEq{}: {==} - case Lex.TBind{}: {==} - case Lex.TArrow{}: {==} - case Lex.TLam{}: {==} - case Lex.TAll{}: {==} - case Lex.TAmp{}: {==} - case Lex.TOpen{}: {==} - case Lex.TClose{}: {==} - case Lex.TComma{}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - -# a Nat literal's arm, what follows it read that way -law inert.arms.lit: - for mm: Maybe<&2, U32> - for +tt: String - for +rest: Tree.Node - for +line: U32 - for +col: U32 - {Arms.lit.of(mm, tt, D1.inert.node(rest), line, col) == Arms.lit.of(mm, tt, rest, line, col) : Arms.Arm} - -def inert.arms.lit(mm, tt, rest, line, col): - match mm: - case None{}: {==} - case Some{+k}: inert.arms.shape(rest, k, tt, line, col) - -# a case pattern read that way makes the arm it made: a Nat literal, a capital -# name and a binder are never cut -law inert.arms.arm: - for pat: Tree.Node - {Arms.arm(D1.inert.node(pat)) == Arms.arm(pat) : Arms.Arm} - -def inert.arms.arm(pat): - match pat: - case Tree.NCons{h, +rest}: - match h: - case Tree.Leaf{tok}: - match tok: - case Lex.Tok{k, +t, +l, +c}: - match k: - case Lex.TName{}: {==} - case Lex.TUpper{}: - match rest: - case Tree.NCons{g, more}: - match g: - case Tree.Group{op, gk, cl}: - match gk: - case Tree.NCons{h3, +r3}: - match h3: - case Tree.Leaf{tok3}: - match tok3: - case Lex.Tok{+k3, +p, l2, c2}: - match r3: - case Tree.NNil{}: - inert.arms.bpick(String.eq(t, "Succ"), k3, p, z => Arms.APlus{1, - "Succ{" ++ z ++ "}", l, c, 4}) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.Leaf{x}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TDotted{}: {==} - case Lex.TWild{}: {==} - case Lex.TKey{}: {==} - case Lex.TNum{}: inert.arms.lit(T.nat_value(t), t, rest, l, c) - case Lex.TStr{}: {==} - case Lex.TChar{}: {==} - case Lex.TComment{}: {==} - case Lex.TSpace{}: {==} - case Lex.TNewline{}: {==} - case Lex.TOp{}: {==} - case Lex.TColon{}: {==} - case Lex.TEq{}: {==} - case Lex.TBind{}: {==} - case Lex.TArrow{}: {==} - case Lex.TLam{}: {==} - case Lex.TAll{}: {==} - case Lex.TAmp{}: {==} - case Lex.TOpen{}: {==} - case Lex.TClose{}: {==} - case Lex.TComma{}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - -# a match statement read that way has one scrutinee as it had: only a -# keyword's text is read -law inert.arms.single: - for kids: Tree.Node - {Arms.single(D1.inert.node(kids)) == Arms.single(kids) : Bool} - -def inert.arms.single(kids): - match kids: - case Tree.NCons{h, +r}: - match h: - case Tree.Leaf{tok}: - match tok: - case Lex.Tok{k, t, l, c}: - match k: - case Lex.TName{}: {==} - case Lex.TUpper{}: {==} - case Lex.TDotted{}: {==} - case Lex.TWild{}: {==} - case Lex.TKey{}: - match r: - case Tree.NCons{x, +r2}: - match r2: - case Tree.NCons{h3, +r3}: - match h3: - case Tree.Leaf{tok3}: - match tok3: - case Lex.Tok{k3, s, l3, c3}: - match k3: - case Lex.TName{}: {==} - case Lex.TUpper{}: {==} - case Lex.TDotted{}: {==} - case Lex.TWild{}: {==} - case Lex.TKey{}: {==} - case Lex.TNum{}: {==} - case Lex.TStr{}: {==} - case Lex.TChar{}: {==} - case Lex.TComment{}: {==} - case Lex.TSpace{}: {==} - case Lex.TNewline{}: {==} - case Lex.TOp{}: {==} - case Lex.TColon{}: - match r3: - case Tree.NNil{}: {==} - case Tree.Leaf{y}: {==} - case Tree.Group{y, z, w}: {==} - case Tree.Stmt{y, z, w}: {==} - case Tree.NCons{y, z}: {==} - case Lex.TEq{}: {==} - case Lex.TBind{}: {==} - case Lex.TArrow{}: {==} - case Lex.TLam{}: {==} - case Lex.TAll{}: {==} - case Lex.TAmp{}: {==} - case Lex.TOpen{}: {==} - case Lex.TClose{}: {==} - case Lex.TComma{}: {==} - case Tree.Group{y, z, w}: {==} - case Tree.Stmt{y, z, w}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{y, z}: {==} - case Tree.Leaf{y}: {==} - case Tree.Group{y, z, w}: {==} - case Tree.Stmt{y, z, w}: {==} - case Tree.NNil{}: {==} - case Tree.Leaf{y}: {==} - case Tree.Group{y, z, w}: {==} - case Tree.Stmt{y, z, w}: {==} - case Tree.NNil{}: {==} - case Lex.TNum{}: {==} - case Lex.TStr{}: {==} - case Lex.TChar{}: {==} - case Lex.TComment{}: {==} - case Lex.TSpace{}: {==} - case Lex.TNewline{}: {==} - case Lex.TOp{}: {==} - case Lex.TColon{}: {==} - case Lex.TEq{}: {==} - case Lex.TBind{}: {==} - case Lex.TArrow{}: {==} - case Lex.TLam{}: {==} - case Lex.TAll{}: {==} - case Lex.TAmp{}: {==} - case Lex.TOpen{}: {==} - case Lex.TClose{}: {==} - case Lex.TComma{}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - -# a case's arm, its tokens read that way -law inert.arms.case: - for +kids: Tree.Node - {Arms.arm(T.pattern(D1.inert.node(kids))) == Arms.arm(T.pattern(kids)) : Arms.Arm} - -def inert.arms.case(kids): - Equal.trans(Arms.Arm, Arms.arm(T.pattern(D1.inert.node(kids))), Arms.arm(D1.inert.node(T.pattern(kids))), - Arms.arm(T.pattern(kids)), Equal.cong(Tree.Node, Arms.Arm, z => Arms.arm(z), T.pattern(D1.inert.node(kids)), - D1.inert.node(T.pattern(kids)), inert.pattern(kids)), inert.arms.arm(T.pattern(kids))) - -# a match's arms read that way are judged as they were -law inert.arms.arms: - for body: Tree.Node - for +wide: Maybe<&2, Arms.Wide> - for +path: String - {Arms.arms(D1.inert.node(body), wide, path) == Arms.arms(body, wide, path) : List<&2, F.Finding>} - -def inert.arms.arms(body, wide, path): - match body: - case Tree.NCons{h, +rest}: - match h: - case Tree.Stmt{sk, +kids, b}: - match sk: - case Tree.SCase{}: - +ao = Arms.arm(T.pattern(kids)) - +ai = Arms.arm(T.pattern(D1.inert.node(kids))) - +lhs = Arms.verdict(wide, ai, path, Arms.arms(D1.inert.node(rest), Arms.widen(wide, ai), path)) - %inert.arms.arms(rest, Arms.widen(wide, ao), path) : {lhs == Arms.verdict(wide, ao, path, _) - : List<&2, F.Finding>} - %inert.arms.case(kids) : {lhs == Arms.verdict(wide, _, path, Arms.arms(D1.inert.node(rest), - Arms.widen(wide, _), path)) : List<&2, F.Finding>} - {==} - case Tree.SDef{}: inert.arms.arms(rest, wide, path) - case Tree.SType{}: inert.arms.arms(rest, wide, path) - case Tree.SLaw{}: inert.arms.arms(rest, wide, path) - case Tree.SImport{}: inert.arms.arms(rest, wide, path) - case Tree.SFor{}: inert.arms.arms(rest, wide, path) - case Tree.SLet{}: inert.arms.arms(rest, wide, path) - case Tree.STerm{}: inert.arms.arms(rest, wide, path) - case Tree.Leaf{x}: inert.arms.arms(rest, wide, path) - case Tree.Group{x, y, z}: inert.arms.arms(rest, wide, path) - case Tree.NNil{}: inert.arms.arms(rest, wide, path) - case Tree.NCons{x, y}: inert.arms.arms(rest, wide, path) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - -# arms reads no string: a Nat literal, `Succ`, `match` and `+` are read by -# their kind, and a binder by its -law inert.arms.walk: - for nn: Tree.Node - for +path: String - {Arms.walk(D1.inert.node(nn), path) == Arms.walk(nn, path) : List<&2, F.Finding>} - -def inert.arms.walk(nn, path): - match nn: - case Tree.NCons{h, +rest}: - match h: - case Tree.Stmt{sk, +kids, +body}: - inert.cat3(Lazy.stop(List<&2, F.Finding>, Bool.not(Arms.single(D1.inert.node(kids))), [], - _u => Arms.arms(D1.inert.node(body), None{}, path)), Lazy.stop(List<&2, F.Finding>, - Bool.not(Arms.single(kids)), [], _u => Arms.arms(body, None{}, path)), - Arms.walk(D1.inert.node(body), path), Arms.walk(body, path), Arms.walk(D1.inert.node(rest), path), - Arms.walk(rest, path), - inert.stop(Bool.not(Arms.single(D1.inert.node(kids))), Bool.not(Arms.single(kids)), - Arms.arms(D1.inert.node(body), None{}, path), Arms.arms(body, None{}, path), - Equal.cong(Bool, Bool, z => Bool.not(z), Arms.single(D1.inert.node(kids)), Arms.single(kids), - inert.arms.single(kids)), inert.arms.arms(body, None{}, path)), - inert.arms.walk(body, path), inert.arms.walk(rest, path)) - case Tree.Leaf{x}: inert.arms.walk(rest, path) - case Tree.Group{x, y, z}: inert.arms.walk(rest, path) - case Tree.NNil{}: inert.arms.walk(rest, path) - case Tree.NCons{x, y}: inert.arms.walk(rest, path) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - -def Laws.inert_arms(path, _text, tree, _toks, _bound, _items, _text2, tree2, _toks2, _bound2, _items2, _ok, _ok2, e): - M1.inert.via(Tree.Node, z => Arms.walk(z, path), tree, tree2, D1.inert.node(tree), D1.inert.node(tree2), - inert.arms.walk(tree, path), inert.arms.walk(tree2, path), e) - -# unused -# ------ - -# a token by where it stands only -def inert.canon(tok: Lex.Tok) -> Lex.Tok: - match tok: - case Lex.Tok{k, t, l, c}: Lex.Tok{Lex.TSpace{}, "", l, c} - -# the tokens, each by where it stands only -def inert.canons(ts: List<&2, Lex.Tok>) -> List<&2, Lex.Tok>: - match ts: - case Nil{}: Nil{} - case Con{h, t}: inert.canon(h) <> inert.canons(t) - -# a token read that way stands where it stood -law inert.canon_tok: - for tok: Lex.Tok - {inert.canon(D1.inert.tok(tok)) == inert.canon(tok) : Lex.Tok} - -def inert.canon_tok(tok): - match tok: - case Lex.Tok{k, t, l, c}: {==} - -# a group's close onto tokens that stand alike -law inert.canon_close: - for close: Maybe<&2, Lex.Tok> - for -x: List<&2, Lex.Tok> - for -y: List<&2, Lex.Tok> - for e: {inert.canons(x) == inert.canons(y) : List<&2, Lex.Tok>} - {inert.canons(Tree.leaf_close(close, x)) == inert.canons(Tree.leaf_close(close, y)) : List<&2, Lex.Tok>} - -def inert.canon_close(close, x, y, e): - match close: - case None{}: e - case Some{+tok}: - Equal.cong(List<&2, Lex.Tok>, List<&2, Lex.Tok>, z => inert.canon(tok) <> z, inert.canons(x), inert.canons(y), - e) - -# the leaves of a node read that way stand where they stood -law inert.canon_leaves: - for nn: Tree.Node - for -a: List<&2, Lex.Tok> - for -b: List<&2, Lex.Tok> - for e: {inert.canons(a) == inert.canons(b) : List<&2, Lex.Tok>} - {inert.canons(Tree.leaves.go(D1.inert.node(nn), a)) == inert.canons(Tree.leaves.go(nn, b)) : List<&2, Lex.Tok>} - -def inert.canon_leaves(nn, a, b, e): - match nn: - case Tree.Leaf{+tok}: - %inert.canon_tok(tok) : {inert.canon(D1.inert.tok(tok)) <> inert.canons(a) == _ <> inert.canons(b) - : List<&2, Lex.Tok>} - %e : {inert.canon(D1.inert.tok(tok)) <> inert.canons(a) == inert.canon(D1.inert.tok(tok)) <> _ - : List<&2, Lex.Tok>} - {==} - case Tree.Group{+open, kids, close}: - inert.canon_close(close, Tree.leaves.go(D1.inert.node(kids), open <> a), Tree.leaves.go(kids, open <> b), - inert.canon_leaves(kids, open <> a, open <> b, Equal.cong(List<&2, Lex.Tok>, List<&2, Lex.Tok>, - z => inert.canon(open) <> z, inert.canons(a), inert.canons(b), e))) - case Tree.Stmt{sk, kids, body}: - inert.canon_leaves(body, Tree.leaves.go(D1.inert.node(kids), a), Tree.leaves.go(kids, b), - inert.canon_leaves(kids, a, b, e)) - case Tree.NNil{}: e - case Tree.NCons{h, t}: - inert.canon_leaves(t, Tree.leaves.go(D1.inert.node(h), a), Tree.leaves.go(h, b), inert.canon_leaves(h, a, b, e)) - -# reversing, by where they stand -law inert.canon_rev: - for xs: List<&2, Lex.Tok> - for +acc: List<&2, Lex.Tok> - {inert.canons(List.reverse.go(&2, Lex.Tok, xs, acc)) == List.reverse.go(&2, Lex.Tok, inert.canons(xs), - inert.canons(acc)) : List<&2, Lex.Tok>} - -def inert.canon_rev(xs, acc): - match xs: - case Nil{}: {==} - case Con{+h, t}: inert.canon_rev(t, h <> acc) - -# the lines of tokens are the lines of where they stand -law inert.canon_lines: - for ts: List<&2, Lex.Tok> - for +acc: List<&2, U32> - {Unused.lines(ts, acc) == Unused.lines(inert.canons(ts), acc) : List<&2, U32>} - -def inert.canon_lines(ts, acc): - match ts: - case Nil{}: {==} - case Con{h, +t}: - match h: - case Lex.Tok{k, x, +l, c}: - Equal.cong(List<&2, U32>, List<&2, U32>, z => l <> z, Unused.lines(t, acc), - Unused.lines(inert.canons(t), acc), inert.canon_lines(t, acc)) - -# the lines of a node's leaves read that way are the lines they were -law inert.unused.lines: - for +kids: Tree.Node - for +acc: List<&2, U32> - {Unused.lines(Tree.leaves(D1.inert.node(kids)), acc) == Unused.lines(Tree.leaves(kids), acc) : List<&2, U32>} - -def inert.unused.lines(kids, acc): - +li = Tree.leaves.go(D1.inert.node(kids), []) - +lo = Tree.leaves.go(kids, []) - +ec = Equal.trans(List<&2, Lex.Tok>, inert.canons(List.reverse.go(&2, Lex.Tok, li, [])), List.reverse.go(&2, Lex.Tok, - inert.canons(li), []), inert.canons(List.reverse.go(&2, Lex.Tok, lo, [])), inert.canon_rev(li, []), - Equal.trans(List<&2, Lex.Tok>, List.reverse.go(&2, Lex.Tok, inert.canons(li), []), List.reverse.go(&2, Lex.Tok, - inert.canons(lo), []), inert.canons(List.reverse.go(&2, Lex.Tok, lo, [])), - Equal.cong(List<&2, Lex.Tok>, List<&2, Lex.Tok>, z => List.reverse.go(&2, Lex.Tok, z, []), inert.canons(li), - inert.canons(lo), inert.canon_leaves(kids, [], [], {==})), - Equal.sym(List<&2, Lex.Tok>, inert.canons(List.reverse.go(&2, Lex.Tok, lo, [])), List.reverse.go(&2, Lex.Tok, - inert.canons(lo), []), inert.canon_rev(lo, [])))) - Equal.trans(List<&2, U32>, Unused.lines(List.reverse.go(&2, Lex.Tok, li, []), acc), - Unused.lines(inert.canons(List.reverse.go(&2, Lex.Tok, li, [])), acc), - Unused.lines(List.reverse.go(&2, Lex.Tok, lo, []), acc), - inert.canon_lines(List.reverse.go(&2, Lex.Tok, li, []), acc), - Equal.trans(List<&2, U32>, Unused.lines(inert.canons(List.reverse.go(&2, Lex.Tok, li, [])), acc), - Unused.lines(inert.canons(List.reverse.go(&2, Lex.Tok, lo, [])), acc), - Unused.lines(List.reverse.go(&2, Lex.Tok, lo, []), acc), - Equal.cong(List<&2, Lex.Tok>, List<&2, U32>, z => Unused.lines(z, acc), - inert.canons(List.reverse.go(&2, Lex.Tok, li, [])), inert.canons(List.reverse.go(&2, Lex.Tok, lo, [])), ec), - Equal.sym(List<&2, U32>, Unused.lines(List.reverse.go(&2, Lex.Tok, lo, []), acc), - Unused.lines(inert.canons(List.reverse.go(&2, Lex.Tok, lo, [])), acc), - inert.canon_lines(List.reverse.go(&2, Lex.Tok, lo, []), acc)))) - -# a node's `:` test, the node read that way, is the one it was -law inert.unused.colon: - for nn: Tree.Node - {Unused.colon(D1.inert.node(nn)) == Unused.colon(nn) : Bool} - -def inert.unused.colon(nn): - match nn: - case Tree.Leaf{tok}: - match tok: - case Lex.Tok{k, t, l, c}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - -# a statement's `:` ending, its chain read that way, is the one it was -law inert.unused.ends: - for kids: Tree.Node - for last: Bool - {Unused.foreign.ends(D1.inert.node(kids), last) == Unused.foreign.ends(kids, last) : Bool} - -def inert.unused.ends(kids, _last): - match kids: - case Tree.NCons{+h, +t}: - Equal.trans(Bool, Unused.foreign.ends(D1.inert.node(t), Unused.colon(D1.inert.node(h))), - Unused.foreign.ends(D1.inert.node(t), Unused.colon(h)), Unused.foreign.ends(t, Unused.colon(h)), - Equal.cong(Bool, Bool, z => Unused.foreign.ends(D1.inert.node(t), z), Unused.colon(D1.inert.node(h)), - Unused.colon(h), inert.unused.colon(h)), - inert.unused.ends(t, Unused.colon(h))) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - -# a body's opening import, the body read that way, is the one it was -law inert.unused.opens: - for body: Tree.Node - for open: Bool - {Unused.foreign.opens(D1.inert.node(body), open) == Unused.foreign.opens(body, open) : Bool} - -def inert.unused.opens(body, open): - match body: - case Tree.NCons{h, +rest}: - match h: - case Tree.Stmt{sk, +kids, b}: - match open: - case True{}: - Equal.trans(Bool, - Unused.foreign.opens(D1.inert.node(rest), - Bool.not(Unused.foreign.ends(D1.inert.node(kids), False{}))), - Unused.foreign.opens(D1.inert.node(rest), Bool.not(Unused.foreign.ends(kids, False{}))), - Unused.foreign.opens(rest, Bool.not(Unused.foreign.ends(kids, False{}))), - Equal.cong(Bool, Bool, z => Unused.foreign.opens(D1.inert.node(rest), Bool.not(z)), - Unused.foreign.ends(D1.inert.node(kids), False{}), Unused.foreign.ends(kids, False{}), - inert.unused.ends(kids, False{})), - inert.unused.opens(rest, Bool.not(Unused.foreign.ends(kids, False{})))) - case False{}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - -# a def's lines onto the lines after it, the tree read that way, are the ones -# they were -law inert.unused.def: - for +kids: Tree.Node - for +body: Tree.Node - for +rest: Tree.Node - for +ih: {Unused.foreign(D1.inert.node(rest)) == Unused.foreign(rest) : List<&2, U32>} - {Bool.pick(List<&2, U32>, - Unused.foreign.opens(D1.inert.node(body), Bool.not(Unused.foreign.ends(D1.inert.node(kids), False{}))), - Unused.lines(Tree.leaves(D1.inert.node(kids)), Unused.foreign(D1.inert.node(rest))), - Unused.foreign(D1.inert.node(rest))) - == Bool.pick(List<&2, U32>, Unused.foreign.opens(body, Bool.not(Unused.foreign.ends(kids, False{}))), - Unused.lines(Tree.leaves(kids), Unused.foreign(rest)), Unused.foreign(rest)) : List<&2, U32>} - -def inert.unused.def(kids, body, rest, ih): - +fi = Unused.foreign(D1.inert.node(rest)) - +fo = Unused.foreign(rest) - +ra = Unused.lines(Tree.leaves(D1.inert.node(kids)), fi) - +rb = Unused.lines(Tree.leaves(kids), fo) - +c1 = Unused.foreign.opens(D1.inert.node(body), Bool.not(Unused.foreign.ends(D1.inert.node(kids), False{}))) - +c2 = Unused.foreign.opens(body, Bool.not(Unused.foreign.ends(kids, False{}))) - +ec = Equal.trans(Bool, c1, - Unused.foreign.opens(D1.inert.node(body), Bool.not(Unused.foreign.ends(kids, False{}))), c2, - Equal.cong(Bool, Bool, z => Unused.foreign.opens(D1.inert.node(body), Bool.not(z)), - Unused.foreign.ends(D1.inert.node(kids), False{}), Unused.foreign.ends(kids, False{}), - inert.unused.ends(kids, False{})), - inert.unused.opens(body, Bool.not(Unused.foreign.ends(kids, False{})))) - +ea = Equal.trans(List<&2, U32>, ra, Unused.lines(Tree.leaves(kids), fi), rb, inert.unused.lines(kids, fi), - Equal.cong(List<&2, U32>, List<&2, U32>, z => Unused.lines(Tree.leaves(kids), z), fi, fo, ih)) - Equal.trans(List<&2, U32>, Bool.pick(List<&2, U32>, c1, ra, fi), Bool.pick(List<&2, U32>, c2, ra, fi), - Bool.pick(List<&2, U32>, c2, rb, fo), - Equal.cong(Bool, List<&2, U32>, z => Bool.pick(List<&2, U32>, z, ra, fi), c1, c2, ec), - M2.unused.pick(c2, ra, rb, fi, fo, ea, ih)) - -# the foreign defs' header lines, the tree read that way, are the ones they were -law inert.unused.foreign: - for root: Tree.Node - {Unused.foreign(D1.inert.node(root)) == Unused.foreign(root) : List<&2, U32>} - -def inert.unused.foreign(root): - match root: - case Tree.NCons{h, +rest}: - match h: - case Tree.Stmt{sk, +kids, +body}: - match sk: - case Tree.SDef{}: inert.unused.def(kids, body, rest, inert.unused.foreign(rest)) - case Tree.SType{}: inert.unused.foreign(rest) - case Tree.SLaw{}: inert.unused.foreign(rest) - case Tree.SImport{}: inert.unused.foreign(rest) - case Tree.SCase{}: inert.unused.foreign(rest) - case Tree.SFor{}: inert.unused.foreign(rest) - case Tree.SLet{}: inert.unused.foreign(rest) - case Tree.STerm{}: inert.unused.foreign(rest) - case Tree.Leaf{x}: inert.unused.foreign(rest) - case Tree.Group{x, y, z}: inert.unused.foreign(rest) - case Tree.NNil{}: inert.unused.foreign(rest) - case Tree.NCons{x, y}: inert.unused.foreign(rest) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - -# does a binder of this kind count, its note read as unused reads it -def inert.unused.rep(kk: Bind.BindKind, +note: String, +line: U32, +fl: List<&2, U32>) -> Bool: - match kk: - case Bind.KLocal{}: - True{} - case Bind.KParam{}: - Bool.and(Bool.not(String.starts_with(note, "-")), Bool.not(List.contains(~U32, ~U32.is_eq, fl, line))) - case other: - False{} - -# unused over binders as it reads them -def inert.unused.go( - binds: List<&2, Bind.Bind>, - +hh: Unused.Hits, - +uses: List<&2, Bind.Use>, - +fl: List<&2, U32>, - +path: String -) -> List<&2, F.Finding>: - match binds: - case Nil{}: - Nil{} - case Con{Bind.Bind{+name, +line, +col, +kind, +note}, rest}: - +more = inert.unused.go(rest, hh, uses, fl, path) - +hit = Bool.and(inert.unused.rep(kind, note, line, fl), Bool.not(String.starts_with(name, "_"))) - Bool.pick(List<&2, F.Finding>, Lazy.and_then(hit, _u => Bool.not(Unused.seen(hh, uses, line, col))), - F.Finding{path, line, col, U32.from_nat(String.length(name)), "unused", - Unused.what(kind) ++ " " ++ name ++ " is never used."} <> more, more) - -# a leading `-`, kept as unused reads a note -law inert.unused.dash: - for s: Bool - {String.starts_with(Bool.pick(String, s, "-", ""), "-") == s : Bool} - -def inert.unused.dash(s): - match s: - case True{}: {==} - case False{}: {==} - -# a binder kept as unused reads it -law inert.unused.one: - for kind: Bind.BindKind - for +note: String - for +line: U32 - for +fl: List<&2, U32> - {Unused.reportable(kind, note, line, fl) == inert.unused.rep(kind, Bool.pick(String, String.starts_with(note, "-"), - "-", ""), line, fl) : Bool} - -def inert.unused.one(kind, note, line, fl): - match kind: - case Bind.KParam{}: - %Equal.sym(Bool, String.starts_with(Bool.pick(String, String.starts_with(note, "-"), "-", ""), "-"), - String.starts_with(note, "-"), inert.unused.dash(String.starts_with(note, "-"))) : - {Bool.and(Bool.not(String.starts_with(note, "-")), - Bool.not(List.contains(~U32, ~U32.is_eq, fl, line))) == Bool.and(Bool.not(_), Bool.not(List.contains(~U32, - ~U32.is_eq, fl, line))) : Bool} - {==} - case Bind.KItem{}: {==} - case Bind.KCtor{}: {==} - case Bind.KTypeParam{}: {==} - case Bind.KField{}: {==} - case Bind.KLocal{}: {==} - case Bind.KPat{}: {==} - case Bind.KFor{}: {==} - case Bind.KTypeVar{}: {==} - -# unused over binders is unused over the binders as it reads them -law inert.unused.via: - for binds: List<&2, Bind.Bind> - for +hh: Unused.Hits - for +uses: List<&2, Bind.Use> - for +fl: List<&2, U32> - for +path: String - {Unused.check.go(binds, hh, uses, fl, path) == inert.unused.go(Laws.inert.ubinds(binds), hh, uses, fl, path) - : List<&2, F.Finding>} - -def inert.unused.via(binds, hh, uses, fl, path): - match binds: - case Nil{}: {==} - case Con{Bind.Bind{+name, +line, +col, +kind, +note}, +rest}: - +mo = Unused.check.go(rest, hh, uses, fl, path) - +x = {F.Finding{path, line, col, U32.from_nat(String.length(name)), "unused", - Unused.what(kind) ++ " " ++ name ++ " is never used."} : F.Finding} - +lhs = Unused.check.go(Con{Bind.Bind{name, line, col, kind, note}, rest}, hh, uses, fl, path) - %inert.unused.via(rest, hh, uses, fl, path) : {lhs == Bool.pick(List<&2, F.Finding>, Lazy.and_then(Bool.and( - inert.unused.rep(kind, Bool.pick(String, String.starts_with(note, "-"), "-", ""), line, fl), - Bool.not(String.starts_with(name, "_"))), _u => Bool.not(Unused.seen(hh, uses, line, col))), x <> _, _) - : List<&2, F.Finding>} - %inert.unused.one(kind, note, line, fl) : {lhs == Bool.pick(List<&2, F.Finding>, Lazy.and_then(Bool.and(_, - Bool.not(String.starts_with(name, "_"))), _u => Bool.not(Unused.seen(hh, uses, line, col))), x <> mo, mo) - : List<&2, F.Finding>} - {==} - -# unused over binders and lines that read alike -law inert.unused.cong: - for -x1: List<&2, Bind.Bind> - for -x2: List<&2, Bind.Bind> - for -f1: List<&2, U32> - for -f2: List<&2, U32> - for +hh: Unused.Hits - for +uses: List<&2, Bind.Use> - for +path: String - for e1: {x1 == x2 : List<&2, Bind.Bind>} - for e2: {f1 == f2 : List<&2, U32>} - {inert.unused.go(x1, hh, uses, f1, path) == inert.unused.go(x2, hh, uses, f2, path) : List<&2, F.Finding>} - -def inert.unused.cong(x1, _x2, f1, f2, hh, uses, path, e1, e2): - %e1 : {inert.unused.go(x1, hh, uses, f1, path) == inert.unused.go(_, hh, uses, f2, path) : List<&2, F.Finding>} - %e2 : {inert.unused.go(x1, hh, uses, f1, path) == inert.unused.go(x1, hh, uses, _, path) : List<&2, F.Finding>} - {==} - -def Laws.inert_unused( - path, - _text, - _toks, - tree, - binds, - uses, - _scopes, - _items, - _text2, - _toks2, - tree2, - binds2, - _scopes2, - _items2, - et, - eb -): - +f1 = Unused.foreign(tree) - +f2 = Unused.foreign(tree2) - +ef = Equal.trans(List<&2, U32>, f1, Unused.foreign(D1.inert.node(tree)), f2, - Equal.sym(List<&2, U32>, Unused.foreign(D1.inert.node(tree)), f1, inert.unused.foreign(tree)), - Equal.trans(List<&2, U32>, Unused.foreign(D1.inert.node(tree)), Unused.foreign(D1.inert.node(tree2)), f2, - Equal.cong(Tree.Node, List<&2, U32>, z => Unused.foreign(z), D1.inert.node(tree), D1.inert.node(tree2), et), - inert.unused.foreign(tree2))) - +hh = Unused.index(uses) - Equal.trans(List<&2, F.Finding>, Unused.check.go(binds, hh, uses, f1, path), - inert.unused.go(Laws.inert.ubinds(binds), hh, uses, f1, path), Unused.check.go(binds2, hh, uses, f2, path), - inert.unused.via(binds, hh, uses, f1, path), - Equal.trans(List<&2, F.Finding>, inert.unused.go(Laws.inert.ubinds(binds), hh, uses, f1, path), - inert.unused.go(Laws.inert.ubinds(binds2), hh, uses, f2, path), Unused.check.go(binds2, hh, uses, f2, path), - inert.unused.cong(Laws.inert.ubinds(binds), Laws.inert.ubinds(binds2), f1, f2, hh, uses, path, eb, ef), - Equal.sym(List<&2, F.Finding>, Unused.check.go(binds2, hh, uses, f2, path), - inert.unused.go(Laws.inert.ubinds(binds2), hh, uses, f2, path), inert.unused.via(binds2, hh, uses, f2, path)))) - -# Inert text, hoist (laws/CodeRulesIgnoreCommentsAndStrings.bend): a def's self-calls, its carried -# parameters, the tables it builds and the names it indexes, each read the -# way the lemmas above read a tree. - -# hoist -# ----- - -# self-calls' argument lists, each argument read that way -def inert.hoist.css(cs: List<&2, List<&2, Tree.Node>>) -> List<&2, List<&2, Tree.Node>>: - match cs: - case Nil{}: Nil{} - case Con{h, t}: inert.nodes(h) <> inert.hoist.css(t) - -# argument lists appended, then read that way, are each read that way, appended -law inert.hoist.css_app: - for a: List<&2, List<&2, Tree.Node>> - for +b: List<&2, List<&2, Tree.Node>> - {inert.hoist.css(List.append(&2, List<&2, Tree.Node>, a, b)) == List.append(&2, List<&2, Tree.Node>, - inert.hoist.css(a), inert.hoist.css(b)) : List<&2, List<&2, Tree.Node>>} - -def inert.hoist.css_app(a, b): - match a: - case Nil{}: {==} - case Con{+h, +t}: - Equal.cong(List<&2, List<&2, Tree.Node>>, List<&2, List<&2, Tree.Node>>, z => inert.nodes(h) <> z, - inert.hoist.css(List.append(&2, List<&2, Tree.Node>, t, b)), - List.append(&2, List<&2, Tree.Node>, inert.hoist.css(t), inert.hoist.css(b)), inert.hoist.css_app(t, b)) - -# two runs of argument lists joined, each read that way, are the join read that way -law inert.hoist.cat2: - for +a: List<&2, List<&2, Tree.Node>> - for +b: List<&2, List<&2, Tree.Node>> - {List.concat(&2, List<&2, Tree.Node>, [inert.hoist.css(a), inert.hoist.css(b)]) == inert.hoist.css(List.concat(&2, - List<&2, Tree.Node>, [a, b])) - : List<&2, List<&2, Tree.Node>>} - -def inert.hoist.cat2(a, b): - +lhs = List.concat(&2, List<&2, Tree.Node>, [inert.hoist.css(a), inert.hoist.css(b)]) - %Equal.sym(List<&2, List<&2, Tree.Node>>, inert.hoist.css(List.append(&2, List<&2, Tree.Node>, a, List.append(&2, - List<&2, Tree.Node>, b, []))), - List.append(&2, List<&2, Tree.Node>, inert.hoist.css(a), inert.hoist.css(List.append(&2, List<&2, Tree.Node>, b, - []))), - inert.hoist.css_app(a, List.append(&2, List<&2, Tree.Node>, b, []))) : {lhs == _ : List<&2, List<&2, Tree.Node>>} - %Equal.sym(List<&2, List<&2, Tree.Node>>, inert.hoist.css(List.append(&2, List<&2, Tree.Node>, b, [])), - List.append(&2, List<&2, Tree.Node>, inert.hoist.css(b), - inert.hoist.css([])), inert.hoist.css_app(b, [])) : {lhs == List.append(&2, List<&2, Tree.Node>, - inert.hoist.css(a), _) : List<&2, List<&2, Tree.Node>>} - {==} - -# three runs of argument lists joined, each read that way, are the join read that way -law inert.hoist.cat3: - for +a: List<&2, List<&2, Tree.Node>> - for +b: List<&2, List<&2, Tree.Node>> - for +c: List<&2, List<&2, Tree.Node>> - {List.concat(&2, List<&2, Tree.Node>, [inert.hoist.css(a), inert.hoist.css(b), inert.hoist.css(c)]) - == inert.hoist.css(List.concat(&2, List<&2, Tree.Node>, [a, b, c])) : List<&2, List<&2, Tree.Node>>} - -def inert.hoist.cat3(a, b, c): - +lhs = List.concat(&2, List<&2, Tree.Node>, [inert.hoist.css(a), inert.hoist.css(b), inert.hoist.css(c)]) - +bc = List.append(&2, List<&2, Tree.Node>, b, List.append(&2, List<&2, Tree.Node>, c, [])) - %Equal.sym(List<&2, List<&2, Tree.Node>>, inert.hoist.css(List.append(&2, List<&2, Tree.Node>, a, bc)), - List.append(&2, List<&2, Tree.Node>, inert.hoist.css(a), inert.hoist.css(bc)), - inert.hoist.css_app(a, bc)) : {lhs == _ : List<&2, List<&2, Tree.Node>>} - %Equal.sym(List<&2, List<&2, Tree.Node>>, inert.hoist.css(bc), List.append(&2, List<&2, Tree.Node>, - inert.hoist.css(b), - inert.hoist.css(List.append(&2, List<&2, Tree.Node>, c, []))), inert.hoist.css_app(b, List.append(&2, List<&2, - Tree.Node>, c, []))) : - {lhs == List.append(&2, List<&2, Tree.Node>, inert.hoist.css(a), _) : List<&2, List<&2, Tree.Node>>} - %Equal.sym(List<&2, List<&2, Tree.Node>>, inert.hoist.css(List.append(&2, List<&2, Tree.Node>, c, [])), - List.append(&2, List<&2, Tree.Node>, inert.hoist.css(c), - inert.hoist.css([])), inert.hoist.css_app(c, [])) : - {lhs == List.append(&2, List<&2, Tree.Node>, inert.hoist.css(a), List.append(&2, List<&2, Tree.Node>, - inert.hoist.css(b), _)) : List<&2, List<&2, Tree.Node>>} - {==} - -# a self-call picked onto the rest, each read that way -law inert.hoist.pick: - for me: Bool - for +as: List<&2, Tree.Node> - for +more: List<&2, List<&2, Tree.Node>> - {Bool.pick(List<&2, List<&2, Tree.Node>>, me, inert.nodes(as) <> inert.hoist.css(more), inert.hoist.css(more)) - == inert.hoist.css(Bool.pick(List<&2, List<&2, Tree.Node>>, me, as <> more, more)) : List<&2, List<&2, Tree.Node>>} - -def inert.hoist.pick(me, _as, _more): - match me: - case True{}: {==} - case False{}: {==} - -# two runs that read as others read that way join as their join does -law inert.hoist.pcat2: - for -x1: List<&2, List<&2, Tree.Node>> - for +x2: List<&2, List<&2, Tree.Node>> - for -y1: List<&2, List<&2, Tree.Node>> - for +y2: List<&2, List<&2, Tree.Node>> - for ix: {x1 == inert.hoist.css(x2) : List<&2, List<&2, Tree.Node>>} - for iy: {y1 == inert.hoist.css(y2) : List<&2, List<&2, Tree.Node>>} - {List.concat(&2, List<&2, Tree.Node>, [x1, y1]) == inert.hoist.css(List.concat(&2, List<&2, Tree.Node>, [x2, - y2])) : List<&2, List<&2, Tree.Node>>} - -def inert.hoist.pcat2(x1, x2, y1, y2, ix, iy): - %Equal.sym(List<&2, List<&2, Tree.Node>>, x1, inert.hoist.css(x2), ix) : - {List.concat(&2, List<&2, Tree.Node>, [_, y1]) == inert.hoist.css(List.concat(&2, List<&2, Tree.Node>, [x2, - y2])) : List<&2, List<&2, Tree.Node>>} - %Equal.sym(List<&2, List<&2, Tree.Node>>, y1, inert.hoist.css(y2), iy) : - {List.concat(&2, List<&2, Tree.Node>, [inert.hoist.css(x2), _]) == inert.hoist.css(List.concat(&2, List<&2, - Tree.Node>, [x2, y2])) : List<&2, List<&2, Tree.Node>>} - inert.hoist.cat2(x2, y2) - -# three runs that read as others read that way join as their join does -law inert.hoist.pcat3: - for -x1: List<&2, List<&2, Tree.Node>> - for +x2: List<&2, List<&2, Tree.Node>> - for -y1: List<&2, List<&2, Tree.Node>> - for +y2: List<&2, List<&2, Tree.Node>> - for -z1: List<&2, List<&2, Tree.Node>> - for +z2: List<&2, List<&2, Tree.Node>> - for ix: {x1 == inert.hoist.css(x2) : List<&2, List<&2, Tree.Node>>} - for iy: {y1 == inert.hoist.css(y2) : List<&2, List<&2, Tree.Node>>} - for iz: {z1 == inert.hoist.css(z2) : List<&2, List<&2, Tree.Node>>} - {List.concat(&2, List<&2, Tree.Node>, [x1, y1, z1]) == inert.hoist.css(List.concat(&2, List<&2, Tree.Node>, [x2, y2, - z2])) : List<&2, List<&2, Tree.Node>>} - -def inert.hoist.pcat3(x1, x2, y1, y2, z1, z2, ix, iy, iz): - +rhs = inert.hoist.css(List.concat(&2, List<&2, Tree.Node>, [x2, y2, z2])) - %Equal.sym(List<&2, List<&2, Tree.Node>>, x1, inert.hoist.css(x2), ix) : {List.concat(&2, List<&2, Tree.Node>, [_, - y1, z1]) == rhs : List<&2, List<&2, Tree.Node>>} - %Equal.sym(List<&2, List<&2, Tree.Node>>, y1, inert.hoist.css(y2), iy) : {List.concat(&2, List<&2, Tree.Node>, - [inert.hoist.css(x2), _, z1]) == rhs : List<&2, List<&2, Tree.Node>>} - %Equal.sym(List<&2, List<&2, Tree.Node>>, z1, inert.hoist.css(z2), iz) : - {List.concat(&2, List<&2, Tree.Node>, [inert.hoist.css(x2), inert.hoist.css(y2), _]) == rhs : List<&2, List<&2, - Tree.Node>>} - inert.hoist.cat3(x2, y2, z2) - -# a name then a group, both read that way: a self-call as it was, and the -# calls under the group and after it read that way -law inert.hoist.pushes_lg: - for +b: Bool - for +t: String - for +o: String - for +self: String - for +kids: Tree.Node - for +r2: Tree.Node - for +ef: {D1.inert.fits.go(b, t) == True{} : Bool} - for +es: {D1.inert.marked(self) == False{} : Bool} - for ik: {Hoist.pushes(D1.inert.node(kids), self) == inert.hoist.css(Hoist.pushes(kids, self)) : List<&2, List<&2, - Tree.Node>>} - for ir: {Hoist.pushes(D1.inert.node(r2), self) == inert.hoist.css(Hoist.pushes(r2, self)) : List<&2, List<&2, - Tree.Node>>} - {Bool.pick(List<&2, List<&2, Tree.Node>>, Bool.and(String.eq(o, "("), String.eq(D1.inert.keep(b, t), self)), - Calls.args(D1.inert.node(kids)) <> List.concat(&2, List<&2, Tree.Node>, [Hoist.pushes(D1.inert.node(kids), - self), Hoist.pushes(D1.inert.node(r2), self)]), - List.concat(&2, List<&2, Tree.Node>, [Hoist.pushes(D1.inert.node(kids), self), Hoist.pushes(D1.inert.node(r2), - self)])) - == inert.hoist.css(Bool.pick(List<&2, List<&2, Tree.Node>>, Bool.and(String.eq(o, "("), String.eq(t, self)), - Calls.args(kids) <> List.concat(&2, List<&2, Tree.Node>, [Hoist.pushes(kids, self), Hoist.pushes(r2, self)]), - List.concat(&2, List<&2, Tree.Node>, [Hoist.pushes(kids, self), Hoist.pushes(r2, self)]))) : List<&2, List<&2, - Tree.Node>>} - -def inert.hoist.pushes_lg(b, t, o, self, kids, r2, ef, es, ik, ir): - +m = List.concat(&2, List<&2, Tree.Node>, [Hoist.pushes(kids, self), Hoist.pushes(r2, self)]) - +im = List.concat(&2, List<&2, Tree.Node>, [Hoist.pushes(D1.inert.node(kids), self), - Hoist.pushes(D1.inert.node(r2), self)]) - +rhs = inert.hoist.css(Bool.pick(List<&2, List<&2, Tree.Node>>, Bool.and(String.eq(o, "("), String.eq(t, self)), - Calls.args(kids) <> m, m)) - %Equal.sym(Bool, String.eq(D1.inert.keep(b, t), self), String.eq(t, self), M1.inert.eq_keep(b, t, self, ef, es)) : - {Bool.pick(List<&2, List<&2, Tree.Node>>, Bool.and(String.eq(o, "("), _), Calls.args(D1.inert.node(kids)) <> im, - im) == rhs : List<&2, List<&2, Tree.Node>>} - %Equal.sym(List<&2, List<&2, Tree.Node>>, im, inert.hoist.css(m), - inert.hoist.pcat2(Hoist.pushes(D1.inert.node(kids), self), Hoist.pushes(kids, self), - Hoist.pushes(D1.inert.node(r2), self), Hoist.pushes(r2, self), ik, ir)) : - {Bool.pick(List<&2, List<&2, Tree.Node>>, Bool.and(String.eq(o, "("), String.eq(t, self)), - Calls.args(D1.inert.node(kids)) <> _, _) == rhs : List<&2, List<&2, Tree.Node>>} - %Equal.sym(List<&2, Tree.Node>, Calls.args(D1.inert.node(kids)), inert.nodes(Calls.args(kids)), inert.args(kids)) : - {Bool.pick(List<&2, List<&2, Tree.Node>>, Bool.and(String.eq(o, "("), String.eq(t, self)), - _ <> inert.hoist.css(m), inert.hoist.css(m)) - == rhs : List<&2, List<&2, Tree.Node>>} - inert.hoist.pick(Bool.and(String.eq(o, "("), String.eq(t, self)), Calls.args(kids), m) - -# the self-calls under a node read that way are its self-calls, each argument -# read that way: a string is never the def's name -law inert.hoist.pushes: - for nn: Tree.Node - for +self: String - for +es: {D1.inert.marked(self) == False{} : Bool} - for +e: {D1.inert.ok(nn) == True{} : Bool} - {Hoist.pushes(D1.inert.node(nn), self) == inert.hoist.css(Hoist.pushes(nn, self)) : List<&2, List<&2, Tree.Node>>} - -def inert.hoist.pushes(nn, self, es, e): - match nn: - case Tree.NCons{h, +rest}: - match h: - case Tree.Leaf{tok}: - match tok: - case Lex.Tok{+k, +t, +l, +c}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{go, +kids, +gcl}: - match go: - case Lex.Tok{+gk, +o, +gl, +gc}: - +lf = {Tree.Leaf{Lex.Tok{k, t, l, c}} : Tree.Node} - +gp = {Tree.Group{Lex.Tok{gk, o, gl, gc}, kids, gcl} : Tree.Node} - +eg = inert.ok_t(lf, Tree.NCons{gp, r2}, e) - inert.hoist.pushes_lg(D1.inert.blanks(k), t, o, self, kids, r2, - inert.ok_h(lf, Tree.NCons{gp, r2}, e), es, - inert.hoist.pushes(kids, self, es, inert.ok_h(gp, r2, eg)), - inert.hoist.pushes(r2, self, es, inert.ok_t(gp, r2, eg))) - case Tree.Leaf{x}: - inert.hoist.pushes(rest, self, es, inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.Stmt{x, y, z}: - inert.hoist.pushes(rest, self, es, inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.NNil{}: - inert.hoist.pushes(rest, self, es, inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.NCons{x, y}: - inert.hoist.pushes(rest, self, es, inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.Leaf{x}: - inert.hoist.pushes(rest, self, es, inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.Group{x, y, z}: - inert.hoist.pushes(rest, self, es, inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.Stmt{x, y, z}: - inert.hoist.pushes(rest, self, es, inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.NNil{}: - inert.hoist.pushes(rest, self, es, inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.Group{+o, +kids, +cl}: - inert.hoist.pcat2(Hoist.pushes(D1.inert.node(kids), self), Hoist.pushes(kids, self), - Hoist.pushes(D1.inert.node(rest), self), - Hoist.pushes(rest, self), inert.hoist.pushes(kids, self, es, inert.ok_h(Tree.Group{o, kids, cl}, rest, e)), - inert.hoist.pushes(rest, self, es, inert.ok_t(Tree.Group{o, kids, cl}, rest, e))) - case Tree.Stmt{+sk, +kids, +body}: - +es2 = inert.ok_h(Tree.Stmt{sk, kids, body}, rest, e) - inert.hoist.pcat3(Hoist.pushes(D1.inert.node(kids), self), Hoist.pushes(kids, self), - Hoist.pushes(D1.inert.node(body), self), - Hoist.pushes(body, self), Hoist.pushes(D1.inert.node(rest), self), Hoist.pushes(rest, self), - inert.hoist.pushes(kids, self, es, M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(body), es2)), - inert.hoist.pushes(body, self, es, M1.inert.and_r(D1.inert.ok(kids), D1.inert.ok(body), es2)), - inert.hoist.pushes(rest, self, es, inert.ok_t(Tree.Stmt{sk, kids, body}, rest, e))) - case Tree.NNil{}: inert.hoist.pushes(rest, self, es, e) - case Tree.NCons{x, y}: inert.hoist.pushes(rest, self, es, inert.ok_t(Tree.NCons{x, y}, rest, e)) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - -# a chain read that way is exactly the name as it was: only a name's text is read -law inert.hoist.lone_eq: - for nn: Tree.Node - for +name: String - {Hoist.lone_eq(D1.inert.node(nn), name) == Hoist.lone_eq(nn, name) : Bool} - -def inert.hoist.lone_eq(nn, _name): - match nn: - case Tree.NCons{h, r}: - match h: - case Tree.Leaf{tok}: - match tok: - case Lex.Tok{k, t, l, c}: - match k: - case Lex.TName{}: - match r: - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Lex.TUpper{}: {==} - case Lex.TDotted{}: {==} - case Lex.TWild{}: {==} - case Lex.TKey{}: {==} - case Lex.TNum{}: {==} - case Lex.TStr{}: {==} - case Lex.TChar{}: {==} - case Lex.TComment{}: {==} - case Lex.TSpace{}: {==} - case Lex.TNewline{}: {==} - case Lex.TOp{}: {==} - case Lex.TColon{}: {==} - case Lex.TEq{}: {==} - case Lex.TBind{}: {==} - case Lex.TArrow{}: {==} - case Lex.TLam{}: {==} - case Lex.TAll{}: {==} - case Lex.TAmp{}: {==} - case Lex.TOpen{}: {==} - case Lex.TClose{}: {==} - case Lex.TComma{}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - -# every self-call, its arguments read that way, passes the name at the -# argument as it did -law inert.hoist.all_same: - for cs: List<&2, List<&2, Tree.Node>> - for +ii: Nat - for +name: String - {Hoist.all_same(inert.hoist.css(cs), ii, name) == Hoist.all_same(cs, ii, name) : Bool} - -def inert.hoist.all_same(cs, ii, name): - match cs: - case Nil{}: {==} - case Con{+as, +rest}: - +lhs = Hoist.all_same(inert.hoist.css(Con{as, rest}), ii, name) - %inert.hoist.all_same(rest, ii, name) : - {lhs == Bool.and(Hoist.lone_eq(Calls.arg(as, ii), name), _) : Bool} - %inert.hoist.lone_eq(Calls.arg(as, ii), name) : - {lhs == Bool.and(_, Hoist.all_same(inert.hoist.css(rest), ii, name)) : Bool} - %inert.arg(as, ii) : - {lhs == Bool.and(Hoist.lone_eq(_, name), Hoist.all_same(inert.hoist.css(rest), ii, name)) : Bool} - {==} - -# the parameters every self-call passes unchanged, parameters and calls read -# that way -law inert.hoist.carried.go: - for ps: List<&2, Tree.Node> - for +cs: List<&2, List<&2, Tree.Node>> - for +ii: Nat - for +acc: List<&2, String> - {Hoist.carried.go(inert.nodes(ps), inert.hoist.css(cs), ii, acc) == Hoist.carried.go(ps, cs, ii, acc) : List<&2, - String>} - -def inert.hoist.carried.go(ps, cs, ii, acc): - match ps: - case Nil{}: {==} - case Con{+p, +rest}: - +nm = Calls.param_name(p) - +lhs = Hoist.carried.go(inert.nodes(Con{p, rest}), inert.hoist.css(cs), ii, acc) - %inert.hoist.all_same(cs, ii, nm) : - {lhs == Hoist.carried.go(rest, cs, (ii + 1n : Nat), Bool.pick(List<&2, String>, - Bool.and(Bool.not(String.is_empty(nm)), - _), nm <> acc, acc)) : List<&2, String>} - %inert.hoist.carried.go(rest, cs, (ii + 1n : Nat), - Bool.pick(List<&2, String>, Bool.and(Bool.not(String.is_empty(nm)), Hoist.all_same(inert.hoist.css(cs), ii, - nm)), - nm <> acc, acc)) : - {lhs == _ : List<&2, String>} - %Equal.sym(String, Calls.param_name(D1.inert.node(p)), nm, inert.param_name(p)) : - {Hoist.carried.go(inert.nodes(rest), inert.hoist.css(cs), (ii + 1n : Nat), - Bool.pick(List<&2, String>, Bool.and(Bool.not(String.is_empty(_)), Hoist.all_same(inert.hoist.css(cs), ii, - _)), - _ <> acc, acc)) - == Hoist.carried.go(inert.nodes(rest), inert.hoist.css(cs), (ii + 1n : Nat), - Bool.pick(List<&2, String>, Bool.and(Bool.not(String.is_empty(nm)), Hoist.all_same(inert.hoist.css(cs), - ii, nm)), - nm <> acc, acc)) : List<&2, String>} - {==} - -# parameters passed through unchanged, calls and signature read that way -law inert.hoist.carried_of: - for cs: List<&2, List<&2, Tree.Node>> - for +sig: Tree.Node - {Hoist.carried_of(inert.hoist.css(cs), D1.inert.node(sig)) == Hoist.carried_of(cs, sig) : List<&2, String>} - -def inert.hoist.carried_of(cs, sig): - match cs: - case Nil{}: {==} - case Con{+h, +t}: - +rhs = Hoist.carried_of(Con{h, t}, sig) - %Equal.sym(Tree.Node, Calls.params.of(D1.inert.node(sig)), D1.inert.node(Calls.params.of(sig)), - inert.params.of(sig)) : - {Hoist.carried.go(Calls.args(_), inert.hoist.css(h <> t), 0n, []) == rhs : List<&2, String>} - %Equal.sym(List<&2, Tree.Node>, Calls.args(D1.inert.node(Calls.params.of(sig))), inert.nodes(Calls.params(sig)), - inert.args(Calls.params.of(sig))) : - {Hoist.carried.go(_, inert.hoist.css(h <> t), 0n, []) == rhs : List<&2, String>} - inert.hoist.carried.go(Calls.params(sig), h <> t, 0n, []) - -# the parameters a def carries, its signature and body read that way -law inert.hoist.carried: - for +sig: Tree.Node - for +body: Tree.Node - for +self: String - for +es: {D1.inert.marked(self) == False{} : Bool} - for +eb: {D1.inert.ok(body) == True{} : Bool} - {Hoist.carried(D1.inert.node(sig), D1.inert.node(body), self) == Hoist.carried(sig, body, self) : List<&2, - String>} - -def inert.hoist.carried(sig, body, self, es, eb): - %Equal.sym(List<&2, List<&2, Tree.Node>>, Hoist.pushes(D1.inert.node(body), self), - inert.hoist.css(Hoist.pushes(body, self)), - inert.hoist.pushes(body, self, es, eb)) : {Hoist.carried_of(_, D1.inert.node(sig)) == Hoist.carried(sig, body, - self) : List<&2, String>} - inert.hoist.carried_of(Hoist.pushes(body, self), sig) - -# two ands whose sides read alike read alike -law inert.hoist.and2: - for -a1: Bool - for -a2: Bool - for -b1: Bool - for -b2: Bool - for ea: {a1 == a2 : Bool} - for eb: {b1 == b2 : Bool} - {Bool.and(a1, b1) == Bool.and(a2, b2) : Bool} - -def inert.hoist.and2(a1, _a2, b1, b2, ea, eb): - %ea : {Bool.and(a1, b1) == Bool.and(_, b2) : Bool} - %eb : {Bool.and(a1, b1) == Bool.and(a1, _) : Bool} - {==} - -# two ors whose sides read alike read alike -law inert.hoist.or2: - for -a1: Bool - for -a2: Bool - for -b1: Bool - for -b2: Bool - for ea: {a1 == a2 : Bool} - for eb: {b1 == b2 : Bool} - {Bool.or(a1, b1) == Bool.or(a2, b2) : Bool} - -def inert.hoist.or2(a1, _a2, b1, b2, ea, eb): - %ea : {Bool.or(a1, b1) == Bool.or(_, b2) : Bool} - %eb : {Bool.or(a1, b1) == Bool.or(a1, _) : Bool} - {==} - -# a token read that way names a carried parameter as it did: only a name's -# text is read -law inert.hoist.name_ok: - for k: Lex.TokKind - for +t: String - for +carried: List<&2, String> - {Hoist.name_ok(k, D1.inert.keep(D1.inert.blanks(k), t), carried) == Hoist.name_ok(k, t, carried) : Bool} - -def inert.hoist.name_ok(k, _t, _carried): - match k: - case Lex.TName{}: {==} - case Lex.TUpper{}: {==} - case Lex.TDotted{}: {==} - case Lex.TWild{}: {==} - case Lex.TKey{}: {==} - case Lex.TNum{}: {==} - case Lex.TStr{}: {==} - case Lex.TChar{}: {==} - case Lex.TComment{}: {==} - case Lex.TSpace{}: {==} - case Lex.TNewline{}: {==} - case Lex.TOp{}: {==} - case Lex.TColon{}: {==} - case Lex.TEq{}: {==} - case Lex.TBind{}: {==} - case Lex.TArrow{}: {==} - case Lex.TLam{}: {==} - case Lex.TAll{}: {==} - case Lex.TAmp{}: {==} - case Lex.TOpen{}: {==} - case Lex.TClose{}: {==} - case Lex.TComma{}: {==} - -# a token after an operator, read that way, is closed as it was -law inert.hoist.leaf_ok: - for skip: Bool - for tok: Lex.Tok - for +carried: List<&2, String> - {Hoist.leaf_ok(skip, D1.inert.tok(tok), carried) == Hoist.leaf_ok(skip, tok, carried) : Bool} - -def inert.hoist.leaf_ok(skip, tok, carried): - match skip: - case True{}: {==} - case False{}: - match tok: - case Lex.Tok{k, +t, l, c}: inert.hoist.name_ok(k, t, carried) - -# a token before a group, read that way, is closed as it was -law inert.hoist.value_ok: - for app: Bool - for k: Lex.TokKind - for +t: String - for +carried: List<&2, String> - {Hoist.value_ok(app, k, D1.inert.keep(D1.inert.blanks(k), t), carried) == Hoist.value_ok(app, k, t, carried) - : Bool} - -def inert.hoist.value_ok(app, k, t, carried): - match app: - case True{}: {==} - case False{}: inert.hoist.name_ok(k, t, carried) - -# a token then a group, read that way: closed as it was, the group and the -# rest too -law inert.hoist.closed_lg: - for k: Lex.TokKind - for +t: String - for +o: String - for +kids: Tree.Node - for +r2: Tree.Node - for +carried: List<&2, String> - for ik: {Hoist.closed(D1.inert.node(kids), carried) == Hoist.closed(kids, carried) : Bool} - for ir: {Hoist.closed(D1.inert.node(r2), carried) == Hoist.closed(r2, carried) : Bool} - {Bool.and(Hoist.value_ok(String.eq(o, "("), k, D1.inert.keep(D1.inert.blanks(k), t), carried), - Bool.and(Hoist.closed(D1.inert.node(kids), carried), Hoist.closed(D1.inert.node(r2), carried))) - == Bool.and(Hoist.value_ok(String.eq(o, "("), k, t, carried), Bool.and(Hoist.closed(kids, carried), - Hoist.closed(r2, carried))) : Bool} - -def inert.hoist.closed_lg(k, t, o, kids, r2, carried, ik, ir): - inert.hoist.and2(Hoist.value_ok(String.eq(o, "("), k, D1.inert.keep(D1.inert.blanks(k), t), carried), - Hoist.value_ok(String.eq(o, "("), k, t, carried), Bool.and(Hoist.closed(D1.inert.node(kids), carried), - Hoist.closed(D1.inert.node(r2), carried)), Bool.and(Hoist.closed(kids, carried), Hoist.closed(r2, carried)), - inert.hoist.value_ok(String.eq(o, "("), k, t, carried), - inert.hoist.and2(Hoist.closed(D1.inert.node(kids), carried), Hoist.closed(kids, carried), - Hoist.closed(D1.inert.node(r2), carried), - Hoist.closed(r2, carried), ik, ir)) - -# a chain read that way is closed over the carried parameters as it was: a -# string is never a value name -law inert.hoist.closed: - for nn: Tree.Node - for +carried: List<&2, String> - {Hoist.closed(D1.inert.node(nn), carried) == Hoist.closed(nn, carried) : Bool} - -def inert.hoist.closed(nn, carried): - match nn: - case Tree.NCons{h, +rest}: - match h: - case Tree.Leaf{tok}: - match tok: - case Lex.Tok{k, +t, l, c}: - match k: - case Lex.TName{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{go, +kids, gcl}: - match go: - case Lex.Tok{gk, +o, gl, gc}: - inert.hoist.closed_lg(Lex.TName{}, t, o, kids, r2, carried, inert.hoist.closed(kids, - carried), inert.hoist.closed(r2, carried)) - case Tree.Leaf{+tok2}: - Equal.cong(Bool, Bool, z => Bool.and(List.contains(~String, ~String.eq, carried, t), z), - Hoist.closed(D1.inert.node(rest), carried), Hoist.closed(rest, carried), - inert.hoist.closed(rest, carried)) - case Tree.Stmt{x, y, z}: - Equal.cong(Bool, Bool, z => Bool.and(List.contains(~String, ~String.eq, carried, t), z), - Hoist.closed(D1.inert.node(rest), carried), Hoist.closed(rest, carried), - inert.hoist.closed(rest, carried)) - case Tree.NNil{}: - Equal.cong(Bool, Bool, z => Bool.and(List.contains(~String, ~String.eq, carried, t), z), - Hoist.closed(D1.inert.node(rest), carried), Hoist.closed(rest, carried), - inert.hoist.closed(rest, carried)) - case Tree.NCons{x, y}: - Equal.cong(Bool, Bool, z => Bool.and(List.contains(~String, ~String.eq, carried, t), z), - Hoist.closed(D1.inert.node(rest), carried), Hoist.closed(rest, carried), - inert.hoist.closed(rest, carried)) - case Tree.Leaf{x}: - Equal.cong(Bool, Bool, z => Bool.and(List.contains(~String, ~String.eq, carried, t), z), - Hoist.closed(D1.inert.node(rest), carried), Hoist.closed(rest, carried), - inert.hoist.closed(rest, carried)) - case Tree.Group{x, y, z}: - Equal.cong(Bool, Bool, z => Bool.and(List.contains(~String, ~String.eq, carried, t), z), - Hoist.closed(D1.inert.node(rest), carried), Hoist.closed(rest, carried), - inert.hoist.closed(rest, carried)) - case Tree.Stmt{x, y, z}: - Equal.cong(Bool, Bool, z => Bool.and(List.contains(~String, ~String.eq, carried, t), z), - Hoist.closed(D1.inert.node(rest), carried), Hoist.closed(rest, carried), - inert.hoist.closed(rest, carried)) - case Tree.NNil{}: - Equal.cong(Bool, Bool, z => Bool.and(List.contains(~String, ~String.eq, carried, t), z), - Hoist.closed(D1.inert.node(rest), carried), Hoist.closed(rest, carried), - inert.hoist.closed(rest, carried)) - case Lex.TUpper{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{go, +kids, gcl}: - match go: - case Lex.Tok{gk, +o, gl, gc}: - inert.hoist.closed_lg(Lex.TUpper{}, t, o, kids, r2, carried, inert.hoist.closed(kids, - carried), inert.hoist.closed(r2, carried)) - case Tree.Leaf{+tok2}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Tree.NCons{x, y}: inert.hoist.closed(rest, carried) - case Tree.Leaf{x}: inert.hoist.closed(rest, carried) - case Tree.Group{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Lex.TDotted{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{go, +kids, gcl}: - match go: - case Lex.Tok{gk, +o, gl, gc}: - inert.hoist.closed_lg(Lex.TDotted{}, t, o, kids, r2, carried, inert.hoist.closed(kids, - carried), inert.hoist.closed(r2, carried)) - case Tree.Leaf{+tok2}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Tree.NCons{x, y}: inert.hoist.closed(rest, carried) - case Tree.Leaf{x}: inert.hoist.closed(rest, carried) - case Tree.Group{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Lex.TWild{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{go, +kids, gcl}: - match go: - case Lex.Tok{gk, +o, gl, gc}: - inert.hoist.closed_lg(Lex.TWild{}, t, o, kids, r2, carried, inert.hoist.closed(kids, - carried), inert.hoist.closed(r2, carried)) - case Tree.Leaf{+tok2}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Tree.NCons{x, y}: inert.hoist.closed(rest, carried) - case Tree.Leaf{x}: inert.hoist.closed(rest, carried) - case Tree.Group{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Lex.TKey{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{go, +kids, gcl}: - match go: - case Lex.Tok{gk, +o, gl, gc}: - inert.hoist.closed_lg(Lex.TKey{}, t, o, kids, r2, carried, inert.hoist.closed(kids, - carried), inert.hoist.closed(r2, carried)) - case Tree.Leaf{+tok2}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Tree.NCons{x, y}: inert.hoist.closed(rest, carried) - case Tree.Leaf{x}: inert.hoist.closed(rest, carried) - case Tree.Group{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Lex.TNum{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{go, +kids, gcl}: - match go: - case Lex.Tok{gk, +o, gl, gc}: - inert.hoist.closed_lg(Lex.TNum{}, t, o, kids, r2, carried, inert.hoist.closed(kids, - carried), inert.hoist.closed(r2, carried)) - case Tree.Leaf{+tok2}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Tree.NCons{x, y}: inert.hoist.closed(rest, carried) - case Tree.Leaf{x}: inert.hoist.closed(rest, carried) - case Tree.Group{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Lex.TStr{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{go, +kids, gcl}: - match go: - case Lex.Tok{gk, +o, gl, gc}: - inert.hoist.closed_lg(Lex.TStr{}, t, o, kids, r2, carried, inert.hoist.closed(kids, - carried), inert.hoist.closed(r2, carried)) - case Tree.Leaf{+tok2}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Tree.NCons{x, y}: inert.hoist.closed(rest, carried) - case Tree.Leaf{x}: inert.hoist.closed(rest, carried) - case Tree.Group{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Lex.TChar{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{go, +kids, gcl}: - match go: - case Lex.Tok{gk, +o, gl, gc}: - inert.hoist.closed_lg(Lex.TChar{}, t, o, kids, r2, carried, inert.hoist.closed(kids, - carried), inert.hoist.closed(r2, carried)) - case Tree.Leaf{+tok2}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Tree.NCons{x, y}: inert.hoist.closed(rest, carried) - case Tree.Leaf{x}: inert.hoist.closed(rest, carried) - case Tree.Group{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Lex.TComment{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{go, +kids, gcl}: - match go: - case Lex.Tok{gk, +o, gl, gc}: - inert.hoist.closed_lg(Lex.TComment{}, t, o, kids, r2, carried, inert.hoist.closed(kids, - carried), inert.hoist.closed(r2, carried)) - case Tree.Leaf{+tok2}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Tree.NCons{x, y}: inert.hoist.closed(rest, carried) - case Tree.Leaf{x}: inert.hoist.closed(rest, carried) - case Tree.Group{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Lex.TSpace{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{go, +kids, gcl}: - match go: - case Lex.Tok{gk, +o, gl, gc}: - inert.hoist.closed_lg(Lex.TSpace{}, t, o, kids, r2, carried, inert.hoist.closed(kids, - carried), inert.hoist.closed(r2, carried)) - case Tree.Leaf{+tok2}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Tree.NCons{x, y}: inert.hoist.closed(rest, carried) - case Tree.Leaf{x}: inert.hoist.closed(rest, carried) - case Tree.Group{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Lex.TNewline{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{go, +kids, gcl}: - match go: - case Lex.Tok{gk, +o, gl, gc}: - inert.hoist.closed_lg(Lex.TNewline{}, t, o, kids, r2, carried, inert.hoist.closed(kids, - carried), inert.hoist.closed(r2, carried)) - case Tree.Leaf{+tok2}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Tree.NCons{x, y}: inert.hoist.closed(rest, carried) - case Tree.Leaf{x}: inert.hoist.closed(rest, carried) - case Tree.Group{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Lex.TOp{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{go, +kids, gcl}: - match go: - case Lex.Tok{gk, +o, gl, gc}: inert.hoist.closed(rest, carried) - case Tree.Leaf{+tok2}: - inert.hoist.and2(Hoist.leaf_ok(String.eq(t, "~"), D1.inert.tok(tok2), carried), - Hoist.leaf_ok(String.eq(t, "~"), tok2, carried), Hoist.closed(D1.inert.node(r2), - carried), Hoist.closed(r2, carried), inert.hoist.leaf_ok(String.eq(t, "~"), tok2, - carried), inert.hoist.closed(r2, carried)) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Tree.NCons{x, y}: inert.hoist.closed(rest, carried) - case Tree.Leaf{x}: inert.hoist.closed(rest, carried) - case Tree.Group{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Lex.TColon{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{go, +kids, gcl}: - match go: - case Lex.Tok{gk, +o, gl, gc}: - inert.hoist.closed_lg(Lex.TColon{}, t, o, kids, r2, carried, inert.hoist.closed(kids, - carried), inert.hoist.closed(r2, carried)) - case Tree.Leaf{+tok2}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Tree.NCons{x, y}: inert.hoist.closed(rest, carried) - case Tree.Leaf{x}: inert.hoist.closed(rest, carried) - case Tree.Group{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Lex.TEq{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{go, +kids, gcl}: - match go: - case Lex.Tok{gk, +o, gl, gc}: - inert.hoist.closed_lg(Lex.TEq{}, t, o, kids, r2, carried, inert.hoist.closed(kids, - carried), inert.hoist.closed(r2, carried)) - case Tree.Leaf{+tok2}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Tree.NCons{x, y}: inert.hoist.closed(rest, carried) - case Tree.Leaf{x}: inert.hoist.closed(rest, carried) - case Tree.Group{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Lex.TBind{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{go, +kids, gcl}: - match go: - case Lex.Tok{gk, +o, gl, gc}: - inert.hoist.closed_lg(Lex.TBind{}, t, o, kids, r2, carried, inert.hoist.closed(kids, - carried), inert.hoist.closed(r2, carried)) - case Tree.Leaf{+tok2}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Tree.NCons{x, y}: inert.hoist.closed(rest, carried) - case Tree.Leaf{x}: inert.hoist.closed(rest, carried) - case Tree.Group{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Lex.TArrow{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{go, +kids, gcl}: - match go: - case Lex.Tok{gk, +o, gl, gc}: - inert.hoist.closed_lg(Lex.TArrow{}, t, o, kids, r2, carried, inert.hoist.closed(kids, - carried), inert.hoist.closed(r2, carried)) - case Tree.Leaf{+tok2}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Tree.NCons{x, y}: inert.hoist.closed(rest, carried) - case Tree.Leaf{x}: inert.hoist.closed(rest, carried) - case Tree.Group{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Lex.TLam{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{go, +kids, gcl}: - match go: - case Lex.Tok{gk, +o, gl, gc}: - inert.hoist.closed_lg(Lex.TLam{}, t, o, kids, r2, carried, inert.hoist.closed(kids, - carried), inert.hoist.closed(r2, carried)) - case Tree.Leaf{+tok2}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Tree.NCons{x, y}: inert.hoist.closed(rest, carried) - case Tree.Leaf{x}: inert.hoist.closed(rest, carried) - case Tree.Group{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Lex.TAll{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{go, +kids, gcl}: - match go: - case Lex.Tok{gk, +o, gl, gc}: - inert.hoist.closed_lg(Lex.TAll{}, t, o, kids, r2, carried, inert.hoist.closed(kids, - carried), inert.hoist.closed(r2, carried)) - case Tree.Leaf{+tok2}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Tree.NCons{x, y}: inert.hoist.closed(rest, carried) - case Tree.Leaf{x}: inert.hoist.closed(rest, carried) - case Tree.Group{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Lex.TAmp{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{go, +kids, gcl}: - match go: - case Lex.Tok{gk, +o, gl, gc}: - inert.hoist.closed_lg(Lex.TAmp{}, t, o, kids, r2, carried, inert.hoist.closed(kids, - carried), inert.hoist.closed(r2, carried)) - case Tree.Leaf{+tok2}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Tree.NCons{x, y}: inert.hoist.closed(rest, carried) - case Tree.Leaf{x}: inert.hoist.closed(rest, carried) - case Tree.Group{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Lex.TOpen{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{go, +kids, gcl}: - match go: - case Lex.Tok{gk, +o, gl, gc}: - inert.hoist.closed_lg(Lex.TOpen{}, t, o, kids, r2, carried, inert.hoist.closed(kids, - carried), inert.hoist.closed(r2, carried)) - case Tree.Leaf{+tok2}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Tree.NCons{x, y}: inert.hoist.closed(rest, carried) - case Tree.Leaf{x}: inert.hoist.closed(rest, carried) - case Tree.Group{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Lex.TClose{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{go, +kids, gcl}: - match go: - case Lex.Tok{gk, +o, gl, gc}: - inert.hoist.closed_lg(Lex.TClose{}, t, o, kids, r2, carried, inert.hoist.closed(kids, - carried), inert.hoist.closed(r2, carried)) - case Tree.Leaf{+tok2}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Tree.NCons{x, y}: inert.hoist.closed(rest, carried) - case Tree.Leaf{x}: inert.hoist.closed(rest, carried) - case Tree.Group{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Lex.TComma{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{go, +kids, gcl}: - match go: - case Lex.Tok{gk, +o, gl, gc}: - inert.hoist.closed_lg(Lex.TComma{}, t, o, kids, r2, carried, inert.hoist.closed(kids, - carried), inert.hoist.closed(r2, carried)) - case Tree.Leaf{+tok2}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Tree.NCons{x, y}: inert.hoist.closed(rest, carried) - case Tree.Leaf{x}: inert.hoist.closed(rest, carried) - case Tree.Group{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.Stmt{x, y, z}: inert.hoist.closed(rest, carried) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Tree.Group{o, +kids, cl}: - inert.hoist.and2(Hoist.closed(D1.inert.node(kids), carried), Hoist.closed(kids, carried), - Hoist.closed(D1.inert.node(rest), carried), - Hoist.closed(rest, carried), inert.hoist.closed(kids, carried), inert.hoist.closed(rest, carried)) - case Tree.Stmt{sk, +kids, +body}: - inert.hoist.and2(Hoist.closed(D1.inert.node(kids), carried), Hoist.closed(kids, carried), - Bool.and(Hoist.closed(D1.inert.node(body), carried), Hoist.closed(D1.inert.node(rest), carried)), - Bool.and(Hoist.closed(body, carried), Hoist.closed(rest, carried)), inert.hoist.closed(kids, carried), - inert.hoist.and2(Hoist.closed(D1.inert.node(body), carried), Hoist.closed(body, carried), - Hoist.closed(D1.inert.node(rest), carried), Hoist.closed(rest, carried), inert.hoist.closed(body, - carried), - inert.hoist.closed(rest, carried))) - case Tree.NNil{}: inert.hoist.closed(rest, carried) - case Tree.NCons{x, y}: inert.hoist.closed(rest, carried) - case Tree.Leaf{tok}: - match tok: - case Lex.Tok{k, t, l, c}: - match k: - case Lex.TName{}: {==} - case Lex.TUpper{}: {==} - case Lex.TDotted{}: {==} - case Lex.TWild{}: {==} - case Lex.TKey{}: {==} - case Lex.TNum{}: {==} - case Lex.TStr{}: {==} - case Lex.TChar{}: {==} - case Lex.TComment{}: {==} - case Lex.TSpace{}: {==} - case Lex.TNewline{}: {==} - case Lex.TOp{}: {==} - case Lex.TColon{}: {==} - case Lex.TEq{}: {==} - case Lex.TBind{}: {==} - case Lex.TArrow{}: {==} - case Lex.TLam{}: {==} - case Lex.TAll{}: {==} - case Lex.TAmp{}: {==} - case Lex.TOpen{}: {==} - case Lex.TClose{}: {==} - case Lex.TComma{}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - -# a chain read that way holds a top-level token of an unmarked text as it did -law inert.hoist.top_mark: - for nn: Tree.Node - for +want: String - for +ew: {D1.inert.marked(want) == False{} : Bool} - for +e: {D1.inert.ok(nn) == True{} : Bool} - {Hoist.top_mark(D1.inert.node(nn), want) == Hoist.top_mark(nn, want) : Bool} - -def inert.hoist.top_mark(nn, want, ew, e): - match nn: - case Tree.NCons{h, +rest}: - match h: - case Tree.Leaf{tok}: - match tok: - case Lex.Tok{+k, +t, +l, +c}: - inert.or2(String.eq(D1.inert.keep(D1.inert.blanks(k), t), want), String.eq(t, want), - Hoist.top_mark(D1.inert.node(rest), want), Hoist.top_mark(rest, want), - M1.inert.eq_keep(D1.inert.blanks(k), t, want, inert.ok_h(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e), ew), - inert.hoist.top_mark(rest, want, ew, inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e))) - case Tree.Group{o, gk, cl}: inert.hoist.top_mark(rest, want, ew, inert.ok_t(Tree.Group{o, gk, cl}, rest, e)) - case Tree.Stmt{sk, sks, sb}: inert.hoist.top_mark(rest, want, ew, inert.ok_t(Tree.Stmt{sk, sks, sb}, rest, e)) - case Tree.NNil{}: inert.hoist.top_mark(rest, want, ew, e) - case Tree.NCons{x, y}: inert.hoist.top_mark(rest, want, ew, inert.ok_t(Tree.NCons{x, y}, rest, e)) - case Tree.Leaf{tok}: {==} - case Tree.Group{o, gk, cl}: {==} - case Tree.Stmt{sk, sks, sb}: {==} - case Tree.NNil{}: {==} - -# a chain read that way holds as many top-level tokens of an unmarked text as -# it did -law inert.hoist.top_count: - for nn: Tree.Node - for +want: String - for +got: Nat - for +ew: {D1.inert.marked(want) == False{} : Bool} - for +e: {D1.inert.ok(nn) == True{} : Bool} - {Hoist.top_count(D1.inert.node(nn), want, got) == Hoist.top_count(nn, want, got) : Nat} - -def inert.hoist.top_count(nn, want, got, ew, e): - match nn: - case Tree.NCons{h, +rest}: - match h: - case Tree.Leaf{tok}: - match tok: - case Lex.Tok{+k, +t, +l, +c}: - +lf = {Tree.Leaf{Lex.Tok{k, t, l, c}} : Tree.Node} - +lhs = Hoist.top_count(D1.inert.node(Tree.NCons{lf, rest}), want, got) - %inert.hoist.top_count(rest, want, Bool.pick(Nat, String.eq(t, want), (got + 1n : Nat), got), ew, - inert.ok_t(lf, rest, e)) : {lhs == _ : Nat} - %M1.inert.eq_keep(D1.inert.blanks(k), t, want, inert.ok_h(lf, rest, e), ew) : - {lhs == Hoist.top_count(D1.inert.node(rest), want, Bool.pick(Nat, _, (got + 1n : Nat), got)) : Nat} - {==} - case Tree.Group{o, gk, cl}: - inert.hoist.top_count(rest, want, got, ew, inert.ok_t(Tree.Group{o, gk, cl}, rest, e)) - case Tree.Stmt{sk, sks, sb}: - inert.hoist.top_count(rest, want, got, ew, inert.ok_t(Tree.Stmt{sk, sks, sb}, rest, e)) - case Tree.NNil{}: inert.hoist.top_count(rest, want, got, ew, e) - case Tree.NCons{x, y}: inert.hoist.top_count(rest, want, got, ew, inert.ok_t(Tree.NCons{x, y}, rest, e)) - case Tree.Leaf{tok}: {==} - case Tree.Group{o, gk, cl}: {==} - case Tree.Stmt{sk, sks, sb}: {==} - case Tree.NNil{}: {==} - -# a bracket's kids read that way are an array as they were -law inert.hoist.is_array: - for +kids: Tree.Node - for +e: {D1.inert.ok(kids) == True{} : Bool} - {Hoist.is_array(D1.inert.node(kids)) == Hoist.is_array(kids) : Bool} - -def inert.hoist.is_array(kids, e): - +lhs = Hoist.is_array(D1.inert.node(kids)) - %inert.hoist.top_mark(kids, ":", {==}, e) : - {lhs == Bool.and(_, Bool.or(Hoist.top_mark(kids, "*"), Hoist.top_mark(kids, "^"))) : Bool} - %inert.hoist.top_mark(kids, "*", {==}, e) : - {lhs == Bool.and(Hoist.top_mark(D1.inert.node(kids), ":"), Bool.or(_, Hoist.top_mark(kids, "^"))) : Bool} - %inert.hoist.top_mark(kids, "^", {==}, e) : - {lhs == Bool.and(Hoist.top_mark(D1.inert.node(kids), ":"), Bool.or(Hoist.top_mark(D1.inert.node(kids), "*"), - _)) : Bool} - {==} - -# a chain read that way is sized small as it was: an operator's and a -# number's text are read, never a string's -law inert.hoist.small_size: - for nn: Tree.Node - {Hoist.small_size(D1.inert.node(nn)) == Hoist.small_size(nn) : Bool} - -def inert.hoist.small_size(nn): - match nn: - case Tree.NCons{h, +rest}: - match h: - case Tree.Leaf{tok}: - match tok: - case Lex.Tok{k, op, l, c}: - match k: - case Lex.TName{}: inert.hoist.small_size(rest) - case Lex.TUpper{}: inert.hoist.small_size(rest) - case Lex.TDotted{}: inert.hoist.small_size(rest) - case Lex.TWild{}: inert.hoist.small_size(rest) - case Lex.TKey{}: inert.hoist.small_size(rest) - case Lex.TNum{}: inert.hoist.small_size(rest) - case Lex.TStr{}: inert.hoist.small_size(rest) - case Lex.TChar{}: inert.hoist.small_size(rest) - case Lex.TComment{}: inert.hoist.small_size(rest) - case Lex.TSpace{}: inert.hoist.small_size(rest) - case Lex.TNewline{}: inert.hoist.small_size(rest) - case Lex.TOp{}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Leaf{tok2}: - match tok2: - case Lex.Tok{k2, t2, l2, c2}: - match k2: - case Lex.TName{}: inert.hoist.small_size(rest) - case Lex.TUpper{}: inert.hoist.small_size(rest) - case Lex.TDotted{}: inert.hoist.small_size(rest) - case Lex.TWild{}: inert.hoist.small_size(rest) - case Lex.TKey{}: inert.hoist.small_size(rest) - case Lex.TNum{}: - Equal.cong(Bool, Bool, z => Bool.or(Bool.or(Bool.and(String.eq(op, "*"), - Hoist.small_num(t2)), Bool.and(String.eq(op, "^"), List.contains(~String, - ~String.eq, ["0", "1", "2", "3", "0n", "1n", "2n", "3n"], t2))), z), - Hoist.small_size(D1.inert.node(r2)), Hoist.small_size(r2), - inert.hoist.small_size(r2)) - case Lex.TStr{}: inert.hoist.small_size(rest) - case Lex.TChar{}: inert.hoist.small_size(rest) - case Lex.TComment{}: inert.hoist.small_size(rest) - case Lex.TSpace{}: inert.hoist.small_size(rest) - case Lex.TNewline{}: inert.hoist.small_size(rest) - case Lex.TOp{}: inert.hoist.small_size(rest) - case Lex.TColon{}: inert.hoist.small_size(rest) - case Lex.TEq{}: inert.hoist.small_size(rest) - case Lex.TBind{}: inert.hoist.small_size(rest) - case Lex.TArrow{}: inert.hoist.small_size(rest) - case Lex.TLam{}: inert.hoist.small_size(rest) - case Lex.TAll{}: inert.hoist.small_size(rest) - case Lex.TAmp{}: inert.hoist.small_size(rest) - case Lex.TOpen{}: inert.hoist.small_size(rest) - case Lex.TClose{}: inert.hoist.small_size(rest) - case Lex.TComma{}: inert.hoist.small_size(rest) - case Tree.Group{x, y, z}: inert.hoist.small_size(rest) - case Tree.Stmt{x, y, z}: inert.hoist.small_size(rest) - case Tree.NNil{}: inert.hoist.small_size(rest) - case Tree.NCons{x, y}: inert.hoist.small_size(rest) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TColon{}: inert.hoist.small_size(rest) - case Lex.TEq{}: inert.hoist.small_size(rest) - case Lex.TBind{}: inert.hoist.small_size(rest) - case Lex.TArrow{}: inert.hoist.small_size(rest) - case Lex.TLam{}: inert.hoist.small_size(rest) - case Lex.TAll{}: inert.hoist.small_size(rest) - case Lex.TAmp{}: inert.hoist.small_size(rest) - case Lex.TOpen{}: inert.hoist.small_size(rest) - case Lex.TClose{}: inert.hoist.small_size(rest) - case Lex.TComma{}: inert.hoist.small_size(rest) - case Tree.Group{o, gk, cl}: inert.hoist.small_size(rest) - case Tree.Stmt{sk, sks, sb}: inert.hoist.small_size(rest) - case Tree.NNil{}: inert.hoist.small_size(rest) - case Tree.NCons{x, y}: inert.hoist.small_size(rest) - case Tree.Leaf{tok}: {==} - case Tree.Group{o, gk, cl}: {==} - case Tree.Stmt{sk, sks, sb}: {==} - case Tree.NNil{}: {==} - -# a table's kids read that way are as wide as they were -law inert.hoist.wide_shape: - for arr: Bool - for +kids: Tree.Node - for +e: {D1.inert.ok(kids) == True{} : Bool} - {Hoist.wide_shape(arr, D1.inert.node(kids)) == Hoist.wide_shape(arr, kids) : Bool} - -def inert.hoist.wide_shape(arr, kids, e): - match arr: - case True{}: - Equal.cong(Bool, Bool, z => Bool.not(z), Hoist.small_size(D1.inert.node(kids)), Hoist.small_size(kids), - inert.hoist.small_size(kids)) - case False{}: - Equal.cong(Nat, Bool, z => Nat.is_gt(z, 7n), Hoist.top_count(D1.inert.node(kids), ",", 0n), - Hoist.top_count(kids, ",", 0n), - inert.hoist.top_count(kids, ",", 0n, {==}, e)) - -# a chain read that way is the number it was: only a number's text is read -law inert.hoist.num_text: - for nn: Tree.Node - {Hoist.num_text(D1.inert.node(nn)) == Hoist.num_text(nn) : String} - -def inert.hoist.num_text(nn): - match nn: - case Tree.NCons{h, r}: - match h: - case Tree.Leaf{tok}: - match tok: - case Lex.Tok{k, t, l, c}: - match k: - case Lex.TName{}: {==} - case Lex.TUpper{}: {==} - case Lex.TDotted{}: {==} - case Lex.TWild{}: {==} - case Lex.TKey{}: {==} - case Lex.TNum{}: - match r: - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Lex.TStr{}: {==} - case Lex.TChar{}: {==} - case Lex.TComment{}: {==} - case Lex.TSpace{}: {==} - case Lex.TNewline{}: {==} - case Lex.TOp{}: {==} - case Lex.TColon{}: {==} - case Lex.TEq{}: {==} - case Lex.TBind{}: {==} - case Lex.TArrow{}: {==} - case Lex.TLam{}: {==} - case Lex.TAll{}: {==} - case Lex.TAmp{}: {==} - case Lex.TOpen{}: {==} - case Lex.TClose{}: {==} - case Lex.TComma{}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - -# a count read that way is wide as it was -law inert.hoist.count_wide: - for +aa: Tree.Node - {Hoist.count_wide(D1.inert.node(aa)) == Hoist.count_wide(aa) : Bool} - -def inert.hoist.count_wide(aa): - Equal.cong(String, Bool, z => Bool.not(Hoist.small_num(z)), Hoist.num_text(D1.inert.node(aa)), Hoist.num_text(aa), - inert.hoist.num_text(aa)) - -# the last of the arguments read that way is the last read that way -law inert.hoist.last_node: - for as: List<&2, Tree.Node> - {Hoist.last_node(inert.nodes(as)) == D1.inert.node(Hoist.last_node(as)) : Tree.Node} - -def inert.hoist.last_node(as): - match as: - case Nil{}: {==} - case Con{h, +rest}: - match rest: - case Nil{}: {==} - case Con{x, y}: inert.hoist.last_node(rest) - -# a builder's count read that way is wide as it was -law inert.hoist.call_arm: - for rep: Bool - for +as: List<&2, Tree.Node> - for +end: Tree.Node - for +range: Bool - {Hoist.call_arm(rep, inert.nodes(as), D1.inert.node(end), range) == Hoist.call_arm(rep, as, end, range) : Bool} - -def inert.hoist.call_arm(rep, as, end, range): - match rep: - case True{}: - %Equal.sym(Tree.Node, Calls.arg(inert.nodes(as), 2n), D1.inert.node(Calls.arg(as, 2n)), inert.arg(as, 2n)) : - {Hoist.count_wide(_) == Hoist.count_wide(Calls.arg(as, 2n)) : Bool} - inert.hoist.count_wide(Calls.arg(as, 2n)) - case False{}: - Equal.cong(Bool, Bool, z => Bool.pick(Bool, range, z, True{}), Hoist.count_wide(D1.inert.node(end)), - Hoist.count_wide(end), - inert.hoist.count_wide(end)) - -# a builder call read that way is wide as it was: a string names no builder -law inert.hoist.call_wide: - for +b: Bool - for +t: String - for +kids: Tree.Node - for +ef: {D1.inert.fits.go(b, t) == True{} : Bool} - {Hoist.call_wide(D1.inert.keep(b, t), D1.inert.node(kids)) == Hoist.call_wide(t, kids) : Bool} - -def inert.hoist.call_wide(b, t, kids, ef): - +kt = D1.inert.keep(b, t) - +lhs = Hoist.call_wide(kt, D1.inert.node(kids)) - +aa = Calls.args(kids) - +rp = Bool.or(String.eq(kt, "List.replicate"), String.eq(kt, "Array.new")) - +rg = String.eq(kt, "List.range") - %M1.inert.eq_keep(b, t, "List.replicate", ef, {==}) : - {lhs == Hoist.call_arm(Bool.or(_, String.eq(t, "Array.new")), aa, Hoist.last_node(aa), String.eq(t, "List.range")) - : Bool} - %M1.inert.eq_keep(b, t, "Array.new", ef, {==}) : - {lhs == Hoist.call_arm(Bool.or(String.eq(kt, "List.replicate"), _), aa, Hoist.last_node(aa), - String.eq(t, "List.range")) : Bool} - %M1.inert.eq_keep(b, t, "List.range", ef, {==}) : {lhs == Hoist.call_arm(rp, aa, Hoist.last_node(aa), _) : Bool} - %inert.hoist.call_arm(rp, aa, Hoist.last_node(aa), rg) : {lhs == _ : Bool} - %inert.hoist.last_node(aa) : {lhs == Hoist.call_arm(rp, inert.nodes(aa), _, rg) : Bool} - %inert.args(kids) : {lhs == Hoist.call_arm(rp, _, Hoist.last_node(_), rg) : Bool} - {==} - -# a table's cells read that way are as wide as they were -law inert.hoist.wide_of: - for nn: Tree.Node - for +e: {D1.inert.ok(nn) == True{} : Bool} - {Hoist.wide_of(D1.inert.node(nn)) == Hoist.wide_of(nn) : Bool} - -def inert.hoist.wide_of(nn, e): - match nn: - case Tree.NCons{h, +r}: - match h: - case Tree.Group{go, +kids, cl}: - match go: - case Lex.Tok{gk, +o, gl, gc}: - match r: - case Tree.NNil{}: - +ek = M1.inert.and_l(D1.inert.ok(kids), True{}, e) - +lhs = Hoist.wide_shape(Hoist.is_array(D1.inert.node(kids)), D1.inert.node(kids)) - %inert.hoist.wide_shape(Hoist.is_array(kids), kids, ek) : {lhs == _ : Bool} - %inert.hoist.is_array(kids, ek) : {lhs == Hoist.wide_shape(_, D1.inert.node(kids)) : Bool} - {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{tok}: - match tok: - case Lex.Tok{+k, +t, +l, +c}: - match r: - case Tree.NCons{g, +r2}: - match g: - case Tree.Group{go, +gkids, gcl}: - match go: - case Lex.Tok{gk, +o, gl, gc}: - match r2: - case Tree.NNil{}: - inert.hoist.call_wide(D1.inert.blanks(k), t, gkids, - inert.ok_h(Tree.Leaf{Lex.Tok{k, t, l, c}}, Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, - gkids, gcl}, Tree.NNil{}}, e)) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - -# a def's body read that way is one wide table as it was -law inert.hoist.wide_one: - for body: Tree.Node - for +name: String - for +acc: List<&2, String> - for +e: {D1.inert.ok(body) == True{} : Bool} - {Hoist.wide_one(D1.inert.node(body), name, acc) == Hoist.wide_one(body, name, acc) : List<&2, String>} - -def inert.hoist.wide_one(body, name, acc, e): - match body: - case Tree.NCons{h, +r}: - match h: - case Tree.Stmt{sk, +kids, +inn}: - match r: - case Tree.NNil{}: - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(inn), M1.inert.and_l(Bool.and(D1.inert.ok(kids), - D1.inert.ok(inn)), True{}, e)) - +lhs = Hoist.wide_one(D1.inert.node(Tree.NCons{Tree.Stmt{sk, kids, inn}, Tree.NNil{}}), name, acc) - %inert.table.built(kids, ek) : {lhs == Bool.pick(List<&2, String>, Bool.and(_, Hoist.wide_of(kids)), - name <> acc, acc) - : List<&2, String>} - %inert.hoist.wide_of(kids, ek) : {lhs == Bool.pick(List<&2, String>, - Bool.and(Table.built(D1.inert.node(kids)), _), name <> acc, - acc) : List<&2, String>} - {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - -# the defs read that way whose body is one wide table are the ones they were -law inert.hoist.wide_defs: - for ds: List<&2, Calls.Def> - for +acc: List<&2, String> - for +e: {inert.dok(ds) == True{} : Bool} - {Hoist.wide_defs(inert.defs(ds), acc) == Hoist.wide_defs(ds, acc) : List<&2, String>} - -def inert.hoist.wide_defs(ds, acc, e): - match ds: - case Nil{}: {==} - case Con{Calls.Def{+name, +sig, +body}, +rest}: - +lhs = Hoist.wide_defs(inert.defs(Calls.Def{name, sig, body} <> rest), acc) - %inert.hoist.wide_defs(rest, Hoist.wide_one(body, name, acc), inert.dok.rest(name, sig, body, rest, e)) : - {lhs == _ : List<&2, String>} - %inert.hoist.wide_one(body, name, acc, inert.dok.body(name, sig, body, rest, e)) : - {lhs == Hoist.wide_defs(inert.defs(rest), _) : List<&2, String>} - {==} - -# a call's name read that way picks the slot it did: a string names no get or set -law inert.hoist.slot_of: - for +b: Bool - for +t: String - for +ef: {D1.inert.fits.go(b, t) == True{} : Bool} - {Hoist.slot_of(D1.inert.keep(b, t)) == Hoist.slot_of(t) : Hoist.Slot} - -def inert.hoist.slot_of(b, t, ef): - +kt = D1.inert.keep(b, t) - +lhs = Hoist.slot_of(kt) - %M1.inert.eq_keep(b, t, "List.get", ef, {==}) : {lhs == Hoist.slot_pick(Bool.or(_, String.eq(t, "List.set")), - String.eq(t, "String.get"), Bool.or(String.eq(t, "Array.get"), String.eq(t, "Array.set"))) : Hoist.Slot} - %M1.inert.eq_keep(b, t, "List.set", ef, {==}) : {lhs == Hoist.slot_pick(Bool.or(String.eq(kt, "List.get"), _), - String.eq(t, "String.get"), Bool.or(String.eq(t, "Array.get"), String.eq(t, "Array.set"))) : Hoist.Slot} - %M1.inert.eq_keep(b, t, "String.get", ef, {==}) : {lhs == Hoist.slot_pick(Bool.or(String.eq(kt, "List.get"), - String.eq(kt, "List.set")), _, Bool.or(String.eq(t, "Array.get"), String.eq(t, "Array.set"))) : Hoist.Slot} - %M1.inert.eq_keep(b, t, "Array.get", ef, {==}) : {lhs == Hoist.slot_pick(Bool.or(String.eq(kt, "List.get"), - String.eq(kt, "List.set")), String.eq(kt, "String.get"), Bool.or(_, String.eq(t, "Array.set"))) : Hoist.Slot} - %M1.inert.eq_keep(b, t, "Array.set", ef, {==}) : {lhs == Hoist.slot_pick(Bool.or(String.eq(kt, "List.get"), - String.eq(kt, "List.set")), String.eq(kt, "String.get"), Bool.or(String.eq(kt, "Array.get"), _)) : Hoist.Slot} - {==} - -# a bracket group read that way is a closed wide table as it was -law inert.hoist.table_group: - for brack: Bool - for +kids: Tree.Node - for +carried: List<&2, String> - for +e: {D1.inert.ok(kids) == True{} : Bool} - {Hoist.table_group(brack, D1.inert.node(kids), carried) == Hoist.table_group(brack, kids, carried) : Bool} - -def inert.hoist.table_group(brack, kids, carried, e): - match brack: - case False{}: {==} - case True{}: - +ia = Hoist.is_array(D1.inert.node(kids)) - +lhs = Hoist.group_yes(ia, D1.inert.node(kids), carried) - %inert.hoist.is_array(kids, e) : {lhs == Hoist.group_yes(_, kids, carried) : Bool} - %inert.hoist.top_mark(kids, ",", {==}, e) : {lhs == Bool.and(Bool.or(_, ia), Bool.and(Hoist.closed(kids, carried), - Hoist.wide_shape(ia, kids))) : Bool} - %inert.hoist.closed(kids, carried) : {lhs == Bool.and(Bool.or(Hoist.top_mark(D1.inert.node(kids), ","), ia), - Bool.and(_, - Hoist.wide_shape(ia, kids))) : Bool} - %inert.hoist.wide_shape(ia, kids, e) : {lhs == Bool.and(Bool.or(Hoist.top_mark(D1.inert.node(kids), ","), ia), - Bool.and(Hoist.closed(D1.inert.node(kids), carried), _)) : Bool} - {==} - -# a builder's closed arguments read that way are closed as they were -law inert.hoist.tb2: - for o: Bool - for w: Bool - for +kids: Tree.Node - for +carried: List<&2, String> - {Hoist.table_built(o, w, D1.inert.node(kids), carried) == Hoist.table_built(o, w, kids, carried) : Bool} - -def inert.hoist.tb2(o, w, kids, carried): - match o w: - case True{} True{}: inert.hoist.closed(kids, carried) - case True{} False{}: {==} - case False{} True{}: {==} - case False{} False{}: {==} - -# a builder's arguments read that way are a closed wide table as they were -law inert.hoist.tb: - for +o: Bool - for +t: String - for +kids: Tree.Node - for +carried: List<&2, String> - for +ef: {D1.inert.fits.go(False{}, t) == True{} : Bool} - {Hoist.table_built(o, Hoist.call_wide(t, D1.inert.node(kids)), D1.inert.node(kids), carried) - == Hoist.table_built(o, Hoist.call_wide(t, kids), kids, carried) : Bool} - -def inert.hoist.tb(o, t, kids, carried, ef): - +lhs = Hoist.table_built(o, Hoist.call_wide(t, D1.inert.node(kids)), D1.inert.node(kids), carried) - %inert.hoist.call_wide(False{}, t, kids, ef) : {lhs == Hoist.table_built(o, _, kids, carried) : Bool} - inert.hoist.tb2(o, Hoist.call_wide(t, D1.inert.node(kids)), kids, carried) - -# a call read that way builds a table as it did: a string names no builder -law inert.hoist.table_call: - for app: Bool - for k: Lex.TokKind - for +t: String - for +kids: Tree.Node - for +self: String - for +wides: List<&2, String> - for +carried: List<&2, String> - for +ef: {D1.inert.fits.go(D1.inert.blanks(k), t) == True{} : Bool} - {Hoist.table_call(app, k, D1.inert.keep(D1.inert.blanks(k), t), D1.inert.node(kids), self, wides, carried) - == Hoist.table_call(app, k, t, kids, self, wides, carried) : Bool} - -def inert.hoist.table_call(app, k, t, kids, self, wides, carried, ef): - match app: - case False{}: {==} - case True{}: - match k: - case Lex.TName{}: inert.hoist.tb(Hoist.builder(Lex.TName{}, t, self, wides), t, kids, carried, ef) - case Lex.TUpper{}: inert.hoist.tb(Hoist.builder(Lex.TUpper{}, t, self, wides), t, kids, carried, ef) - case Lex.TDotted{}: inert.hoist.tb(Hoist.builder(Lex.TDotted{}, t, self, wides), t, kids, carried, ef) - case Lex.TWild{}: inert.hoist.tb(Hoist.builder(Lex.TWild{}, t, self, wides), t, kids, carried, ef) - case Lex.TKey{}: inert.hoist.tb(Hoist.builder(Lex.TKey{}, t, self, wides), t, kids, carried, ef) - case Lex.TNum{}: inert.hoist.tb(Hoist.builder(Lex.TNum{}, t, self, wides), t, kids, carried, ef) - case Lex.TStr{}: {==} - case Lex.TChar{}: inert.hoist.tb(Hoist.builder(Lex.TChar{}, t, self, wides), t, kids, carried, ef) - case Lex.TComment{}: {==} - case Lex.TSpace{}: inert.hoist.tb(Hoist.builder(Lex.TSpace{}, t, self, wides), t, kids, carried, ef) - case Lex.TNewline{}: inert.hoist.tb(Hoist.builder(Lex.TNewline{}, t, self, wides), t, kids, carried, ef) - case Lex.TOp{}: inert.hoist.tb(Hoist.builder(Lex.TOp{}, t, self, wides), t, kids, carried, ef) - case Lex.TColon{}: inert.hoist.tb(Hoist.builder(Lex.TColon{}, t, self, wides), t, kids, carried, ef) - case Lex.TEq{}: inert.hoist.tb(Hoist.builder(Lex.TEq{}, t, self, wides), t, kids, carried, ef) - case Lex.TBind{}: inert.hoist.tb(Hoist.builder(Lex.TBind{}, t, self, wides), t, kids, carried, ef) - case Lex.TArrow{}: inert.hoist.tb(Hoist.builder(Lex.TArrow{}, t, self, wides), t, kids, carried, ef) - case Lex.TLam{}: inert.hoist.tb(Hoist.builder(Lex.TLam{}, t, self, wides), t, kids, carried, ef) - case Lex.TAll{}: inert.hoist.tb(Hoist.builder(Lex.TAll{}, t, self, wides), t, kids, carried, ef) - case Lex.TAmp{}: inert.hoist.tb(Hoist.builder(Lex.TAmp{}, t, self, wides), t, kids, carried, ef) - case Lex.TOpen{}: inert.hoist.tb(Hoist.builder(Lex.TOpen{}, t, self, wides), t, kids, carried, ef) - case Lex.TClose{}: inert.hoist.tb(Hoist.builder(Lex.TClose{}, t, self, wides), t, kids, carried, ef) - case Lex.TComma{}: inert.hoist.tb(Hoist.builder(Lex.TComma{}, t, self, wides), t, kids, carried, ef) - -# a call named by a string is no table, whatever it says -law inert.hoist.tc_TStr: - for app: Bool - for -t1: String - for -t2: String - for -k1: Tree.Node - for -k2: Tree.Node - for +self: String - for +wides: List<&2, String> - for +carried: List<&2, String> - for -n1: Tree.Node - for -n2: Tree.Node - for +path: String - {Hoist.one_yes(Hoist.table_call(app, Lex.TStr{}, t1, k1, self, wides, carried), n1, path) - == Hoist.one_yes(Hoist.table_call(app, Lex.TStr{}, t2, k2, self, wides, carried), n2, path) : List<&2, F.Finding>} - -def inert.hoist.tc_TStr(app, _t1, _t2, _k1, _k2, _self, _wides, _carried, _n1, _n2, _path): - match app: - case True{}: {==} - case False{}: {==} - -# a call named by a comment is no table, whatever it says -law inert.hoist.tc_TComment: - for app: Bool - for -t1: String - for -t2: String - for -k1: Tree.Node - for -k2: Tree.Node - for +self: String - for +wides: List<&2, String> - for +carried: List<&2, String> - for -n1: Tree.Node - for -n2: Tree.Node - for +path: String - {Hoist.one_yes(Hoist.table_call(app, Lex.TComment{}, t1, k1, self, wides, carried), n1, path) - == Hoist.one_yes(Hoist.table_call(app, Lex.TComment{}, t2, k2, self, wides, carried), n2, path) : List<&2, - F.Finding>} - -def inert.hoist.tc_TComment(app, _t1, _t2, _k1, _k2, _self, _wides, _carried, _n1, _n2, _path): - match app: - case True{}: {==} - case False{}: {==} - -# a finding that is there or not, on two nodes it cites alike -law inert.hoist.one: - for yes: Bool - for -n1: Tree.Node - for -n2: Tree.Node - for +path: String - for ec: {Hoist.cite(n1, path) == Hoist.cite(n2, path) : List<&2, F.Finding>} - {Hoist.one_yes(yes, n1, path) == Hoist.one_yes(yes, n2, path) : List<&2, F.Finding>} - -def inert.hoist.one(yes, _n1, _n2, _path, ec): - match yes: - case True{}: ec - case False{}: {==} - -# a table read that way is the table it was, cited where it was: a table -# starts with a bracket or a builder's name, never a string -law inert.hoist.yes: - for nn: Tree.Node - for +self: String - for +wides: List<&2, String> - for +carried: List<&2, String> - for +path: String - for +e: {D1.inert.ok(nn) == True{} : Bool} - {Hoist.one_yes(Hoist.is_table(D1.inert.node(nn), self, wides, carried), D1.inert.node(nn), path) - == Hoist.one_yes(Hoist.is_table(nn, self, wides, carried), nn, path) : List<&2, F.Finding>} - -def inert.hoist.yes(nn, self, wides, carried, path, e): - match nn: - case Tree.NCons{h, +r}: - match h: - case Tree.Group{go, +kids, +gcl}: - match go: - case Lex.Tok{+gk, +o, +gl, +gc}: - match r: - case Tree.NNil{}: - +n1 = D1.inert.node(Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, kids, gcl}, Tree.NNil{}}) - +n2 = {Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, kids, gcl}, Tree.NNil{}} : Tree.Node} - +tg = Hoist.table_group(String.eq(o, "["), D1.inert.node(kids), carried) - %inert.hoist.table_group(String.eq(o, "["), kids, carried, M1.inert.and_l(D1.inert.ok(kids), True{}, - e)) : {Hoist.one_yes(tg, n1, path) == Hoist.one_yes(_, n2, path) : List<&2, F.Finding>} - inert.hoist.one(tg, n1, n2, path, {==}) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{tok}: - match tok: - case Lex.Tok{k, +t, +l, +c}: - match k: - case Lex.TName{}: - match r: - case Tree.NCons{g, +r2}: - match g: - case Tree.Group{go, +gkids, +gcl}: - match go: - case Lex.Tok{+gk, +o, +gl, +gc}: - match r2: - case Tree.NNil{}: - +n1 = D1.inert.node(Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TName{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}}) - +n2 = {Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TName{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, - Tree.NNil{}}} : Tree.Node} - %inert.hoist.table_call(String.eq(o, "("), Lex.TName{}, t, gkids, self, wides, - carried, - inert.ok_h(Tree.Leaf{Lex.Tok{Lex.TName{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}, e)) : - {Hoist.one_yes(Hoist.table_call(String.eq(o, "("), Lex.TName{}, t, - D1.inert.node(gkids), self, wides, carried), n1, path) == Hoist.one_yes(_, n2, - path) : List<&2, F.Finding>} - inert.hoist.one(Hoist.table_call(String.eq(o, "("), Lex.TName{}, t, - D1.inert.node(gkids), self, wides, carried), n1, n2, path, {==}) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TUpper{}: - match r: - case Tree.NCons{g, +r2}: - match g: - case Tree.Group{go, +gkids, +gcl}: - match go: - case Lex.Tok{+gk, +o, +gl, +gc}: - match r2: - case Tree.NNil{}: - +n1 = D1.inert.node(Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TUpper{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}}) - +n2 = {Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TUpper{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, - Tree.NNil{}}} : Tree.Node} - %inert.hoist.table_call(String.eq(o, "("), Lex.TUpper{}, t, gkids, self, wides, - carried, - inert.ok_h(Tree.Leaf{Lex.Tok{Lex.TUpper{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}, e)) : - {Hoist.one_yes(Hoist.table_call(String.eq(o, "("), Lex.TUpper{}, t, - D1.inert.node(gkids), self, wides, carried), n1, path) == Hoist.one_yes(_, n2, - path) : List<&2, F.Finding>} - inert.hoist.one(Hoist.table_call(String.eq(o, "("), Lex.TUpper{}, t, - D1.inert.node(gkids), self, wides, carried), n1, n2, path, {==}) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TDotted{}: - match r: - case Tree.NCons{g, +r2}: - match g: - case Tree.Group{go, +gkids, +gcl}: - match go: - case Lex.Tok{+gk, +o, +gl, +gc}: - match r2: - case Tree.NNil{}: - +n1 = D1.inert.node(Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TDotted{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}}) - +n2 = {Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TDotted{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, - Tree.NNil{}}} : Tree.Node} - %inert.hoist.table_call(String.eq(o, "("), Lex.TDotted{}, t, gkids, self, wides, - carried, - inert.ok_h(Tree.Leaf{Lex.Tok{Lex.TDotted{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}, e)) : - {Hoist.one_yes(Hoist.table_call(String.eq(o, "("), Lex.TDotted{}, t, - D1.inert.node(gkids), self, wides, carried), n1, path) == Hoist.one_yes(_, n2, - path) : List<&2, F.Finding>} - inert.hoist.one(Hoist.table_call(String.eq(o, "("), Lex.TDotted{}, t, - D1.inert.node(gkids), self, wides, carried), n1, n2, path, {==}) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TWild{}: - match r: - case Tree.NCons{g, +r2}: - match g: - case Tree.Group{go, +gkids, +gcl}: - match go: - case Lex.Tok{+gk, +o, +gl, +gc}: - match r2: - case Tree.NNil{}: - +n1 = D1.inert.node(Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TWild{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}}) - +n2 = {Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TWild{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, - Tree.NNil{}}} : Tree.Node} - %inert.hoist.table_call(String.eq(o, "("), Lex.TWild{}, t, gkids, self, wides, - carried, - inert.ok_h(Tree.Leaf{Lex.Tok{Lex.TWild{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}, e)) : - {Hoist.one_yes(Hoist.table_call(String.eq(o, "("), Lex.TWild{}, t, - D1.inert.node(gkids), self, wides, carried), n1, path) == Hoist.one_yes(_, n2, - path) : List<&2, F.Finding>} - inert.hoist.one(Hoist.table_call(String.eq(o, "("), Lex.TWild{}, t, - D1.inert.node(gkids), self, wides, carried), n1, n2, path, {==}) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TKey{}: - match r: - case Tree.NCons{g, +r2}: - match g: - case Tree.Group{go, +gkids, +gcl}: - match go: - case Lex.Tok{+gk, +o, +gl, +gc}: - match r2: - case Tree.NNil{}: - +n1 = D1.inert.node(Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TKey{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}}) - +n2 = {Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TKey{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, - Tree.NNil{}}} : Tree.Node} - %inert.hoist.table_call(String.eq(o, "("), Lex.TKey{}, t, gkids, self, wides, carried, - inert.ok_h(Tree.Leaf{Lex.Tok{Lex.TKey{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}, e)) : - {Hoist.one_yes(Hoist.table_call(String.eq(o, "("), Lex.TKey{}, t, - D1.inert.node(gkids), self, wides, carried), n1, path) == Hoist.one_yes(_, n2, - path) : List<&2, F.Finding>} - inert.hoist.one(Hoist.table_call(String.eq(o, "("), Lex.TKey{}, t, - D1.inert.node(gkids), self, wides, carried), n1, n2, path, {==}) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TNum{}: - match r: - case Tree.NCons{g, +r2}: - match g: - case Tree.Group{go, +gkids, +gcl}: - match go: - case Lex.Tok{+gk, +o, +gl, +gc}: - match r2: - case Tree.NNil{}: - +n1 = D1.inert.node(Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TNum{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}}) - +n2 = {Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TNum{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, - Tree.NNil{}}} : Tree.Node} - %inert.hoist.table_call(String.eq(o, "("), Lex.TNum{}, t, gkids, self, wides, carried, - inert.ok_h(Tree.Leaf{Lex.Tok{Lex.TNum{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}, e)) : - {Hoist.one_yes(Hoist.table_call(String.eq(o, "("), Lex.TNum{}, t, - D1.inert.node(gkids), self, wides, carried), n1, path) == Hoist.one_yes(_, n2, - path) : List<&2, F.Finding>} - inert.hoist.one(Hoist.table_call(String.eq(o, "("), Lex.TNum{}, t, - D1.inert.node(gkids), self, wides, carried), n1, n2, path, {==}) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TStr{}: - match r: - case Tree.NCons{g, +r2}: - match g: - case Tree.Group{go, +gkids, +gcl}: - match go: - case Lex.Tok{+gk, +o, +gl, +gc}: - match r2: - case Tree.NNil{}: - inert.hoist.tc_TStr(String.eq(o, "("), D1.inert.cut(t), t, D1.inert.node(gkids), - gkids, self, wides, carried, - D1.inert.node(Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TStr{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}}), - Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TStr{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}}, path) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TChar{}: - match r: - case Tree.NCons{g, +r2}: - match g: - case Tree.Group{go, +gkids, +gcl}: - match go: - case Lex.Tok{+gk, +o, +gl, +gc}: - match r2: - case Tree.NNil{}: - +n1 = D1.inert.node(Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TChar{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}}) - +n2 = {Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TChar{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, - Tree.NNil{}}} : Tree.Node} - %inert.hoist.table_call(String.eq(o, "("), Lex.TChar{}, t, gkids, self, wides, - carried, - inert.ok_h(Tree.Leaf{Lex.Tok{Lex.TChar{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}, e)) : - {Hoist.one_yes(Hoist.table_call(String.eq(o, "("), Lex.TChar{}, t, - D1.inert.node(gkids), self, wides, carried), n1, path) == Hoist.one_yes(_, n2, - path) : List<&2, F.Finding>} - inert.hoist.one(Hoist.table_call(String.eq(o, "("), Lex.TChar{}, t, - D1.inert.node(gkids), self, wides, carried), n1, n2, path, {==}) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TComment{}: - match r: - case Tree.NCons{g, +r2}: - match g: - case Tree.Group{go, +gkids, +gcl}: - match go: - case Lex.Tok{+gk, +o, +gl, +gc}: - match r2: - case Tree.NNil{}: - inert.hoist.tc_TComment(String.eq(o, "("), D1.inert.cut(t), t, - D1.inert.node(gkids), gkids, self, wides, carried, - D1.inert.node(Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TComment{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}}), - Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TComment{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}}, path) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TSpace{}: - match r: - case Tree.NCons{g, +r2}: - match g: - case Tree.Group{go, +gkids, +gcl}: - match go: - case Lex.Tok{+gk, +o, +gl, +gc}: - match r2: - case Tree.NNil{}: - +n1 = D1.inert.node(Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TSpace{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}}) - +n2 = {Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TSpace{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, - Tree.NNil{}}} : Tree.Node} - %inert.hoist.table_call(String.eq(o, "("), Lex.TSpace{}, t, gkids, self, wides, - carried, - inert.ok_h(Tree.Leaf{Lex.Tok{Lex.TSpace{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}, e)) : - {Hoist.one_yes(Hoist.table_call(String.eq(o, "("), Lex.TSpace{}, t, - D1.inert.node(gkids), self, wides, carried), n1, path) == Hoist.one_yes(_, n2, - path) : List<&2, F.Finding>} - inert.hoist.one(Hoist.table_call(String.eq(o, "("), Lex.TSpace{}, t, - D1.inert.node(gkids), self, wides, carried), n1, n2, path, {==}) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TNewline{}: - match r: - case Tree.NCons{g, +r2}: - match g: - case Tree.Group{go, +gkids, +gcl}: - match go: - case Lex.Tok{+gk, +o, +gl, +gc}: - match r2: - case Tree.NNil{}: - +n1 = D1.inert.node(Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TNewline{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}}) - +n2 = {Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TNewline{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, - Tree.NNil{}}} : Tree.Node} - %inert.hoist.table_call(String.eq(o, "("), Lex.TNewline{}, t, gkids, self, wides, - carried, - inert.ok_h(Tree.Leaf{Lex.Tok{Lex.TNewline{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}, e)) : - {Hoist.one_yes(Hoist.table_call(String.eq(o, "("), Lex.TNewline{}, t, - D1.inert.node(gkids), self, wides, carried), n1, path) == Hoist.one_yes(_, n2, - path) : List<&2, F.Finding>} - inert.hoist.one(Hoist.table_call(String.eq(o, "("), Lex.TNewline{}, t, - D1.inert.node(gkids), self, wides, carried), n1, n2, path, {==}) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TOp{}: - match r: - case Tree.NCons{g, +r2}: - match g: - case Tree.Group{go, +gkids, +gcl}: - match go: - case Lex.Tok{+gk, +o, +gl, +gc}: - match r2: - case Tree.NNil{}: - +n1 = D1.inert.node(Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TOp{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}}) - +n2 = {Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TOp{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, - Tree.NNil{}}} : Tree.Node} - %inert.hoist.table_call(String.eq(o, "("), Lex.TOp{}, t, gkids, self, wides, carried, - inert.ok_h(Tree.Leaf{Lex.Tok{Lex.TOp{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}, e)) : - {Hoist.one_yes(Hoist.table_call(String.eq(o, "("), Lex.TOp{}, t, - D1.inert.node(gkids), self, wides, carried), n1, path) == Hoist.one_yes(_, n2, - path) : List<&2, F.Finding>} - inert.hoist.one(Hoist.table_call(String.eq(o, "("), Lex.TOp{}, t, - D1.inert.node(gkids), self, wides, carried), n1, n2, path, {==}) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TColon{}: - match r: - case Tree.NCons{g, +r2}: - match g: - case Tree.Group{go, +gkids, +gcl}: - match go: - case Lex.Tok{+gk, +o, +gl, +gc}: - match r2: - case Tree.NNil{}: - +n1 = D1.inert.node(Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TColon{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}}) - +n2 = {Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TColon{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, - Tree.NNil{}}} : Tree.Node} - %inert.hoist.table_call(String.eq(o, "("), Lex.TColon{}, t, gkids, self, wides, - carried, - inert.ok_h(Tree.Leaf{Lex.Tok{Lex.TColon{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}, e)) : - {Hoist.one_yes(Hoist.table_call(String.eq(o, "("), Lex.TColon{}, t, - D1.inert.node(gkids), self, wides, carried), n1, path) == Hoist.one_yes(_, n2, - path) : List<&2, F.Finding>} - inert.hoist.one(Hoist.table_call(String.eq(o, "("), Lex.TColon{}, t, - D1.inert.node(gkids), self, wides, carried), n1, n2, path, {==}) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TEq{}: - match r: - case Tree.NCons{g, +r2}: - match g: - case Tree.Group{go, +gkids, +gcl}: - match go: - case Lex.Tok{+gk, +o, +gl, +gc}: - match r2: - case Tree.NNil{}: - +n1 = D1.inert.node(Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TEq{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}}) - +n2 = {Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TEq{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, - Tree.NNil{}}} : Tree.Node} - %inert.hoist.table_call(String.eq(o, "("), Lex.TEq{}, t, gkids, self, wides, carried, - inert.ok_h(Tree.Leaf{Lex.Tok{Lex.TEq{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}, e)) : - {Hoist.one_yes(Hoist.table_call(String.eq(o, "("), Lex.TEq{}, t, - D1.inert.node(gkids), self, wides, carried), n1, path) == Hoist.one_yes(_, n2, - path) : List<&2, F.Finding>} - inert.hoist.one(Hoist.table_call(String.eq(o, "("), Lex.TEq{}, t, - D1.inert.node(gkids), self, wides, carried), n1, n2, path, {==}) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TBind{}: - match r: - case Tree.NCons{g, +r2}: - match g: - case Tree.Group{go, +gkids, +gcl}: - match go: - case Lex.Tok{+gk, +o, +gl, +gc}: - match r2: - case Tree.NNil{}: - +n1 = D1.inert.node(Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TBind{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}}) - +n2 = {Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TBind{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, - Tree.NNil{}}} : Tree.Node} - %inert.hoist.table_call(String.eq(o, "("), Lex.TBind{}, t, gkids, self, wides, - carried, - inert.ok_h(Tree.Leaf{Lex.Tok{Lex.TBind{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}, e)) : - {Hoist.one_yes(Hoist.table_call(String.eq(o, "("), Lex.TBind{}, t, - D1.inert.node(gkids), self, wides, carried), n1, path) == Hoist.one_yes(_, n2, - path) : List<&2, F.Finding>} - inert.hoist.one(Hoist.table_call(String.eq(o, "("), Lex.TBind{}, t, - D1.inert.node(gkids), self, wides, carried), n1, n2, path, {==}) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TArrow{}: - match r: - case Tree.NCons{g, +r2}: - match g: - case Tree.Group{go, +gkids, +gcl}: - match go: - case Lex.Tok{+gk, +o, +gl, +gc}: - match r2: - case Tree.NNil{}: - +n1 = D1.inert.node(Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TArrow{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}}) - +n2 = {Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TArrow{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, - Tree.NNil{}}} : Tree.Node} - %inert.hoist.table_call(String.eq(o, "("), Lex.TArrow{}, t, gkids, self, wides, - carried, - inert.ok_h(Tree.Leaf{Lex.Tok{Lex.TArrow{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}, e)) : - {Hoist.one_yes(Hoist.table_call(String.eq(o, "("), Lex.TArrow{}, t, - D1.inert.node(gkids), self, wides, carried), n1, path) == Hoist.one_yes(_, n2, - path) : List<&2, F.Finding>} - inert.hoist.one(Hoist.table_call(String.eq(o, "("), Lex.TArrow{}, t, - D1.inert.node(gkids), self, wides, carried), n1, n2, path, {==}) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TLam{}: - match r: - case Tree.NCons{g, +r2}: - match g: - case Tree.Group{go, +gkids, +gcl}: - match go: - case Lex.Tok{+gk, +o, +gl, +gc}: - match r2: - case Tree.NNil{}: - +n1 = D1.inert.node(Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TLam{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}}) - +n2 = {Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TLam{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, - Tree.NNil{}}} : Tree.Node} - %inert.hoist.table_call(String.eq(o, "("), Lex.TLam{}, t, gkids, self, wides, carried, - inert.ok_h(Tree.Leaf{Lex.Tok{Lex.TLam{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}, e)) : - {Hoist.one_yes(Hoist.table_call(String.eq(o, "("), Lex.TLam{}, t, - D1.inert.node(gkids), self, wides, carried), n1, path) == Hoist.one_yes(_, n2, - path) : List<&2, F.Finding>} - inert.hoist.one(Hoist.table_call(String.eq(o, "("), Lex.TLam{}, t, - D1.inert.node(gkids), self, wides, carried), n1, n2, path, {==}) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TAll{}: - match r: - case Tree.NCons{g, +r2}: - match g: - case Tree.Group{go, +gkids, +gcl}: - match go: - case Lex.Tok{+gk, +o, +gl, +gc}: - match r2: - case Tree.NNil{}: - +n1 = D1.inert.node(Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TAll{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}}) - +n2 = {Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TAll{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, - Tree.NNil{}}} : Tree.Node} - %inert.hoist.table_call(String.eq(o, "("), Lex.TAll{}, t, gkids, self, wides, carried, - inert.ok_h(Tree.Leaf{Lex.Tok{Lex.TAll{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}, e)) : - {Hoist.one_yes(Hoist.table_call(String.eq(o, "("), Lex.TAll{}, t, - D1.inert.node(gkids), self, wides, carried), n1, path) == Hoist.one_yes(_, n2, - path) : List<&2, F.Finding>} - inert.hoist.one(Hoist.table_call(String.eq(o, "("), Lex.TAll{}, t, - D1.inert.node(gkids), self, wides, carried), n1, n2, path, {==}) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TAmp{}: - match r: - case Tree.NCons{g, +r2}: - match g: - case Tree.Group{go, +gkids, +gcl}: - match go: - case Lex.Tok{+gk, +o, +gl, +gc}: - match r2: - case Tree.NNil{}: - +n1 = D1.inert.node(Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TAmp{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}}) - +n2 = {Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TAmp{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, - Tree.NNil{}}} : Tree.Node} - %inert.hoist.table_call(String.eq(o, "("), Lex.TAmp{}, t, gkids, self, wides, carried, - inert.ok_h(Tree.Leaf{Lex.Tok{Lex.TAmp{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}, e)) : - {Hoist.one_yes(Hoist.table_call(String.eq(o, "("), Lex.TAmp{}, t, - D1.inert.node(gkids), self, wides, carried), n1, path) == Hoist.one_yes(_, n2, - path) : List<&2, F.Finding>} - inert.hoist.one(Hoist.table_call(String.eq(o, "("), Lex.TAmp{}, t, - D1.inert.node(gkids), self, wides, carried), n1, n2, path, {==}) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TOpen{}: - match r: - case Tree.NCons{g, +r2}: - match g: - case Tree.Group{go, +gkids, +gcl}: - match go: - case Lex.Tok{+gk, +o, +gl, +gc}: - match r2: - case Tree.NNil{}: - +n1 = D1.inert.node(Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TOpen{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}}) - +n2 = {Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TOpen{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, - Tree.NNil{}}} : Tree.Node} - %inert.hoist.table_call(String.eq(o, "("), Lex.TOpen{}, t, gkids, self, wides, - carried, - inert.ok_h(Tree.Leaf{Lex.Tok{Lex.TOpen{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}, e)) : - {Hoist.one_yes(Hoist.table_call(String.eq(o, "("), Lex.TOpen{}, t, - D1.inert.node(gkids), self, wides, carried), n1, path) == Hoist.one_yes(_, n2, - path) : List<&2, F.Finding>} - inert.hoist.one(Hoist.table_call(String.eq(o, "("), Lex.TOpen{}, t, - D1.inert.node(gkids), self, wides, carried), n1, n2, path, {==}) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TClose{}: - match r: - case Tree.NCons{g, +r2}: - match g: - case Tree.Group{go, +gkids, +gcl}: - match go: - case Lex.Tok{+gk, +o, +gl, +gc}: - match r2: - case Tree.NNil{}: - +n1 = D1.inert.node(Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TClose{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}}) - +n2 = {Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TClose{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, - Tree.NNil{}}} : Tree.Node} - %inert.hoist.table_call(String.eq(o, "("), Lex.TClose{}, t, gkids, self, wides, - carried, - inert.ok_h(Tree.Leaf{Lex.Tok{Lex.TClose{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}, e)) : - {Hoist.one_yes(Hoist.table_call(String.eq(o, "("), Lex.TClose{}, t, - D1.inert.node(gkids), self, wides, carried), n1, path) == Hoist.one_yes(_, n2, - path) : List<&2, F.Finding>} - inert.hoist.one(Hoist.table_call(String.eq(o, "("), Lex.TClose{}, t, - D1.inert.node(gkids), self, wides, carried), n1, n2, path, {==}) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Lex.TComma{}: - match r: - case Tree.NCons{g, +r2}: - match g: - case Tree.Group{go, +gkids, +gcl}: - match go: - case Lex.Tok{+gk, +o, +gl, +gc}: - match r2: - case Tree.NNil{}: - +n1 = D1.inert.node(Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TComma{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}}) - +n2 = {Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TComma{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, - Tree.NNil{}}} : Tree.Node} - %inert.hoist.table_call(String.eq(o, "("), Lex.TComma{}, t, gkids, self, wides, - carried, - inert.ok_h(Tree.Leaf{Lex.Tok{Lex.TComma{}, t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{gk, o, gl, gc}, gkids, gcl}, Tree.NNil{}}, e)) : - {Hoist.one_yes(Hoist.table_call(String.eq(o, "("), Lex.TComma{}, t, - D1.inert.node(gkids), self, wides, carried), n1, path) == Hoist.one_yes(_, n2, - path) : List<&2, F.Finding>} - inert.hoist.one(Hoist.table_call(String.eq(o, "("), Lex.TComma{}, t, - D1.inert.node(gkids), self, wides, carried), n1, n2, path, {==}) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - -# a let's table cited when it is indexed: a choice on the index of the table's -# finding -law inert.hoist.use_pick: - for tab: Bool - for used: Bool - for +expr: Tree.Node - for +path: String - {Hoist.let_use(tab, used, expr, path) == Bool.pick(List<&2, F.Finding>, used, Hoist.one_yes(tab, expr, path), - []) : List<&2, F.Finding>} - -def inert.hoist.use_pick(tab, used, _expr, _path): - match tab used: - case True{} True{}: {==} - case True{} False{}: {==} - case False{} True{}: {==} - case False{} False{}: {==} - -# a let's table read that way, indexed as it was, is cited as it was -law inert.hoist.use: - for +expr: Tree.Node - for +used: Bool - for +self: String - for +wides: List<&2, String> - for +carried: List<&2, String> - for +path: String - for +e: {D1.inert.ok(expr) == True{} : Bool} - {Hoist.let_use(Hoist.is_table(D1.inert.node(expr), self, wides, carried), used, D1.inert.node(expr), path) - == Hoist.let_use(Hoist.is_table(expr, self, wides, carried), used, expr, path) : List<&2, F.Finding>} - -def inert.hoist.use(expr, used, self, wides, carried, path, e): - +t1 = Hoist.is_table(D1.inert.node(expr), self, wides, carried) - +t2 = Hoist.is_table(expr, self, wides, carried) - Equal.trans(List<&2, F.Finding>, Hoist.let_use(t1, used, D1.inert.node(expr), path), Bool.pick(List<&2, - F.Finding>, used, Hoist.one_yes(t1, D1.inert.node(expr), path), []), - Hoist.let_use(t2, used, expr, path), inert.hoist.use_pick(t1, used, D1.inert.node(expr), path), - Equal.trans(List<&2, F.Finding>, Bool.pick(List<&2, F.Finding>, used, Hoist.one_yes(t1, D1.inert.node(expr), - path), []), - Bool.pick(List<&2, F.Finding>, used, Hoist.one_yes(t2, expr, path), []), Hoist.let_use(t2, used, expr, path), - Equal.cong(List<&2, F.Finding>, List<&2, F.Finding>, z => Bool.pick(List<&2, F.Finding>, used, z, []), - Hoist.one_yes(t1, D1.inert.node(expr), path), - Hoist.one_yes(t2, expr, path), inert.hoist.yes(expr, self, wides, carried, path, e)), - Equal.sym(List<&2, F.Finding>, Hoist.let_use(t2, used, expr, path), Bool.pick(List<&2, F.Finding>, used, - Hoist.one_yes(t2, expr, path), []), - inert.hoist.use_pick(t2, used, expr, path)))) - -# a chain read that way is exactly the name as it was: only a name's text is read -law inert.hoist.wrapped_inside: - for nn: Tree.Node - for +binder: String - {Hoist.wrapped_inside(D1.inert.node(nn), binder) == Hoist.wrapped_inside(nn, binder) : Bool} - -def inert.hoist.wrapped_inside(nn, _binder): - match nn: - case Tree.NCons{h, r}: - match h: - case Tree.Leaf{tok}: - match tok: - case Lex.Tok{k, t, l, c}: - match k: - case Lex.TName{}: - match r: - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Lex.TUpper{}: {==} - case Lex.TDotted{}: {==} - case Lex.TWild{}: {==} - case Lex.TKey{}: {==} - case Lex.TNum{}: {==} - case Lex.TStr{}: {==} - case Lex.TChar{}: {==} - case Lex.TComment{}: {==} - case Lex.TSpace{}: {==} - case Lex.TNewline{}: {==} - case Lex.TOp{}: {==} - case Lex.TColon{}: {==} - case Lex.TEq{}: {==} - case Lex.TBind{}: {==} - case Lex.TArrow{}: {==} - case Lex.TLam{}: {==} - case Lex.TAll{}: {==} - case Lex.TAmp{}: {==} - case Lex.TOpen{}: {==} - case Lex.TClose{}: {==} - case Lex.TComma{}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - -# a paren's kids read that way are the name as they were -law inert.hoist.wrapped_open: - for paren: Bool - for +kids: Tree.Node - for +binder: String - {Hoist.wrapped_open(paren, D1.inert.node(kids), binder) == Hoist.wrapped_open(paren, kids, binder) : Bool} - -def inert.hoist.wrapped_open(paren, kids, binder): - match paren: - case True{}: inert.hoist.wrapped_inside(kids, binder) - case False{}: {==} - -# a chain read that way is the name, or the name in parentheses, as it was -law inert.hoist.wrapped: - for nn: Tree.Node - for +binder: String - {Hoist.wrapped(D1.inert.node(nn), binder) == Hoist.wrapped(nn, binder) : Bool} - -def inert.hoist.wrapped(nn, binder): - match nn: - case Tree.NCons{h, r}: - match h: - case Tree.Leaf{tok}: - match tok: - case Lex.Tok{k, t, l, c}: - match k: - case Lex.TName{}: - match r: - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Lex.TUpper{}: {==} - case Lex.TDotted{}: {==} - case Lex.TWild{}: {==} - case Lex.TKey{}: {==} - case Lex.TNum{}: {==} - case Lex.TStr{}: {==} - case Lex.TChar{}: {==} - case Lex.TComment{}: {==} - case Lex.TSpace{}: {==} - case Lex.TNewline{}: {==} - case Lex.TOp{}: {==} - case Lex.TColon{}: {==} - case Lex.TEq{}: {==} - case Lex.TBind{}: {==} - case Lex.TArrow{}: {==} - case Lex.TLam{}: {==} - case Lex.TAll{}: {==} - case Lex.TAmp{}: {==} - case Lex.TOpen{}: {==} - case Lex.TClose{}: {==} - case Lex.TComma{}: {==} - case Tree.Group{go, +kids, gcl}: - match go: - case Lex.Tok{gk, +o, gl, gc}: - match r: - case Tree.NNil{}: inert.hoist.wrapped_open(String.eq(o, "("), kids, binder) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - case Tree.NCons{x, y}: {==} - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - -# a get's or set's collection argument, the arguments read that way, is the -# name as it was -law inert.hoist.uses_arg: - for ss: Hoist.Slot - for +as: List<&2, Tree.Node> - for +binder: String - {Hoist.uses_arg(ss, inert.nodes(as), binder) == Hoist.uses_arg(ss, as, binder) : Bool} - -def inert.hoist.uses_arg(ss, as, binder): - match ss: - case Hoist.Slot{on, +at}: - match on: - case False{}: {==} - case True{}: - %Equal.sym(Tree.Node, Calls.arg(inert.nodes(as), at), D1.inert.node(Calls.arg(as, at)), inert.arg(as, at)) : - {Hoist.wrapped(_, binder) == Hoist.wrapped(Calls.arg(as, at), binder) : Bool} - inert.hoist.wrapped(Calls.arg(as, at), binder) - -# a name then a group, read that way, indexes the name as it did: a string is -# never the name nor a get or set -law inert.hoist.uses_here: - for brack: Bool - for call: Bool - for +b: Bool - for +t: String - for +kids: Tree.Node - for +binder: String - for +ef: {D1.inert.fits.go(b, t) == True{} : Bool} - for +eb: {D1.inert.marked(binder) == False{} : Bool} - {Hoist.uses_here(brack, call, D1.inert.keep(b, t), D1.inert.node(kids), binder) == Hoist.uses_here(brack, call, - t, kids, binder) - : Bool} - -def inert.hoist.uses_here(brack, call, b, t, kids, binder, ef, eb): - match brack: - case True{}: M1.inert.eq_keep(b, t, binder, ef, eb) - case False{}: - match call: - case False{}: {==} - case True{}: - +lhs = Hoist.uses_arg(Hoist.slot_of(D1.inert.keep(b, t)), Calls.args(D1.inert.node(kids)), binder) - %inert.hoist.uses_arg(Hoist.slot_of(t), Calls.args(kids), binder) : {lhs == _ : Bool} - %inert.args(kids) : {lhs == Hoist.uses_arg(Hoist.slot_of(t), _, binder) : Bool} - %inert.hoist.slot_of(b, t, ef) : {lhs == Hoist.uses_arg(_, Calls.args(D1.inert.node(kids)), binder) : Bool} - {==} - -# a name then a group: the index here, under the group and after it read as -# they were -law inert.hoist.uses_lg: - for +b: Bool - for +t: String - for +o: String - for +kids: Tree.Node - for +r2: Tree.Node - for +binder: String - for +ef: {D1.inert.fits.go(b, t) == True{} : Bool} - for +eb: {D1.inert.marked(binder) == False{} : Bool} - for ik: {Hoist.uses_name(D1.inert.node(kids), binder) == Hoist.uses_name(kids, binder) : Bool} - for ir: {Hoist.uses_name(D1.inert.node(r2), binder) == Hoist.uses_name(r2, binder) : Bool} - {Bool.or(Hoist.uses_here(String.eq(o, "["), String.eq(o, "("), D1.inert.keep(b, t), D1.inert.node(kids), binder), - Bool.or(Hoist.uses_name(D1.inert.node(kids), binder), Hoist.uses_name(D1.inert.node(r2), binder))) - == Bool.or(Hoist.uses_here(String.eq(o, "["), String.eq(o, "("), t, kids, binder), - Bool.or(Hoist.uses_name(kids, binder), Hoist.uses_name(r2, binder))) : Bool} - -def inert.hoist.uses_lg(b, t, o, kids, r2, binder, ef, eb, ik, ir): - inert.hoist.or2(Hoist.uses_here(String.eq(o, "["), String.eq(o, "("), D1.inert.keep(b, t), D1.inert.node(kids), - binder), - Hoist.uses_here(String.eq(o, "["), String.eq(o, "("), t, kids, binder), - Bool.or(Hoist.uses_name(D1.inert.node(kids), binder), Hoist.uses_name(D1.inert.node(r2), binder)), - Bool.or(Hoist.uses_name(kids, binder), Hoist.uses_name(r2, binder)), - inert.hoist.uses_here(String.eq(o, "["), String.eq(o, "("), b, t, kids, binder, ef, eb), - inert.hoist.or2(Hoist.uses_name(D1.inert.node(kids), binder), Hoist.uses_name(kids, binder), - Hoist.uses_name(D1.inert.node(r2), binder), - Hoist.uses_name(r2, binder), ik, ir)) - -# a chain read that way indexes the name as it did -law inert.hoist.uses_name: - for nn: Tree.Node - for +binder: String - for +eb: {D1.inert.marked(binder) == False{} : Bool} - for +e: {D1.inert.ok(nn) == True{} : Bool} - {Hoist.uses_name(D1.inert.node(nn), binder) == Hoist.uses_name(nn, binder) : Bool} - -def inert.hoist.uses_name(nn, binder, eb, e): - match nn: - case Tree.NCons{h, +rest}: - match h: - case Tree.Leaf{tok}: - match tok: - case Lex.Tok{+k, +t, +l, +c}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{go, +kids, +gcl}: - match go: - case Lex.Tok{+gk, +o, +gl, +gc}: - +lf = {Tree.Leaf{Lex.Tok{k, t, l, c}} : Tree.Node} - +gp = {Tree.Group{Lex.Tok{gk, o, gl, gc}, kids, gcl} : Tree.Node} - +eg = inert.ok_t(lf, Tree.NCons{gp, r2}, e) - inert.hoist.uses_lg(D1.inert.blanks(k), t, o, kids, r2, binder, - inert.ok_h(lf, Tree.NCons{gp, r2}, e), eb, - inert.hoist.uses_name(kids, binder, eb, inert.ok_h(gp, r2, eg)), - inert.hoist.uses_name(r2, binder, eb, inert.ok_t(gp, r2, eg))) - case Tree.Leaf{x}: - inert.hoist.uses_name(rest, binder, eb, inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.Stmt{x, y, z}: - inert.hoist.uses_name(rest, binder, eb, inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.NNil{}: - inert.hoist.uses_name(rest, binder, eb, inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.NCons{x, y}: - inert.hoist.uses_name(rest, binder, eb, inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.Leaf{x}: - inert.hoist.uses_name(rest, binder, eb, inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.Group{x, y, z}: - inert.hoist.uses_name(rest, binder, eb, inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.Stmt{x, y, z}: - inert.hoist.uses_name(rest, binder, eb, inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.NNil{}: - inert.hoist.uses_name(rest, binder, eb, inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.Group{+o, +kids, +cl}: - inert.hoist.or2(Hoist.uses_name(D1.inert.node(kids), binder), Hoist.uses_name(kids, binder), - Hoist.uses_name(D1.inert.node(rest), binder), - Hoist.uses_name(rest, binder), inert.hoist.uses_name(kids, binder, eb, inert.ok_h(Tree.Group{o, kids, cl}, - rest, e)), - inert.hoist.uses_name(rest, binder, eb, inert.ok_t(Tree.Group{o, kids, cl}, rest, e))) - case Tree.Stmt{+sk, +kids, +body}: - +es = inert.ok_h(Tree.Stmt{sk, kids, body}, rest, e) - inert.hoist.or2(Hoist.uses_name(D1.inert.node(kids), binder), Hoist.uses_name(kids, binder), - Bool.or(Hoist.uses_name(D1.inert.node(body), binder), Hoist.uses_name(D1.inert.node(rest), binder)), - Bool.or(Hoist.uses_name(body, binder), Hoist.uses_name(rest, binder)), - inert.hoist.uses_name(kids, binder, eb, M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(body), es)), - inert.hoist.or2(Hoist.uses_name(D1.inert.node(body), binder), Hoist.uses_name(body, binder), - Hoist.uses_name(D1.inert.node(rest), binder), Hoist.uses_name(rest, binder), - inert.hoist.uses_name(body, binder, eb, M1.inert.and_r(D1.inert.ok(kids), D1.inert.ok(body), es)), - inert.hoist.uses_name(rest, binder, eb, inert.ok_t(Tree.Stmt{sk, kids, body}, rest, e)))) - case Tree.NNil{}: inert.hoist.uses_name(rest, binder, eb, e) - case Tree.NCons{x, y}: inert.hoist.uses_name(rest, binder, eb, inert.ok_t(Tree.NCons{x, y}, rest, e)) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - -# a let's name and table, the table read that way -def inert.hoist.got(mm: Maybe<&2, Hoist.Got>) -> Maybe<&2, Hoist.Got>: - match mm: - case None{}: None{} - case Some{Hoist.Got{nm, expr}}: Some{Hoist.Got{nm, D1.inert.node(expr)}} - -# a let's name plain and its table's leaves fitting, or no let -def inert.hoist.gok(mm: Maybe<&2, Hoist.Got>) -> Bool: - match mm: - case None{}: True{} - case Some{Hoist.Got{nm, expr}}: Bool.and(Bool.not(D1.inert.marked(nm)), D1.inert.ok(expr)) - -# a Bool that is False has a negation that holds -law inert.hoist.not_f: - for -x: Bool - for e: {x == False{} : Bool} - {Bool.not(x) == True{} : Bool} - -def inert.hoist.not_f(x, e): - %Equal.sym(Bool, x, False{}, e) : {Bool.not(_) == True{} : Bool} - {==} - -# the chain after `=`, read that way, is the table it was -law inert.hoist.got_at: - for seen: Bool - for bad: Bool - for +nm: String - for +expr: Tree.Node - {Hoist.got_at(seen, bad, nm, D1.inert.node(expr)) == inert.hoist.got(Hoist.got_at(seen, bad, nm, - expr)) : Maybe<&2, Hoist.Got>} - -def inert.hoist.got_at(seen, bad, _nm, _expr): - match seen: - case False{}: {==} - case True{}: - match bad: - case True{}: {==} - case False{}: {==} - -# the chain after `=` of a plain name fits -law inert.hoist.got_at_ok: - for seen: Bool - for bad: Bool - for +nm: String - for +expr: Tree.Node - for +en: {D1.inert.marked(nm) == False{} : Bool} - for +ee: {D1.inert.ok(expr) == True{} : Bool} - {inert.hoist.gok(Hoist.got_at(seen, bad, nm, expr)) == True{} : Bool} - -def inert.hoist.got_at_ok(seen, bad, nm, expr, en, ee): - match seen: - case False{}: {==} - case True{}: - match bad: - case True{}: {==} - case False{}: - M1.inert.and_both(Bool.not(D1.inert.marked(nm)), D1.inert.ok(expr), - inert.hoist.not_f(D1.inert.marked(nm), en), ee) - -# a let read that way binds the name it did to its table read that way: only -# a name's text is read -law inert.hoist.eat: - for nn: Tree.Node - for +seen: Bool - for +bad: Bool - for +nm: String - {Hoist.eat(D1.inert.node(nn), seen, bad, nm) == inert.hoist.got(Hoist.eat(nn, seen, bad, nm)) : Maybe<&2, - Hoist.Got>} - -def inert.hoist.eat(nn, seen, bad, nm): - match nn: - case Tree.NCons{h, +rest}: - match h: - case Tree.Leaf{tok}: - match tok: - case Lex.Tok{k, t, l, c}: - match k: - case Lex.TName{}: inert.hoist.eat(rest, True{}, Bool.or(bad, seen), t) - case Lex.TUpper{}: inert.hoist.eat(rest, seen, True{}, nm) - case Lex.TDotted{}: inert.hoist.eat(rest, seen, bad, nm) - case Lex.TWild{}: inert.hoist.eat(rest, seen, bad, nm) - case Lex.TKey{}: inert.hoist.eat(rest, seen, bad, nm) - case Lex.TNum{}: inert.hoist.eat(rest, seen, bad, nm) - case Lex.TStr{}: inert.hoist.eat(rest, seen, bad, nm) - case Lex.TChar{}: inert.hoist.eat(rest, seen, bad, nm) - case Lex.TComment{}: inert.hoist.eat(rest, seen, bad, nm) - case Lex.TSpace{}: inert.hoist.eat(rest, seen, bad, nm) - case Lex.TNewline{}: inert.hoist.eat(rest, seen, bad, nm) - case Lex.TOp{}: inert.hoist.eat(rest, seen, bad, nm) - case Lex.TColon{}: inert.hoist.eat(rest, seen, bad, nm) - case Lex.TEq{}: inert.hoist.got_at(seen, bad, nm, rest) - case Lex.TBind{}: inert.hoist.eat(rest, seen, bad, nm) - case Lex.TArrow{}: inert.hoist.eat(rest, seen, bad, nm) - case Lex.TLam{}: inert.hoist.eat(rest, seen, bad, nm) - case Lex.TAll{}: inert.hoist.eat(rest, seen, bad, nm) - case Lex.TAmp{}: inert.hoist.eat(rest, seen, bad, nm) - case Lex.TOpen{}: inert.hoist.eat(rest, seen, bad, nm) - case Lex.TClose{}: inert.hoist.eat(rest, seen, bad, nm) - case Lex.TComma{}: inert.hoist.eat(rest, seen, bad, nm) - case Tree.Group{o, gk, cl}: inert.hoist.eat(rest, seen, True{}, nm) - case Tree.Stmt{sk, sks, sb}: inert.hoist.eat(rest, seen, bad, nm) - case Tree.NNil{}: inert.hoist.eat(rest, seen, bad, nm) - case Tree.NCons{x, y}: inert.hoist.eat(rest, seen, bad, nm) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - -# a let of a source as the lexer makes it binds a plain name to leaves that fit -law inert.hoist.eat_ok: - for nn: Tree.Node - for +seen: Bool - for +bad: Bool - for +nm: String - for +en: {D1.inert.marked(nm) == False{} : Bool} - for +e: {D1.inert.ok(nn) == True{} : Bool} - {inert.hoist.gok(Hoist.eat(nn, seen, bad, nm)) == True{} : Bool} - -def inert.hoist.eat_ok(nn, seen, bad, nm, en, e): - match nn: - case Tree.NCons{h, +rest}: - match h: - case Tree.Leaf{tok}: - match tok: - case Lex.Tok{k, +t, +l, +c}: - match k: - case Lex.TName{}: - inert.hoist.eat_ok(rest, True{}, Bool.or(bad, seen), t, inert.not_true(D1.inert.marked(t), - inert.ok_h(Tree.Leaf{Lex.Tok{Lex.TName{}, t, l, c}}, rest, e)), - inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TName{}, t, l, c}}, rest, e)) - case Lex.TUpper{}: - inert.hoist.eat_ok(rest, seen, True{}, nm, en, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TUpper{}, t, l, c}}, - rest, e)) - case Lex.TDotted{}: - inert.hoist.eat_ok(rest, seen, bad, nm, en, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TDotted{}, t, l, c}}, - rest, e)) - case Lex.TWild{}: - inert.hoist.eat_ok(rest, seen, bad, nm, en, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TWild{}, t, l, c}}, - rest, e)) - case Lex.TKey{}: - inert.hoist.eat_ok(rest, seen, bad, nm, en, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TKey{}, t, l, c}}, - rest, e)) - case Lex.TNum{}: - inert.hoist.eat_ok(rest, seen, bad, nm, en, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TNum{}, t, l, c}}, - rest, e)) - case Lex.TStr{}: - inert.hoist.eat_ok(rest, seen, bad, nm, en, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TStr{}, t, l, c}}, - rest, e)) - case Lex.TChar{}: - inert.hoist.eat_ok(rest, seen, bad, nm, en, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TChar{}, t, l, c}}, - rest, e)) - case Lex.TComment{}: - inert.hoist.eat_ok(rest, seen, bad, nm, en, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TComment{}, t, l, c}}, - rest, e)) - case Lex.TSpace{}: - inert.hoist.eat_ok(rest, seen, bad, nm, en, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TSpace{}, t, l, c}}, - rest, e)) - case Lex.TNewline{}: - inert.hoist.eat_ok(rest, seen, bad, nm, en, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TNewline{}, t, l, c}}, - rest, e)) - case Lex.TOp{}: - inert.hoist.eat_ok(rest, seen, bad, nm, en, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TOp{}, t, l, c}}, rest, - e)) - case Lex.TColon{}: - inert.hoist.eat_ok(rest, seen, bad, nm, en, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TColon{}, t, l, c}}, - rest, e)) - case Lex.TEq{}: - inert.hoist.got_at_ok(seen, bad, nm, rest, en, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TEq{}, t, l, c}}, - rest, e)) - case Lex.TBind{}: - inert.hoist.eat_ok(rest, seen, bad, nm, en, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TBind{}, t, l, c}}, - rest, e)) - case Lex.TArrow{}: - inert.hoist.eat_ok(rest, seen, bad, nm, en, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TArrow{}, t, l, c}}, - rest, e)) - case Lex.TLam{}: - inert.hoist.eat_ok(rest, seen, bad, nm, en, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TLam{}, t, l, c}}, - rest, e)) - case Lex.TAll{}: - inert.hoist.eat_ok(rest, seen, bad, nm, en, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TAll{}, t, l, c}}, - rest, e)) - case Lex.TAmp{}: - inert.hoist.eat_ok(rest, seen, bad, nm, en, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TAmp{}, t, l, c}}, - rest, e)) - case Lex.TOpen{}: - inert.hoist.eat_ok(rest, seen, bad, nm, en, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TOpen{}, t, l, c}}, - rest, e)) - case Lex.TClose{}: - inert.hoist.eat_ok(rest, seen, bad, nm, en, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TClose{}, t, l, c}}, - rest, e)) - case Lex.TComma{}: - inert.hoist.eat_ok(rest, seen, bad, nm, en, inert.ok_t(Tree.Leaf{Lex.Tok{Lex.TComma{}, t, l, c}}, - rest, e)) - case Tree.Group{o, gk, cl}: - inert.hoist.eat_ok(rest, seen, True{}, nm, en, inert.ok_t(Tree.Group{o, gk, cl}, rest, e)) - case Tree.Stmt{sk, sks, sb}: - inert.hoist.eat_ok(rest, seen, bad, nm, en, inert.ok_t(Tree.Stmt{sk, sks, sb}, rest, e)) - case Tree.NNil{}: inert.hoist.eat_ok(rest, seen, bad, nm, en, e) - case Tree.NCons{x, y}: inert.hoist.eat_ok(rest, seen, bad, nm, en, inert.ok_t(Tree.NCons{x, y}, rest, e)) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - -# the let's body and what follows, read that way, index the name as they did -law inert.hoist.uses_or: - for +body: Tree.Node - for +rest: Tree.Node - for +binder: String - for +eb: {D1.inert.marked(binder) == False{} : Bool} - for +ebd: {D1.inert.ok(body) == True{} : Bool} - for +er: {D1.inert.ok(rest) == True{} : Bool} - {Hoist.uses_or(D1.inert.node(body), D1.inert.node(rest), binder) == Hoist.uses_or(body, rest, binder) : Bool} - -def inert.hoist.uses_or(body, rest, binder, eb, ebd, er): - inert.hoist.or2(Hoist.uses_name(D1.inert.node(body), binder), Hoist.uses_name(body, binder), - Hoist.uses_name(D1.inert.node(rest), binder), - Hoist.uses_name(rest, binder), inert.hoist.uses_name(body, binder, eb, ebd), - inert.hoist.uses_name(rest, binder, eb, er)) - -# a plain let read that way is judged as it was -law inert.hoist.let_of: - for mm: Maybe<&2, Hoist.Got> - for +body: Tree.Node - for +rest: Tree.Node - for +self: String - for +wides: List<&2, String> - for +carried: List<&2, String> - for +path: String - for +eg: {inert.hoist.gok(mm) == True{} : Bool} - for +eb: {D1.inert.ok(body) == True{} : Bool} - for +er: {D1.inert.ok(rest) == True{} : Bool} - {Hoist.let_of(inert.hoist.got(mm), D1.inert.node(body), D1.inert.node(rest), self, wides, carried, path) - == Hoist.let_of(mm, body, rest, self, wides, carried, path) : List<&2, F.Finding>} - -def inert.hoist.let_of(mm, body, rest, self, wides, carried, path, eg, eb, er): - match mm: - case None{}: {==} - case Some{Hoist.Got{+nm, +expr}}: - +en = inert.not_true(D1.inert.marked(nm), M1.inert.and_l(Bool.not(D1.inert.marked(nm)), D1.inert.ok(expr), eg)) - +ee = M1.inert.and_r(Bool.not(D1.inert.marked(nm)), D1.inert.ok(expr), eg) - +lhs = Hoist.let_use(Hoist.is_table(D1.inert.node(expr), self, wides, carried), - Hoist.uses_or(D1.inert.node(body), D1.inert.node(rest), nm), D1.inert.node(expr), - path) - %inert.hoist.uses_or(body, rest, nm, en, eb, er) : - {lhs == Hoist.let_use(Hoist.is_table(expr, self, wides, carried), _, expr, path) : List<&2, F.Finding>} - inert.hoist.use(expr, Hoist.uses_or(D1.inert.node(body), D1.inert.node(rest), nm), self, wides, carried, - path, ee) - -# a hot let read that way is judged as it was -law inert.hoist.let_hit: - for hot: Bool - for +kids: Tree.Node - for +body: Tree.Node - for +rest: Tree.Node - for +self: String - for +wides: List<&2, String> - for +carried: List<&2, String> - for +path: String - for +ek: {D1.inert.ok(kids) == True{} : Bool} - for +eb: {D1.inert.ok(body) == True{} : Bool} - for +er: {D1.inert.ok(rest) == True{} : Bool} - {Hoist.let_hit(hot, D1.inert.node(kids), D1.inert.node(body), D1.inert.node(rest), self, wides, carried, path) - == Hoist.let_hit(hot, kids, body, rest, self, wides, carried, path) : List<&2, F.Finding>} - -def inert.hoist.let_hit(hot, kids, body, rest, self, wides, carried, path, ek, eb, er): - match hot: - case False{}: {==} - case True{}: - %Equal.sym(Maybe<&2, Hoist.Got>, Hoist.eat(D1.inert.node(kids), False{}, False{}, ""), - inert.hoist.got(Hoist.eat(kids, False{}, False{}, "")), inert.hoist.eat(kids, False{}, False{}, "")) : - {Hoist.let_of(_, D1.inert.node(body), D1.inert.node(rest), self, wides, carried, path) - == Hoist.let_of(Hoist.eat(kids, False{}, False{}, ""), body, rest, self, wides, carried, path) : List<&2, - F.Finding>} - inert.hoist.let_of(Hoist.eat(kids, False{}, False{}, ""), body, rest, self, wides, carried, path, - inert.hoist.eat_ok(kids, False{}, False{}, "", {==}, ek), eb, er) - -# a get's or set's collection argument, the arguments read that way, is the -# table it was -law inert.hoist.arg_table: - for ss: Hoist.Slot - for +as: List<&2, Tree.Node> - for +self: String - for +wides: List<&2, String> - for +carried: List<&2, String> - for +path: String - for +ea: {inert.noks(as) == True{} : Bool} - {Hoist.arg_table(ss, inert.nodes(as), self, wides, carried, path) - == Hoist.arg_table(ss, as, self, wides, carried, path) : List<&2, F.Finding>} - -def inert.hoist.arg_table(ss, as, self, wides, carried, path, ea): - match ss: - case Hoist.Slot{on, +at}: - match on: - case False{}: {==} - case True{}: - %Equal.sym(Tree.Node, Calls.arg(inert.nodes(as), at), D1.inert.node(Calls.arg(as, at)), inert.arg(as, at)) : - {Hoist.one_yes(Hoist.is_table(_, self, wides, carried), _, path) - == Hoist.one_yes(Hoist.is_table(Calls.arg(as, at), self, wides, carried), Calls.arg(as, at), - path) : List<&2, F.Finding>} - inert.hoist.yes(Calls.arg(as, at), self, wides, carried, path, inert.ok.nth(as, at, ea)) - -# a get or set read that way, its table passed inline, is judged as it was -law inert.hoist.call_table: - for hot: Bool - for open: Bool - for +b: Bool - for +t: String - for +kids: Tree.Node - for +self: String - for +wides: List<&2, String> - for +carried: List<&2, String> - for +path: String - for +ef: {D1.inert.fits.go(b, t) == True{} : Bool} - for +ek: {D1.inert.ok(kids) == True{} : Bool} - {Hoist.call_table(hot, open, D1.inert.keep(b, t), D1.inert.node(kids), self, wides, carried, path) - == Hoist.call_table(hot, open, t, kids, self, wides, carried, path) : List<&2, F.Finding>} - -def inert.hoist.call_table(hot, open, b, t, kids, self, wides, carried, path, ef, ek): - match hot: - case False{}: {==} - case True{}: - match open: - case False{}: {==} - case True{}: - +lhs = Hoist.arg_table(Hoist.slot_of(D1.inert.keep(b, t)), Calls.args(D1.inert.node(kids)), self, wides, - carried, path) - %inert.hoist.arg_table(Hoist.slot_of(t), Calls.args(kids), self, wides, carried, path, - inert.ok.args.go(kids, Tree.NNil{}, [], ek, {==}, {==})) : {lhs == _ : List<&2, F.Finding>} - %inert.args(kids) : {lhs == Hoist.arg_table(Hoist.slot_of(t), _, self, wides, carried, path) : List<&2, - F.Finding>} - %inert.hoist.slot_of(b, t, ef) : - {lhs == Hoist.arg_table(_, Calls.args(D1.inert.node(kids)), self, wides, carried, path) : List<&2, - F.Finding>} - {==} - -# four finding lists that read alike join alike -law inert.hoist.cat4: - for -a1: List<&2, F.Finding> - for -a2: List<&2, F.Finding> - for -b1: List<&2, F.Finding> - for -b2: List<&2, F.Finding> - for -c1: List<&2, F.Finding> - for -c2: List<&2, F.Finding> - for -d1: List<&2, F.Finding> - for -d2: List<&2, F.Finding> - for ea: {a1 == a2 : List<&2, F.Finding>} - for eb: {b1 == b2 : List<&2, F.Finding>} - for ec: {c1 == c2 : List<&2, F.Finding>} - for ed: {d1 == d2 : List<&2, F.Finding>} - {List.concat(&2, F.Finding, [a1, b1, c1, d1]) == List.concat(&2, F.Finding, [a2, b2, c2, d2]) : List<&2, F.Finding>} - -def inert.hoist.cat4(a1, _a2, b1, b2, c1, c2, d1, d2, ea, eb, ec, ed): - %ea : {List.concat(&2, F.Finding, [a1, b1, c1, d1]) == List.concat(&2, F.Finding, [_, b2, c2, d2]) : List<&2, - F.Finding>} - %eb : {List.concat(&2, F.Finding, [a1, b1, c1, d1]) == List.concat(&2, F.Finding, [a1, _, c2, d2]) : List<&2, - F.Finding>} - %ec : {List.concat(&2, F.Finding, [a1, b1, c1, d1]) == List.concat(&2, F.Finding, [a1, b1, _, d2]) : List<&2, - F.Finding>} - %ed : {List.concat(&2, F.Finding, [a1, b1, c1, d1]) == List.concat(&2, F.Finding, [a1, b1, c1, _]) : List<&2, - F.Finding>} - {==} - -# a name then a group: its inline table, the group and the rest walked as -# they were -law inert.hoist.walk_lg: - for +k: Lex.TokKind - for +t: String - for +o: String - for +kids: Tree.Node - for +r2: Tree.Node - for +self: String - for +wides: List<&2, String> - for +carried: List<&2, String> - for +path: String - for +hot: Bool - for +ef: {D1.inert.fits.go(D1.inert.blanks(k), t) == True{} : Bool} - for +ek: {D1.inert.ok(kids) == True{} : Bool} - for ik: {Hoist.walk(D1.inert.node(kids), self, wides, carried, path, Hoist.warm(hot, k)) - == Hoist.walk(kids, self, wides, carried, path, Hoist.warm(hot, k)) : List<&2, F.Finding>} - for ir: {Hoist.walk(D1.inert.node(r2), self, wides, carried, path, Hoist.warm(hot, k)) - == Hoist.walk(r2, self, wides, carried, path, Hoist.warm(hot, k)) : List<&2, F.Finding>} - {List.concat(&2, F.Finding, [Hoist.call_table(Hoist.warm(hot, k), String.eq(o, "("), - D1.inert.keep(D1.inert.blanks(k), t), D1.inert.node(kids), self, wides, carried, path), - Hoist.walk(D1.inert.node(kids), self, wides, carried, path, Hoist.warm(hot, k)), - Hoist.walk(D1.inert.node(r2), self, wides, carried, path, Hoist.warm(hot, k))]) - == List.concat(&2, F.Finding, [Hoist.call_table(Hoist.warm(hot, k), String.eq(o, "("), t, kids, self, wides, - carried, path), Hoist.walk(kids, self, wides, carried, path, Hoist.warm(hot, k)), - Hoist.walk(r2, self, wides, carried, path, Hoist.warm(hot, k))]) : List<&2, F.Finding>} - -def inert.hoist.walk_lg(k, t, o, kids, r2, self, wides, carried, path, hot, ef, ek, ik, ir): - +w = Hoist.warm(hot, k) - inert.cat3(Hoist.call_table(w, String.eq(o, "("), D1.inert.keep(D1.inert.blanks(k), t), D1.inert.node(kids), - self, wides, - carried, path), Hoist.call_table(w, String.eq(o, "("), t, kids, self, wides, carried, path), - Hoist.walk(D1.inert.node(kids), self, wides, carried, path, w), Hoist.walk(kids, self, wides, carried, path, w), - Hoist.walk(D1.inert.node(r2), self, wides, carried, path, w), Hoist.walk(r2, self, wides, carried, path, w), - inert.hoist.call_table(w, String.eq(o, "("), D1.inert.blanks(k), t, kids, self, wides, carried, path, ef, ek), - ik, ir) - -# a case arm read that way: hot as it was, and its pattern, body and the rest -# walked as they were -law inert.hoist.walk_case: - for +kids: Tree.Node - for +body: Tree.Node - for +rest: Tree.Node - for +self: String - for +wides: List<&2, String> - for +carried: List<&2, String> - for +path: String - for +hot: Bool - for +es: {D1.inert.marked(self) == False{} : Bool} - for +eb: {D1.inert.ok(body) == True{} : Bool} - for ik: {Hoist.walk(D1.inert.node(kids), self, wides, carried, path, False{}) - == Hoist.walk(kids, self, wides, carried, path, False{}) : List<&2, F.Finding>} - for ib: {Hoist.walk(D1.inert.node(body), self, wides, carried, path, Bool.and(hot, - Calls.calls(D1.inert.node(body), self))) - == Hoist.walk(body, self, wides, carried, path, Bool.and(hot, Calls.calls(D1.inert.node(body), - self))) : List<&2, F.Finding>} - for ir: {Hoist.walk(D1.inert.node(rest), self, wides, carried, path, hot) == Hoist.walk(rest, self, wides, - carried, path, hot) - : List<&2, F.Finding>} - {List.concat(&2, F.Finding, [Hoist.walk(D1.inert.node(kids), self, wides, carried, path, False{}), - Hoist.walk(D1.inert.node(body), self, wides, carried, path, Bool.and(hot, Calls.calls(D1.inert.node(body), - self))), - Hoist.walk(D1.inert.node(rest), self, wides, carried, path, hot)]) - == List.concat(&2, F.Finding, [Hoist.walk(kids, self, wides, carried, path, False{}), - Hoist.walk(body, self, wides, carried, path, Bool.and(hot, Calls.calls(body, self))), - Hoist.walk(rest, self, wides, carried, path, hot)]) : List<&2, F.Finding>} - -def inert.hoist.walk_case(kids, body, rest, self, wides, carried, path, hot, es, eb, ik, ib, ir): - +arm = Bool.and(hot, Calls.calls(D1.inert.node(body), self)) - +lhs = List.concat(&2, F.Finding, [Hoist.walk(D1.inert.node(kids), self, wides, carried, path, False{}), - Hoist.walk(D1.inert.node(body), self, wides, carried, path, arm), Hoist.walk(D1.inert.node(rest), self, wides, - carried, path, hot)]) - %inert.calls(body, self, es, eb) : {lhs == List.concat(&2, F.Finding, [Hoist.walk(kids, self, wides, carried, path, - False{}), Hoist.walk(body, self, wides, carried, path, Bool.and(hot, _)), Hoist.walk(rest, self, wides, carried, - path, hot)]) : List<&2, F.Finding>} - inert.cat3(Hoist.walk(D1.inert.node(kids), self, wides, carried, path, False{}), Hoist.walk(kids, self, wides, - carried, path, - False{}), Hoist.walk(D1.inert.node(body), self, wides, carried, path, arm), Hoist.walk(body, self, wides, - carried, path, arm), - Hoist.walk(D1.inert.node(rest), self, wides, carried, path, hot), Hoist.walk(rest, self, wides, carried, path, - hot), ik, ib, ir) - -# any other statement read that way: its let, its kids, body and the rest -# walked as they were -law inert.hoist.walk_st: - for +kids: Tree.Node - for +body: Tree.Node - for +rest: Tree.Node - for +self: String - for +wides: List<&2, String> - for +carried: List<&2, String> - for +path: String - for +hot: Bool - for +ek: {D1.inert.ok(kids) == True{} : Bool} - for +eb: {D1.inert.ok(body) == True{} : Bool} - for +er: {D1.inert.ok(rest) == True{} : Bool} - for ik: {Hoist.walk(D1.inert.node(kids), self, wides, carried, path, hot) == Hoist.walk(kids, self, wides, - carried, path, hot) - : List<&2, F.Finding>} - for ib: {Hoist.walk(D1.inert.node(body), self, wides, carried, path, hot) == Hoist.walk(body, self, wides, - carried, path, hot) - : List<&2, F.Finding>} - for ir: {Hoist.walk(D1.inert.node(rest), self, wides, carried, path, hot) == Hoist.walk(rest, self, wides, - carried, path, hot) - : List<&2, F.Finding>} - {List.concat(&2, F.Finding, [Hoist.let_hit(hot, D1.inert.node(kids), D1.inert.node(body), D1.inert.node(rest), - self, wides, carried, path), - Hoist.walk(D1.inert.node(kids), self, wides, carried, path, hot), Hoist.walk(D1.inert.node(body), self, wides, - carried, path, hot), - Hoist.walk(D1.inert.node(rest), self, wides, carried, path, hot)]) - == List.concat(&2, F.Finding, [Hoist.let_hit(hot, kids, body, rest, self, wides, carried, path), - Hoist.walk(kids, self, wides, carried, path, hot), Hoist.walk(body, self, wides, carried, path, hot), - Hoist.walk(rest, self, wides, carried, path, hot)]) : List<&2, F.Finding>} - -def inert.hoist.walk_st(kids, body, rest, self, wides, carried, path, hot, ek, eb, er, ik, ib, ir): - inert.hoist.cat4(Hoist.let_hit(hot, D1.inert.node(kids), D1.inert.node(body), D1.inert.node(rest), self, - wides, carried, path), - Hoist.let_hit(hot, kids, body, rest, self, wides, carried, path), - Hoist.walk(D1.inert.node(kids), self, wides, carried, path, hot), Hoist.walk(kids, self, wides, carried, path, - hot), - Hoist.walk(D1.inert.node(body), self, wides, carried, path, hot), Hoist.walk(body, self, wides, carried, path, - hot), - Hoist.walk(D1.inert.node(rest), self, wides, carried, path, hot), Hoist.walk(rest, self, wides, carried, path, - hot), - inert.hoist.let_hit(hot, kids, body, rest, self, wides, carried, path, ek, eb, er), ik, ib, ir) - -# hoist's walk reads no string: a string names no builder, no get or set, no -# carried parameter and no let, and a table never starts with one -law inert.hoist.walk: - for nn: Tree.Node - for +self: String - for +wides: List<&2, String> - for +carried: List<&2, String> - for +path: String - for +hot: Bool - for +es: {D1.inert.marked(self) == False{} : Bool} - for +e: {D1.inert.ok(nn) == True{} : Bool} - {Hoist.walk(D1.inert.node(nn), self, wides, carried, path, hot) == Hoist.walk(nn, self, wides, carried, path, - hot) : List<&2, F.Finding>} - -def inert.hoist.walk(nn, self, wides, carried, path, hot, es, e): - match nn: - case Tree.NCons{h, +rest}: - match h: - case Tree.Leaf{tok}: - match tok: - case Lex.Tok{+k, +t, +l, +c}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{go, +kids, +gcl}: - match go: - case Lex.Tok{+gk, +o, +gl, +gc}: - +lf = {Tree.Leaf{Lex.Tok{k, t, l, c}} : Tree.Node} - +gp = {Tree.Group{Lex.Tok{gk, o, gl, gc}, kids, gcl} : Tree.Node} - +eg = inert.ok_t(lf, Tree.NCons{gp, r2}, e) - inert.hoist.walk_lg(k, t, o, kids, r2, self, wides, carried, path, hot, - inert.ok_h(lf, Tree.NCons{gp, r2}, e), inert.ok_h(gp, r2, eg), - inert.hoist.walk(kids, self, wides, carried, path, Hoist.warm(hot, k), es, inert.ok_h(gp, - r2, - eg)), - inert.hoist.walk(r2, self, wides, carried, path, Hoist.warm(hot, k), es, inert.ok_t(gp, r2, - eg))) - case Tree.Leaf{x}: - inert.hoist.walk(rest, self, wides, carried, path, Hoist.warm(hot, k), es, - inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.Stmt{x, y, z}: - inert.hoist.walk(rest, self, wides, carried, path, Hoist.warm(hot, k), es, - inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.NNil{}: - inert.hoist.walk(rest, self, wides, carried, path, Hoist.warm(hot, k), es, - inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.NCons{x, y}: - inert.hoist.walk(rest, self, wides, carried, path, Hoist.warm(hot, k), es, - inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.Leaf{x}: - inert.hoist.walk(rest, self, wides, carried, path, Hoist.warm(hot, k), es, - inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.Group{x, y, z}: - inert.hoist.walk(rest, self, wides, carried, path, Hoist.warm(hot, k), es, - inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.Stmt{x, y, z}: - inert.hoist.walk(rest, self, wides, carried, path, Hoist.warm(hot, k), es, - inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.NNil{}: - inert.hoist.walk(rest, self, wides, carried, path, Hoist.warm(hot, k), es, - inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.Group{+o, +kids, +cl}: - inert.cat2(Hoist.walk(D1.inert.node(kids), self, wides, carried, path, hot), Hoist.walk(kids, self, wides, - carried, path, - hot), Hoist.walk(D1.inert.node(rest), self, wides, carried, path, hot), Hoist.walk(rest, self, wides, - carried, path, hot), - inert.hoist.walk(kids, self, wides, carried, path, hot, es, inert.ok_h(Tree.Group{o, kids, cl}, rest, e)), - inert.hoist.walk(rest, self, wides, carried, path, hot, es, inert.ok_t(Tree.Group{o, kids, cl}, rest, e))) - case Tree.Stmt{sk, +kids, +body}: - match sk: - case Tree.SDef{}: - +es2 = M1.inert.and_l(Bool.and(D1.inert.ok(kids), D1.inert.ok(body)), D1.inert.ok(rest), e) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(body), es2) - +eb = M1.inert.and_r(D1.inert.ok(kids), D1.inert.ok(body), es2) - +er = M1.inert.and_r(Bool.and(D1.inert.ok(kids), D1.inert.ok(body)), D1.inert.ok(rest), e) - inert.hoist.walk_st(kids, body, rest, self, wides, carried, path, hot, ek, eb, er, - inert.hoist.walk(kids, self, wides, carried, path, hot, es, ek), - inert.hoist.walk(body, self, wides, carried, path, hot, es, eb), - inert.hoist.walk(rest, self, wides, carried, path, hot, es, er)) - case Tree.SType{}: - +es2 = M1.inert.and_l(Bool.and(D1.inert.ok(kids), D1.inert.ok(body)), D1.inert.ok(rest), e) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(body), es2) - +eb = M1.inert.and_r(D1.inert.ok(kids), D1.inert.ok(body), es2) - +er = M1.inert.and_r(Bool.and(D1.inert.ok(kids), D1.inert.ok(body)), D1.inert.ok(rest), e) - inert.hoist.walk_st(kids, body, rest, self, wides, carried, path, hot, ek, eb, er, - inert.hoist.walk(kids, self, wides, carried, path, hot, es, ek), - inert.hoist.walk(body, self, wides, carried, path, hot, es, eb), - inert.hoist.walk(rest, self, wides, carried, path, hot, es, er)) - case Tree.SLaw{}: - +es2 = M1.inert.and_l(Bool.and(D1.inert.ok(kids), D1.inert.ok(body)), D1.inert.ok(rest), e) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(body), es2) - +eb = M1.inert.and_r(D1.inert.ok(kids), D1.inert.ok(body), es2) - +er = M1.inert.and_r(Bool.and(D1.inert.ok(kids), D1.inert.ok(body)), D1.inert.ok(rest), e) - inert.hoist.walk_st(kids, body, rest, self, wides, carried, path, hot, ek, eb, er, - inert.hoist.walk(kids, self, wides, carried, path, hot, es, ek), - inert.hoist.walk(body, self, wides, carried, path, hot, es, eb), - inert.hoist.walk(rest, self, wides, carried, path, hot, es, er)) - case Tree.SImport{}: - +es2 = M1.inert.and_l(Bool.and(D1.inert.ok(kids), D1.inert.ok(body)), D1.inert.ok(rest), e) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(body), es2) - +eb = M1.inert.and_r(D1.inert.ok(kids), D1.inert.ok(body), es2) - +er = M1.inert.and_r(Bool.and(D1.inert.ok(kids), D1.inert.ok(body)), D1.inert.ok(rest), e) - inert.hoist.walk_st(kids, body, rest, self, wides, carried, path, hot, ek, eb, er, - inert.hoist.walk(kids, self, wides, carried, path, hot, es, ek), - inert.hoist.walk(body, self, wides, carried, path, hot, es, eb), - inert.hoist.walk(rest, self, wides, carried, path, hot, es, er)) - case Tree.SCase{}: - +es2 = M1.inert.and_l(Bool.and(D1.inert.ok(kids), D1.inert.ok(body)), D1.inert.ok(rest), e) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(body), es2) - +eb = M1.inert.and_r(D1.inert.ok(kids), D1.inert.ok(body), es2) - +er = M1.inert.and_r(Bool.and(D1.inert.ok(kids), D1.inert.ok(body)), D1.inert.ok(rest), e) - inert.hoist.walk_case(kids, body, rest, self, wides, carried, path, hot, es, eb, - inert.hoist.walk(kids, self, wides, carried, path, False{}, es, ek), - inert.hoist.walk(body, self, wides, carried, path, Bool.and(hot, Calls.calls(D1.inert.node(body), - self)), es, eb), - inert.hoist.walk(rest, self, wides, carried, path, hot, es, er)) - case Tree.SFor{}: - +es2 = M1.inert.and_l(Bool.and(D1.inert.ok(kids), D1.inert.ok(body)), D1.inert.ok(rest), e) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(body), es2) - +eb = M1.inert.and_r(D1.inert.ok(kids), D1.inert.ok(body), es2) - +er = M1.inert.and_r(Bool.and(D1.inert.ok(kids), D1.inert.ok(body)), D1.inert.ok(rest), e) - inert.hoist.walk_st(kids, body, rest, self, wides, carried, path, hot, ek, eb, er, - inert.hoist.walk(kids, self, wides, carried, path, hot, es, ek), - inert.hoist.walk(body, self, wides, carried, path, hot, es, eb), - inert.hoist.walk(rest, self, wides, carried, path, hot, es, er)) - case Tree.SLet{}: - +es2 = M1.inert.and_l(Bool.and(D1.inert.ok(kids), D1.inert.ok(body)), D1.inert.ok(rest), e) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(body), es2) - +eb = M1.inert.and_r(D1.inert.ok(kids), D1.inert.ok(body), es2) - +er = M1.inert.and_r(Bool.and(D1.inert.ok(kids), D1.inert.ok(body)), D1.inert.ok(rest), e) - inert.hoist.walk_st(kids, body, rest, self, wides, carried, path, hot, ek, eb, er, - inert.hoist.walk(kids, self, wides, carried, path, hot, es, ek), - inert.hoist.walk(body, self, wides, carried, path, hot, es, eb), - inert.hoist.walk(rest, self, wides, carried, path, hot, es, er)) - case Tree.STerm{}: - +es2 = M1.inert.and_l(Bool.and(D1.inert.ok(kids), D1.inert.ok(body)), D1.inert.ok(rest), e) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(body), es2) - +eb = M1.inert.and_r(D1.inert.ok(kids), D1.inert.ok(body), es2) - +er = M1.inert.and_r(Bool.and(D1.inert.ok(kids), D1.inert.ok(body)), D1.inert.ok(rest), e) - inert.hoist.walk_st(kids, body, rest, self, wides, carried, path, hot, ek, eb, er, - inert.hoist.walk(kids, self, wides, carried, path, hot, es, ek), - inert.hoist.walk(body, self, wides, carried, path, hot, es, eb), - inert.hoist.walk(rest, self, wides, carried, path, hot, es, er)) - case Tree.NNil{}: inert.hoist.walk(rest, self, wides, carried, path, hot, es, e) - case Tree.NCons{x, y}: - inert.hoist.walk(rest, self, wides, carried, path, hot, es, inert.ok_t(Tree.NCons{x, y}, rest, e)) - case Tree.Leaf{x}: {==} - case Tree.Group{x, y, z}: {==} - case Tree.Stmt{x, y, z}: {==} - case Tree.NNil{}: {==} - -# hoist over the defs read that way reports what it reports over the defs -law inert.hoist.go: - for ds: List<&2, Calls.Def> - for +wide: List<&2, String> - for +path: String - for +acc: List<&2, List<&2, F.Finding>> - for +e: {inert.dok(ds) == True{} : Bool} - {Hoist.check.go(inert.defs(ds), wide, path, acc) == Hoist.check.go(ds, wide, path, acc) : List<&2, F.Finding>} - -def inert.hoist.go(ds, wide, path, acc, e): - match ds: - case Nil{}: {==} - case Con{Calls.Def{+name, +sig, +body}, +rest}: - +en = inert.dok.head(name, sig, body, rest, e) - +eb = inert.dok.body(name, sig, body, rest, e) - +ib = D1.inert.node(body) - +isg = D1.inert.node(sig) - +cr = Hoist.carried(isg, ib, name) - +lst = Lazy.stop(List<&2, F.Finding>, Bool.not(Bool.and(Calls.calls(ib, name), Bool.not(Calls.exempt(path, - isg)))), [], - _u => Hoist.walk(ib, name, wide, cr, path, True{})) - %inert.hoist.carried(sig, body, name, en, eb) : {Hoist.check.go(inert.defs(rest), wide, path, lst <> acc) - == Hoist.check.go(rest, wide, path, Lazy.stop(List<&2, F.Finding>, Bool.not(Bool.and(Calls.calls(body, name), - Bool.not(Calls.exempt(path, sig)))), [], _u => Hoist.walk(body, name, wide, _, path, - True{})) <> acc) : List<&2, F.Finding>} - %inert.stop(Bool.not(Bool.and(Calls.calls(ib, name), Bool.not(Calls.exempt(path, isg)))), - Bool.not(Bool.and(Calls.calls(body, name), Bool.not(Calls.exempt(path, sig)))), - Hoist.walk(ib, name, wide, cr, path, True{}), Hoist.walk(body, name, wide, cr, path, True{}), - inert.gate(path, name, sig, body, en, eb), inert.hoist.walk(body, name, wide, cr, path, True{}, en, eb)) : - {Hoist.check.go(inert.defs(rest), wide, path, lst <> acc) == Hoist.check.go(rest, wide, path, - _ <> acc) : List<&2, F.Finding>} - inert.hoist.go(rest, wide, path, lst <> acc, inert.dok.rest(name, sig, body, rest, e)) - -# hoist over the defs read that way, their wide tables read that way too -law inert.hoist.all: - for +ds: List<&2, Calls.Def> - for +path: String - for +e: {inert.dok(ds) == True{} : Bool} - {Hoist.check.go(inert.defs(ds), Hoist.wide_defs(inert.defs(ds), []), path, []) - == Hoist.check.go(ds, Hoist.wide_defs(ds, []), path, []) : List<&2, F.Finding>} - -def inert.hoist.all(ds, path, e): - %inert.hoist.wide_defs(ds, [], e) : {Hoist.check.go(inert.defs(ds), Hoist.wide_defs(inert.defs(ds), []), path, []) - == Hoist.check.go(ds, _, path, []) : List<&2, F.Finding>} - inert.hoist.go(ds, Hoist.wide_defs(inert.defs(ds), []), path, [], e) - -def Laws.inert_hoist(path, _text, tree, _toks, _bound, _items, _text2, tree2, _toks2, _bound2, _items2, ok, ok2, e): - M1.inert.via(Tree.Node, z => Hoist.check.go(Calls.defs(z), Hoist.wide_defs(Calls.defs(z), []), path, []), tree, tree2, - D1.inert.node(tree), D1.inert.node(tree2), - inert.lift(z => Hoist.check.go(z, Hoist.wide_defs(z, []), path, []), tree, inert.hoist.all(Calls.defs(tree), path, - inert.dok.of(tree, ok))), - inert.lift(z => Hoist.check.go(z, Hoist.wide_defs(z, []), path, []), tree2, inert.hoist.all(Calls.defs(tree2), path, - inert.dok.of(tree2, ok2))), - e) - -# wrap -# ---- - -# a token's share of the width reads a string's kind only, and a comment has -# none -law inert.wrap.cost: - for k: Lex.TokKind - for +t: String - {Wrap.check.cost(k, t) == Wrap.check.cost(k, D1.inert.keep(D1.inert.blanks(k), t)) : U32} - -def inert.wrap.cost(k, _t): - match k: - case Lex.TName{}: - {==} - case Lex.TUpper{}: - {==} - case Lex.TDotted{}: - {==} - case Lex.TWild{}: - {==} - case Lex.TKey{}: - {==} - case Lex.TNum{}: - {==} - case Lex.TStr{}: - {==} - case Lex.TChar{}: - {==} - case Lex.TComment{}: - {==} - case Lex.TSpace{}: - {==} - case Lex.TNewline{}: - {==} - case Lex.TOp{}: - {==} - case Lex.TColon{}: - {==} - case Lex.TEq{}: - {==} - case Lex.TBind{}: - {==} - case Lex.TArrow{}: - {==} - case Lex.TLam{}: - {==} - case Lex.TAll{}: - {==} - case Lex.TAmp{}: - {==} - case Lex.TOpen{}: - {==} - case Lex.TClose{}: - {==} - case Lex.TComma{}: + case Tree.Leaf{+tok}: + %inert.canon_tok(tok) : {inert.canon(D1.inert.tok(tok)) <> inert.canons(a) == _ <> inert.canons(b) + : List<&2, Lex.Tok>} + %e : {inert.canon(D1.inert.tok(tok)) <> inert.canons(a) == inert.canon(D1.inert.tok(tok)) <> _ + : List<&2, Lex.Tok>} {==} + case Tree.Group{+open, kids, close}: + inert.canon_close(close, Tree.leaves.go(D1.inert.node(kids), open <> a), Tree.leaves.go(kids, open <> b), + inert.canon_leaves(kids, open <> a, open <> b, Equal.cong(List<&2, Lex.Tok>, List<&2, Lex.Tok>, + z => inert.canon(open) <> z, inert.canons(a), inert.canons(b), e))) + case Tree.Stmt{sk, kids, body}: + inert.canon_leaves(body, Tree.leaves.go(D1.inert.node(kids), a), Tree.leaves.go(kids, b), + inert.canon_leaves(kids, a, b, e)) + case Tree.NNil{}: e + case Tree.NCons{h, t}: + inert.canon_leaves(t, Tree.leaves.go(D1.inert.node(h), a), Tree.leaves.go(h, b), inert.canon_leaves(h, a, b, e)) -# the width walk reads no comment or string -law inert.wrap.width: - for ts: List<&2, Lex.Tok> - for +acc: U32 - for +upto: U32 - for +seen: Bool - {Wrap.check.width.go(D1.inert.toks(ts), acc, upto, seen) == Wrap.check.width.go(ts, acc, upto, seen) : U32} +# reversing, by where they stand +law inert.canon_rev: + for xs: List<&2, Lex.Tok> + for +acc: List<&2, Lex.Tok> + {inert.canons(List.reverse.go(&2, Lex.Tok, xs, acc)) == List.reverse.go(&2, Lex.Tok, inert.canons(xs), + inert.canons(acc)) : List<&2, Lex.Tok>} -def inert.wrap.width(ts, acc, upto, seen): - match ts: - case Nil{}: - {==} - case Con{Lex.Tok{+k, +t, l, c}, +rest}: - +nx = Wrap.check.add(acc, k, t) - +co = Wrap.check.is_colon(k) - %inert.wrap.cost(k, t) : {Wrap.check.width.go(D1.inert.toks(rest), (acc + _ : U32), - Bool.pick(U32, co, (acc + _ : U32), upto), Bool.or(co, seen)) - == Wrap.check.width.go(rest, nx, Bool.pick(U32, co, nx, upto), Bool.or(co, seen)) : U32} - inert.wrap.width(rest, nx, Bool.pick(U32, co, nx, upto), Bool.or(co, seen)) +def inert.canon_rev(xs, acc): + match xs: + case Nil{}: {==} + case Con{+h, t}: inert.canon_rev(t, h <> acc) -# the line test reads kinds only -law inert.wrap.broken: +# the lines of tokens are the lines of where they stand +law inert.canon_lines: for ts: List<&2, Lex.Tok> - {Wrap.check.broken(D1.inert.toks(ts)) == Wrap.check.broken(ts) : Bool} + for +acc: List<&2, U32> + {Unused.lines(ts, acc) == Unused.lines(inert.canons(ts), acc) : List<&2, U32>} -def inert.wrap.broken(ts): +def inert.canon_lines(ts, acc): match ts: - case Nil{}: - {==} - case Con{Lex.Tok{+k, t, l, c}, +rest}: - %inert.wrap.broken(rest) : {Lazy.or_else(Lex.is_nl(k), _u => Wrap.check.broken(D1.inert.toks(rest))) - == Lazy.or_else(Lex.is_nl(k), _u => _) : Bool} - {==} - -# a string moves nothing, whatever the walk's phase: its text is read only -# for the angles a `>` run closes, and a string is no such run -law inert.wrap.str: - for +wk: Wrap.Walk - for +t: String - for +l: U32 - for +c: U32 - for glued: Bool - {Wrap.check.step_hit(wk, Lex.Tok{Lex.TStr{}, D1.inert.cut(t), l, c}, glued) - == Wrap.check.step_hit(wk, Lex.Tok{Lex.TStr{}, t, l, c}, glued) : Wrap.Walk} - -def inert.wrap.str(wk, _t, _l, _c, _glued): - match wk: - case Wrap.Walk{mode, paren, bracket, brace, angle, shut, open, on, pline, pend, pcol, spans}: - match mode: - case Wrap.Seek{}: - {==} - case Wrap.Args{}: - {==} - case Wrap.Past{}: - {==} - case Wrap.Shut{}: - {==} - case Wrap.Loose{}: - {==} - -# one token of the header moves the walk as it did: a comment moves nothing, -# a string as above, and any other token keeps its text -law inert.wrap.step: - for k: Lex.TokKind - for +t: String - for +l: U32 - for +c: U32 - for +wk: Wrap.Walk - for glued: Bool - {Wrap.check.step(D1.inert.tok(Lex.Tok{k, t, l, c}), wk, glued) == Wrap.check.step(Lex.Tok{k, t, l, c}, wk, glued) - : Wrap.Walk} - -def inert.wrap.step(k, t, l, c, wk, glued): - match k: - case Lex.TName{}: - {==} - case Lex.TUpper{}: - {==} - case Lex.TDotted{}: - {==} - case Lex.TWild{}: - {==} - case Lex.TKey{}: - {==} - case Lex.TNum{}: - {==} - case Lex.TStr{}: - inert.wrap.str(wk, t, l, c, glued) - case Lex.TChar{}: - {==} - case Lex.TComment{}: - {==} - case Lex.TSpace{}: - {==} - case Lex.TNewline{}: - {==} - case Lex.TOp{}: - {==} - case Lex.TColon{}: - {==} - case Lex.TEq{}: - {==} - case Lex.TBind{}: - {==} - case Lex.TArrow{}: - {==} - case Lex.TLam{}: - {==} - case Lex.TAll{}: - {==} - case Lex.TAmp{}: - {==} - case Lex.TOpen{}: - {==} - case Lex.TClose{}: - {==} - case Lex.TComma{}: - {==} + case Nil{}: {==} + case Con{h, +t}: + match h: + case Lex.Tok{k, x, +l, c}: + Equal.cong(List<&2, U32>, List<&2, U32>, z => l <> z, Unused.lines(t, acc), + Unused.lines(inert.canons(t), acc), inert.canon_lines(t, acc)) -# the shape walk reads no comment or string -law inert.wrap.walk: - for ts: List<&2, Lex.Tok> - for +wk: Wrap.Walk - for +glued: Bool - {Wrap.check.walk(D1.inert.toks(ts), wk, glued) == Wrap.check.walk(ts, wk, glued) : Wrap.Walk} +# the lines of a node's leaves read that way are the lines they were +law inert.unused.lines: + for +kids: Tree.Node + for +acc: List<&2, U32> + {Unused.lines(Tree.leaves(D1.inert.node(kids)), acc) == Unused.lines(Tree.leaves(kids), acc) : List<&2, U32>} -def inert.wrap.walk(ts, wk, glued): - match ts: - case Nil{}: - {==} - case Con{Lex.Tok{+k, +t, +l, +c}, +rest}: - +s1 = Wrap.check.step(D1.inert.tok(Lex.Tok{k, t, l, c}), wk, glued) - +s2 = Wrap.check.step(Lex.Tok{k, t, l, c}, wk, glued) - Equal.trans(Wrap.Walk, Wrap.check.walk(D1.inert.toks(rest), s1, Lex.is_name(k)), - Wrap.check.walk(rest, s1, Lex.is_name(k)), Wrap.check.walk(rest, s2, Lex.is_name(k)), - inert.wrap.walk(rest, s1, Lex.is_name(k)), - Equal.cong(Wrap.Walk, Wrap.Walk, w => Wrap.check.walk(rest, w, Lex.is_name(k)), s1, s2, - inert.wrap.step(k, t, l, c, wk, glued))) +def inert.unused.lines(kids, acc): + +li = Tree.leaves.go(D1.inert.node(kids), []) + +lo = Tree.leaves.go(kids, []) + +ec = Equal.trans(List<&2, Lex.Tok>, inert.canons(List.reverse.go(&2, Lex.Tok, li, [])), List.reverse.go(&2, Lex.Tok, + inert.canons(li), []), inert.canons(List.reverse.go(&2, Lex.Tok, lo, [])), inert.canon_rev(li, []), + Equal.trans(List<&2, Lex.Tok>, List.reverse.go(&2, Lex.Tok, inert.canons(li), []), List.reverse.go(&2, Lex.Tok, + inert.canons(lo), []), inert.canons(List.reverse.go(&2, Lex.Tok, lo, [])), + Equal.cong(List<&2, Lex.Tok>, List<&2, Lex.Tok>, z => List.reverse.go(&2, Lex.Tok, z, []), inert.canons(li), + inert.canons(lo), inert.canon_leaves(kids, [], [], {==})), + Equal.sym(List<&2, Lex.Tok>, inert.canons(List.reverse.go(&2, Lex.Tok, lo, [])), List.reverse.go(&2, Lex.Tok, + inert.canons(lo), []), inert.canon_rev(lo, [])))) + Equal.trans(List<&2, U32>, Unused.lines(List.reverse.go(&2, Lex.Tok, li, []), acc), + Unused.lines(inert.canons(List.reverse.go(&2, Lex.Tok, li, [])), acc), + Unused.lines(List.reverse.go(&2, Lex.Tok, lo, []), acc), + inert.canon_lines(List.reverse.go(&2, Lex.Tok, li, []), acc), + Equal.trans(List<&2, U32>, Unused.lines(inert.canons(List.reverse.go(&2, Lex.Tok, li, [])), acc), + Unused.lines(inert.canons(List.reverse.go(&2, Lex.Tok, lo, [])), acc), + Unused.lines(List.reverse.go(&2, Lex.Tok, lo, []), acc), + Equal.cong(List<&2, Lex.Tok>, List<&2, U32>, z => Unused.lines(z, acc), + inert.canons(List.reverse.go(&2, Lex.Tok, li, [])), inert.canons(List.reverse.go(&2, Lex.Tok, lo, [])), ec), + Equal.sym(List<&2, U32>, Unused.lines(List.reverse.go(&2, Lex.Tok, lo, []), acc), + Unused.lines(inert.canons(List.reverse.go(&2, Lex.Tok, lo, [])), acc), + inert.canon_lines(List.reverse.go(&2, Lex.Tok, lo, []), acc)))) -# a header, broken or not, is judged alike with its comments and strings cut -law inert.wrap.header: - for b: Bool - for +ts: List<&2, Lex.Tok> - for +line: U32 - for +path: String - for +more: List<&2, F.Finding> - {Wrap.check.header(b, line, D1.inert.toks(ts), path, more) == Wrap.check.header(b, line, ts, path, more) - : List<&2, F.Finding>} +# a node's `:` test, the node read that way, is the one it was +law inert.unused.colon: + for nn: Tree.Node + {Unused.colon(D1.inert.node(nn)) == Unused.colon(nn) : Bool} -def inert.wrap.header(b, ts, line, path, more): - match b: - case True{}: - Equal.cong(Wrap.Walk, List<&2, F.Finding>, wk => Wrap.check.place(Wrap.check.jam_span(wk), - Wrap.check.empty_span(wk), Wrap.check.shut_span(wk), Wrap.check.loose_span(wk), line, path, more), - Wrap.check.walk(D1.inert.toks(ts), Wrap.check.start(), False{}), - Wrap.check.walk(ts, Wrap.check.start(), False{}), inert.wrap.walk(ts, Wrap.check.start(), False{})) - case False{}: - Equal.cong(U32, List<&2, F.Finding>, ww => Lazy.stop(List<&2, F.Finding>, U32.is_gt(ww, 120), - F.Finding{path, line, 120, 0, "wrap", - "This def header is longer than 120 characters; wrap it with one parameter per line."} <> more, - _u => more), Wrap.check.width.go(D1.inert.toks(ts), 0, 0, False{}), Wrap.check.width.go(ts, 0, 0, False{}), - inert.wrap.width(ts, 0, 0, False{})) +def inert.unused.colon(nn): + match nn: + case Tree.Leaf{tok}: + match tok: + case Lex.Tok{k, t, l, c}: {==} + case Tree.Group{x, y, z}: {==} + case Tree.Stmt{x, y, z}: {==} + case Tree.NNil{}: {==} + case Tree.NCons{x, y}: {==} -# a def header's tokens are judged alike with their comments and strings cut -law inert.wrap.lexed: - for +ts: List<&2, Lex.Tok> - for +line: U32 - for +path: String - for +more: List<&2, F.Finding> - {Wrap.check.lexed(line, D1.inert.toks(ts), path, more) == Wrap.check.lexed(line, ts, path, more) - : List<&2, F.Finding>} +# a statement's `:` ending, its chain read that way, is the one it was +law inert.unused.ends: + for kids: Tree.Node + for last: Bool + {Unused.foreign.ends(D1.inert.node(kids), last) == Unused.foreign.ends(kids, last) : Bool} -def inert.wrap.lexed(ts, line, path, more): - %Equal.sym(Bool, Wrap.check.broken(D1.inert.toks(ts)), Wrap.check.broken(ts), inert.wrap.broken(ts)) : - {Wrap.check.header(_, line, D1.inert.toks(ts), path, more) - == Wrap.check.header(Wrap.check.broken(ts), line, ts, path, more) : List<&2, F.Finding>} - inert.wrap.header(Wrap.check.broken(ts), ts, line, path, more) +def inert.unused.ends(kids, _last): + match kids: + case Tree.NCons{+h, +t}: + Equal.trans(Bool, Unused.foreign.ends(D1.inert.node(t), Unused.colon(D1.inert.node(h))), + Unused.foreign.ends(D1.inert.node(t), Unused.colon(h)), Unused.foreign.ends(t, Unused.colon(h)), + Equal.cong(Bool, Bool, z => Unused.foreign.ends(D1.inert.node(t), z), Unused.colon(D1.inert.node(h)), + Unused.colon(h), inert.unused.colon(h)), + inert.unused.ends(t, Unused.colon(h))) + case Tree.Leaf{x}: {==} + case Tree.Group{x, y, z}: {==} + case Tree.Stmt{x, y, z}: {==} + case Tree.NNil{}: {==} -# one item as wrap reads it: its kind and line, and its header's tokens -def inert.wrap.head( - it: Outline.Item, - ts: List<&2, Lex.Tok>, - +path: String, - +more: List<&2, F.Finding> -) -> List<&2, F.Finding>: - match it: - case Outline.Item{kk, _n, +line, _s, _d, _p}: - Lazy.stop(List<&2, F.Finding>, Bool.not(Outline.is_def(kk)), more, _u => Wrap.check.lexed(line, ts, path, more)) +# a body's opening import, the body read that way, is the one it was +law inert.unused.opens: + for body: Tree.Node + for open: Bool + {Unused.foreign.opens(D1.inert.node(body), open) == Unused.foreign.opens(body, open) : Bool} -# wrap over the items read that way: each item with its header's tokens -def inert.wrap.go( - hs: List<&2, Outline.Item>, - ss: List<&2, List<&2, Lex.Tok>>, - +path: String -) -> List<&2, F.Finding>: - match hs ss: - case Nil{} ss: - Nil{} - case Con{h, t} Nil{}: - Nil{} - case Con{h, t} Con{ts, st}: - inert.wrap.head(h, ts, path, inert.wrap.go(t, st, path)) +def inert.unused.opens(body, open): + match body: + case Tree.NCons{h, +rest}: + match h: + case Tree.Stmt{sk, +kids, b}: + match open: + case True{}: + Equal.trans(Bool, + Unused.foreign.opens(D1.inert.node(rest), + Bool.not(Unused.foreign.ends(D1.inert.node(kids), False{}))), + Unused.foreign.opens(D1.inert.node(rest), Bool.not(Unused.foreign.ends(kids, False{}))), + Unused.foreign.opens(rest, Bool.not(Unused.foreign.ends(kids, False{}))), + Equal.cong(Bool, Bool, z => Unused.foreign.opens(D1.inert.node(rest), Bool.not(z)), + Unused.foreign.ends(D1.inert.node(kids), False{}), Unused.foreign.ends(kids, False{}), + inert.unused.ends(kids, False{})), + inert.unused.opens(rest, Bool.not(Unused.foreign.ends(kids, False{})))) + case False{}: {==} + case Tree.Leaf{x}: {==} + case Tree.Group{x, y, z}: {==} + case Tree.NNil{}: {==} + case Tree.NCons{x, y}: {==} + case Tree.Leaf{x}: {==} + case Tree.Group{x, y, z}: {==} + case Tree.Stmt{x, y, z}: {==} + case Tree.NNil{}: {==} -# wrap's walk over the items is its walk over them read that way -law inert.wrap.items: - for items: List<&2, Outline.Item> - for +path: String - {Wrap.check.go(items, path) == inert.wrap.go(Laws.inert.items(items), Laws.inert.sigs(items), path) - : List<&2, F.Finding>} +# a def's lines onto the lines after it, the tree read that way, are the ones +# they were +law inert.unused.def: + for +kids: Tree.Node + for +body: Tree.Node + for +rest: Tree.Node + for +ih: {Unused.foreign(D1.inert.node(rest)) == Unused.foreign(rest) : List<&2, U32>} + {Bool.pick(List<&2, U32>, + Unused.foreign.opens(D1.inert.node(body), Bool.not(Unused.foreign.ends(D1.inert.node(kids), False{}))), + Unused.lines(Tree.leaves(D1.inert.node(kids)), Unused.foreign(D1.inert.node(rest))), + Unused.foreign(D1.inert.node(rest))) + == Bool.pick(List<&2, U32>, Unused.foreign.opens(body, Bool.not(Unused.foreign.ends(kids, False{}))), + Unused.lines(Tree.leaves(kids), Unused.foreign(rest)), Unused.foreign(rest)) : List<&2, U32>} -def inert.wrap.items(items, path): - match items: - case Nil{}: - {==} - case Con{Outline.Item{+kk, n, +line, +sig, d, p}, +rest}: - +more = Wrap.check.go(rest, path) - +ts = Lex.tokens(sig) - %inert.wrap.items(rest, path) : {Lazy.stop(List<&2, F.Finding>, Bool.not(Outline.is_def(kk)), more, - _u => Wrap.check.lexed(line, ts, path, more)) - == Lazy.stop(List<&2, F.Finding>, Bool.not(Outline.is_def(kk)), _, - _u => Wrap.check.lexed(line, D1.inert.toks(ts), path, _)) : List<&2, F.Finding>} - %Equal.sym(List<&2, F.Finding>, Wrap.check.lexed(line, D1.inert.toks(ts), path, more), - Wrap.check.lexed(line, ts, path, more), inert.wrap.lexed(ts, line, path, more)) : - {Lazy.stop(List<&2, F.Finding>, Bool.not(Outline.is_def(kk)), more, - _u => Wrap.check.lexed(line, ts, path, more)) - == Lazy.stop(List<&2, F.Finding>, Bool.not(Outline.is_def(kk)), more, _u => _) : List<&2, F.Finding>} - {==} +def inert.unused.def(kids, body, rest, ih): + +fi = Unused.foreign(D1.inert.node(rest)) + +fo = Unused.foreign(rest) + +ra = Unused.lines(Tree.leaves(D1.inert.node(kids)), fi) + +rb = Unused.lines(Tree.leaves(kids), fo) + +c1 = Unused.foreign.opens(D1.inert.node(body), Bool.not(Unused.foreign.ends(D1.inert.node(kids), False{}))) + +c2 = Unused.foreign.opens(body, Bool.not(Unused.foreign.ends(kids, False{}))) + +ec = Equal.trans(Bool, c1, + Unused.foreign.opens(D1.inert.node(body), Bool.not(Unused.foreign.ends(kids, False{}))), c2, + Equal.cong(Bool, Bool, z => Unused.foreign.opens(D1.inert.node(body), Bool.not(z)), + Unused.foreign.ends(D1.inert.node(kids), False{}), Unused.foreign.ends(kids, False{}), + inert.unused.ends(kids, False{})), + inert.unused.opens(body, Bool.not(Unused.foreign.ends(kids, False{})))) + +ea = Equal.trans(List<&2, U32>, ra, Unused.lines(Tree.leaves(kids), fi), rb, inert.unused.lines(kids, fi), + Equal.cong(List<&2, U32>, List<&2, U32>, z => Unused.lines(Tree.leaves(kids), z), fi, fo, ih)) + Equal.trans(List<&2, U32>, Bool.pick(List<&2, U32>, c1, ra, fi), Bool.pick(List<&2, U32>, c2, ra, fi), + Bool.pick(List<&2, U32>, c2, rb, fo), + Equal.cong(Bool, List<&2, U32>, z => Bool.pick(List<&2, U32>, z, ra, fi), c1, c2, ec), + M2.unused.pick(c2, ra, rb, fi, fo, ea, ih)) -def Laws.inert_wrap(path, _text, _toks, _tree, _bound, items, _text2, _toks2, _tree2, _bound2, items2, ei, es): - +i1 = Laws.inert.items(items) - +s1 = Laws.inert.sigs(items) - %Equal.sym(List<&2, F.Finding>, Wrap.check.go(items, path), inert.wrap.go(i1, s1, path), - inert.wrap.items(items, path)) : {_ == Wrap.check.go(items2, path) : List<&2, F.Finding>} - %Equal.sym(List<&2, F.Finding>, Wrap.check.go(items2, path), - inert.wrap.go(Laws.inert.items(items2), Laws.inert.sigs(items2), path), inert.wrap.items(items2, path)) : - {inert.wrap.go(i1, s1, path) == _ : List<&2, F.Finding>} - %ei : {inert.wrap.go(i1, s1, path) == inert.wrap.go(_, Laws.inert.sigs(items2), path) : List<&2, F.Finding>} - %es : {inert.wrap.go(i1, s1, path) == inert.wrap.go(i1, _, path) : List<&2, F.Finding>} - {==} +# the foreign defs' header lines, the tree read that way, are the ones they were +law inert.unused.foreign: + for root: Tree.Node + {Unused.foreign(D1.inert.node(root)) == Unused.foreign(root) : List<&2, U32>} -# scan reads no string: a string is never a callee or a lone name, so each -# walk and each call reads the same over a tree read that way +def inert.unused.foreign(root): + match root: + case Tree.NCons{h, +rest}: + match h: + case Tree.Stmt{sk, +kids, +body}: + match sk: + case Tree.SDef{}: inert.unused.def(kids, body, rest, inert.unused.foreign(rest)) + case Tree.SType{}: inert.unused.foreign(rest) + case Tree.SLaw{}: inert.unused.foreign(rest) + case Tree.SImport{}: inert.unused.foreign(rest) + case Tree.SCase{}: inert.unused.foreign(rest) + case Tree.SFor{}: inert.unused.foreign(rest) + case Tree.SLet{}: inert.unused.foreign(rest) + case Tree.STerm{}: inert.unused.foreign(rest) + case Tree.Leaf{x}: inert.unused.foreign(rest) + case Tree.Group{x, y, z}: inert.unused.foreign(rest) + case Tree.NNil{}: inert.unused.foreign(rest) + case Tree.NCons{x, y}: inert.unused.foreign(rest) + case Tree.Leaf{x}: {==} + case Tree.Group{x, y, z}: {==} + case Tree.Stmt{x, y, z}: {==} + case Tree.NNil{}: {==} -# a lone name read that way is the name it was -law inert.scan.name_tok: - for tok: Lex.Tok - {Scan.name_tok(D1.inert.tok(tok)) == Scan.name_tok(tok) : Maybe<&2, String>} +# does a binder of this kind count, its note read as unused reads it +def inert.unused.rep(kk: Bind.BindKind, +note: String, +line: U32, +fl: List<&2, U32>) -> Bool: + match kk: + case Bind.KLocal{}: + True{} + case Bind.KParam{}: + Bool.and(Bool.not(String.starts_with(note, "-")), Bool.not(List.contains(~U32, ~U32.is_eq, fl, line))) + case other: + False{} -def inert.scan.name_tok(tok): - match tok: - case Lex.Tok{k, t, l, c}: - match k: - case Lex.TName{}: - {==} - case Lex.TUpper{}: - {==} - case Lex.TDotted{}: - {==} - case Lex.TWild{}: - {==} - case Lex.TKey{}: - {==} - case Lex.TNum{}: - {==} - case Lex.TStr{}: - {==} - case Lex.TChar{}: - {==} - case Lex.TComment{}: - {==} - case Lex.TSpace{}: - {==} - case Lex.TNewline{}: - {==} - case Lex.TOp{}: - {==} - case Lex.TColon{}: - {==} - case Lex.TEq{}: - {==} - case Lex.TBind{}: - {==} - case Lex.TArrow{}: - {==} - case Lex.TLam{}: - {==} - case Lex.TAll{}: - {==} - case Lex.TAmp{}: - {==} - case Lex.TOpen{}: - {==} - case Lex.TClose{}: - {==} - case Lex.TComma{}: - {==} +# unused over binders as it reads them +def inert.unused.go( + binds: List<&2, Bind.Bind>, + +hh: Unused.Hits, + +uses: List<&2, Bind.Use>, + +fl: List<&2, U32>, + +path: String +) -> List<&2, F.Finding>: + match binds: + case Nil{}: + Nil{} + case Con{Bind.Bind{+name, +line, +col, +kind, +note}, rest}: + +more = inert.unused.go(rest, hh, uses, fl, path) + +hit = Bool.and(inert.unused.rep(kind, note, line, fl), Bool.not(String.starts_with(name, "_"))) + Bool.pick(List<&2, F.Finding>, Lazy.and_then(hit, _u => Bool.not(Unused.seen(hh, uses, line, col))), + F.Finding{path, line, col, U32.from_nat(String.length(name)), "unused", + Unused.what(kind) ++ " " ++ name ++ " is never used."} <> more, more) -# an argument read that way is the lone name it was -law inert.scan.name_of: - for nn: Tree.Node - {Scan.name_of(D1.inert.node(nn)) == Scan.name_of(nn) : Maybe<&2, String>} +# a leading `-`, kept as unused reads a note +law inert.unused.dash: + for s: Bool + {String.starts_with(Bool.pick(String, s, "-", ""), "-") == s : Bool} -def inert.scan.name_of(nn): - match nn: - case Tree.NCons{h, r}: - match h: - case Tree.Leaf{tok}: - match r: - case Tree.NNil{}: - inert.scan.name_tok(tok) - case Tree.Leaf{x}: - {==} - case Tree.Group{x, y, z}: - {==} - case Tree.Stmt{x, y, z}: - {==} - case Tree.NCons{x, y}: - {==} - case Tree.Group{x, y, z}: - {==} - case Tree.Stmt{x, y, z}: - {==} - case Tree.NNil{}: - {==} - case Tree.NCons{x, y}: - {==} - case Tree.Leaf{x}: - {==} - case Tree.Group{x, y, z}: - {==} - case Tree.Stmt{x, y, z}: - {==} - case Tree.NNil{}: - {==} +def inert.unused.dash(s): + match s: + case True{}: {==} + case False{}: {==} -# the walked argument of arguments read that way is the lone name it was -law inert.scan.arg_name: - for +as: List<&2, Tree.Node> - for +at: Nat - {Scan.name_of(Calls.arg(inert.nodes(as), at)) == Scan.name_of(Calls.arg(as, at)) : Maybe<&2, String>} +# a binder kept as unused reads it +law inert.unused.one: + for kind: Bind.BindKind + for +note: String + for +line: U32 + for +fl: List<&2, U32> + {Unused.reportable(kind, note, line, fl) == inert.unused.rep(kind, Bool.pick(String, String.starts_with(note, "-"), + "-", ""), line, fl) : Bool} -def inert.scan.arg_name(as, at): - %Equal.sym(Tree.Node, Calls.arg(inert.nodes(as), at), D1.inert.node(Calls.arg(as, at)), inert.arg(as, at)) : - {Scan.name_of(_) == Scan.name_of(Calls.arg(as, at)) : Maybe<&2, String>} - inert.scan.name_of(Calls.arg(as, at)) +def inert.unused.one(kind, note, line, fl): + match kind: + case Bind.KParam{}: + %Equal.sym(Bool, String.starts_with(Bool.pick(String, String.starts_with(note, "-"), "-", ""), "-"), + String.starts_with(note, "-"), inert.unused.dash(String.starts_with(note, "-"))) : + {Bool.and(Bool.not(String.starts_with(note, "-")), + Bool.not(List.contains(~U32, ~U32.is_eq, fl, line))) == Bool.and(Bool.not(_), Bool.not(List.contains(~U32, + ~U32.is_eq, fl, line))) : Bool} + {==} + case Bind.KItem{}: {==} + case Bind.KCtor{}: {==} + case Bind.KTypeParam{}: {==} + case Bind.KField{}: {==} + case Bind.KLocal{}: {==} + case Bind.KPat{}: {==} + case Bind.KFor{}: {==} + case Bind.KTypeVar{}: {==} -# the first carried name among arguments read that way is the one it was -law inert.scan.found: - for ss: List<&2, Nat> - for +as: List<&2, Tree.Node> - for +carried: List<&2, String> - {Scan.found(ss, inert.nodes(as), carried) == Scan.found(ss, as, carried) : Maybe<&2, String>} +# unused over binders is unused over the binders as it reads them +law inert.unused.via: + for binds: List<&2, Bind.Bind> + for +hh: Unused.Hits + for +uses: List<&2, Bind.Use> + for +fl: List<&2, U32> + for +path: String + {Unused.check.go(binds, hh, uses, fl, path) == inert.unused.go(Laws.inert.ubinds(binds), hh, uses, fl, path) + : List<&2, F.Finding>} -def inert.scan.found(ss, as, carried): - match ss: - case Nil{}: - {==} - case Con{+at, +rest}: - %inert.scan.arg_name(as, at) : {Scan.first(Scan.keep_in(Scan.name_of(Calls.arg(inert.nodes(as), at)), carried), - Scan.found(rest, inert.nodes(as), carried)) == Scan.first(Scan.keep_in(_, carried), Scan.found(rest, as, - carried)) : Maybe<&2, String>} - %inert.scan.found(rest, as, carried) : {Scan.first(Scan.keep_in(Scan.name_of(Calls.arg(inert.nodes(as), at)), - carried), Scan.found(rest, inert.nodes(as), carried)) == Scan.first(Scan.keep_in(Scan.name_of(Calls.arg( - inert.nodes(as), at)), carried), _) : Maybe<&2, String>} +def inert.unused.via(binds, hh, uses, fl, path): + match binds: + case Nil{}: {==} + case Con{Bind.Bind{+name, +line, +col, +kind, +note}, +rest}: + +mo = Unused.check.go(rest, hh, uses, fl, path) + +x = {F.Finding{path, line, col, U32.from_nat(String.length(name)), "unused", + Unused.what(kind) ++ " " ++ name ++ " is never used."} : F.Finding} + +lhs = Unused.check.go(Con{Bind.Bind{name, line, col, kind, note}, rest}, hh, uses, fl, path) + %inert.unused.via(rest, hh, uses, fl, path) : {lhs == Bool.pick(List<&2, F.Finding>, Lazy.and_then(Bool.and( + inert.unused.rep(kind, Bool.pick(String, String.starts_with(note, "-"), "-", ""), line, fl), + Bool.not(String.starts_with(name, "_"))), _u => Bool.not(Unused.seen(hh, uses, line, col))), x <> _, _) + : List<&2, F.Finding>} + %inert.unused.one(kind, note, line, fl) : {lhs == Bool.pick(List<&2, F.Finding>, Lazy.and_then(Bool.and(_, + Bool.not(String.starts_with(name, "_"))), _u => Bool.not(Unused.seen(hh, uses, line, col))), x <> mo, mo) + : List<&2, F.Finding>} {==} -# a named call whose arguments are read that way reports what it did -law inert.scan.on_name: - for +hot: Bool - for +open: Bool - for +tt: String - for +line: U32 - for +col: U32 - for +kids: Tree.Node - for +self: String - for +ws: List<&2, Scan.Walk> - for +carried: List<&2, String> - for +lead: String +# unused over binders and lines that read alike +law inert.unused.cong: + for -x1: List<&2, Bind.Bind> + for -x2: List<&2, Bind.Bind> + for -f1: List<&2, U32> + for -f2: List<&2, U32> + for +hh: Unused.Hits + for +uses: List<&2, Bind.Use> for +path: String - {Scan.on_name(hot, open, tt, line, col, D1.inert.node(kids), self, ws, carried, lead, path) - == Scan.on_name(hot, open, tt, line, col, kids, self, ws, carried, lead, path) : List<&2, F.Finding>} - -def inert.scan.on_name(hot, open, tt, line, col, kids, self, ws, carried, lead, path): - +g = Bool.not(Bool.and(hot, Bool.and(open, Bool.not(String.eq(tt, self))))) - +lhs = Scan.on_name(hot, open, tt, line, col, D1.inert.node(kids), self, ws, carried, lead, path) - %inert.scan.found(Scan.slots(tt, ws), Calls.args(kids), carried) : {lhs == Lazy.stop(List<&2, F.Finding>, g, [], - _u => Scan.cite(_, tt, line, col, self, lead, path)) : List<&2, F.Finding>} - %inert.args(kids) : {lhs == Lazy.stop(List<&2, F.Finding>, g, [], - _u => Scan.cite(Scan.found(Scan.slots(tt, ws), _, carried), tt, line, col, self, lead, path)) - : List<&2, F.Finding>} + for e1: {x1 == x2 : List<&2, Bind.Bind>} + for e2: {f1 == f2 : List<&2, U32>} + {inert.unused.go(x1, hh, uses, f1, path) == inert.unused.go(x2, hh, uses, f2, path) : List<&2, F.Finding>} + +def inert.unused.cong(x1, _x2, f1, f2, hh, uses, path, e1, e2): + %e1 : {inert.unused.go(x1, hh, uses, f1, path) == inert.unused.go(_, hh, uses, f2, path) : List<&2, F.Finding>} + %e2 : {inert.unused.go(x1, hh, uses, f1, path) == inert.unused.go(x1, hh, uses, _, path) : List<&2, F.Finding>} {==} -# a call read that way reports what it did: only a name is a callee, and a -# name is never cut -law inert.scan.call_hit: - for kk: Lex.TokKind - for +hot: Bool - for +open: Bool - for +tt: String - for +line: U32 - for +col: U32 - for +kids: Tree.Node - for +self: String - for +ws: List<&2, Scan.Walk> - for +carried: List<&2, String> - for +lead: String - for +path: String - {Scan.call_hit(kk, hot, open, D1.inert.keep(D1.inert.blanks(kk), tt), line, col, D1.inert.node(kids), self, - ws, carried, lead, path) - == Scan.call_hit(kk, hot, open, tt, line, col, kids, self, ws, carried, lead, path) : List<&2, F.Finding>} +def Laws.inert_unused( + path, + _text, + _toks, + tree, + binds, + uses, + _scopes, + _items, + _text2, + _toks2, + tree2, + binds2, + _scopes2, + _items2, + et, + eb +): + +f1 = Unused.foreign(tree) + +f2 = Unused.foreign(tree2) + +ef = Equal.trans(List<&2, U32>, f1, Unused.foreign(D1.inert.node(tree)), f2, + Equal.sym(List<&2, U32>, Unused.foreign(D1.inert.node(tree)), f1, inert.unused.foreign(tree)), + Equal.trans(List<&2, U32>, Unused.foreign(D1.inert.node(tree)), Unused.foreign(D1.inert.node(tree2)), f2, + Equal.cong(Tree.Node, List<&2, U32>, z => Unused.foreign(z), D1.inert.node(tree), D1.inert.node(tree2), et), + inert.unused.foreign(tree2))) + +hh = Unused.index(uses) + Equal.trans(List<&2, F.Finding>, Unused.check.go(binds, hh, uses, f1, path), + inert.unused.go(Laws.inert.ubinds(binds), hh, uses, f1, path), Unused.check.go(binds2, hh, uses, f2, path), + inert.unused.via(binds, hh, uses, f1, path), + Equal.trans(List<&2, F.Finding>, inert.unused.go(Laws.inert.ubinds(binds), hh, uses, f1, path), + inert.unused.go(Laws.inert.ubinds(binds2), hh, uses, f2, path), Unused.check.go(binds2, hh, uses, f2, path), + inert.unused.cong(Laws.inert.ubinds(binds), Laws.inert.ubinds(binds2), f1, f2, hh, uses, path, eb, ef), + Equal.sym(List<&2, F.Finding>, Unused.check.go(binds2, hh, uses, f2, path), + inert.unused.go(Laws.inert.ubinds(binds2), hh, uses, f2, path), inert.unused.via(binds2, hh, uses, f2, path)))) -def inert.scan.call_hit(kk, hot, open, tt, line, col, kids, self, ws, carried, lead, path): - match kk: +# Inert text, hoist (laws/CodeRulesIgnoreCommentsAndStrings.bend): a def's self-calls, its carried +# parameters, the tables it builds and the names it indexes, each read the +# way the lemmas above read a tree. + +# hoist +# ----- + +# a token's share of the width reads a string's kind only, and a comment has +# none +law inert.wrap.cost: + for k: Lex.TokKind + for +t: String + {Wrap.check.cost(k, t) == Wrap.check.cost(k, D1.inert.keep(D1.inert.blanks(k), t)) : U32} + +def inert.wrap.cost(k, _t): + match k: case Lex.TName{}: - inert.scan.on_name(hot, open, tt, line, col, kids, self, ws, carried, lead, path) + {==} case Lex.TUpper{}: {==} case Lex.TDotted{}: - inert.scan.on_name(hot, open, tt, line, col, kids, self, ws, carried, lead, path) + {==} case Lex.TWild{}: {==} case Lex.TKey{}: @@ -13448,368 +7216,86 @@ def inert.scan.call_hit(kk, hot, open, tt, line, col, kids, self, ws, carried, l case Lex.TComma{}: {==} -# a name then a group read that way: its call, the group and the rest walked -# as they were -law inert.scan.walk_lg: - for +k: Lex.TokKind - for +t: String - for +o: String - for +l: U32 - for +c: U32 - for +kids: Tree.Node - for +r2: Tree.Node - for +self: String - for +ws: List<&2, Scan.Walk> - for +carried: List<&2, String> - for +lead: String - for +path: String - for +hot: Bool - for ik: {Scan.walk(D1.inert.node(kids), self, ws, carried, lead, path, Hoist.warm(hot, k)) == Scan.walk(kids, self, - ws, carried, lead, path, Hoist.warm(hot, k)) : List<&2, F.Finding>} - for ir: {Scan.walk(D1.inert.node(r2), self, ws, carried, lead, path, Hoist.warm(hot, k)) == Scan.walk(r2, self, ws, - carried, lead, path, Hoist.warm(hot, k)) : List<&2, F.Finding>} - {List.concat(&2, F.Finding, [Scan.call_hit(k, Hoist.warm(hot, k), String.eq(o, "("), - D1.inert.keep(D1.inert.blanks(k), t), - l, c, D1.inert.node(kids), self, ws, carried, lead, path), Scan.walk(D1.inert.node(kids), self, ws, carried, - lead, path, Hoist.warm(hot, k)), Scan.walk(D1.inert.node(r2), self, ws, carried, lead, path, Hoist.warm(hot, - k))]) - == List.concat(&2, F.Finding, [Scan.call_hit(k, Hoist.warm(hot, k), String.eq(o, "("), t, l, c, kids, self, ws, - carried, lead, - path), Scan.walk(kids, self, ws, carried, lead, path, Hoist.warm(hot, - k)), Scan.walk(r2, self, ws, carried, lead, path, Hoist.warm(hot, k))]) : List<&2, F.Finding>} - -def inert.scan.walk_lg(k, t, o, l, c, kids, r2, self, ws, carried, lead, path, hot, ik, ir): - +w = Hoist.warm(hot, k) - inert.cat3(Scan.call_hit(k, w, String.eq(o, "("), D1.inert.keep(D1.inert.blanks(k), t), l, c, - D1.inert.node(kids), self, - ws, carried, lead, path), Scan.call_hit(k, w, String.eq(o, "("), t, l, c, kids, self, ws, carried, lead, path), - Scan.walk(D1.inert.node(kids), self, ws, carried, lead, path, w), Scan.walk(kids, self, ws, carried, lead, path, - w), - Scan.walk(D1.inert.node(r2), self, ws, carried, lead, path, w), Scan.walk(r2, self, ws, carried, lead, path, w), - inert.scan.call_hit(k, w, String.eq(o, "("), t, l, c, kids, self, ws, carried, lead, path), ik, ir) - -# a case arm read that way: hot as it was, and its pattern, body and the rest -# walked as they were -law inert.scan.walk_case: - for +kids: Tree.Node - for +body: Tree.Node - for +rest: Tree.Node - for +self: String - for +ws: List<&2, Scan.Walk> - for +carried: List<&2, String> - for +lead: String - for +path: String - for +hot: Bool - for +es: {D1.inert.marked(self) == False{} : Bool} - for +eb: {D1.inert.ok(body) == True{} : Bool} - for ik: {Scan.walk(D1.inert.node(kids), self, ws, carried, lead, path, False{}) == Scan.walk(kids, self, ws, - carried, lead, path, False{}) : List<&2, F.Finding>} - for ib: {Scan.walk(D1.inert.node(body), self, ws, carried, lead, path, Bool.and(hot, - Calls.calls(D1.inert.node(body), - self))) == Scan.walk(body, self, ws, carried, lead, path, Bool.and(hot, Calls.calls(D1.inert.node(body), - self))) : List<&2, F.Finding>} - for ir: {Scan.walk(D1.inert.node(rest), self, ws, carried, lead, path, hot) == Scan.walk(rest, self, ws, carried, - lead, path, hot) : List<&2, F.Finding>} - {List.concat(&2, F.Finding, [Scan.walk(D1.inert.node(kids), self, ws, carried, lead, path, False{}), - Scan.walk(D1.inert.node(body), self, ws, carried, lead, path, Bool.and(hot, Calls.calls(D1.inert.node(body), - self))), Scan.walk(D1.inert.node(rest), self, ws, carried, lead, path, hot)]) - == List.concat(&2, F.Finding, [Scan.walk(kids, self, ws, carried, lead, path, False{}), Scan.walk(body, self, ws, - carried, lead, path, Bool.and(hot, Calls.calls(body, - self))), Scan.walk(rest, self, ws, carried, lead, path, hot)]) - : List<&2, F.Finding>} - -def inert.scan.walk_case(kids, body, rest, self, ws, carried, lead, path, hot, es, eb, ik, ib, ir): - +arm = Bool.and(hot, Calls.calls(D1.inert.node(body), self)) - +lhs = List.concat(&2, F.Finding, [Scan.walk(D1.inert.node(kids), self, ws, carried, lead, path, False{}), - Scan.walk(D1.inert.node(body), self, ws, carried, lead, path, arm), Scan.walk(D1.inert.node(rest), self, ws, - carried, lead, path, hot)]) - %inert.calls(body, self, es, eb) : {lhs == List.concat(&2, F.Finding, [Scan.walk(kids, self, ws, carried, lead, path, - False{}), - Scan.walk(body, self, ws, carried, lead, path, Bool.and(hot, _)), Scan.walk(rest, self, ws, carried, lead, path, - hot)]) : List<&2, F.Finding>} - inert.cat3(Scan.walk(D1.inert.node(kids), self, ws, carried, lead, path, False{}), Scan.walk(kids, self, ws, - carried, lead, path, False{}), Scan.walk(D1.inert.node(body), self, ws, carried, lead, path, arm), - Scan.walk(body, self, ws, carried, lead, path, arm), - Scan.walk(D1.inert.node(rest), self, ws, carried, lead, path, hot), Scan.walk(rest, self, ws, carried, lead, - path, hot), ik, ib, ir) - -# scan's walk reads no string: a string is never a callee or a lone name -law inert.scan.walk: - for nn: Tree.Node - for +self: String - for +ws: List<&2, Scan.Walk> - for +carried: List<&2, String> - for +lead: String - for +path: String - for +hot: Bool - for +es: {D1.inert.marked(self) == False{} : Bool} - for +e: {D1.inert.ok(nn) == True{} : Bool} - {Scan.walk(D1.inert.node(nn), self, ws, carried, lead, path, hot) == Scan.walk(nn, self, ws, carried, lead, path, - hot) : List<&2, F.Finding>} +# the width walk reads no comment or string +law inert.wrap.width: + for ts: List<&2, Lex.Tok> + for +acc: U32 + for +upto: U32 + for +seen: Bool + {Wrap.check.width.go(D1.inert.toks(ts), acc, upto, seen) == Wrap.check.width.go(ts, acc, upto, seen) : U32} -def inert.scan.walk(nn, self, ws, carried, lead, path, hot, es, e): - match nn: - case Tree.NCons{h, +rest}: - match h: - case Tree.Leaf{tok}: - match tok: - case Lex.Tok{+k, +t, +l, +c}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{go, +kids, +gcl}: - match go: - case Lex.Tok{+gk, +o, +gl, +gc}: - +lf = {Tree.Leaf{Lex.Tok{k, t, l, c}} : Tree.Node} - +gp = {Tree.Group{Lex.Tok{gk, o, gl, gc}, kids, gcl} : Tree.Node} - +eg = inert.ok_t(lf, Tree.NCons{gp, r2}, e) - inert.scan.walk_lg(k, t, o, l, c, kids, r2, self, ws, carried, lead, path, hot, - inert.scan.walk(kids, self, ws, carried, lead, path, Hoist.warm(hot, k), es, inert.ok_h(gp, - r2, eg)), - inert.scan.walk(r2, self, ws, carried, lead, path, Hoist.warm(hot, k), es, inert.ok_t(gp, - r2, eg))) - case Tree.Leaf{x}: - inert.scan.walk(rest, self, ws, carried, lead, path, Hoist.warm(hot, k), es, - inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.Stmt{x, y, z}: - inert.scan.walk(rest, self, ws, carried, lead, path, Hoist.warm(hot, k), es, - inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.NNil{}: - inert.scan.walk(rest, self, ws, carried, lead, path, Hoist.warm(hot, k), es, - inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.NCons{x, y}: - inert.scan.walk(rest, self, ws, carried, lead, path, Hoist.warm(hot, k), es, - inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.Leaf{x}: - inert.scan.walk(rest, self, ws, carried, lead, path, Hoist.warm(hot, k), es, - inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.Group{x, y, z}: - inert.scan.walk(rest, self, ws, carried, lead, path, Hoist.warm(hot, k), es, - inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.Stmt{x, y, z}: - inert.scan.walk(rest, self, ws, carried, lead, path, Hoist.warm(hot, k), es, - inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.NNil{}: - inert.scan.walk(rest, self, ws, carried, lead, path, Hoist.warm(hot, k), es, - inert.ok_t(Tree.Leaf{Lex.Tok{k, t, l, c}}, rest, e)) - case Tree.Group{+o, +kids, +cl}: - inert.cat2(Scan.walk(D1.inert.node(kids), self, ws, carried, lead, path, hot), Scan.walk(kids, self, ws, - carried, lead, path, hot), Scan.walk(D1.inert.node(rest), self, ws, carried, lead, path, hot), - Scan.walk(rest, self, ws, carried, lead, path, hot), - inert.scan.walk(kids, self, ws, carried, lead, path, hot, es, inert.ok_h(Tree.Group{o, kids, cl}, rest, e)), - inert.scan.walk(rest, self, ws, carried, lead, path, hot, es, inert.ok_t(Tree.Group{o, kids, cl}, rest, e))) - case Tree.Stmt{sk, +kids, +body}: - match sk: - case Tree.SDef{}: - +es2 = M1.inert.and_l(Bool.and(D1.inert.ok(kids), D1.inert.ok(body)), D1.inert.ok(rest), e) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(body), es2) - +eb = M1.inert.and_r(D1.inert.ok(kids), D1.inert.ok(body), es2) - +er = M1.inert.and_r(Bool.and(D1.inert.ok(kids), D1.inert.ok(body)), D1.inert.ok(rest), e) - inert.cat3(Scan.walk(D1.inert.node(kids), self, ws, carried, lead, path, hot), Scan.walk(kids, self, - ws, carried, lead, path, hot), Scan.walk(D1.inert.node(body), self, ws, carried, lead, path, hot), - Scan.walk(body, self, ws, carried, lead, path, hot), - Scan.walk(D1.inert.node(rest), self, ws, carried, lead, path, hot), Scan.walk(rest, self, ws, - carried, lead, path, hot), - inert.scan.walk(kids, self, ws, carried, lead, path, hot, es, ek), inert.scan.walk(body, self, ws, - carried, lead, path, hot, es, eb), inert.scan.walk(rest, self, ws, carried, lead, path, hot, es, - er)) - case Tree.SType{}: - +es2 = M1.inert.and_l(Bool.and(D1.inert.ok(kids), D1.inert.ok(body)), D1.inert.ok(rest), e) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(body), es2) - +eb = M1.inert.and_r(D1.inert.ok(kids), D1.inert.ok(body), es2) - +er = M1.inert.and_r(Bool.and(D1.inert.ok(kids), D1.inert.ok(body)), D1.inert.ok(rest), e) - inert.cat3(Scan.walk(D1.inert.node(kids), self, ws, carried, lead, path, hot), Scan.walk(kids, self, - ws, carried, lead, path, hot), Scan.walk(D1.inert.node(body), self, ws, carried, lead, path, hot), - Scan.walk(body, self, ws, carried, lead, path, hot), - Scan.walk(D1.inert.node(rest), self, ws, carried, lead, path, hot), Scan.walk(rest, self, ws, - carried, lead, path, hot), - inert.scan.walk(kids, self, ws, carried, lead, path, hot, es, ek), inert.scan.walk(body, self, ws, - carried, lead, path, hot, es, eb), inert.scan.walk(rest, self, ws, carried, lead, path, hot, es, - er)) - case Tree.SLaw{}: - +es2 = M1.inert.and_l(Bool.and(D1.inert.ok(kids), D1.inert.ok(body)), D1.inert.ok(rest), e) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(body), es2) - +eb = M1.inert.and_r(D1.inert.ok(kids), D1.inert.ok(body), es2) - +er = M1.inert.and_r(Bool.and(D1.inert.ok(kids), D1.inert.ok(body)), D1.inert.ok(rest), e) - inert.cat3(Scan.walk(D1.inert.node(kids), self, ws, carried, lead, path, hot), Scan.walk(kids, self, - ws, carried, lead, path, hot), Scan.walk(D1.inert.node(body), self, ws, carried, lead, path, hot), - Scan.walk(body, self, ws, carried, lead, path, hot), - Scan.walk(D1.inert.node(rest), self, ws, carried, lead, path, hot), Scan.walk(rest, self, ws, - carried, lead, path, hot), - inert.scan.walk(kids, self, ws, carried, lead, path, hot, es, ek), inert.scan.walk(body, self, ws, - carried, lead, path, hot, es, eb), inert.scan.walk(rest, self, ws, carried, lead, path, hot, es, - er)) - case Tree.SImport{}: - +es2 = M1.inert.and_l(Bool.and(D1.inert.ok(kids), D1.inert.ok(body)), D1.inert.ok(rest), e) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(body), es2) - +eb = M1.inert.and_r(D1.inert.ok(kids), D1.inert.ok(body), es2) - +er = M1.inert.and_r(Bool.and(D1.inert.ok(kids), D1.inert.ok(body)), D1.inert.ok(rest), e) - inert.cat3(Scan.walk(D1.inert.node(kids), self, ws, carried, lead, path, hot), Scan.walk(kids, self, - ws, carried, lead, path, hot), Scan.walk(D1.inert.node(body), self, ws, carried, lead, path, hot), - Scan.walk(body, self, ws, carried, lead, path, hot), - Scan.walk(D1.inert.node(rest), self, ws, carried, lead, path, hot), Scan.walk(rest, self, ws, - carried, lead, path, hot), - inert.scan.walk(kids, self, ws, carried, lead, path, hot, es, ek), inert.scan.walk(body, self, ws, - carried, lead, path, hot, es, eb), inert.scan.walk(rest, self, ws, carried, lead, path, hot, es, - er)) - case Tree.SCase{}: - +es2 = M1.inert.and_l(Bool.and(D1.inert.ok(kids), D1.inert.ok(body)), D1.inert.ok(rest), e) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(body), es2) - +eb = M1.inert.and_r(D1.inert.ok(kids), D1.inert.ok(body), es2) - +er = M1.inert.and_r(Bool.and(D1.inert.ok(kids), D1.inert.ok(body)), D1.inert.ok(rest), e) - inert.scan.walk_case(kids, body, rest, self, ws, carried, lead, path, hot, es, eb, - inert.scan.walk(kids, self, ws, carried, lead, path, False{}, es, ek), - inert.scan.walk(body, self, ws, carried, lead, path, Bool.and(hot, Calls.calls(D1.inert.node(body), - self)), es, eb), - inert.scan.walk(rest, self, ws, carried, lead, path, hot, es, er)) - case Tree.SFor{}: - +es2 = M1.inert.and_l(Bool.and(D1.inert.ok(kids), D1.inert.ok(body)), D1.inert.ok(rest), e) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(body), es2) - +eb = M1.inert.and_r(D1.inert.ok(kids), D1.inert.ok(body), es2) - +er = M1.inert.and_r(Bool.and(D1.inert.ok(kids), D1.inert.ok(body)), D1.inert.ok(rest), e) - inert.cat3(Scan.walk(D1.inert.node(kids), self, ws, carried, lead, path, hot), Scan.walk(kids, self, - ws, carried, lead, path, hot), Scan.walk(D1.inert.node(body), self, ws, carried, lead, path, hot), - Scan.walk(body, self, ws, carried, lead, path, hot), - Scan.walk(D1.inert.node(rest), self, ws, carried, lead, path, hot), Scan.walk(rest, self, ws, - carried, lead, path, hot), - inert.scan.walk(kids, self, ws, carried, lead, path, hot, es, ek), inert.scan.walk(body, self, ws, - carried, lead, path, hot, es, eb), inert.scan.walk(rest, self, ws, carried, lead, path, hot, es, - er)) - case Tree.SLet{}: - +es2 = M1.inert.and_l(Bool.and(D1.inert.ok(kids), D1.inert.ok(body)), D1.inert.ok(rest), e) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(body), es2) - +eb = M1.inert.and_r(D1.inert.ok(kids), D1.inert.ok(body), es2) - +er = M1.inert.and_r(Bool.and(D1.inert.ok(kids), D1.inert.ok(body)), D1.inert.ok(rest), e) - inert.cat3(Scan.walk(D1.inert.node(kids), self, ws, carried, lead, path, hot), Scan.walk(kids, self, - ws, carried, lead, path, hot), Scan.walk(D1.inert.node(body), self, ws, carried, lead, path, hot), - Scan.walk(body, self, ws, carried, lead, path, hot), - Scan.walk(D1.inert.node(rest), self, ws, carried, lead, path, hot), Scan.walk(rest, self, ws, - carried, lead, path, hot), - inert.scan.walk(kids, self, ws, carried, lead, path, hot, es, ek), inert.scan.walk(body, self, ws, - carried, lead, path, hot, es, eb), inert.scan.walk(rest, self, ws, carried, lead, path, hot, es, - er)) - case Tree.STerm{}: - +es2 = M1.inert.and_l(Bool.and(D1.inert.ok(kids), D1.inert.ok(body)), D1.inert.ok(rest), e) - +ek = M1.inert.and_l(D1.inert.ok(kids), D1.inert.ok(body), es2) - +eb = M1.inert.and_r(D1.inert.ok(kids), D1.inert.ok(body), es2) - +er = M1.inert.and_r(Bool.and(D1.inert.ok(kids), D1.inert.ok(body)), D1.inert.ok(rest), e) - inert.cat3(Scan.walk(D1.inert.node(kids), self, ws, carried, lead, path, hot), Scan.walk(kids, self, - ws, carried, lead, path, hot), Scan.walk(D1.inert.node(body), self, ws, carried, lead, path, hot), - Scan.walk(body, self, ws, carried, lead, path, hot), - Scan.walk(D1.inert.node(rest), self, ws, carried, lead, path, hot), Scan.walk(rest, self, ws, - carried, lead, path, hot), - inert.scan.walk(kids, self, ws, carried, lead, path, hot, es, ek), inert.scan.walk(body, self, ws, - carried, lead, path, hot, es, eb), inert.scan.walk(rest, self, ws, carried, lead, path, hot, es, - er)) - case Tree.NNil{}: - inert.scan.walk(rest, self, ws, carried, lead, path, hot, es, e) - case Tree.NCons{x, y}: - inert.scan.walk(rest, self, ws, carried, lead, path, hot, es, inert.ok_t(Tree.NCons{x, y}, rest, e)) - case Tree.Leaf{x}: - {==} - case Tree.Group{x, y, z}: - {==} - case Tree.Stmt{x, y, z}: - {==} - case Tree.NNil{}: +def inert.wrap.width(ts, acc, upto, seen): + match ts: + case Nil{}: {==} + case Con{Lex.Tok{+k, +t, l, c}, +rest}: + +nx = Wrap.check.add(acc, k, t) + +co = Wrap.check.is_colon(k) + %inert.wrap.cost(k, t) : {Wrap.check.width.go(D1.inert.toks(rest), (acc + _ : U32), + Bool.pick(U32, co, (acc + _ : U32), upto), Bool.or(co, seen)) + == Wrap.check.width.go(rest, nx, Bool.pick(U32, co, nx, upto), Bool.or(co, seen)) : U32} + inert.wrap.width(rest, nx, Bool.pick(U32, co, nx, upto), Bool.or(co, seen)) -# three lists of numbers that read alike join alike -law scan.ncat3: - for -a1: List<&2, Nat> - for -a2: List<&2, Nat> - for -b1: List<&2, Nat> - for -b2: List<&2, Nat> - for -c1: List<&2, Nat> - for -c2: List<&2, Nat> - for ea: {a1 == a2 : List<&2, Nat>} - for eb: {b1 == b2 : List<&2, Nat>} - for ec: {c1 == c2 : List<&2, Nat>} - {List.concat(&2, Nat, [a1, b1, c1]) == List.concat(&2, Nat, [a2, b2, c2]) : List<&2, Nat>} - -def scan.ncat3(a1, _a2, b1, b2, c1, c2, ea, eb, ec): - %ea : {List.concat(&2, Nat, [a1, b1, c1]) == List.concat(&2, Nat, [_, b2, c2]) : List<&2, Nat>} - %eb : {List.concat(&2, Nat, [a1, b1, c1]) == List.concat(&2, Nat, [a1, _, c2]) : List<&2, Nat>} - %ec : {List.concat(&2, Nat, [a1, b1, c1]) == List.concat(&2, Nat, [a1, b1, _]) : List<&2, Nat>} - {==} - -# two lists of numbers that read alike join alike -law scan.ncat2: - for -a1: List<&2, Nat> - for -a2: List<&2, Nat> - for -b1: List<&2, Nat> - for -b2: List<&2, Nat> - for ea: {a1 == a2 : List<&2, Nat>} - for eb: {b1 == b2 : List<&2, Nat>} - {List.concat(&2, Nat, [a1, b1]) == List.concat(&2, Nat, [a2, b2]) : List<&2, Nat>} - -def scan.ncat2(a1, _a2, b1, b2, ea, eb): - %ea : {List.concat(&2, Nat, [a1, b1]) == List.concat(&2, Nat, [_, b2]) : List<&2, Nat>} - %eb : {List.concat(&2, Nat, [a1, b1]) == List.concat(&2, Nat, [a1, _]) : List<&2, Nat>} - {==} - -# the parameters at walked arguments read that way are the ones they were -law inert.scan.params_at: - for ss: List<&2, Nat> - for +as: List<&2, Tree.Node> - for +ps: List<&2, String> - {Scan.params_at(ss, inert.nodes(as), ps) == Scan.params_at(ss, as, ps) : List<&2, Nat>} +# the line test reads kinds only +law inert.wrap.broken: + for ts: List<&2, Lex.Tok> + {Wrap.check.broken(D1.inert.toks(ts)) == Wrap.check.broken(ts) : Bool} -def inert.scan.params_at(ss, as, ps): - match ss: +def inert.wrap.broken(ts): + match ts: case Nil{}: {==} - case Con{+at, +rest}: - %inert.scan.arg_name(as, at) : {List.append(&2, Nat, - Scan.opt(Scan.index_in(Scan.name_of(Calls.arg(inert.nodes(as), - at)), ps)), Scan.params_at(rest, inert.nodes(as), ps)) == List.append(&2, Nat, Scan.opt(Scan.index_in(_, ps)), - Scan.params_at(rest, as, ps)) : List<&2, Nat>} - %inert.scan.params_at(rest, as, ps) : {List.append(&2, Nat, Scan.opt(Scan.index_in(Scan.name_of(Calls.arg( - inert.nodes(as), at)), ps)), Scan.params_at(rest, inert.nodes(as), ps)) == List.append(&2, Nat, - Scan.opt(Scan.index_in(Scan.name_of(Calls.arg(inert.nodes(as), at)), ps)), _) : List<&2, Nat>} + case Con{Lex.Tok{+k, t, l, c}, +rest}: + %inert.wrap.broken(rest) : {Lazy.or_else(Lex.is_nl(k), _u => Wrap.check.broken(D1.inert.toks(rest))) + == Lazy.or_else(Lex.is_nl(k), _u => _) : Bool} {==} -# a named call's walked parameters, its arguments read that way -law inert.scan.pass_name: - for +open: Bool - for +tt: String - for +kids: Tree.Node - for +ps: List<&2, String> - for +ws: List<&2, Scan.Walk> - {Lazy.stop(List<&2, Nat>, Bool.not(open), [], _u => Scan.params_at(Scan.slots(tt, ws), - Calls.args(D1.inert.node(kids)), ps)) - == Lazy.stop(List<&2, Nat>, Bool.not(open), [], _u => Scan.params_at(Scan.slots(tt, ws), Calls.args(kids), - ps)) : List<&2, Nat>} - -def inert.scan.pass_name(open, tt, kids, ps, ws): - +lhs = Lazy.stop(List<&2, Nat>, Bool.not(open), [], _u => Scan.params_at(Scan.slots(tt, ws), - Calls.args(D1.inert.node(kids)), ps)) - %inert.scan.params_at(Scan.slots(tt, ws), Calls.args(kids), ps) : {lhs == Lazy.stop(List<&2, Nat>, Bool.not(open), [], - _u => _) : List<&2, Nat>} - %inert.args(kids) : {lhs == Lazy.stop(List<&2, Nat>, Bool.not(open), [], _u => Scan.params_at(Scan.slots(tt, ws), _, - ps)) - : List<&2, Nat>} - {==} +# a string moves nothing, whatever the walk's phase: its text is read only +# for the angles a `>` run closes, and a string is no such run +law inert.wrap.str: + for +wk: Wrap.Walk + for +t: String + for +l: U32 + for +c: U32 + for glued: Bool + {Wrap.check.step_hit(wk, Lex.Tok{Lex.TStr{}, D1.inert.cut(t), l, c}, glued) + == Wrap.check.step_hit(wk, Lex.Tok{Lex.TStr{}, t, l, c}, glued) : Wrap.Walk} -# a call read that way passes what it passed: only a name is a callee -law inert.scan.pass_hit: - for kk: Lex.TokKind - for +open: Bool - for +tt: String - for +kids: Tree.Node - for +ps: List<&2, String> - for +ws: List<&2, Scan.Walk> - {Scan.pass_hit(kk, open, D1.inert.keep(D1.inert.blanks(kk), tt), D1.inert.node(kids), ps, ws) - == Scan.pass_hit(kk, open, tt, kids, ps, ws) : List<&2, Nat>} +def inert.wrap.str(wk, _t, _l, _c, _glued): + match wk: + case Wrap.Walk{mode, paren, bracket, brace, angle, shut, open, on, pline, pend, pcol, spans}: + match mode: + case Wrap.Seek{}: + {==} + case Wrap.Args{}: + {==} + case Wrap.Past{}: + {==} + case Wrap.Shut{}: + {==} + case Wrap.Loose{}: + {==} -def inert.scan.pass_hit(kk, open, tt, kids, ps, ws): - match kk: +# one token of the header moves the walk as it did: a comment moves nothing, +# a string as above, and any other token keeps its text +law inert.wrap.step: + for k: Lex.TokKind + for +t: String + for +l: U32 + for +c: U32 + for +wk: Wrap.Walk + for glued: Bool + {Wrap.check.step(D1.inert.tok(Lex.Tok{k, t, l, c}), wk, glued) == Wrap.check.step(Lex.Tok{k, t, l, c}, wk, glued) + : Wrap.Walk} + +def inert.wrap.step(k, t, l, c, wk, glued): + match k: case Lex.TName{}: - inert.scan.pass_name(open, tt, kids, ps, ws) + {==} case Lex.TUpper{}: {==} case Lex.TDotted{}: - inert.scan.pass_name(open, tt, kids, ps, ws) + {==} case Lex.TWild{}: {==} case Lex.TKey{}: @@ -13817,7 +7303,7 @@ def inert.scan.pass_hit(kk, open, tt, kids, ps, ws): case Lex.TNum{}: {==} case Lex.TStr{}: - {==} + inert.wrap.str(wk, t, l, c, glued) case Lex.TChar{}: {==} case Lex.TComment{}: @@ -13849,280 +7335,126 @@ def inert.scan.pass_hit(kk, open, tt, kids, ps, ws): case Lex.TComma{}: {==} -# what a body read that way passes is what it passed -law inert.scan.passes: - for nn: Tree.Node - for +ps: List<&2, String> - for +ws: List<&2, Scan.Walk> - {Scan.passes(D1.inert.node(nn), ps, ws) == Scan.passes(nn, ps, ws) : List<&2, Nat>} - -def inert.scan.passes(nn, ps, ws): - match nn: - case Tree.NCons{h, +rest}: - match h: - case Tree.Leaf{tok}: - match tok: - case Lex.Tok{+k, +t, +l, +c}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{go, +kids, +gcl}: - match go: - case Lex.Tok{+gk, +o, +gl, +gc}: - scan.ncat3(Scan.pass_hit(k, String.eq(o, "("), D1.inert.keep(D1.inert.blanks(k), t), - D1.inert.node(kids), ps, ws), Scan.pass_hit(k, String.eq(o, "("), t, kids, ps, ws), - Scan.passes(D1.inert.node(kids), ps, ws), Scan.passes(kids, ps, ws), - Scan.passes(D1.inert.node(r2), ps, ws), Scan.passes(r2, ps, ws), - inert.scan.pass_hit(k, String.eq(o, "("), t, kids, ps, ws), - inert.scan.passes(kids, ps, ws), inert.scan.passes(r2, ps, ws)) - case Tree.Leaf{x}: - inert.scan.passes(rest, ps, ws) - case Tree.Stmt{x, y, z}: - inert.scan.passes(rest, ps, ws) - case Tree.NNil{}: - inert.scan.passes(rest, ps, ws) - case Tree.NCons{x, y}: - inert.scan.passes(rest, ps, ws) - case Tree.Leaf{x}: - inert.scan.passes(rest, ps, ws) - case Tree.Group{x, y, z}: - inert.scan.passes(rest, ps, ws) - case Tree.Stmt{x, y, z}: - inert.scan.passes(rest, ps, ws) - case Tree.NNil{}: - inert.scan.passes(rest, ps, ws) - case Tree.Group{o, +kids, cl}: - scan.ncat2(Scan.passes(D1.inert.node(kids), ps, ws), Scan.passes(kids, ps, ws), - Scan.passes(D1.inert.node(rest), ps, ws), - Scan.passes(rest, ps, ws), inert.scan.passes(kids, ps, ws), inert.scan.passes(rest, ps, ws)) - case Tree.Stmt{sk, +kids, +body}: - scan.ncat3(Scan.passes(D1.inert.node(kids), ps, ws), Scan.passes(kids, ps, ws), - Scan.passes(D1.inert.node(body), ps, ws), - Scan.passes(body, ps, ws), Scan.passes(D1.inert.node(rest), ps, ws), Scan.passes(rest, ps, ws), - inert.scan.passes(kids, ps, ws), inert.scan.passes(body, ps, ws), inert.scan.passes(rest, ps, ws)) - case Tree.NNil{}: - inert.scan.passes(rest, ps, ws) - case Tree.NCons{x, y}: - inert.scan.passes(rest, ps, ws) - case Tree.Leaf{x}: - {==} - case Tree.Group{x, y, z}: - {==} - case Tree.Stmt{x, y, z}: - {==} - case Tree.NNil{}: - {==} - -# a parameter read that way moves as it did -law inert.scan.moves: - for +pp: Tree.Node - for +carried: List<&2, String> - {Scan.moves(D1.inert.node(pp), carried) == Scan.moves(pp, carried) : Bool} - -def inert.scan.moves(pp, carried): - +lhs = Scan.moves(D1.inert.node(pp), carried) - %inert.is_live(pp) : {lhs == Bool.and(_, Bool.not(List.contains(~String, ~String.eq, carried, Calls.param_name(pp)))) - : Bool} - %inert.param_name(pp) : {lhs == Bool.and(Calls.is_live(D1.inert.node(pp)), Bool.not(List.contains(~String, - ~String.eq, - carried, _))) : Bool} - {==} - -# a parameter read that way is a count or a text as it was -law inert.scan.place: - for +pp: Tree.Node - for +ii: Nat - {Scan.place(D1.inert.node(pp), ii) == Scan.place(pp, ii) : Maybe<&2, Nat>} - -def inert.scan.place(pp, ii): - %inert.type_head(pp, False{}) : {Scan.place(D1.inert.node(pp), ii) == Bool.pick(Maybe<&2, Nat>, - Bool.or(String.eq(_, "Nat"), String.eq(_, "String")), None{}, Some{ii}) : Maybe<&2, Nat>} - {==} - -# the parameter shrunk, among parameters read that way, sits where it sat -law inert.scan.own.go: - for ps: List<&2, Tree.Node> - for +carried: List<&2, String> - for +ii: Nat - {Scan.own.go(inert.nodes(ps), carried, ii) == Scan.own.go(ps, carried, ii) : Maybe<&2, Nat>} +# the shape walk reads no comment or string +law inert.wrap.walk: + for ts: List<&2, Lex.Tok> + for +wk: Wrap.Walk + for +glued: Bool + {Wrap.check.walk(D1.inert.toks(ts), wk, glued) == Wrap.check.walk(ts, wk, glued) : Wrap.Walk} -def inert.scan.own.go(ps, carried, ii): - match ps: +def inert.wrap.walk(ts, wk, glued): + match ts: case Nil{}: {==} - case Con{+p, +rest}: - +lhs = Scan.own.go(inert.nodes(p <> rest), carried, ii) - %inert.scan.moves(p, carried) : {lhs == Lazy.stop(Maybe<&2, Nat>, _, Scan.place(p, ii), - _u => Scan.own.go(rest, carried, 1n+ii)) : Maybe<&2, Nat>} - %inert.scan.place(p, ii) : {lhs == Lazy.stop(Maybe<&2, Nat>, Scan.moves(D1.inert.node(p), carried), _, - _u => Scan.own.go(rest, carried, 1n+ii)) : Maybe<&2, Nat>} - %inert.scan.own.go(rest, carried, 1n+ii) : {lhs == Lazy.stop(Maybe<&2, Nat>, Scan.moves(D1.inert.node(p), - carried), Scan.place(D1.inert.node(p), ii), _u => _) : Maybe<&2, Nat>} - {==} + case Con{Lex.Tok{+k, +t, +l, +c}, +rest}: + +s1 = Wrap.check.step(D1.inert.tok(Lex.Tok{k, t, l, c}), wk, glued) + +s2 = Wrap.check.step(Lex.Tok{k, t, l, c}, wk, glued) + Equal.trans(Wrap.Walk, Wrap.check.walk(D1.inert.toks(rest), s1, Lex.is_name(k)), + Wrap.check.walk(rest, s1, Lex.is_name(k)), Wrap.check.walk(rest, s2, Lex.is_name(k)), + inert.wrap.walk(rest, s1, Lex.is_name(k)), + Equal.cong(Wrap.Walk, Wrap.Walk, w => Wrap.check.walk(rest, w, Lex.is_name(k)), s1, s2, + inert.wrap.step(k, t, l, c, wk, glued))) -# the name of the parameter shrunk, among parameters read that way, is the one it was -law inert.scan.lead.go: - for ps: List<&2, Tree.Node> - for +carried: List<&2, String> - {Scan.lead.go(inert.nodes(ps), carried) == Scan.lead.go(ps, carried) : String} +# a header, broken or not, is judged alike with its comments and strings cut +law inert.wrap.header: + for b: Bool + for +ts: List<&2, Lex.Tok> + for +line: U32 + for +path: String + for +more: List<&2, F.Finding> + {Wrap.check.header(b, line, D1.inert.toks(ts), path, more) == Wrap.check.header(b, line, ts, path, more) + : List<&2, F.Finding>} -def inert.scan.lead.go(ps, carried): - match ps: - case Nil{}: - {==} - case Con{+p, +rest}: - +lhs = Scan.lead.go(inert.nodes(p <> rest), carried) - %inert.scan.moves(p, carried) : {lhs == Lazy.stop(String, _, Calls.param_name(p), _u => Scan.lead.go(rest, - carried)) : String} - %inert.param_name(p) : {lhs == Lazy.stop(String, Scan.moves(D1.inert.node(p), carried), _, - _u => Scan.lead.go(rest, - carried)) : String} - %inert.scan.lead.go(rest, carried) : {lhs == Lazy.stop(String, Scan.moves(D1.inert.node(p), carried), - Calls.param_name(D1.inert.node(p)), _u => _) : String} - {==} +def inert.wrap.header(b, ts, line, path, more): + match b: + case True{}: + Equal.cong(Wrap.Walk, List<&2, F.Finding>, wk => Wrap.check.place(Wrap.check.jam_span(wk), + Wrap.check.empty_span(wk), Wrap.check.shut_span(wk), Wrap.check.loose_span(wk), line, path, more), + Wrap.check.walk(D1.inert.toks(ts), Wrap.check.start(), False{}), + Wrap.check.walk(ts, Wrap.check.start(), False{}), inert.wrap.walk(ts, Wrap.check.start(), False{})) + case False{}: + Equal.cong(U32, List<&2, F.Finding>, ww => Lazy.stop(List<&2, F.Finding>, U32.is_gt(ww, 120), + F.Finding{path, line, 120, 0, "wrap", + "This def header is longer than 120 characters; wrap it with one parameter per line."} <> more, + _u => more), Wrap.check.width.go(D1.inert.toks(ts), 0, 0, False{}), Wrap.check.width.go(ts, 0, 0, False{}), + inert.wrap.width(ts, 0, 0, False{})) -# a signature read that way has its parameters read that way -law inert.scan.params: - for +sig: Tree.Node - {Calls.params(D1.inert.node(sig)) == inert.nodes(Calls.params(sig)) : List<&2, Tree.Node>} +# a def header's tokens are judged alike with their comments and strings cut +law inert.wrap.lexed: + for +ts: List<&2, Lex.Tok> + for +line: U32 + for +path: String + for +more: List<&2, F.Finding> + {Wrap.check.lexed(line, D1.inert.toks(ts), path, more) == Wrap.check.lexed(line, ts, path, more) + : List<&2, F.Finding>} -def inert.scan.params(sig): - %Equal.sym(Tree.Node, Calls.params.of(D1.inert.node(sig)), D1.inert.node(Calls.params.of(sig)), - inert.params.of(sig)) : {Calls.args(_) == inert.nodes(Calls.params(sig)) : List<&2, Tree.Node>} - inert.args(Calls.params.of(sig)) +def inert.wrap.lexed(ts, line, path, more): + %Equal.sym(Bool, Wrap.check.broken(D1.inert.toks(ts)), Wrap.check.broken(ts), inert.wrap.broken(ts)) : + {Wrap.check.header(_, line, D1.inert.toks(ts), path, more) + == Wrap.check.header(Wrap.check.broken(ts), line, ts, path, more) : List<&2, F.Finding>} + inert.wrap.header(Wrap.check.broken(ts), ts, line, path, more) -# the parameter a def read that way shrinks is the one it shrank -law inert.scan.own: - for +sig: Tree.Node - for +body: Tree.Node - for +name: String - for +en: {D1.inert.marked(name) == False{} : Bool} - for +eb: {D1.inert.ok(body) == True{} : Bool} - {Scan.own(D1.inert.node(sig), D1.inert.node(body), name) == Scan.own(sig, body, name) : Maybe<&2, Nat>} - -def inert.scan.own(sig, body, name, en, eb): - +isg = D1.inert.node(sig) - +ib = D1.inert.node(body) - +lhs = Scan.own(isg, ib, name) - %inert.calls(body, name, en, eb) : {lhs == Lazy.stop(Maybe<&2, Nat>, Bool.not(_), None{}, - _u => Scan.own.go(Calls.params(sig), Hoist.carried(sig, body, name), 0n)) : Maybe<&2, Nat>} - %inert.hoist.carried(sig, body, name, en, eb) : {lhs == Lazy.stop(Maybe<&2, Nat>, Bool.not(Calls.calls(ib, name)), - None{}, _u => Scan.own.go(Calls.params(sig), _, 0n)) : Maybe<&2, Nat>} - %inert.scan.own.go(Calls.params(sig), Hoist.carried(isg, ib, name), 0n) : {lhs == Lazy.stop(Maybe<&2, Nat>, - Bool.not(Calls.calls(ib, name)), None{}, _u => _) : Maybe<&2, Nat>} - %inert.scan.params(sig) : {lhs == Lazy.stop(Maybe<&2, Nat>, Bool.not(Calls.calls(ib, name)), None{}, - _u => Scan.own.go(_, Hoist.carried(isg, ib, name), 0n)) : Maybe<&2, Nat>} - {==} +# one item as wrap reads it: its kind and line, and its header's tokens +def inert.wrap.head( + it: Outline.Item, + ts: List<&2, Lex.Tok>, + +path: String, + +more: List<&2, F.Finding> +) -> List<&2, F.Finding>: + match it: + case Outline.Item{kk, _n, +line, _s, _d, _p}: + Lazy.stop(List<&2, F.Finding>, Bool.not(Outline.is_def(kk)), more, _u => Wrap.check.lexed(line, ts, path, more)) -# a def read that way records the walks it recorded -law inert.scan.walks.go: - for ds: List<&2, Calls.Def> - for +ws: List<&2, Scan.Walk> - for +e: {inert.dok(ds) == True{} : Bool} - {Scan.walks.go(inert.defs(ds), ws) == Scan.walks.go(ds, ws) : List<&2, Scan.Walk>} +# wrap over the items read that way: each item with its header's tokens +def inert.wrap.go( + hs: List<&2, Outline.Item>, + ss: List<&2, List<&2, Lex.Tok>>, + +path: String +) -> List<&2, F.Finding>: + match hs ss: + case Nil{} ss: + Nil{} + case Con{h, t} Nil{}: + Nil{} + case Con{h, t} Con{ts, st}: + inert.wrap.head(h, ts, path, inert.wrap.go(t, st, path)) -def inert.scan.walks.go(ds, ws, e): - match ds: - case Nil{}: - {==} - case Con{Calls.Def{+name, +sig, +body}, +rest}: - +en = inert.dok.head(name, sig, body, rest, e) - +eb = inert.dok.body(name, sig, body, rest, e) - +isg = D1.inert.node(sig) - +ib = D1.inert.node(body) - +lhs = Scan.walks.go(inert.defs(Calls.Def{name, sig, body} <> rest), ws) - %inert.scan.own(sig, body, name, en, eb) : {lhs == Scan.walks.go(rest, Scan.tag(List.append(&2, Nat, - Scan.opt(_), Scan.passes(body, Calls.names(sig), ws)), name, ws)) : List<&2, Scan.Walk>} - %inert.scan.passes(body, Calls.names(sig), ws) : {lhs == Scan.walks.go(rest, Scan.tag(List.append(&2, Nat, - Scan.opt(Scan.own(isg, ib, name)), _), name, ws)) : List<&2, Scan.Walk>} - %inert.names(sig) : {lhs == Scan.walks.go(rest, Scan.tag(List.append(&2, Nat, - Scan.opt(Scan.own(isg, ib, name)), Scan.passes(ib, _, ws)), name, ws)) : List<&2, Scan.Walk>} - inert.scan.walks.go(rest, Scan.tag(List.append(&2, Nat, Scan.opt(Scan.own(isg, ib, name)), - Scan.passes(ib, Calls.names(isg), ws)), name, ws), inert.dok.rest(name, sig, body, rest, e)) - -# a def read that way is walked as it was -law inert.scan.run: - for +sig: Tree.Node - for +body: Tree.Node - for +name: String - for +ws: List<&2, Scan.Walk> +# wrap's walk over the items is its walk over them read that way +law inert.wrap.items: + for items: List<&2, Outline.Item> for +path: String - for +en: {D1.inert.marked(name) == False{} : Bool} - for +eb: {D1.inert.ok(body) == True{} : Bool} - {Scan.run(D1.inert.node(sig), D1.inert.node(body), name, ws, path) == Scan.run(sig, body, name, ws, - path) : List<&2, F.Finding>} - -def inert.scan.run(sig, body, name, ws, path, en, eb): - +isg = D1.inert.node(sig) - +ib = D1.inert.node(body) - +cr = Hoist.carried(isg, ib, name) - +lhs = Scan.run(isg, ib, name, ws, path) - %inert.hoist.carried(sig, body, name, en, eb) : {lhs == Scan.walk(body, name, ws, _, Scan.lead.go(Calls.params(sig), - _), path, True{}) : List<&2, F.Finding>} - %inert.scan.lead.go(Calls.params(sig), cr) : {lhs == Scan.walk(body, name, ws, cr, _, path, True{}) - : List<&2, F.Finding>} - %inert.scan.params(sig) : {lhs == Scan.walk(body, name, ws, cr, Scan.lead.go(_, cr), path, True{}) + {Wrap.check.go(items, path) == inert.wrap.go(Laws.inert.items(items), Laws.inert.sigs(items), path) : List<&2, F.Finding>} - inert.scan.walk(body, name, ws, cr, Scan.lead.go(Calls.params(isg), cr), path, True{}, en, eb) - -# scan over the defs read that way reports what it reports over the defs -law inert.scan.go: - for ds: List<&2, Calls.Def> - for +ws: List<&2, Scan.Walk> - for +path: String - for +acc: List<&2, List<&2, F.Finding>> - for +e: {inert.dok(ds) == True{} : Bool} - {Scan.check.go(inert.defs(ds), ws, path, acc) == Scan.check.go(ds, ws, path, acc) : List<&2, F.Finding>} -def inert.scan.go(ds, ws, path, acc, e): - match ds: +def inert.wrap.items(items, path): + match items: case Nil{}: {==} - case Con{Calls.Def{+name, +sig, +body}, +rest}: - +en = inert.dok.head(name, sig, body, rest, e) - +eb = inert.dok.body(name, sig, body, rest, e) - +ib = D1.inert.node(body) - +isg = D1.inert.node(sig) - +lst = Lazy.stop(List<&2, F.Finding>, Bool.not(Bool.and(Calls.calls(ib, name), Bool.not(Calls.exempt(path, - isg)))), [], _u => Scan.run(isg, ib, name, ws, path)) - %inert.stop(Bool.not(Bool.and(Calls.calls(ib, name), Bool.not(Calls.exempt(path, isg)))), - Bool.not(Bool.and(Calls.calls(body, name), Bool.not(Calls.exempt(path, sig)))), - Scan.run(isg, ib, name, ws, path), Scan.run(sig, body, name, ws, path), - inert.gate(path, name, sig, body, en, eb), inert.scan.run(sig, body, name, ws, path, en, eb)) : - {Scan.check.go(inert.defs(rest), ws, path, lst <> acc) == Scan.check.go(rest, ws, path, _ <> acc) - : List<&2, F.Finding>} - inert.scan.go(rest, ws, path, lst <> acc, inert.dok.rest(name, sig, body, rest, e)) - -# scan over the defs read that way, their walks read that way too -law inert.scan.on: - for +ds: List<&2, Calls.Def> - for +path: String - for +e: {inert.dok(ds) == True{} : Bool} - {Scan.check.on(inert.defs(ds), path) == Scan.check.on(ds, path) : List<&2, F.Finding>} + case Con{Outline.Item{+kk, n, +line, +sig, d, p}, +rest}: + +more = Wrap.check.go(rest, path) + +ts = Lex.tokens(sig) + %inert.wrap.items(rest, path) : {Lazy.stop(List<&2, F.Finding>, Bool.not(Outline.is_def(kk)), more, + _u => Wrap.check.lexed(line, ts, path, more)) + == Lazy.stop(List<&2, F.Finding>, Bool.not(Outline.is_def(kk)), _, + _u => Wrap.check.lexed(line, D1.inert.toks(ts), path, _)) : List<&2, F.Finding>} + %Equal.sym(List<&2, F.Finding>, Wrap.check.lexed(line, D1.inert.toks(ts), path, more), + Wrap.check.lexed(line, ts, path, more), inert.wrap.lexed(ts, line, path, more)) : + {Lazy.stop(List<&2, F.Finding>, Bool.not(Outline.is_def(kk)), more, + _u => Wrap.check.lexed(line, ts, path, more)) + == Lazy.stop(List<&2, F.Finding>, Bool.not(Outline.is_def(kk)), more, _u => _) : List<&2, F.Finding>} + {==} -def inert.scan.on(ds, path, e): - %inert.scan.walks.go(ds, [], e) : {Scan.check.go(inert.defs(ds), Scan.walks(inert.defs(ds)), path, []) - == Scan.check.go(ds, _, path, []) : List<&2, F.Finding>} - inert.scan.go(ds, Scan.walks(inert.defs(ds)), path, [], e) +def Laws.inert_wrap(path, _text, _toks, _tree, _bound, items, _text2, _toks2, _tree2, _bound2, items2, ei, es): + +i1 = Laws.inert.items(items) + +s1 = Laws.inert.sigs(items) + %Equal.sym(List<&2, F.Finding>, Wrap.check.go(items, path), inert.wrap.go(i1, s1, path), + inert.wrap.items(items, path)) : {_ == Wrap.check.go(items2, path) : List<&2, F.Finding>} + %Equal.sym(List<&2, F.Finding>, Wrap.check.go(items2, path), + inert.wrap.go(Laws.inert.items(items2), Laws.inert.sigs(items2), path), inert.wrap.items(items2, path)) : + {inert.wrap.go(i1, s1, path) == _ : List<&2, F.Finding>} + %ei : {inert.wrap.go(i1, s1, path) == inert.wrap.go(_, Laws.inert.sigs(items2), path) : List<&2, F.Finding>} + %es : {inert.wrap.go(i1, s1, path) == inert.wrap.go(i1, _, path) : List<&2, F.Finding>} + {==} -# the rule's law-file gate over the defs read that way -law inert.scan.gated: - for +ds: List<&2, Calls.Def> - for +path: String - for +e: {inert.dok(ds) == True{} : Bool} - {Lazy.stop(List<&2, F.Finding>, Paths.is_law_file(path), [], _u => Scan.check.on(inert.defs(ds), path)) - == Lazy.stop(List<&2, F.Finding>, Paths.is_law_file(path), [], _v => Scan.check.on(ds, path)) - : List<&2, F.Finding>} - -def inert.scan.gated(ds, path, e): - inert.stop(Paths.is_law_file(path), Paths.is_law_file(path), Scan.check.on(inert.defs(ds), path), - Scan.check.on(ds, path), {==}, inert.scan.on(ds, path, e)) - -def Laws.inert_scan(path, _text, tree, _toks, _bound, _items, _text2, tree2, _toks2, _bound2, _items2, ok, ok2, e): - M1.inert.via(Tree.Node, z => Lazy.stop(List<&2, F.Finding>, Paths.is_law_file(path), [], - _u => Scan.check.on(Calls.defs(z), path)), tree, tree2, D1.inert.node(tree), D1.inert.node(tree2), - inert.lift(z => Lazy.stop(List<&2, F.Finding>, Paths.is_law_file(path), [], _u => Scan.check.on(z, path)), tree, - inert.scan.gated(Calls.defs(tree), path, inert.dok.of(tree, ok))), - inert.lift(z => Lazy.stop(List<&2, F.Finding>, Paths.is_law_file(path), [], _u => Scan.check.on(z, path)), tree2, - inert.scan.gated(Calls.defs(tree2), path, inert.dok.of(tree2, ok2))), - e) +# scan reads no string: a string is never a callee or a lone name, so each +# walk and each call reads the same over a tree read that way diff --git a/proofs/ExemptPathsYieldNoFindings.bend b/proofs/ExemptPathsYieldNoFindings.bend index 056df54..ad84162 100644 --- a/proofs/ExemptPathsYieldNoFindings.bend +++ b/proofs/ExemptPathsYieldNoFindings.bend @@ -4,7 +4,6 @@ import ../src/finding.bend as F import ../src/paths.bend as Paths import ../src/lazy/lazy.bend as Lazy import ../src/rules/calls.bend as Calls -import ../src/rules/suspicious/scan.bend as Scan import ../laws/ExemptPathsYieldNoFindings.bend as Laws import ./lemmas/LawFileExemptions.bend as M1 @@ -32,9 +31,3 @@ def Laws.untyped_exempt(path, sig, e): # scan # ---- - -def Laws.scan_exempt(path, _text, _toks, tree, _bound, _items, e): - %Equal.sym(Bool, Paths.is_law_file(path), True{}, e) : {Lazy.stop(List<&2, F.Finding>, _, [], - _u => Scan.check.on(Calls.defs(tree), path)) == Nil{} - : List<&2, F.Finding>} - {==} diff --git a/proofs/ForeignReportsMissingBackend.bend b/proofs/ForeignReportsMissingBackend.bend index 33f8419..b59a102 100644 --- a/proofs/ForeignReportsMissingBackend.bend +++ b/proofs/ForeignReportsMissingBackend.bend @@ -4,7 +4,7 @@ import ../src/finding.bend as F import ../src/syntax/bind.bend as Bind import ../src/lazy/lazy.bend as Lazy import ../src/syntax/tree.bend as Tree -import ../src/rules/correctness/foreign.bend as Foreign +import ../src/rules/pedantic/foreign.bend as Foreign import ../laws/ForeignReportsMissingBackend.bend as Laws # foreign (laws/ForeignReportsMissingBackend.bend): the rule's lanes are the law's imports and diff --git a/proofs/FuelReportsLiteralBudgets.bend b/proofs/FuelReportsLiteralBudgets.bend index 8f4dd6d..5986163 100644 --- a/proofs/FuelReportsLiteralBudgets.bend +++ b/proofs/FuelReportsLiteralBudgets.bend @@ -8,7 +8,7 @@ import ../src/lazy/lazy.bend as Lazy import ../src/syntax/tree.bend as Tree import ../src/rules/calls.bend as Calls import ../src/rules/correctness/pick.bend as Pick -import ../src/rules/suspicious/fuel.bend as FuelRule +import ../src/rules/pedantic/fuel.bend as FuelRule import ../laws/FuelReportsLiteralBudgets.bend as Laws import ./lemmas/Pick.bend as M1 diff --git a/proofs/HoistReportsTablesInLoops.bend b/proofs/HoistReportsTablesInLoops.bend deleted file mode 100644 index 43281b1..0000000 --- a/proofs/HoistReportsTablesInLoops.bend +++ /dev/null @@ -1,398 +0,0 @@ -# proofs of laws/HoistReportsTablesInLoops.bend -import Base -import ../src/syntax/lex.bend as Lex -import ../src/finding.bend as F -import ../src/lazy/lazy.bend as Lazy -import ../src/syntax/tree.bend as Tree -import ../src/rules/calls.bend as Calls -import ../src/rules/correctness/pick.bend as Pick -import ../src/rules/suspicious/hoist.bend as Hoist -import ../laws/HoistReportsTablesInLoops.bend as Laws -import ./lemmas/MatchStatementCounts.bend as M1 -import ./lemmas/Pick.bend as M2 - -# The recursion rule, hoist: each call adds the one the law's call test -# counts, each plain let the one its let test counts, the heat follows the -# same lambdas and case arms, and the def walk joins the per-def lists into -# their sum. - -# a table found or not: one finding or none -law hoist.yes: - for b: Bool - for -aa: Tree.Node - for -path: String - {List.length(&2, F.Finding, Hoist.one_yes(b, aa, path)) == Bool.pick(Nat, b, 1n, 0n) : Nat} - -def hoist.yes(b, _aa, _path): - match b: - case True{}: - {==} - case False{}: - {==} - -# a get or set's collection: one finding when the law's slot test holds -law hoist.slot: - for ss: Hoist.Slot - for +as: List<&2, Tree.Node> - for +self: String - for +wides: List<&2, String> - for +carried: List<&2, String> - for +path: String - {List.length(&2, F.Finding, Hoist.arg_table(ss, as, self, wides, carried, path)) - == Bool.pick(Nat, Laws.hoist.slot(ss, as, self, wides, carried), 1n, 0n) : Nat} - -def hoist.slot(ss, as, self, wides, carried, path): - match ss: - case Hoist.Slot{on, at}: - match on: - case False{}: - {==} - case True{}: - hoist.yes(Hoist.is_table(Calls.arg(as, at), self, wides, carried), Calls.arg(as, at), path) - -# a call site: one finding when the law's call test holds -law hoist.call: - for hot: Bool - for open: Bool - for +tt: String - for +kids: Tree.Node - for +self: String - for +wides: List<&2, String> - for +carried: List<&2, String> - for +path: String - {List.length(&2, F.Finding, Hoist.call_table(hot, open, tt, kids, self, wides, carried, path)) - == Bool.pick(Nat, Laws.hoist.call(hot, open, tt, kids, self, wides, carried), 1n, 0n) : Nat} - -def hoist.call(hot, open, tt, kids, self, wides, carried, path): - match hot: - case False{}: - {==} - case True{}: - match open: - case False{}: - {==} - case True{}: - hoist.slot(Hoist.slot_of(tt), Calls.args(kids), self, wides, carried, path) - -# a let's table and use: one finding when both hold -law hoist.use: - for a: Bool - for b: Bool - for -expr: Tree.Node - for -path: String - {List.length(&2, F.Finding, Hoist.let_use(a, b, expr, path)) == Bool.pick(Nat, Bool.and(a, b), 1n, 0n) : Nat} - -def hoist.use(a, b, _expr, _path): - match a: - case False{}: - {==} - case True{}: - match b: - case True{}: - {==} - case False{}: - {==} - -# a let's left side read: one finding when the law's got test holds -law hoist.got: - for mm: Maybe<&2, Hoist.Got> - for +body: Tree.Node - for +rest: Tree.Node - for +self: String - for +wides: List<&2, String> - for +carried: List<&2, String> - for +path: String - {List.length(&2, F.Finding, Hoist.let_of(mm, body, rest, self, wides, carried, path)) - == Bool.pick(Nat, Laws.hoist.got(mm, body, rest, self, wides, carried), 1n, 0n) : Nat} - -def hoist.got(mm, body, rest, self, wides, carried, path): - match mm: - case None{}: - {==} - case Some{Hoist.Got{+nm, +expr}}: - hoist.use(Hoist.is_table(expr, self, wides, carried), Hoist.uses_or(body, rest, nm), expr, path) - -# a statement: one finding when the law's let test holds -law hoist.let: - for hot: Bool - for +kids: Tree.Node - for +body: Tree.Node - for +rest: Tree.Node - for +self: String - for +wides: List<&2, String> - for +carried: List<&2, String> - for +path: String - {List.length(&2, F.Finding, Hoist.let_hit(hot, kids, body, rest, self, wides, carried, path)) - == Bool.pick(Nat, Laws.hoist.let(hot, kids, body, rest, self, wides, carried), 1n, 0n) : Nat} - -def hoist.let(hot, kids, body, rest, self, wides, carried, path): - match hot: - case False{}: - {==} - case True{}: - hoist.got(Hoist.eat(kids, False{}, False{}, ""), body, rest, self, wides, carried, path) - -# a list joined before another counts as its own count plus the other's -law hoist.front: - for +a: List<&2, F.Finding> - for +b: List<&2, F.Finding> - for -x: Nat - for -y: Nat - for e1: {List.length(&2, F.Finding, a) == x : Nat} - for e2: {List.length(&2, F.Finding, b) == y : Nat} - {List.length(&2, F.Finding, List.append(&2, F.Finding, a, b)) == Nat.add(x, y) : Nat} - -def hoist.front(a, b, x, y, e1, e2): - Equal.trans(Nat, List.length(&2, F.Finding, List.append(&2, F.Finding, a, b)), - Nat.add(List.length(&2, F.Finding, a), List.length(&2, F.Finding, b)), Nat.add(x, y), - M2.esc.app(a, b), M2.esc.add(List.length(&2, F.Finding, a), x, List.length(&2, F.Finding, b), y, e1, e2)) - -# a name token, then a group: its own finding, then its group's and the -# rest's, all at the heat the token leaves -law hoist.site: - for +k: Lex.TokKind - for +t: String - for +o: String - for +kids: Tree.Node - for +rr: Tree.Node - for +self: String - for +wides: List<&2, String> - for +carried: List<&2, String> - for +path: String - for +hot: Bool - for ih1: {List.length(&2, F.Finding, Hoist.walk(kids, self, wides, carried, path, Hoist.warm(hot, k))) - == Laws.hoist.count(kids, self, wides, carried, Hoist.warm(hot, k)) : Nat} - for ih2: {List.length(&2, F.Finding, Hoist.walk(rr, self, wides, carried, path, Hoist.warm(hot, k))) - == Laws.hoist.count(rr, self, wides, carried, Hoist.warm(hot, k)) : Nat} - {List.length(&2, F.Finding, List.concat(&2, F.Finding, - [Hoist.call_table(Hoist.warm(hot, k), String.eq(o, "("), t, kids, self, wides, carried, path), - Hoist.walk(kids, self, wides, carried, path, Hoist.warm(hot, k)), - Hoist.walk(rr, self, wides, carried, path, Hoist.warm(hot, k))])) - == Nat.add(Bool.pick(Nat, Laws.hoist.call(Hoist.warm(hot, k), String.eq(o, "("), t, kids, self, wides, carried), - 1n, 0n), Nat.add(Laws.hoist.count(kids, self, wides, carried, Hoist.warm(hot, k)), - Laws.hoist.count(rr, self, wides, carried, Hoist.warm(hot, k)))) : Nat} - -def hoist.site(k, t, o, kids, rr, self, wides, carried, path, hot, ih1, ih2): - +heat = Hoist.warm(hot, k) - M1.chars.sum(Hoist.call_table(heat, String.eq(o, "("), t, kids, self, wides, carried, path), - Hoist.walk(kids, self, wides, carried, path, heat), Hoist.walk(rr, self, wides, carried, path, heat), - Bool.pick(Nat, Laws.hoist.call(heat, String.eq(o, "("), t, kids, self, wides, carried), 1n, 0n), - Laws.hoist.count(kids, self, wides, carried, heat), Laws.hoist.count(rr, self, wides, carried, heat), - hoist.call(heat, String.eq(o, "("), t, kids, self, wides, carried, path), ih1, ih2) - -# a case: its pattern cold, its arm hot when it calls the def, then the rest -law hoist.arm: - for +kids: Tree.Node - for +body: Tree.Node - for +rest: Tree.Node - for +self: String - for +wides: List<&2, String> - for +carried: List<&2, String> - for +path: String - for +hot: Bool - for ih1: {List.length(&2, F.Finding, Hoist.walk(kids, self, wides, carried, path, False{})) - == Laws.hoist.count(kids, self, wides, carried, False{}) : Nat} - for ih2: {List.length(&2, F.Finding, - Hoist.walk(body, self, wides, carried, path, Bool.and(hot, Calls.calls(body, self)))) - == Laws.hoist.count(body, self, wides, carried, Bool.and(hot, Calls.calls(body, self))) : Nat} - for ih3: {List.length(&2, F.Finding, Hoist.walk(rest, self, wides, carried, path, hot)) - == Laws.hoist.count(rest, self, wides, carried, hot) : Nat} - {List.length(&2, F.Finding, Hoist.walk(Tree.NCons{Tree.Stmt{Tree.SCase{}, kids, body}, rest}, - self, wides, carried, path, hot)) - == Laws.hoist.count(Tree.NCons{Tree.Stmt{Tree.SCase{}, kids, body}, rest}, self, wides, carried, hot) : Nat} - -def hoist.arm(kids, body, rest, self, wides, carried, path, hot, ih1, ih2, ih3): - +arm = Bool.and(hot, Calls.calls(body, self)) - M1.chars.sum(Hoist.walk(kids, self, wides, carried, path, False{}), Hoist.walk(body, self, wides, carried, path, arm), - Hoist.walk(rest, self, wides, carried, path, hot), - Laws.hoist.count(kids, self, wides, carried, False{}), Laws.hoist.count(body, self, wides, carried, arm), - Laws.hoist.count(rest, self, wides, carried, hot), ih1, ih2, ih3) - -# any other statement: its own let finding, then its tokens', its block's -# and the rest's -law hoist.plain: - for +kids: Tree.Node - for +body: Tree.Node - for +rest: Tree.Node - for +self: String - for +wides: List<&2, String> - for +carried: List<&2, String> - for +path: String - for +hot: Bool - for ih1: {List.length(&2, F.Finding, Hoist.walk(kids, self, wides, carried, path, hot)) - == Laws.hoist.count(kids, self, wides, carried, hot) : Nat} - for ih2: {List.length(&2, F.Finding, Hoist.walk(body, self, wides, carried, path, hot)) - == Laws.hoist.count(body, self, wides, carried, hot) : Nat} - for ih3: {List.length(&2, F.Finding, Hoist.walk(rest, self, wides, carried, path, hot)) - == Laws.hoist.count(rest, self, wides, carried, hot) : Nat} - {List.length(&2, F.Finding, List.concat(&2, F.Finding, - [Hoist.let_hit(hot, kids, body, rest, self, wides, carried, path), - Hoist.walk(kids, self, wides, carried, path, hot), Hoist.walk(body, self, wides, carried, path, hot), - Hoist.walk(rest, self, wides, carried, path, hot)])) - == Nat.add(Bool.pick(Nat, Laws.hoist.let(hot, kids, body, rest, self, wides, carried), 1n, 0n), - Nat.add(Laws.hoist.count(kids, self, wides, carried, hot), - Nat.add(Laws.hoist.count(body, self, wides, carried, hot), Laws.hoist.count(rest, self, wides, carried, hot)))) - : Nat} - -def hoist.plain(kids, body, rest, self, wides, carried, path, hot, ih1, ih2, ih3): - +own = Hoist.let_hit(hot, kids, body, rest, self, wides, carried, path) - +wk = Hoist.walk(kids, self, wides, carried, path, hot) - +wb = Hoist.walk(body, self, wides, carried, path, hot) - +wr = Hoist.walk(rest, self, wides, carried, path, hot) - +ck = Laws.hoist.count(kids, self, wides, carried, hot) - +cb = Laws.hoist.count(body, self, wides, carried, hot) - +cr = Laws.hoist.count(rest, self, wides, carried, hot) - hoist.front(own, List.concat(&2, F.Finding, [wk, wb, wr]), - Bool.pick(Nat, Laws.hoist.let(hot, kids, body, rest, self, wides, carried), 1n, 0n), - Nat.add(ck, Nat.add(cb, cr)), - hoist.let(hot, kids, body, rest, self, wides, carried, path), - M1.chars.sum(wk, wb, wr, ck, cb, cr, ih1, ih2, ih3)) - -# hoist's walk counts the calls and lets the law counts, at every heat -law hoist.walk: - for nn: Tree.Node - for +self: String - for +wides: List<&2, String> - for +carried: List<&2, String> - for +path: String - for +hot: Bool - {List.length(&2, F.Finding, Hoist.walk(nn, self, wides, carried, path, hot)) - == Laws.hoist.count(nn, self, wides, carried, hot) : Nat} - -def hoist.walk(nn, self, wides, carried, path, hot): - match nn: - case Tree.Leaf{tok}: - {==} - case Tree.Group{o, gk, cl}: - {==} - case Tree.Stmt{kind, sk, sb}: - {==} - case Tree.NNil{}: - {==} - case Tree.NCons{h, +rest}: - match h: - case Tree.Leaf{Lex.Tok{+k, +t, l, c}}: - match rest: - case Tree.Leaf{tok}: - {==} - case Tree.Group{o, gk, cl}: - {==} - case Tree.Stmt{kind, sk, sb}: - {==} - case Tree.NNil{}: - {==} - case Tree.NCons{rh, +rr}: - match rh: - case Tree.Group{Lex.Tok{gk, +o, gl, gc}, +kids, cl}: - hoist.site(k, t, o, kids, rr, self, wides, carried, path, hot, - hoist.walk(kids, self, wides, carried, path, Hoist.warm(hot, k)), - hoist.walk(rr, self, wides, carried, path, Hoist.warm(hot, k))) - case Tree.Leaf{tok}: - hoist.walk(rest, self, wides, carried, path, Hoist.warm(hot, k)) - case Tree.Stmt{kind, sk, sb}: - hoist.walk(rest, self, wides, carried, path, Hoist.warm(hot, k)) - case Tree.NNil{}: - hoist.walk(rest, self, wides, carried, path, Hoist.warm(hot, k)) - case Tree.NCons{nh, nt}: - hoist.walk(rest, self, wides, carried, path, Hoist.warm(hot, k)) - case Tree.Group{o, +kids, cl}: - M1.index.two(Hoist.walk(kids, self, wides, carried, path, hot), - Hoist.walk(rest, self, wides, carried, path, hot), - Laws.hoist.count(kids, self, wides, carried, hot), Laws.hoist.count(rest, self, wides, carried, hot), - hoist.walk(kids, self, wides, carried, path, hot), hoist.walk(rest, self, wides, carried, path, hot)) - case Tree.Stmt{kind, +kids, +body}: - match kind: - case Tree.SCase{}: - hoist.arm(kids, body, rest, self, wides, carried, path, hot, - hoist.walk(kids, self, wides, carried, path, False{}), - hoist.walk(body, self, wides, carried, path, Bool.and(hot, Calls.calls(body, self))), - hoist.walk(rest, self, wides, carried, path, hot)) - case Tree.SDef{}: - hoist.plain(kids, body, rest, self, wides, carried, path, hot, - hoist.walk(kids, self, wides, carried, path, hot), - hoist.walk(body, self, wides, carried, path, hot), hoist.walk(rest, self, wides, carried, path, hot)) - case Tree.SType{}: - hoist.plain(kids, body, rest, self, wides, carried, path, hot, - hoist.walk(kids, self, wides, carried, path, hot), - hoist.walk(body, self, wides, carried, path, hot), hoist.walk(rest, self, wides, carried, path, hot)) - case Tree.SLaw{}: - hoist.plain(kids, body, rest, self, wides, carried, path, hot, - hoist.walk(kids, self, wides, carried, path, hot), - hoist.walk(body, self, wides, carried, path, hot), hoist.walk(rest, self, wides, carried, path, hot)) - case Tree.SImport{}: - hoist.plain(kids, body, rest, self, wides, carried, path, hot, - hoist.walk(kids, self, wides, carried, path, hot), - hoist.walk(body, self, wides, carried, path, hot), hoist.walk(rest, self, wides, carried, path, hot)) - case Tree.SFor{}: - hoist.plain(kids, body, rest, self, wides, carried, path, hot, - hoist.walk(kids, self, wides, carried, path, hot), - hoist.walk(body, self, wides, carried, path, hot), hoist.walk(rest, self, wides, carried, path, hot)) - case Tree.SLet{}: - hoist.plain(kids, body, rest, self, wides, carried, path, hot, - hoist.walk(kids, self, wides, carried, path, hot), - hoist.walk(body, self, wides, carried, path, hot), hoist.walk(rest, self, wides, carried, path, hot)) - case Tree.STerm{}: - hoist.plain(kids, body, rest, self, wides, carried, path, hot, - hoist.walk(kids, self, wides, carried, path, hot), - hoist.walk(body, self, wides, carried, path, hot), hoist.walk(rest, self, wides, carried, path, hot)) - case Tree.NNil{}: - hoist.walk(rest, self, wides, carried, path, hot) - case Tree.NCons{nh, nt}: - hoist.walk(rest, self, wides, carried, path, hot) - -# a def that calls itself and is not exempt gathers its walk, entered hot, -# any other nothing -law hoist.def: - for g: Bool - for +body: Tree.Node - for +self: String - for +wides: List<&2, String> - for +carried: List<&2, String> - for +path: String - {List.length(&2, F.Finding, - Lazy.stop(List<&2, F.Finding>, Bool.not(g), [], _u => Hoist.walk(body, self, wides, carried, path, True{}))) - == Bool.pick(Nat, g, Laws.hoist.count(body, self, wides, carried, True{}), 0n) : Nat} - -def hoist.def(g, body, self, wides, carried, path): - match g: - case True{}: - hoist.walk(body, self, wides, carried, path, True{}) - case False{}: - {==} - -# over the defs, the rule's join counts what it gathered and what the law -# counts -law hoist.go: - for ds: List<&2, Calls.Def> - for +wides: List<&2, String> - for +path: String - for +acc: List<&2, List<&2, F.Finding>> - {List.length(&2, F.Finding, Hoist.check.go(ds, wides, path, acc)) - == Nat.add(M1.index.sum(acc), Laws.hoist.total(ds, wides, path)) : Nat} - -def hoist.go(ds, wides, path, acc): - match ds: - case Nil{}: - M1.index.flat(acc, Nil{}) - case Con{Calls.Def{+name, +sig, +body}, +rest}: - +g = Bool.and(Calls.calls(body, name), Bool.not(Calls.exempt(path, sig))) - +carried = Hoist.carried(sig, body, name) - +x = Lazy.stop(List<&2, F.Finding>, Bool.not(g), [], _u => Hoist.walk(body, name, wides, carried, path, True{})) - +n = Bool.pick(Nat, g, Laws.hoist.count(body, name, wides, carried, True{}), 0n) - +tot = Laws.hoist.total(rest, wides, path) - Equal.trans(Nat, - List.length(&2, F.Finding, Hoist.check.go(rest, wides, path, x <> acc)), - Nat.add(Nat.add(M1.index.sum(acc), List.length(&2, F.Finding, x)), tot), - Nat.add(M1.index.sum(acc), Nat.add(n, tot)), - hoist.go(rest, wides, path, x <> acc), - Equal.trans(Nat, - Nat.add(Nat.add(M1.index.sum(acc), List.length(&2, F.Finding, x)), tot), - Nat.add(M1.index.sum(acc), Nat.add(List.length(&2, F.Finding, x), tot)), - Nat.add(M1.index.sum(acc), Nat.add(n, tot)), - M1.index.assoc(M1.index.sum(acc), List.length(&2, F.Finding, x), tot), - Equal.cong(Nat, Nat, z => Nat.add(M1.index.sum(acc), Nat.add(z, tot)), List.length(&2, F.Finding, x), n, - hoist.def(g, body, name, wides, carried, path)))) - -def Laws.hoist_counts(path, _text, _toks, tree, _bound, _items): - +ds = Calls.defs(tree) - hoist.go(ds, Hoist.wide_defs(ds, Nil{}), path, Nil{}) diff --git a/proofs/ImportReachIsClosed.bend b/proofs/ImportReachIsClosed.bend new file mode 100644 index 0000000..8818b3a --- /dev/null +++ b/proofs/ImportReachIsClosed.bend @@ -0,0 +1,4 @@ +# proofs of laws/ImportReachIsClosed.bend +import Base +import ../laws/ImportReachIsClosed.bend as Laws +import ./lemmas/ImportClosure.bend as M1 diff --git a/proofs/IndexReportsGetsInRecursion.bend b/proofs/IndexReportsGetsInRecursion.bend index 1d7a134..40d93fd 100644 --- a/proofs/IndexReportsGetsInRecursion.bend +++ b/proofs/IndexReportsGetsInRecursion.bend @@ -5,15 +5,15 @@ import ../src/finding.bend as F import ../src/lazy/lazy.bend as Lazy import ../src/syntax/tree.bend as Tree import ../src/rules/calls.bend as Calls -import ../src/rules/suspicious/index.bend as Index -import ../src/rules/suspicious/table.bend as Table +import ../src/rules/pedantic/index.bend as Index +import ../src/rules/table.bend as Table import ../laws/IndexReportsGetsInRecursion.bend as Laws import ./lemmas/MatchStatementCounts.bend as M1 import ./lemmas/Nat.bend as M2 # The recursion rule, index: its literal test is the law's, its fire test is -# the law's (a List.get held by a fixed table is exactly one `table` -# reports), each call adds what the law counts, and the def walk joins the +# the law's (a List.get held by a fixed table is exactly one +# src/rules/table.bend calls one), each call adds what the law counts, and the def walk joins the # per-def lists into their sum. # the rule's literal test is the law's diff --git a/proofs/LawNamesEveryItem.bend b/proofs/LawNamesEveryItem.bend index 2ceabc1..f9881f7 100644 --- a/proofs/LawNamesEveryItem.bend +++ b/proofs/LawNamesEveryItem.bend @@ -14,7 +14,7 @@ import ../src/rules/laws/law.bend as Coverage import ../src/rules/imports.bend as Imports import ../src/rules/digest.bend as Digest import ../laws/LawNamesEveryItem.bend as Laws -import ../laws/UnsafeReportsReachableForeignDefs.bend as L1 +import ../laws/ImportReachIsClosed.bend as L1 import ./lemmas/ImportClosure.bend as M1 import ./lemmas/CarriedSearch.bend as CS @@ -368,17 +368,17 @@ def reach.files_eq(fs, cl): match fs: case Nil{}: {==} - case Con{Digest.Digest{+path, +norm, dir, is_laws, is_law_file, exempt, +tops, says, deps, unsafes, laws}, +rest}: + case Con{Digest.Digest{+path, +norm, dir, is_laws, is_law_file, exempt, +tops, says, deps, laws}, +rest}: match exempt: case True{}: reach.files_eq(rest, cl) case False{}: %reach.files_eq(rest, cl) : - {Coverage.check.go(Digest.Digest{path, norm, dir, is_laws, is_law_file, False{}, tops, says, deps, unsafes, + {Coverage.check.go(Digest.Digest{path, norm, dir, is_laws, is_law_file, False{}, tops, says, deps, laws} <> rest, cl) == List.append(&2, F.Finding, Laws.reach.gaps(tops, cl, norm, path), _) : List<&2, F.Finding>} %reach.gaps_eq(tops, cl, norm, path) : - {Coverage.check.go(Digest.Digest{path, norm, dir, is_laws, is_law_file, False{}, tops, says, deps, unsafes, + {Coverage.check.go(Digest.Digest{path, norm, dir, is_laws, is_law_file, False{}, tops, says, deps, laws} <> rest, cl) == List.append(&2, F.Finding, _, Coverage.check.go(rest, cl)) : List<&2, F.Finding>} {==} @@ -395,7 +395,7 @@ def reach.first(fs, ds): match ds: case Nil{}: {==} - case Con{Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, unsafes, laws}, +rest}: + case Con{Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, laws}, +rest}: match is_laws: case True{}: {==} @@ -424,7 +424,7 @@ def reach.named_eq(ds): match ds: case Nil{}: {==} - case Con{Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, +says, deps, unsafes, laws}, +rest}: + case Con{Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, +says, deps, laws}, +rest}: match is_laws: case True{}: Equal.cong(List<&2, Digest.Mention>, List<&2, Digest.Mention>, @@ -499,7 +499,7 @@ def reach.items_eq(ds): match ds: case Nil{}: {==} - case Con{Digest.Digest{path, +norm, dir, is_laws, is_law_file, exempt, +tops, says, deps, unsafes, laws}, +rest}: + case Con{Digest.Digest{path, +norm, dir, is_laws, is_law_file, exempt, +tops, says, deps, laws}, +rest}: Equal.trans(List<&2, Imports.Edge>, Digest.edges(Laws.reach.nodes(tops, norm, Laws.reach.items(rest))), Coverage.nodes(tops, norm, Digest.edges(Laws.reach.items(rest))), Coverage.nodes(tops, norm, Coverage.graph.items(rest)), @@ -533,7 +533,7 @@ def reach.len_edges(ds): match ds: case Nil{}: {==} - case Con{Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, unsafes, laws}, +rest}: + case Con{Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, laws}, +rest}: Equal.cong(Nat, Nat, n => 1n+n, List.length(&2, Imports.Edge, Digest.edges(rest)), List.length(&2, Digest.Digest, rest), reach.len_edges(rest)) diff --git a/proofs/NoqaReportsStaleComments.bend b/proofs/NoqaReportsStaleComments.bend index a29b084..ad7cac9 100644 --- a/proofs/NoqaReportsStaleComments.bend +++ b/proofs/NoqaReportsStaleComments.bend @@ -79,15 +79,13 @@ def nqp.used(gs, path, line, code): {==})), nqp.used(rest, path, line, code)) -# the rule's project codes are the spec's: `coverage` and `unsafe` +# the rule's project code is the spec's: `coverage` law nqp.project: for +code: String {NoqaRule.project(code) == Laws.nq.project(code) : Bool} -def nqp.project(code): - M1.nqp.or_else(String.eq(code, Codes.code("coverage")), String.eq(code, Codes.code("unsafe")), String.eq(code, - "L003"), - {==}) +def nqp.project(_code): + {==} # the rule's test of a code is the spec's: judged, and silencing nothing law nqp.bad: diff --git a/proofs/RewalkReportsRepeatedWalks.bend b/proofs/RewalkReportsRepeatedWalks.bend index 00bac6c..3fa3c05 100644 --- a/proofs/RewalkReportsRepeatedWalks.bend +++ b/proofs/RewalkReportsRepeatedWalks.bend @@ -6,7 +6,7 @@ import ../src/lazy/lazy.bend as Lazy import ../src/syntax/tree.bend as Tree import ../src/rules/calls.bend as Calls import ../src/rules/correctness/pick.bend as Pick -import ../src/rules/suspicious/rewalk.bend as Rewalk +import ../src/rules/pedantic/rewalk.bend as Rewalk import ../laws/RewalkReportsRepeatedWalks.bend as Laws import ./lemmas/Pick.bend as M1 diff --git a/proofs/RingReportsDropAppendRotation.bend b/proofs/RingReportsDropAppendRotation.bend deleted file mode 100644 index 586a680..0000000 --- a/proofs/RingReportsDropAppendRotation.bend +++ /dev/null @@ -1,157 +0,0 @@ -# proofs of laws/RingReportsDropAppendRotation.bend -import Base -import ../src/syntax/lex.bend as Lex -import ../src/finding.bend as F -import ../src/lazy/lazy.bend as Lazy -import ../src/syntax/tree.bend as Tree -import ../src/rules/calls.bend as Calls -import ../src/rules/correctness/pick.bend as Pick -import ../src/rules/suspicious/ring.bend as Ring -import ../laws/RingReportsDropAppendRotation.bend as Laws -import ./lemmas/Pick.bend as M1 - -# a scan's finding counts one when it found a drop, else none -law ring.one: - for mm: Maybe<&2, Lex.Tok> - for +path: String - {List.length(&2, F.Finding, Ring.slide_at(mm, path)) == Laws.ring.hit(mm) : Nat} - -def ring.one(mm, _path): - match mm: - case None{}: - {==} - case Some{Lex.Tok{k, t, l, c}}: - {==} - -# a self-call's arguments, each read against its own slot -law ring.slides: - for as: List<&2, Tree.Node> - for +params: List<&2, String> - for +path: String - for +wins: List<&2, String> - {List.length(&2, F.Finding, Ring.slides(as, params, path, wins)) == Laws.ring.args(as, params, wins) : Nat} - -def ring.slides(as, params, path, wins): - match as: - case Nil{}: - {==} - case Con{+h, +rest}: - +scan = Ring.plus_scan(h, None{}, Ring.slot_wins(Maybe.default(&2, String, List.head(&2, String, params), ""), - wins)) - +x = Ring.slide_at(scan, path) - +more = Ring.slides(rest, List.tail(&2, String, params), path, wins) - Equal.trans(Nat, List.length(&2, F.Finding, List.append(&2, F.Finding, x, more)), - Nat.add(List.length(&2, F.Finding, x), List.length(&2, F.Finding, more)), - Nat.add(Laws.ring.hit(scan), Laws.ring.args(rest, List.tail(&2, String, params), wins)), - M1.esc.app(x, more), - M1.esc.add(List.length(&2, F.Finding, x), Laws.ring.hit(scan), List.length(&2, F.Finding, more), - Laws.ring.args(rest, List.tail(&2, String, params), wins), ring.one(scan, path), - ring.slides(rest, List.tail(&2, String, params), path, wins))) - -def Laws.ring_walk_counts(nn, self, params, path, wins): - match nn: - case Tree.NCons{h, +rest}: - match h: - case Tree.Leaf{Lex.Tok{k, +t, l, c}}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{Lex.Tok{k2, +o, l2, c2}, +kids, cl}: - +me = Bool.and(String.eq(o, "("), String.eq(t, self)) - M1.table.three( - Lazy.stop(List<&2, F.Finding>, Bool.not(me), [], _u => Ring.slides(Calls.args(kids), params, path, - wins)), - Ring.walk(kids, self, params, path, wins), Ring.walk(r2, self, params, path, wins), - Bool.pick(Nat, me, Laws.ring.args(Calls.args(kids), params, wins), 0n), - Laws.ring.count(kids, self, params, wins), Laws.ring.count(r2, self, params, wins), - M1.tail.stop(me, Ring.slides(Calls.args(kids), params, path, wins), - Laws.ring.args(Calls.args(kids), params, wins), - ring.slides(Calls.args(kids), params, path, wins)), - Laws.ring_walk_counts(kids, self, params, path, wins), - Laws.ring_walk_counts(r2, self, params, path, wins)) - case Tree.Leaf{x}: - Laws.ring_walk_counts(rest, self, params, path, wins) - case Tree.Stmt{a, b, d}: - Laws.ring_walk_counts(rest, self, params, path, wins) - case Tree.NNil{}: - Laws.ring_walk_counts(rest, self, params, path, wins) - case Tree.NCons{a, b}: - Laws.ring_walk_counts(rest, self, params, path, wins) - case Tree.Leaf{x}: - Laws.ring_walk_counts(rest, self, params, path, wins) - case Tree.Group{a, b, d}: - Laws.ring_walk_counts(rest, self, params, path, wins) - case Tree.Stmt{a, b, d}: - Laws.ring_walk_counts(rest, self, params, path, wins) - case Tree.NNil{}: - Laws.ring_walk_counts(rest, self, params, path, wins) - case Tree.Group{open, +kids, close}: - M1.table.two(Ring.walk(kids, self, params, path, wins), Ring.walk(rest, self, params, path, wins), - Laws.ring.count(kids, self, params, wins), Laws.ring.count(rest, self, params, wins), - Laws.ring_walk_counts(kids, self, params, path, wins), - Laws.ring_walk_counts(rest, self, params, path, wins)) - case Tree.Stmt{kind, +kids, +body}: - M1.table.three(Ring.walk(kids, self, params, path, wins), Ring.walk(body, self, params, path, wins), - Ring.walk(rest, self, params, path, wins), Laws.ring.count(kids, self, params, wins), - Laws.ring.count(body, self, params, wins), Laws.ring.count(rest, self, params, wins), - Laws.ring_walk_counts(kids, self, params, path, wins), - Laws.ring_walk_counts(body, self, params, path, wins), - Laws.ring_walk_counts(rest, self, params, path, wins)) - case Tree.NNil{}: - Laws.ring_walk_counts(rest, self, params, path, wins) - case Tree.NCons{a, b}: - Laws.ring_walk_counts(rest, self, params, path, wins) - case Tree.Leaf{x}: - {==} - case Tree.Group{a, b, d}: - {==} - case Tree.Stmt{a, b, d}: - {==} - case Tree.NNil{}: - {==} - -# one def's findings: walked when its guard keeps it, else none -law ring.stop: - for +b: Bool - for +body: Tree.Node - for +self: String - for +params: List<&2, String> - for +path: String - for +wins: List<&2, String> - {List.length(&2, F.Finding, - Lazy.stop(List<&2, F.Finding>, Bool.not(b), [], _u => Ring.walk(body, self, params, path, wins))) - == Bool.pick(Nat, b, Laws.ring.count(body, self, params, wins), 0n) : Nat} - -def ring.stop(b, body, self, params, path, wins): - match b: - case True{}: - Laws.ring_walk_counts(body, self, params, path, wins) - case False{}: - {==} - -# ring's def walk: the findings gathered so far, then each def's -law ring.go: - for ds: List<&2, Calls.Def> - for +path: String - for +acc: List<&2, List<&2, F.Finding>> - {List.length(&2, F.Finding, Ring.check.go(ds, path, acc)) == M1.pick.sum(acc, Laws.ring.defs(ds, path)) : Nat} - -def ring.go(ds, path, acc): - match ds: - case Nil{}: - M1.pick.rev(acc, Nil{}) - case Con{Calls.Def{+name, +sig, +body}, +rest}: - +keep = Bool.and(Calls.calls(body, name), Bool.not(Calls.exempt(path, sig))) - +x = Lazy.stop(List<&2, F.Finding>, Bool.not(keep), [], - _u => Ring.walk(body, name, Calls.names(sig), path, Ring.windows(sig, body))) - +cb = Bool.pick(Nat, keep, Laws.ring.count(body, name, Calls.names(sig), Ring.windows(sig, body)), 0n) - Equal.trans(Nat, List.length(&2, F.Finding, Ring.check.go(rest, path, x <> acc)), - M1.pick.sum(acc, Nat.add(List.length(&2, F.Finding, x), Laws.ring.defs(rest, path))), - M1.pick.sum(acc, Nat.add(cb, Laws.ring.defs(rest, path))), - ring.go(rest, path, x <> acc), - Equal.cong(Nat, Nat, n => M1.pick.sum(acc, Nat.add(n, Laws.ring.defs(rest, path))), - List.length(&2, F.Finding, x), cb, - ring.stop(keep, body, name, Calls.names(sig), path, Ring.windows(sig, body)))) - -def Laws.ring_counts(path, _text, _toks, tree, _bound, _items): - ring.go(Calls.defs(tree), path, Nil{}) diff --git a/proofs/RoleHoist.bend b/proofs/RoleHoist.bend deleted file mode 100644 index bd53b28..0000000 --- a/proofs/RoleHoist.bend +++ /dev/null @@ -1,11 +0,0 @@ -# proofs of laws/RoleHoist.bend -import Base -import ../src/src.bend as Src -import ../src/rules/suspicious/hoist.bend as Hoist -import ../laws/ExemptPathsYieldNoFindings.bend as L1 -import ../laws/RoleHoist.bend as Laws -import ./lemmas/LawFileExemptions.bend as M1 - -def Laws.role_hoist(path, text, toks, tree, bound, items): - M1.role.skip_named(path, _u => Hoist.check(Src.Src{path, text, toks, tree, bound, items}), - e => L1.hoist_exempt(path, text, toks, tree, bound, items, e)) diff --git a/proofs/RoleIndex.bend b/proofs/RoleIndex.bend index 2b52ae8..cafa016 100644 --- a/proofs/RoleIndex.bend +++ b/proofs/RoleIndex.bend @@ -1,7 +1,7 @@ # proofs of laws/RoleIndex.bend import Base import ../src/src.bend as Src -import ../src/rules/suspicious/index.bend as Index +import ../src/rules/pedantic/index.bend as Index import ../laws/ExemptPathsYieldNoFindings.bend as L1 import ../laws/RoleIndex.bend as Laws import ./lemmas/LawFileExemptions.bend as M1 diff --git a/proofs/RoleRewalk.bend b/proofs/RoleRewalk.bend index 1200c09..732a2e2 100644 --- a/proofs/RoleRewalk.bend +++ b/proofs/RoleRewalk.bend @@ -1,7 +1,7 @@ # proofs of laws/RoleRewalk.bend import Base import ../src/src.bend as Src -import ../src/rules/suspicious/rewalk.bend as Rewalk +import ../src/rules/pedantic/rewalk.bend as Rewalk import ../laws/ExemptPathsYieldNoFindings.bend as L1 import ../laws/RoleRewalk.bend as Laws import ./lemmas/LawFileExemptions.bend as M1 diff --git a/proofs/RoleRing.bend b/proofs/RoleRing.bend deleted file mode 100644 index 7034c1e..0000000 --- a/proofs/RoleRing.bend +++ /dev/null @@ -1,11 +0,0 @@ -# proofs of laws/RoleRing.bend -import Base -import ../src/src.bend as Src -import ../src/rules/suspicious/ring.bend as Ring -import ../laws/ExemptPathsYieldNoFindings.bend as L1 -import ../laws/RoleRing.bend as Laws -import ./lemmas/LawFileExemptions.bend as M1 - -def Laws.role_ring(path, text, toks, tree, bound, items): - M1.role.skip_named(path, _u => Ring.check(Src.Src{path, text, toks, tree, bound, items}), - e => L1.ring_exempt(path, text, toks, tree, bound, items, e)) diff --git a/proofs/RoleScan.bend b/proofs/RoleScan.bend deleted file mode 100644 index 1aec112..0000000 --- a/proofs/RoleScan.bend +++ /dev/null @@ -1,17 +0,0 @@ -# proofs of laws/RoleScan.bend -import Base -import ../src/finding.bend as F -import ../src/paths.bend as Paths -import ../src/role.bend as Role -import ../src/lazy/lazy.bend as Lazy -import ../src/rules/calls.bend as Calls -import ../src/rules/suspicious/scan.bend as Scan -import ../laws/RoleLawFileNamed.bend as L1 -import ../laws/RoleScan.bend as Laws -import ./lemmas/RoleLawFilePicked.bend as M1 - -def Laws.role_scan(path, _text, _toks, tree, _bound, _items): - Equal.cong(Bool, List<&2, F.Finding>, - z => Lazy.stop(List<&2, F.Finding>, z, [], _u => Scan.check.on(Calls.defs(tree), path)), - Role.is_law_file(Role.of_path(path)), Paths.is_law_file(path), - L1.role_law_file_named(path)) diff --git a/proofs/RoleScanExempt.bend b/proofs/RoleScanExempt.bend deleted file mode 100644 index 6a22545..0000000 --- a/proofs/RoleScanExempt.bend +++ /dev/null @@ -1,10 +0,0 @@ -# proofs of laws/RoleScanExempt.bend -import Base -import ../src/role.bend as Role -import ../src/rules/calls.bend as Calls -import ../src/rules/suspicious/scan.bend as Scan -import ../laws/RoleScanExempt.bend as Laws -import ./lemmas/LawFileExemptions.bend as M1 - -def Laws.role_scan_exempt(rr, e, path, _text, _toks, tree, _bound, _items): - M1.ex.stop(Role.is_law_file(rr), e, _u => Scan.check.on(Calls.defs(tree), path)) diff --git a/proofs/RoleStrict.bend b/proofs/RoleStrict.bend index c23a533..e14632d 100644 --- a/proofs/RoleStrict.bend +++ b/proofs/RoleStrict.bend @@ -1,7 +1,7 @@ # proofs of laws/RoleStrict.bend import Base import ../src/src.bend as Src -import ../src/rules/suspicious/strict.bend as Strict +import ../src/rules/pedantic/strict.bend as Strict import ../laws/ExemptPathsYieldNoFindings.bend as L1 import ../laws/RoleStrict.bend as Laws import ./lemmas/LawFileExemptions.bend as M1 diff --git a/proofs/RoleTable.bend b/proofs/RoleTable.bend deleted file mode 100644 index 6b307aa..0000000 --- a/proofs/RoleTable.bend +++ /dev/null @@ -1,11 +0,0 @@ -# proofs of laws/RoleTable.bend -import Base -import ../src/src.bend as Src -import ../src/rules/suspicious/table.bend as Table -import ../laws/ExemptPathsYieldNoFindings.bend as L1 -import ../laws/RoleTable.bend as Laws -import ./lemmas/LawFileExemptions.bend as M1 - -def Laws.role_table(path, text, toks, tree, bound, items): - M1.role.skip_named(path, _u => Table.check(Src.Src{path, text, toks, tree, bound, items}), - e => L1.table_exempt(path, text, toks, tree, bound, items, e)) diff --git a/proofs/RoleUnit.bend b/proofs/RoleUnit.bend deleted file mode 100644 index 5e70ce6..0000000 --- a/proofs/RoleUnit.bend +++ /dev/null @@ -1,11 +0,0 @@ -# proofs of laws/RoleUnit.bend -import Base -import ../src/src.bend as Src -import ../src/rules/suspicious/unit.bend as UnitRule -import ../laws/ExemptPathsYieldNoFindings.bend as L1 -import ../laws/RoleUnit.bend as Laws -import ./lemmas/LawFileExemptions.bend as M1 - -def Laws.role_unit(path, text, toks, tree, bound, items): - M1.role.skip_named(path, _u => UnitRule.check(Src.Src{path, text, toks, tree, bound, items}), - e => L1.unit_exempt(path, text, toks, tree, bound, items, e)) diff --git a/proofs/ScanReportsCarriedArgumentWalks.bend b/proofs/ScanReportsCarriedArgumentWalks.bend deleted file mode 100644 index 4aa1d5c..0000000 --- a/proofs/ScanReportsCarriedArgumentWalks.bend +++ /dev/null @@ -1,773 +0,0 @@ -# proofs of laws/ScanReportsCarriedArgumentWalks.bend -import Base -import ../src/syntax/lex.bend as Lex -import ../src/finding.bend as F -import ../src/paths.bend as Paths -import ../src/lazy/lazy.bend as Lazy -import ../src/syntax/tree.bend as Tree -import ../src/rules/calls.bend as Calls -import ../src/rules/suspicious/hoist.bend as Hoist -import ../src/rules/suspicious/scan.bend as Scan -import ../laws/ScanReportsCarriedArgumentWalks.bend as Laws -import ./lemmas/MatchStatementCounts.bend as M1 - -# the file's walks of a name are the law's -law scan.file_at: - for ws: List<&2, Scan.Walk> - for +tt: String - {Scan.file_at(ws, tt) == Laws.scan.file(ws, tt) : List<&2, Nat>} - -def scan.file_at(ws, tt): - match ws: - case Nil{}: - {==} - case Con{Scan.Walk{+nm, +at}, +rest}: - %scan.file_at(rest, tt) : {Scan.file_at(Scan.Walk{nm, at} <> rest, tt) - == Bool.pick(List<&2, Nat>, String.eq(nm, tt), at <> _, _) : List<&2, Nat>} - {==} - -def Laws.scan_slots(tt, ws): - %scan.file_at(ws, tt) : {Scan.slots(tt, ws) == List.append(&2, Nat, Laws.scan.base(tt), _) : List<&2, Nat>} - {==} - -# three finding lists, each empty, join empty -law scan.nil3: - for -a: List<&2, F.Finding> - for -b: List<&2, F.Finding> - for -c: List<&2, F.Finding> - for ea: {a == Nil{} : List<&2, F.Finding>} - for eb: {b == Nil{} : List<&2, F.Finding>} - for ec: {c == Nil{} : List<&2, F.Finding>} - {List.concat(&2, F.Finding, [a, b, c]) == Nil{} : List<&2, F.Finding>} - -def scan.nil3(a, b, c, ea, eb, ec): - %Equal.sym(List<&2, F.Finding>, a, Nil{}, ea) : {List.concat(&2, F.Finding, [_, b, c]) == Nil{} - : List<&2, F.Finding>} - %Equal.sym(List<&2, F.Finding>, b, Nil{}, eb) : {List.concat(&2, F.Finding, [Nil{}, _, c]) == Nil{} - : List<&2, F.Finding>} - %Equal.sym(List<&2, F.Finding>, c, Nil{}, ec) : {List.concat(&2, F.Finding, [Nil{}, Nil{}, _]) == Nil{} - : List<&2, F.Finding>} - {==} - -# two finding lists, each empty, join empty -law scan.nil2: - for -a: List<&2, F.Finding> - for -b: List<&2, F.Finding> - for ea: {a == Nil{} : List<&2, F.Finding>} - for eb: {b == Nil{} : List<&2, F.Finding>} - {List.concat(&2, F.Finding, [a, b]) == Nil{} : List<&2, F.Finding>} - -def scan.nil2(a, b, ea, eb): - %Equal.sym(List<&2, F.Finding>, a, Nil{}, ea) : {List.concat(&2, F.Finding, [_, b]) == Nil{} : List<&2, F.Finding>} - %Equal.sym(List<&2, F.Finding>, b, Nil{}, eb) : {List.concat(&2, F.Finding, [Nil{}, _]) == Nil{} - : List<&2, F.Finding>} - {==} - -# no name is kept against no names -law scan.keep_nil: - for mm: Maybe<&2, String> - {Scan.keep_in(mm, []) == None{} : Maybe<&2, String>} - -def scan.keep_nil(mm): - match mm: - case None{}: - {==} - case Some{nm}: - {==} - -# no walked argument is one of no names -law scan.found_nil: - for ss: List<&2, Nat> - for +as: List<&2, Tree.Node> - {Scan.found(ss, as, []) == None{} : Maybe<&2, String>} - -def scan.found_nil(ss, as): - match ss: - case Nil{}: - {==} - case Con{+at, +rest}: - %Equal.sym(Maybe<&2, String>, Scan.keep_in(Scan.name_of(Calls.arg(as, at)), []), None{}, - scan.keep_nil(Scan.name_of(Calls.arg(as, at)))) : {Scan.first(_, Scan.found(rest, as, [])) == None{} - : Maybe<&2, String>} - scan.found_nil(rest, as) - -# a stop that gives nothing either way gives nothing -law scan.stop_nil: - for c: Bool - {Lazy.stop(List<&2, F.Finding>, c, [], _u => Nil{}) == Nil{} : List<&2, F.Finding>} - -def scan.stop_nil(c): - match c: - case True{}: - {==} - case False{}: - {==} - -# a named call reports nothing when nothing is carried -law scan.on_nil: - for +hot: Bool - for +open: Bool - for +tt: String - for +line: U32 - for +col: U32 - for +kids: Tree.Node - for +self: String - for +ws: List<&2, Scan.Walk> - for +lead: String - for +path: String - {Scan.on_name(hot, open, tt, line, col, kids, self, ws, [], lead, path) == Nil{} : List<&2, F.Finding>} - -def scan.on_nil(hot, open, tt, line, col, kids, self, ws, lead, path): - %Equal.sym(Maybe<&2, String>, Scan.found(Scan.slots(tt, ws), Calls.args(kids), []), None{}, - scan.found_nil(Scan.slots(tt, ws), Calls.args(kids))) : {Lazy.stop(List<&2, F.Finding>, - Bool.not(Bool.and(hot, Bool.and(open, Bool.not(String.eq(tt, self))))), [], - _u => Scan.cite(_, tt, line, col, self, lead, path)) == Nil{} : List<&2, F.Finding>} - scan.stop_nil(Bool.not(Bool.and(hot, Bool.and(open, Bool.not(String.eq(tt, self)))))) - -# a call reports nothing when nothing is carried, whatever its callee's kind -law scan.hit_nil: - for kk: Lex.TokKind - for +hot: Bool - for +open: Bool - for +tt: String - for +line: U32 - for +col: U32 - for +kids: Tree.Node - for +self: String - for +ws: List<&2, Scan.Walk> - for +lead: String - for +path: String - {Scan.call_hit(kk, hot, open, tt, line, col, kids, self, ws, [], lead, path) == Nil{} : List<&2, F.Finding>} - -def scan.hit_nil(kk, hot, open, tt, line, col, kids, self, ws, lead, path): - match kk: - case Lex.TName{}: - scan.on_nil(hot, open, tt, line, col, kids, self, ws, lead, path) - case Lex.TUpper{}: - {==} - case Lex.TDotted{}: - scan.on_nil(hot, open, tt, line, col, kids, self, ws, lead, path) - case Lex.TWild{}: - {==} - case Lex.TKey{}: - {==} - case Lex.TNum{}: - {==} - case Lex.TStr{}: - {==} - case Lex.TChar{}: - {==} - case Lex.TComment{}: - {==} - case Lex.TSpace{}: - {==} - case Lex.TNewline{}: - {==} - case Lex.TOp{}: - {==} - case Lex.TColon{}: - {==} - case Lex.TEq{}: - {==} - case Lex.TBind{}: - {==} - case Lex.TArrow{}: - {==} - case Lex.TLam{}: - {==} - case Lex.TAll{}: - {==} - case Lex.TAmp{}: - {==} - case Lex.TOpen{}: - {==} - case Lex.TClose{}: - {==} - case Lex.TComma{}: - {==} - -def Laws.scan_quiet(nn, self, ws, lead, path, hot): - match nn: - case Tree.Leaf{tok}: - {==} - case Tree.Group{o, gk, cl}: - {==} - case Tree.Stmt{kind, sk, sb}: - {==} - case Tree.NNil{}: - {==} - case Tree.NCons{h, +rest}: - match h: - case Tree.Leaf{Lex.Tok{+k, +t, +l, +c}}: - match rest: - case Tree.Leaf{tok}: - {==} - case Tree.Group{o, gk, cl}: - {==} - case Tree.Stmt{kind, sk, sb}: - {==} - case Tree.NNil{}: - {==} - case Tree.NCons{rh, +rr}: - match rh: - case Tree.Group{Lex.Tok{gk, +o, gl, gc}, +kids, cl}: - scan.nil3(Scan.call_hit(k, Hoist.warm(hot, k), String.eq(o, "("), t, l, c, kids, self, ws, [], lead, - path), - Scan.walk(kids, self, ws, [], lead, path, Hoist.warm(hot, k)), - Scan.walk(rr, self, ws, [], lead, path, Hoist.warm(hot, k)), - scan.hit_nil(k, Hoist.warm(hot, k), String.eq(o, "("), t, l, c, kids, self, ws, lead, path), - Laws.scan_quiet(kids, self, ws, lead, path, Hoist.warm(hot, k)), Laws.scan_quiet(rr, self, ws, - lead, path, Hoist.warm(hot, k))) - case Tree.Leaf{tok}: - Laws.scan_quiet(rest, self, ws, lead, path, Hoist.warm(hot, k)) - case Tree.Stmt{kind, sk, sb}: - Laws.scan_quiet(rest, self, ws, lead, path, Hoist.warm(hot, k)) - case Tree.NNil{}: - Laws.scan_quiet(rest, self, ws, lead, path, Hoist.warm(hot, k)) - case Tree.NCons{nh, nt}: - Laws.scan_quiet(rest, self, ws, lead, path, Hoist.warm(hot, k)) - case Tree.Group{o, +kids, cl}: - scan.nil2(Scan.walk(kids, self, ws, [], lead, path, hot), Scan.walk(rest, self, ws, [], lead, path, hot), - Laws.scan_quiet(kids, self, ws, lead, path, hot), Laws.scan_quiet(rest, self, ws, lead, path, hot)) - case Tree.Stmt{kind, +kids, +body}: - match kind: - case Tree.SDef{}: - scan.nil3(Scan.walk(kids, self, ws, [], lead, path, hot), Scan.walk(body, self, ws, [], lead, path, hot), - Scan.walk(rest, self, ws, [], lead, path, hot), - Laws.scan_quiet(kids, self, ws, lead, path, hot), Laws.scan_quiet(body, self, ws, lead, path, - hot), Laws.scan_quiet(rest, self, ws, lead, path, hot)) - case Tree.SType{}: - scan.nil3(Scan.walk(kids, self, ws, [], lead, path, hot), Scan.walk(body, self, ws, [], lead, path, hot), - Scan.walk(rest, self, ws, [], lead, path, hot), - Laws.scan_quiet(kids, self, ws, lead, path, hot), Laws.scan_quiet(body, self, ws, lead, path, - hot), Laws.scan_quiet(rest, self, ws, lead, path, hot)) - case Tree.SLaw{}: - scan.nil3(Scan.walk(kids, self, ws, [], lead, path, hot), Scan.walk(body, self, ws, [], lead, path, hot), - Scan.walk(rest, self, ws, [], lead, path, hot), - Laws.scan_quiet(kids, self, ws, lead, path, hot), Laws.scan_quiet(body, self, ws, lead, path, - hot), Laws.scan_quiet(rest, self, ws, lead, path, hot)) - case Tree.SImport{}: - scan.nil3(Scan.walk(kids, self, ws, [], lead, path, hot), Scan.walk(body, self, ws, [], lead, path, hot), - Scan.walk(rest, self, ws, [], lead, path, hot), - Laws.scan_quiet(kids, self, ws, lead, path, hot), Laws.scan_quiet(body, self, ws, lead, path, - hot), Laws.scan_quiet(rest, self, ws, lead, path, hot)) - case Tree.SCase{}: - scan.nil3(Scan.walk(kids, self, ws, [], lead, path, False{}), Scan.walk(body, self, ws, [], lead, path, - Bool.and(hot, Calls.calls(body, self))), Scan.walk(rest, self, ws, [], lead, path, hot), - Laws.scan_quiet(kids, self, ws, lead, path, False{}), Laws.scan_quiet(body, self, ws, lead, path, - Bool.and(hot, Calls.calls(body, self))), Laws.scan_quiet(rest, self, ws, lead, path, hot)) - case Tree.SFor{}: - scan.nil3(Scan.walk(kids, self, ws, [], lead, path, hot), Scan.walk(body, self, ws, [], lead, path, hot), - Scan.walk(rest, self, ws, [], lead, path, hot), - Laws.scan_quiet(kids, self, ws, lead, path, hot), Laws.scan_quiet(body, self, ws, lead, path, - hot), Laws.scan_quiet(rest, self, ws, lead, path, hot)) - case Tree.SLet{}: - scan.nil3(Scan.walk(kids, self, ws, [], lead, path, hot), Scan.walk(body, self, ws, [], lead, path, hot), - Scan.walk(rest, self, ws, [], lead, path, hot), - Laws.scan_quiet(kids, self, ws, lead, path, hot), Laws.scan_quiet(body, self, ws, lead, path, - hot), Laws.scan_quiet(rest, self, ws, lead, path, hot)) - case Tree.STerm{}: - scan.nil3(Scan.walk(kids, self, ws, [], lead, path, hot), Scan.walk(body, self, ws, [], lead, path, hot), - Scan.walk(rest, self, ws, [], lead, path, hot), - Laws.scan_quiet(kids, self, ws, lead, path, hot), Laws.scan_quiet(body, self, ws, lead, path, - hot), Laws.scan_quiet(rest, self, ws, lead, path, hot)) - case Tree.NNil{}: - Laws.scan_quiet(rest, self, ws, lead, path, hot) - case Tree.NCons{nh, nt}: - Laws.scan_quiet(rest, self, ws, lead, path, hot) - -# whether an answer has a name -def scan.some(mm: Maybe<&2, String>) -> Bool: - match mm: - case None{}: - False{} - case Some{nm}: - True{} - -# a finding exactly when there is a name -law scan.cite_len: - for mm: Maybe<&2, String> - for +tt: String - for +line: U32 - for +col: U32 - for +self: String - for +lead: String - for +path: String - {List.length(&2, F.Finding, Scan.cite(mm, tt, line, col, self, lead, path)) == Bool.pick(Nat, scan.some(mm), 1n, 0n) - : Nat} - -def scan.cite_len(mm, _tt, _line, _col, _self, _lead, _path): - match mm: - case None{}: - {==} - case Some{nm}: - {==} - -# the first of two answers has a name when either has -law scan.first_some: - for aa: Maybe<&2, String> - for bb: Maybe<&2, String> - {scan.some(Scan.first(aa, bb)) == Bool.or(scan.some(aa), scan.some(bb)) : Bool} - -def scan.first_some(aa, _bb): - match aa: - case None{}: - {==} - case Some{nm}: - {==} - -# a kept name is there exactly when the list holds it -law scan.pick_some: - for b: Bool - for +nm: String - {scan.some(Bool.pick(Maybe<&2, String>, b, Some{nm}, None{})) == b : Bool} - -def scan.pick_some(b, _nm): - match b: - case True{}: - {==} - case False{}: - {==} - -# a token kept against the names is the law's named token -law scan.named: - for tok: Lex.Tok - for +carried: List<&2, String> - {scan.some(Scan.keep_in(Scan.name_tok(tok), carried)) == Laws.scan.named(tok, carried) : Bool} - -def scan.named(tok, carried): - match tok: - case Lex.Tok{k, +t, l, c}: - match k: - case Lex.TName{}: - scan.pick_some(List.contains(~String, ~String.eq, carried, t), t) - case Lex.TUpper{}: - {==} - case Lex.TDotted{}: - {==} - case Lex.TWild{}: - {==} - case Lex.TKey{}: - {==} - case Lex.TNum{}: - {==} - case Lex.TStr{}: - {==} - case Lex.TChar{}: - {==} - case Lex.TComment{}: - {==} - case Lex.TSpace{}: - {==} - case Lex.TNewline{}: - {==} - case Lex.TOp{}: - {==} - case Lex.TColon{}: - {==} - case Lex.TEq{}: - {==} - case Lex.TBind{}: - {==} - case Lex.TArrow{}: - {==} - case Lex.TLam{}: - {==} - case Lex.TAll{}: - {==} - case Lex.TAmp{}: - {==} - case Lex.TOpen{}: - {==} - case Lex.TClose{}: - {==} - case Lex.TComma{}: - {==} - -# an argument kept against the names is the law's lone name -law scan.lone: - for nn: Tree.Node - for +carried: List<&2, String> - {scan.some(Scan.keep_in(Scan.name_of(nn), carried)) == Laws.scan.lone(nn, carried) : Bool} - -def scan.lone(nn, carried): - match nn: - case Tree.NCons{h, r}: - match h: - case Tree.Leaf{tok}: - match r: - case Tree.NNil{}: - scan.named(tok, carried) - case Tree.Leaf{x}: - {==} - case Tree.Group{x, y, z}: - {==} - case Tree.Stmt{x, y, z}: - {==} - case Tree.NCons{x, y}: - {==} - case Tree.Group{x, y, z}: - {==} - case Tree.Stmt{x, y, z}: - {==} - case Tree.NNil{}: - {==} - case Tree.NCons{x, y}: - {==} - case Tree.Leaf{x}: - {==} - case Tree.Group{x, y, z}: - {==} - case Tree.Stmt{x, y, z}: - {==} - case Tree.NNil{}: - {==} - -# a found step's two sides are the law's -law scan.found.or: - for +arg: Tree.Node - for rest: List<&2, Nat> - for +as: List<&2, Tree.Node> - for +carried: List<&2, String> - for ih: {scan.some(Scan.found(rest, as, carried)) == Laws.scan.held(rest, as, carried) : Bool} - {Bool.or(scan.some(Scan.keep_in(Scan.name_of(arg), carried)), scan.some(Scan.found(rest, as, carried))) - == Bool.or(Laws.scan.lone(arg, carried), Laws.scan.held(rest, as, carried)) : Bool} - -def scan.found.or(arg, rest, as, carried, ih): - %scan.lone(arg, carried) : {Bool.or(scan.some(Scan.keep_in(Scan.name_of(arg), carried)), - scan.some(Scan.found(rest, as, carried))) == Bool.or(_, Laws.scan.held(rest, as, carried)) : Bool} - %ih : {Bool.or(scan.some(Scan.keep_in(Scan.name_of(arg), carried)), - scan.some(Scan.found(rest, as, carried))) == Bool.or(scan.some(Scan.keep_in(Scan.name_of(arg), carried)), _) - : Bool} - {==} - -# a walked argument is found exactly when the law's held says so -law scan.found: - for ss: List<&2, Nat> - for +as: List<&2, Tree.Node> - for +carried: List<&2, String> - {scan.some(Scan.found(ss, as, carried)) == Laws.scan.held(ss, as, carried) : Bool} - -def scan.found(ss, as, carried): - match ss: - case Nil{}: - {==} - case Con{+at, +rest}: - +arg = Calls.arg(as, at) - +lh = Laws.scan.lone(arg, carried) - Equal.trans(Bool, scan.some(Scan.first(Scan.keep_in(Scan.name_of(arg), carried), Scan.found(rest, as, carried))), - Bool.or(scan.some(Scan.keep_in(Scan.name_of(arg), carried)), scan.some(Scan.found(rest, as, carried))), - Bool.or(lh, Laws.scan.held(rest, as, carried)), - scan.first_some(Scan.keep_in(Scan.name_of(arg), carried), Scan.found(rest, as, carried)), - scan.found.or(arg, rest, as, carried, scan.found(rest, as, carried))) - -# a gated finding list counts when the gate opens and it counts -law scan.gate_len: - for hot: Bool - for open: Bool - for q: Bool - for -x: List<&2, F.Finding> - for -hb: Bool - for ex: {List.length(&2, F.Finding, x) == Bool.pick(Nat, hb, 1n, 0n) : Nat} - {List.length(&2, F.Finding, Lazy.stop(List<&2, F.Finding>, Bool.not(Bool.and(hot, Bool.and(open, Bool.not(q)))), [], - _u => x)) == Bool.pick(Nat, Bool.and(hot, Bool.and(open, Bool.and(Bool.not(q), hb))), 1n, 0n) : Nat} - -def scan.gate_len(hot, open, q, _x, _hb, ex): - match hot: - case False{}: - {==} - case True{}: - match open: - case False{}: - {==} - case True{}: - match q: - case True{}: - {==} - case False{}: - ex - -# a named call's finding count is the law's -law scan.on_len: - for +hot: Bool - for +open: Bool - for +tt: String - for +line: U32 - for +col: U32 - for +kids: Tree.Node - for +self: String - for +ws: List<&2, Scan.Walk> - for +carried: List<&2, String> - for +lead: String - for +path: String - {List.length(&2, F.Finding, Scan.on_name(hot, open, tt, line, col, kids, self, ws, carried, lead, path)) - == Bool.pick(Nat, Bool.and(hot, Bool.and(open, Bool.and(Bool.not(String.eq(tt, self)), - Laws.scan.held(Scan.slots(tt, ws), Calls.args(kids), carried)))), 1n, 0n) : Nat} - -def scan.on_len(hot, open, tt, line, col, kids, self, ws, carried, lead, path): - +fd = Scan.found(Scan.slots(tt, ws), Calls.args(kids), carried) - +hb = Laws.scan.held(Scan.slots(tt, ws), Calls.args(kids), carried) - scan.gate_len(hot, open, String.eq(tt, self), Scan.cite(fd, tt, line, col, self, lead, path), hb, - Equal.trans(Nat, List.length(&2, F.Finding, Scan.cite(fd, tt, line, col, self, lead, path)), - Bool.pick(Nat, scan.some(fd), 1n, 0n), Bool.pick(Nat, hb, 1n, 0n), - scan.cite_len(fd, tt, line, col, self, lead, path), - Equal.cong(Bool, Nat, b => Bool.pick(Nat, b, 1n, 0n), scan.some(fd), hb, - scan.found(Scan.slots(tt, ws), Calls.args(kids), carried)))) - -# a call's finding count is the law's, whatever its callee's kind -law scan.hit_len: - for kk: Lex.TokKind - for +hot: Bool - for +open: Bool - for +tt: String - for +line: U32 - for +col: U32 - for +kids: Tree.Node - for +self: String - for +ws: List<&2, Scan.Walk> - for +carried: List<&2, String> - for +lead: String - for +path: String - {List.length(&2, F.Finding, Scan.call_hit(kk, hot, open, tt, line, col, kids, self, ws, carried, lead, path)) - == Bool.pick(Nat, Laws.scan.call(kk, hot, open, tt, kids, self, ws, carried), 1n, 0n) : Nat} - -def scan.hit_len(kk, hot, open, tt, line, col, kids, self, ws, carried, lead, path): - match kk: - case Lex.TName{}: - scan.on_len(hot, open, tt, line, col, kids, self, ws, carried, lead, path) - case Lex.TUpper{}: - {==} - case Lex.TDotted{}: - scan.on_len(hot, open, tt, line, col, kids, self, ws, carried, lead, path) - case Lex.TWild{}: - {==} - case Lex.TKey{}: - {==} - case Lex.TNum{}: - {==} - case Lex.TStr{}: - {==} - case Lex.TChar{}: - {==} - case Lex.TComment{}: - {==} - case Lex.TSpace{}: - {==} - case Lex.TNewline{}: - {==} - case Lex.TOp{}: - {==} - case Lex.TColon{}: - {==} - case Lex.TEq{}: - {==} - case Lex.TBind{}: - {==} - case Lex.TArrow{}: - {==} - case Lex.TLam{}: - {==} - case Lex.TAll{}: - {==} - case Lex.TAmp{}: - {==} - case Lex.TOpen{}: - {==} - case Lex.TClose{}: - {==} - case Lex.TComma{}: - {==} - -# scan's walk counts the calls the law counts, at every heat -law scan.walk: - for nn: Tree.Node - for +self: String - for +ws: List<&2, Scan.Walk> - for +carried: List<&2, String> - for +lead: String - for +path: String - for +hot: Bool - {List.length(&2, F.Finding, Scan.walk(nn, self, ws, carried, lead, path, hot)) - == Laws.scan.count(nn, self, ws, carried, hot) : Nat} - -def scan.walk(nn, self, ws, carried, lead, path, hot): - match nn: - case Tree.Leaf{tok}: - {==} - case Tree.Group{o, gk, cl}: - {==} - case Tree.Stmt{kind, sk, sb}: - {==} - case Tree.NNil{}: - {==} - case Tree.NCons{h, +rest}: - match h: - case Tree.Leaf{Lex.Tok{+k, +t, +l, +c}}: - match rest: - case Tree.Leaf{tok}: - {==} - case Tree.Group{o, gk, cl}: - {==} - case Tree.Stmt{kind, sk, sb}: - {==} - case Tree.NNil{}: - {==} - case Tree.NCons{rh, +rr}: - match rh: - case Tree.Group{Lex.Tok{gk, +o, gl, gc}, +kids, cl}: - M1.chars.sum(Scan.call_hit(k, Hoist.warm(hot, k), String.eq(o, "("), t, l, c, kids, self, ws, carried, - lead, path), - Scan.walk(kids, self, ws, carried, lead, path, Hoist.warm(hot, k)), - Scan.walk(rr, self, ws, carried, lead, path, Hoist.warm(hot, k)), - Bool.pick(Nat, Laws.scan.call(k, Hoist.warm(hot, k), String.eq(o, "("), t, kids, self, ws, - carried), 1n, 0n), - Laws.scan.count(kids, self, ws, carried, Hoist.warm(hot, k)), Laws.scan.count(rr, self, ws, - carried, Hoist.warm(hot, k)), - scan.hit_len(k, Hoist.warm(hot, k), String.eq(o, "("), t, l, c, kids, self, ws, carried, lead, - path), - scan.walk(kids, self, ws, carried, lead, path, Hoist.warm(hot, k)), - scan.walk(rr, self, ws, carried, lead, path, Hoist.warm(hot, k))) - case Tree.Leaf{tok}: - scan.walk(rest, self, ws, carried, lead, path, Hoist.warm(hot, k)) - case Tree.Stmt{kind, sk, sb}: - scan.walk(rest, self, ws, carried, lead, path, Hoist.warm(hot, k)) - case Tree.NNil{}: - scan.walk(rest, self, ws, carried, lead, path, Hoist.warm(hot, k)) - case Tree.NCons{nh, nt}: - scan.walk(rest, self, ws, carried, lead, path, Hoist.warm(hot, k)) - case Tree.Group{o, +kids, cl}: - M1.index.two(Scan.walk(kids, self, ws, carried, lead, path, hot), Scan.walk(rest, self, ws, carried, lead, - path, - hot), - Laws.scan.count(kids, self, ws, carried, hot), Laws.scan.count(rest, self, ws, carried, hot), - scan.walk(kids, self, ws, carried, lead, path, hot), scan.walk(rest, self, ws, carried, lead, path, hot)) - case Tree.Stmt{kind, +kids, +body}: - match kind: - case Tree.SDef{}: - M1.chars.sum(Scan.walk(kids, self, ws, carried, lead, path, hot), Scan.walk(body, self, ws, carried, lead, - path, hot), - Scan.walk(rest, self, ws, carried, lead, path, hot), Laws.scan.count(kids, self, ws, carried, hot), - Laws.scan.count(body, self, ws, carried, hot), Laws.scan.count(rest, self, ws, carried, hot), - scan.walk(kids, self, ws, carried, lead, path, hot), scan.walk(body, self, ws, carried, lead, path, - hot), - scan.walk(rest, self, ws, carried, lead, path, hot)) - case Tree.SType{}: - M1.chars.sum(Scan.walk(kids, self, ws, carried, lead, path, hot), Scan.walk(body, self, ws, carried, lead, - path, hot), - Scan.walk(rest, self, ws, carried, lead, path, hot), Laws.scan.count(kids, self, ws, carried, hot), - Laws.scan.count(body, self, ws, carried, hot), Laws.scan.count(rest, self, ws, carried, hot), - scan.walk(kids, self, ws, carried, lead, path, hot), scan.walk(body, self, ws, carried, lead, path, - hot), - scan.walk(rest, self, ws, carried, lead, path, hot)) - case Tree.SLaw{}: - M1.chars.sum(Scan.walk(kids, self, ws, carried, lead, path, hot), Scan.walk(body, self, ws, carried, lead, - path, hot), - Scan.walk(rest, self, ws, carried, lead, path, hot), Laws.scan.count(kids, self, ws, carried, hot), - Laws.scan.count(body, self, ws, carried, hot), Laws.scan.count(rest, self, ws, carried, hot), - scan.walk(kids, self, ws, carried, lead, path, hot), scan.walk(body, self, ws, carried, lead, path, - hot), - scan.walk(rest, self, ws, carried, lead, path, hot)) - case Tree.SImport{}: - M1.chars.sum(Scan.walk(kids, self, ws, carried, lead, path, hot), Scan.walk(body, self, ws, carried, lead, - path, hot), - Scan.walk(rest, self, ws, carried, lead, path, hot), Laws.scan.count(kids, self, ws, carried, hot), - Laws.scan.count(body, self, ws, carried, hot), Laws.scan.count(rest, self, ws, carried, hot), - scan.walk(kids, self, ws, carried, lead, path, hot), scan.walk(body, self, ws, carried, lead, path, - hot), - scan.walk(rest, self, ws, carried, lead, path, hot)) - case Tree.SCase{}: - M1.chars.sum(Scan.walk(kids, self, ws, carried, lead, path, False{}), Scan.walk(body, self, ws, carried, - lead, path, Bool.and(hot, Calls.calls(body, self))), - Scan.walk(rest, self, ws, carried, lead, path, hot), Laws.scan.count(kids, self, ws, carried, False{}), - Laws.scan.count(body, self, ws, carried, Bool.and(hot, Calls.calls(body, self))), Laws.scan.count(rest, - self, ws, carried, hot), - scan.walk(kids, self, ws, carried, lead, path, False{}), scan.walk(body, self, ws, carried, lead, path, - Bool.and(hot, Calls.calls(body, self))), - scan.walk(rest, self, ws, carried, lead, path, hot)) - case Tree.SFor{}: - M1.chars.sum(Scan.walk(kids, self, ws, carried, lead, path, hot), Scan.walk(body, self, ws, carried, lead, - path, hot), - Scan.walk(rest, self, ws, carried, lead, path, hot), Laws.scan.count(kids, self, ws, carried, hot), - Laws.scan.count(body, self, ws, carried, hot), Laws.scan.count(rest, self, ws, carried, hot), - scan.walk(kids, self, ws, carried, lead, path, hot), scan.walk(body, self, ws, carried, lead, path, - hot), - scan.walk(rest, self, ws, carried, lead, path, hot)) - case Tree.SLet{}: - M1.chars.sum(Scan.walk(kids, self, ws, carried, lead, path, hot), Scan.walk(body, self, ws, carried, lead, - path, hot), - Scan.walk(rest, self, ws, carried, lead, path, hot), Laws.scan.count(kids, self, ws, carried, hot), - Laws.scan.count(body, self, ws, carried, hot), Laws.scan.count(rest, self, ws, carried, hot), - scan.walk(kids, self, ws, carried, lead, path, hot), scan.walk(body, self, ws, carried, lead, path, - hot), - scan.walk(rest, self, ws, carried, lead, path, hot)) - case Tree.STerm{}: - M1.chars.sum(Scan.walk(kids, self, ws, carried, lead, path, hot), Scan.walk(body, self, ws, carried, lead, - path, hot), - Scan.walk(rest, self, ws, carried, lead, path, hot), Laws.scan.count(kids, self, ws, carried, hot), - Laws.scan.count(body, self, ws, carried, hot), Laws.scan.count(rest, self, ws, carried, hot), - scan.walk(kids, self, ws, carried, lead, path, hot), scan.walk(body, self, ws, carried, lead, path, - hot), - scan.walk(rest, self, ws, carried, lead, path, hot)) - case Tree.NNil{}: - scan.walk(rest, self, ws, carried, lead, path, hot) - case Tree.NCons{nh, nt}: - scan.walk(rest, self, ws, carried, lead, path, hot) - -# a def that calls itself and is not a proof gathers its walk, entered hot, -# any other nothing -law scan.def: - for g: Bool - for +sig: Tree.Node - for +body: Tree.Node - for +name: String - for +ws: List<&2, Scan.Walk> - for +path: String - {List.length(&2, F.Finding, Lazy.stop(List<&2, F.Finding>, Bool.not(g), [], _u => Scan.run(sig, body, name, ws, - path))) - == Bool.pick(Nat, g, Laws.scan.count(body, name, ws, Hoist.carried(sig, body, name), True{}), 0n) : Nat} - -def scan.def(g, sig, body, name, ws, path): - match g: - case True{}: - +carried = Hoist.carried(sig, body, name) - scan.walk(body, name, ws, carried, Scan.lead.go(Calls.params(sig), carried), path, True{}) - case False{}: - {==} - -# over the defs, the rule's join counts what it gathered and what the law -# counts -law scan.go: - for ds: List<&2, Calls.Def> - for +ws: List<&2, Scan.Walk> - for +path: String - for +acc: List<&2, List<&2, F.Finding>> - {List.length(&2, F.Finding, Scan.check.go(ds, ws, path, acc)) - == Nat.add(M1.index.sum(acc), Laws.scan.total(ds, ws, path)) : Nat} - -def scan.go(ds, ws, path, acc): - match ds: - case Nil{}: - M1.index.flat(acc, Nil{}) - case Con{Calls.Def{+name, +sig, +body}, +rest}: - +g = Bool.and(Calls.calls(body, name), Bool.not(Calls.exempt(path, sig))) - +x = Lazy.stop(List<&2, F.Finding>, Bool.not(g), [], _u => Scan.run(sig, body, name, ws, path)) - +n = Bool.pick(Nat, g, Laws.scan.count(body, name, ws, Hoist.carried(sig, body, name), True{}), 0n) - +tot = Laws.scan.total(rest, ws, path) - Equal.trans(Nat, - List.length(&2, F.Finding, Scan.check.go(rest, ws, path, x <> acc)), - Nat.add(Nat.add(M1.index.sum(acc), List.length(&2, F.Finding, x)), tot), - Nat.add(M1.index.sum(acc), Nat.add(n, tot)), - scan.go(rest, ws, path, x <> acc), - Equal.trans(Nat, - Nat.add(Nat.add(M1.index.sum(acc), List.length(&2, F.Finding, x)), tot), - Nat.add(M1.index.sum(acc), Nat.add(List.length(&2, F.Finding, x), tot)), - Nat.add(M1.index.sum(acc), Nat.add(n, tot)), - M1.index.assoc(M1.index.sum(acc), List.length(&2, F.Finding, x), tot), - Equal.cong(Nat, Nat, z => Nat.add(M1.index.sum(acc), Nat.add(z, tot)), List.length(&2, F.Finding, x), n, - scan.def(g, sig, body, name, ws, path)))) - -def Laws.scan_counts(path, _text, _toks, tree, _bound, _items, e): - +ds = Calls.defs(tree) - %Equal.sym(Bool, Paths.is_law_file(path), False{}, e) : {List.length(&2, F.Finding, Lazy.stop(List<&2, F.Finding>, - _, [], _u => Scan.check.on(ds, path))) == Laws.scan.total(ds, Scan.walks(ds), path) : Nat} - scan.go(ds, Scan.walks(ds), path, Nil{}) diff --git a/proofs/ScopeExemptionsFollowPaths.bend b/proofs/ScopeExemptionsFollowPaths.bend index 2c0972d..fec4b1f 100644 --- a/proofs/ScopeExemptionsFollowPaths.bend +++ b/proofs/ScopeExemptionsFollowPaths.bend @@ -12,13 +12,9 @@ import ../src/rules/correctness/pick.bend as Pick import ../src/rules/pedantic/tail.bend as Tail import ../src/rules/suspicious/concat.bend as Concat import ../src/rules/suspicious/eager.bend as Eager -import ../src/rules/suspicious/rewalk.bend as Rewalk -import ../src/rules/suspicious/strict.bend as Strict -import ../src/rules/suspicious/hoist.bend as Hoist -import ../src/rules/suspicious/index.bend as Index -import ../src/rules/suspicious/ring.bend as Ring -import ../src/rules/suspicious/table.bend as Table -import ../src/rules/suspicious/unit.bend as UnitRule +import ../src/rules/pedantic/rewalk.bend as Rewalk +import ../src/rules/pedantic/strict.bend as Strict +import ../src/rules/pedantic/index.bend as Index import ../src/rules/laws/closed.bend as Closed import ../src/rules/suspicious/thunk.bend as Thunk import ../laws/ExemptPathsYieldNoFindings.bend as L1 @@ -82,66 +78,36 @@ def Laws.scope_law_file(path, text, toks, tree, bound, items, text2, toks2, tree scope.con(Pick.check(s), Pick.check(t), L1.pick_exempt(path, text, toks, tree, bound, items, e), L1.pick_exempt(path, text2, toks2, tree2, bound2, items2, e), - [Tail.check(s), Concat.check(s), Eager.check(s), Rewalk.check(s), Strict.check(s), - Hoist.check(s), Index.check(s), Ring.check(s), Table.check(s), UnitRule.check(s), Thunk.check(s)], - [Tail.check(t), Concat.check(t), Eager.check(t), Rewalk.check(t), Strict.check(t), - Hoist.check(t), Index.check(t), Ring.check(t), Table.check(t), UnitRule.check(t), Thunk.check(t)], + [Tail.check(s), Concat.check(s), Eager.check(s), Rewalk.check(s), Strict.check(s), Index.check(s), Thunk.check(s)], + [Tail.check(t), Concat.check(t), Eager.check(t), Rewalk.check(t), Strict.check(t), Index.check(t), Thunk.check(t)], scope.con(Tail.check(s), Tail.check(t), L1.tail_exempt(path, text, toks, tree, bound, items, e), L1.tail_exempt(path, text2, toks2, tree2, bound2, items2, e), - [Concat.check(s), Eager.check(s), Rewalk.check(s), Strict.check(s), Hoist.check(s), - Index.check(s), Ring.check(s), Table.check(s), UnitRule.check(s), Thunk.check(s)], - [Concat.check(t), Eager.check(t), Rewalk.check(t), Strict.check(t), Hoist.check(t), - Index.check(t), Ring.check(t), Table.check(t), UnitRule.check(t), Thunk.check(t)], + [Concat.check(s), Eager.check(s), Rewalk.check(s), Strict.check(s), Index.check(s), Thunk.check(s)], + [Concat.check(t), Eager.check(t), Rewalk.check(t), Strict.check(t), Index.check(t), Thunk.check(t)], scope.con(Concat.check(s), Concat.check(t), L1.concat_exempt(path, text, toks, tree, bound, items, e), L1.concat_exempt(path, text2, toks2, tree2, bound2, items2, e), - [Eager.check(s), Rewalk.check(s), Strict.check(s), Hoist.check(s), Index.check(s), - Ring.check(s), Table.check(s), UnitRule.check(s), Thunk.check(s)], - [Eager.check(t), Rewalk.check(t), Strict.check(t), Hoist.check(t), Index.check(t), - Ring.check(t), Table.check(t), UnitRule.check(t), Thunk.check(t)], + [Eager.check(s), Rewalk.check(s), Strict.check(s), Index.check(s), Thunk.check(s)], + [Eager.check(t), Rewalk.check(t), Strict.check(t), Index.check(t), Thunk.check(t)], scope.con(Eager.check(s), Eager.check(t), L1.eager_exempt(path, text, toks, tree, bound, items, e), L1.eager_exempt(path, text2, toks2, tree2, bound2, items2, e), - [Rewalk.check(s), Strict.check(s), Hoist.check(s), Index.check(s), Ring.check(s), - Table.check(s), UnitRule.check(s), Thunk.check(s)], - [Rewalk.check(t), Strict.check(t), Hoist.check(t), Index.check(t), Ring.check(t), - Table.check(t), UnitRule.check(t), Thunk.check(t)], + [Rewalk.check(s), Strict.check(s), Index.check(s), Thunk.check(s)], + [Rewalk.check(t), Strict.check(t), Index.check(t), Thunk.check(t)], scope.con(Rewalk.check(s), Rewalk.check(t), L1.rewalk_exempt(path, text, toks, tree, bound, items, e), L1.rewalk_exempt(path, text2, toks2, tree2, bound2, items2, e), - [Strict.check(s), Hoist.check(s), Index.check(s), Ring.check(s), Table.check(s), - UnitRule.check(s), Thunk.check(s)], - [Strict.check(t), Hoist.check(t), Index.check(t), Ring.check(t), Table.check(t), - UnitRule.check(t), Thunk.check(t)], + [Strict.check(s), Index.check(s), Thunk.check(s)], + [Strict.check(t), Index.check(t), Thunk.check(t)], scope.con(Strict.check(s), Strict.check(t), L1.strict_exempt(path, text, toks, tree, bound, items, e), L1.strict_exempt(path, text2, toks2, tree2, bound2, items2, e), - [Hoist.check(s), Index.check(s), Ring.check(s), Table.check(s), UnitRule.check(s), Thunk.check(s)], - [Hoist.check(t), Index.check(t), Ring.check(t), Table.check(t), UnitRule.check(t), Thunk.check(t)], - scope.con(Hoist.check(s), Hoist.check(t), - L1.hoist_exempt(path, text, toks, tree, bound, items, e), - L1.hoist_exempt(path, text2, toks2, tree2, bound2, items2, e), - [Index.check(s), Ring.check(s), Table.check(s), UnitRule.check(s), Thunk.check(s)], - [Index.check(t), Ring.check(t), Table.check(t), UnitRule.check(t), Thunk.check(t)], + [Index.check(s), Thunk.check(s)], + [Index.check(t), Thunk.check(t)], scope.con(Index.check(s), Index.check(t), L1.index_exempt(path, text, toks, tree, bound, items, e), L1.index_exempt(path, text2, toks2, tree2, bound2, items2, e), - [Ring.check(s), Table.check(s), UnitRule.check(s), Thunk.check(s)], - [Ring.check(t), Table.check(t), UnitRule.check(t), Thunk.check(t)], - scope.con(Ring.check(s), Ring.check(t), - L1.ring_exempt(path, text, toks, tree, bound, items, e), - L1.ring_exempt(path, text2, toks2, tree2, bound2, items2, e), - [Table.check(s), UnitRule.check(s), Thunk.check(s)], - [Table.check(t), UnitRule.check(t), Thunk.check(t)], - scope.con(Table.check(s), Table.check(t), - L1.table_exempt(path, text, toks, tree, bound, items, e), - L1.table_exempt(path, text2, toks2, tree2, bound2, items2, e), - [UnitRule.check(s), Thunk.check(s)], - [UnitRule.check(t), Thunk.check(t)], - scope.con(UnitRule.check(s), UnitRule.check(t), - L1.unit_exempt(path, text, toks, tree, bound, items, e), - L1.unit_exempt(path, text2, toks2, tree2, bound2, items2, e), [Thunk.check(s)], [Thunk.check(t)], scope.con(Thunk.check(s), Thunk.check(t), @@ -149,7 +115,7 @@ def Laws.scope_law_file(path, text, toks, tree, bound, items, text2, toks2, tree L1.thunk_exempt(path, text2, toks2, tree2, bound2, items2, e), Nil{}, Nil{}, - {==})))))))))))) + {==})))))))) # closed reports nothing outside a LAWS.bend law scope.closed: diff --git a/proofs/ScopeLawsGovernPackage.bend b/proofs/ScopeLawsGovernPackage.bend index d1f35f4..efba7e8 100644 --- a/proofs/ScopeLawsGovernPackage.bend +++ b/proofs/ScopeLawsGovernPackage.bend @@ -43,7 +43,7 @@ def scope3.go(ds, cl): match ds: case Nil{}: {==} - case Con{Digest.Digest{+path, +norm, _dir, _is_laws, _is_law_file, exempt, +tops, _says, _deps, _unsafes, _laws}, + case Con{Digest.Digest{+path, +norm, _dir, _is_laws, _is_law_file, exempt, +tops, _says, _deps, _laws}, +rest}: scope3.go.step(exempt, tops, cl, norm, path, Law.check.go(rest, cl), Laws.under_law(rest, cl), scope3.go(rest, cl)) @@ -78,7 +78,7 @@ def scope3.first(ds, all): match ds: case Nil{}: {==} - case Con{Digest.Digest{_path, _norm, +dir, is_laws, _is_law_file, _exempt, _tops, _says, _deps, _unsafes, _laws}, + case Con{Digest.Digest{_path, _norm, +dir, is_laws, _is_law_file, _exempt, _tops, _says, _deps, _laws}, +rest}: scope3.first.step(is_laws, all, dir, Law.law_dirs(rest), Laws.has_laws(rest), scope3.first(rest, all)) diff --git a/proofs/ScopeProjectRulesRunOnRoot.bend b/proofs/ScopeProjectRulesRunOnRoot.bend index 2ac160b..f5d654d 100644 --- a/proofs/ScopeProjectRulesRunOnRoot.bend +++ b/proofs/ScopeProjectRulesRunOnRoot.bend @@ -5,7 +5,6 @@ import ../src/rules.bend as Rules import ../src/role.bend as Role import ../src/rules/digest.bend as Digest import ../src/rules/laws/law.bend as Law -import ../src/rules/laws/unsafe.bend as Unsafe import ../laws/ScopeProjectRulesRunOnRoot.bend as Laws # with no root but `.`, every directory's package is `.` @@ -29,20 +28,20 @@ def pk.none(ds): match ds: case Nil{}: {==} - case Con{Digest.Digest{+path, +norm, +dir, +is_laws, +is_law_file, +exempt, +tops, +says, +deps, +unsafes, +laws}, + case Con{Digest.Digest{+path, +norm, +dir, +is_laws, +is_law_file, +exempt, +tops, +says, +deps, +laws}, +tl}: o = Equal.sym(String, Rules.owner.go(Rules.ups(dir), []), "", pk.go_none(Rules.ups(dir))) %o : {Bool.pick(List<&2, Digest.Digest>, String.eq(_, ""), - Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, unsafes, laws} + Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, laws} <> Rules.members(tl, [], ""), Rules.members(tl, [], "")) - == Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, unsafes, laws} <> tl + == Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, laws} <> tl : List<&2, Digest.Digest>} Equal.cong(List<&2, Digest.Digest>, List<&2, Digest.Digest>, - xs => Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, unsafes, laws} <> xs, + xs => Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, laws} <> xs, Rules.members(tl, [], ""), tl, pk.none(tl)) def Laws.one_package(s, ss): +ds = Digest.all.role(Role.marks(s <> ss), s <> ss) Equal.cong(List<&2, Digest.Digest>, List<&2, F.Finding>, - mm => List.concat(&2, F.Finding, [Law.check(mm), [], Unsafe.check(ds)]), + mm => List.concat(&2, F.Finding, [Law.check(mm), []]), Rules.members(ds, [], ""), ds, pk.none(ds)) diff --git a/proofs/StrictReportsEagerSelfCalls.bend b/proofs/StrictReportsEagerSelfCalls.bend index ef0e0eb..58485c4 100644 --- a/proofs/StrictReportsEagerSelfCalls.bend +++ b/proofs/StrictReportsEagerSelfCalls.bend @@ -6,7 +6,7 @@ import ../src/lazy/lazy.bend as Lazy import ../src/syntax/tree.bend as Tree import ../src/rules/calls.bend as Calls import ../src/rules/correctness/pick.bend as Pick -import ../src/rules/suspicious/strict.bend as Strict +import ../src/rules/pedantic/strict.bend as Strict import ../laws/StrictReportsEagerSelfCalls.bend as Laws import ./lemmas/Nat.bend as M1 import ./lemmas/Pick.bend as M2 diff --git a/proofs/StringsReportLongLiteralArms.bend b/proofs/StringsReportLongLiteralArms.bend index 9360abd..0055589 100644 --- a/proofs/StringsReportLongLiteralArms.bend +++ b/proofs/StringsReportLongLiteralArms.bend @@ -5,7 +5,7 @@ import ../src/finding.bend as F import ../src/rules/tokens.bend as T import ../src/syntax/tree.bend as Tree import ../src/rules/correctness/pick.bend as Pick -import ../src/rules/correctness/strings.bend as Strings +import ../src/rules/perf/strings.bend as Strings import ../laws/StringsReportLongLiteralArms.bend as Laws import ../laws/defs/MatchArmRules.bend as D1 import ./lemmas/MatchStatementCounts.bend as M1 diff --git a/proofs/TableReportsFixedTableAccess.bend b/proofs/TableReportsFixedTableAccess.bend deleted file mode 100644 index 7a4f52d..0000000 --- a/proofs/TableReportsFixedTableAccess.bend +++ /dev/null @@ -1,123 +0,0 @@ -# proofs of laws/TableReportsFixedTableAccess.bend -import Base -import ../src/syntax/lex.bend as Lex -import ../src/finding.bend as F -import ../src/lazy/lazy.bend as Lazy -import ../src/syntax/tree.bend as Tree -import ../src/rules/calls.bend as Calls -import ../src/rules/correctness/pick.bend as Pick -import ../src/rules/suspicious/table.bend as Table -import ../laws/TableReportsFixedTableAccess.bend as Laws -import ./lemmas/Pick.bend as M1 - -# a call site's own finding: one exactly when the law's site test holds -law table.one: - for +tt: String - for +o: String - for +kids: Tree.Node - for +fixed: List<&2, String> - for +path: String - for +line: U32 - for +col: U32 - {List.length(&2, F.Finding, Table.hit(String.eq(o, "("), tt, kids, fixed, path, line, col)) - == Bool.pick(Nat, Laws.table.site(tt, o, kids, fixed), 1n, 0n) : Nat} - -def table.one(tt, o, kids, fixed, path, line, col): - M1.chars.one(Laws.table.site(tt, o, kids, fixed), - Table.cite(String.eq(tt, "List.get"), path, line, col, U32.from_nat(String.length(tt)))) - -def Laws.table_walk_counts(nn, path, fixed): - match nn: - case Tree.NCons{h, +rest}: - match h: - case Tree.Leaf{Lex.Tok{k, +t, +l, +c}}: - match rest: - case Tree.NCons{h2, +r2}: - match h2: - case Tree.Group{Lex.Tok{k2, +o, l2, c2}, +kids, cl}: - M1.table.three(Table.hit(String.eq(o, "("), t, kids, fixed, path, l, c), Table.walk(kids, path, - fixed), - Table.walk(r2, path, fixed), Bool.pick(Nat, Laws.table.site(t, o, kids, fixed), 1n, 0n), - Laws.table.count(kids, fixed), Laws.table.count(r2, fixed), - table.one(t, o, kids, fixed, path, l, c), - Laws.table_walk_counts(kids, path, fixed), Laws.table_walk_counts(r2, path, fixed)) - case Tree.Leaf{x}: - Laws.table_walk_counts(rest, path, fixed) - case Tree.Stmt{a, b, d}: - Laws.table_walk_counts(rest, path, fixed) - case Tree.NNil{}: - Laws.table_walk_counts(rest, path, fixed) - case Tree.NCons{a, b}: - Laws.table_walk_counts(rest, path, fixed) - case Tree.Leaf{x}: - Laws.table_walk_counts(rest, path, fixed) - case Tree.Group{a, b, d}: - Laws.table_walk_counts(rest, path, fixed) - case Tree.Stmt{a, b, d}: - Laws.table_walk_counts(rest, path, fixed) - case Tree.NNil{}: - Laws.table_walk_counts(rest, path, fixed) - case Tree.Group{open, +kids, close}: - M1.table.two(Table.walk(kids, path, fixed), Table.walk(rest, path, fixed), - Laws.table.count(kids, fixed), Laws.table.count(rest, fixed), - Laws.table_walk_counts(kids, path, fixed), Laws.table_walk_counts(rest, path, fixed)) - case Tree.Stmt{kind, +kids, +body}: - M1.table.three(Table.walk(kids, path, fixed), Table.walk(body, path, fixed), Table.walk(rest, path, fixed), - Laws.table.count(kids, fixed), Laws.table.count(body, fixed), Laws.table.count(rest, fixed), - Laws.table_walk_counts(kids, path, fixed), Laws.table_walk_counts(body, path, fixed), - Laws.table_walk_counts(rest, path, fixed)) - case Tree.NNil{}: - Laws.table_walk_counts(rest, path, fixed) - case Tree.NCons{a, b}: - Laws.table_walk_counts(rest, path, fixed) - case Tree.Leaf{x}: - {==} - case Tree.Group{a, b, d}: - {==} - case Tree.Stmt{a, b, d}: - {==} - case Tree.NNil{}: - {==} - -# one def's findings: walked when its guard keeps it, else none -law table.stop: - for +b: Bool - for +body: Tree.Node - for +path: String - for +fixed: List<&2, String> - {List.length(&2, F.Finding, Lazy.stop(List<&2, F.Finding>, Bool.not(b), [], _u => Table.walk(body, path, fixed))) - == Bool.pick(Nat, b, Laws.table.count(body, fixed), 0n) : Nat} - -def table.stop(b, body, path, fixed): - match b: - case True{}: - Laws.table_walk_counts(body, path, fixed) - case False{}: - {==} - -# table's def walk: the findings gathered so far, then each def's -law table.go: - for ds: List<&2, Calls.Def> - for +mods: List<&2, String> - for +path: String - for +acc: List<&2, List<&2, F.Finding>> - {List.length(&2, F.Finding, Table.check.go(ds, mods, path, acc)) == M1.pick.sum(acc, Laws.table.defs(ds, mods, path)) - : Nat} - -def table.go(ds, mods, path, acc): - match ds: - case Nil{}: - M1.pick.rev(acc, Nil{}) - case Con{Calls.Def{+name, +sig, +body}, +rest}: - +keep = Bool.and(Calls.calls(body, name), Bool.not(Calls.exempt(path, sig))) - +x = Lazy.stop(List<&2, F.Finding>, Bool.not(keep), [], _u => Table.walk(body, path, Table.scope(body, mods))) - +cb = Bool.pick(Nat, keep, Laws.table.count(body, Table.scope(body, mods)), 0n) - Equal.trans(Nat, List.length(&2, F.Finding, Table.check.go(rest, mods, path, x <> acc)), - M1.pick.sum(acc, Nat.add(List.length(&2, F.Finding, x), Laws.table.defs(rest, mods, path))), - M1.pick.sum(acc, Nat.add(cb, Laws.table.defs(rest, mods, path))), - table.go(rest, mods, path, x <> acc), - Equal.cong(Nat, Nat, n => M1.pick.sum(acc, Nat.add(n, Laws.table.defs(rest, mods, path))), - List.length(&2, F.Finding, x), cb, table.stop(keep, body, path, Table.scope(body, mods)))) - -def Laws.table_counts(path, _text, _toks, tree, _bound, _items): - table.go(Calls.defs(tree), Table.modules(Calls.defs(tree), []), path, Nil{}) diff --git a/proofs/UnitReportsMultiplyByOne.bend b/proofs/UnitReportsMultiplyByOne.bend deleted file mode 100644 index 5116bd5..0000000 --- a/proofs/UnitReportsMultiplyByOne.bend +++ /dev/null @@ -1,715 +0,0 @@ -# proofs of laws/UnitReportsMultiplyByOne.bend -import Base -import ../src/syntax/lex.bend as Lex -import ../src/finding.bend as F -import ../src/lazy/lazy.bend as Lazy -import ../src/syntax/tree.bend as Tree -import ../src/rules/calls.bend as Calls -import ../src/rules/correctness/pick.bend as Pick -import ../src/rules/suspicious/unit.bend as UnitRule -import ../laws/UnitReportsMultiplyByOne.bend as Laws -import ./lemmas/Pick.bend as M1 - -# unit (laws/UnitReportsMultiplyByOne.bend): the rule's literal, operator and call tests are the -# law's, read through whether a maybe holds a token (unit.has), and its walk -# counts the law's count. - -# does the maybe hold a token? -def unit.has(mm: Maybe<&2, Lex.Tok>) -> Bool: - match mm: - case None{}: - False{} - case Some{tok}: - True{} - -# a finding on the maybe's token: one when it holds one -law unit.cite: - for mm: Maybe<&2, Lex.Tok> - for +path: String - {List.length(&2, F.Finding, UnitRule.cite(mm, path)) == Bool.pick(Nat, unit.has(mm), 1n, 0n) : Nat} - -def unit.cite(mm, _path): - match mm: - case None{}: - {==} - case Some{tok}: - match tok: - case Lex.Tok{k, t, l, c}: - {==} - -# a pick between a token and none holds one exactly when it picks the token -law unit.hp: - for b: Bool - for -tok: Lex.Tok - {unit.has(Bool.pick(Maybe<&2, Lex.Tok>, b, Some{tok}, None{})) == b : Bool} - -def unit.hp(b, _tok): - match b: - case True{}: - {==} - case False{}: - {==} - -# the rule's literal one is the law's -law unit.one_is: - for mm: Maybe<&2, Lex.Tok> - {unit.has(UnitRule.one_of(mm)) == Laws.unit.one(mm) : Bool} - -def unit.one_is(mm): - match mm: - case None{}: - {==} - case Some{tok}: - match tok: - case Lex.Tok{k, +t, l, c}: - match k: - case Lex.TName{}: - {==} - case Lex.TUpper{}: - {==} - case Lex.TDotted{}: - {==} - case Lex.TWild{}: - {==} - case Lex.TKey{}: - {==} - case Lex.TNum{}: - unit.hp(UnitRule.one_text(t), Lex.Tok{Lex.TNum{}, t, l, c}) - case Lex.TStr{}: - {==} - case Lex.TChar{}: - {==} - case Lex.TComment{}: - {==} - case Lex.TSpace{}: - {==} - case Lex.TNewline{}: - {==} - case Lex.TOp{}: - {==} - case Lex.TColon{}: - {==} - case Lex.TEq{}: - {==} - case Lex.TBind{}: - {==} - case Lex.TArrow{}: - {==} - case Lex.TLam{}: - {==} - case Lex.TAll{}: - {==} - case Lex.TAmp{}: - {==} - case Lex.TOpen{}: - {==} - case Lex.TClose{}: - {==} - case Lex.TComma{}: - {==} - -# the first of two maybes that holds a token: one when either holds one -law unit.either: - for xx: Maybe<&2, Lex.Tok> - for -yy: Maybe<&2, Lex.Tok> - {unit.has(UnitRule.either(xx, yy)) == Bool.or(unit.has(xx), unit.has(yy)) : Bool} - -def unit.either(xx, _yy): - match xx: - case None{}: - {==} - case Some{tok}: - {==} - -# equal Bools give equal ors -law unit.or2: - for -a: Bool - for -b: Bool - for -c: Bool - for -d: Bool - for e1: {a == b : Bool} - for e2: {c == d : Bool} - {Bool.or(a, c) == Bool.or(b, d) : Bool} - -def unit.or2(a, b, c, d, e1, e2): - Equal.trans(Bool, Bool.or(a, c), Bool.or(b, c), Bool.or(b, d), Equal.cong(Bool, Bool, x => Bool.or(x, c), a, b, e1), - Equal.cong(Bool, Bool, x => Bool.or(b, x), c, d, e2)) - -# or commutes -law unit.comm: - for a: Bool - for b: Bool - {Bool.or(a, b) == Bool.or(b, a) : Bool} - -def unit.comm(a, b): - match a: - case True{}: - match b: - case True{}: - {==} - case False{}: - {==} - case False{}: - match b: - case True{}: - {==} - case False{}: - {==} - -# the literal an operator's finding cites is there exactly when the law's -# operator is by one: `*` (s) with one after or before, `/` (d) with one after -law unit.which: - for s: Bool - for d: Bool - for +pp: Maybe<&2, Lex.Tok> - for +nn: Maybe<&2, Lex.Tok> - {unit.has(Bool.pick(Maybe<&2, Lex.Tok>, s, UnitRule.either(UnitRule.one_of(nn), UnitRule.one_of(pp)), - Bool.pick(Maybe<&2, Lex.Tok>, d, UnitRule.one_of(nn), None{}))) - == Bool.pick(Bool, s, Bool.or(Laws.unit.one(nn), Laws.unit.one(pp)), Bool.and(d, Laws.unit.one(nn))) : Bool} - -def unit.which(s, d, pp, nn): - match s: - case True{}: - Equal.trans(Bool, unit.has(UnitRule.either(UnitRule.one_of(nn), UnitRule.one_of(pp))), - Bool.or(unit.has(UnitRule.one_of(nn)), unit.has(UnitRule.one_of(pp))), - Bool.or(Laws.unit.one(nn), Laws.unit.one(pp)), - unit.either(UnitRule.one_of(nn), UnitRule.one_of(pp)), - unit.or2(unit.has(UnitRule.one_of(nn)), Laws.unit.one(nn), unit.has(UnitRule.one_of(pp)), Laws.unit.one(pp), - unit.one_is(nn), unit.one_is(pp))) - case False{}: - match d: - case True{}: - unit.one_is(nn) - case False{}: - {==} - -# the node after, to the rule and to the law -law unit.first_is: - for nn: Tree.Node - {UnitRule.first_tok(nn) == Laws.unit.first(nn) : Maybe<&2, Lex.Tok>} - -def unit.first_is(nn): - match nn: - case Tree.NCons{h, r}: - match h: - case Tree.Leaf{tok}: - {==} - case Tree.Group{x, y, z}: - {==} - case Tree.Stmt{x, y, z}: - {==} - case Tree.NNil{}: - {==} - case Tree.NCons{x, y}: - {==} - case Tree.Leaf{x}: - {==} - case Tree.Group{x, y, z}: - {==} - case Tree.Stmt{x, y, z}: - {==} - case Tree.NNil{}: - {==} - -# an operator's finding: one exactly when it is by one -law unit.opx: - for +t: String - for +prev: Maybe<&2, Lex.Tok> - for +rest: Tree.Node - for +path: String - {List.length(&2, F.Finding, UnitRule.cite(UnitRule.which(t, prev, UnitRule.first_tok(rest)), path)) - == Bool.pick(Nat, Laws.unit.by_one(t, prev, Laws.unit.first(rest)), 1n, 0n) : Nat} - -def unit.opx(t, prev, rest, path): - +ft = UnitRule.first_tok(rest) - +uf = Laws.unit.first(rest) - +wh = UnitRule.which(t, prev, ft) - Equal.trans(Nat, List.length(&2, F.Finding, UnitRule.cite(wh, path)), Bool.pick(Nat, unit.has(wh), 1n, 0n), - Bool.pick(Nat, Laws.unit.by_one(t, prev, uf), 1n, 0n), unit.cite(wh, path), - Equal.cong(Bool, Nat, b => Bool.pick(Nat, b, 1n, 0n), unit.has(wh), Laws.unit.by_one(t, prev, uf), - Equal.trans(Bool, unit.has(wh), Laws.unit.by_one(t, prev, ft), Laws.unit.by_one(t, prev, uf), - unit.which(String.eq(t, "*"), String.eq(t, "/"), prev, ft), - Equal.cong(Maybe<&2, Lex.Tok>, Bool, m => Laws.unit.by_one(t, prev, m), ft, uf, unit.first_is(rest))))) - -# an operator in a region as hot as hot: its finding when hot -law unit.on: - for hot: Bool - for -x: List<&2, F.Finding> - for -b: Bool - for e: {List.length(&2, F.Finding, x) == Bool.pick(Nat, b, 1n, 0n) : Nat} - {List.length(&2, F.Finding, Bool.pick(List<&2, F.Finding>, Bool.and(hot, True{}), x, [])) - == Bool.pick(Nat, Bool.and(hot, b), 1n, 0n) : Nat} - -def unit.on(hot, _x, _b, e): - match hot: - case True{}: - e - case False{}: - {==} - -# a leaf that is no operator: no finding -law unit.off: - for hot: Bool - for -x: List<&2, F.Finding> - {List.length(&2, F.Finding, Bool.pick(List<&2, F.Finding>, Bool.and(hot, False{}), x, [])) - == Bool.pick(Nat, Bool.and(hot, False{}), 1n, 0n) : Nat} - -def unit.off(hot, _x): - match hot: - case True{}: - {==} - case False{}: - {==} - -# a leaf's own finding onto the rest's -law unit.leaf: - for +x: List<&2, F.Finding> - for +more: List<&2, F.Finding> - for +b: Bool - for -m: Nat - for ex: {List.length(&2, F.Finding, x) == Bool.pick(Nat, b, 1n, 0n) : Nat} - for em: {List.length(&2, F.Finding, more) == m : Nat} - {List.length(&2, F.Finding, List.append(&2, F.Finding, x, more)) == Bool.pick(Nat, b, 1n+m, m) : Nat} - -def unit.leaf(x, more, b, m, ex, em): - Equal.trans(Nat, List.length(&2, F.Finding, List.append(&2, F.Finding, x, more)), Nat.add(Bool.pick(Nat, b, 1n, 0n), - m), - Bool.pick(Nat, b, 1n+m, m), M1.pick.join(x, more, Bool.pick(Nat, b, 1n, 0n), m, ex, em), M1.fuel.addpick(b, m)) - -# the rule's argument that is the literal one alone is the law's -law unit.arg_is: - for aa: Tree.Node - {unit.has(UnitRule.arg_one(aa)) == Laws.unit.lone(aa) : Bool} - -def unit.arg_is(aa): - match aa: - case Tree.NCons{h, r}: - match h: - case Tree.Leaf{tok}: - match r: - case Tree.NNil{}: - unit.one_is(Some{tok}) - case Tree.Leaf{x}: - {==} - case Tree.Group{x, y, z}: - {==} - case Tree.Stmt{x, y, z}: - {==} - case Tree.NCons{x, y}: - {==} - case Tree.Group{x, y, z}: - {==} - case Tree.Stmt{x, y, z}: - {==} - case Tree.NNil{}: - {==} - case Tree.NCons{x, y}: - {==} - case Tree.Leaf{x}: - {==} - case Tree.Group{x, y, z}: - {==} - case Tree.Stmt{x, y, z}: - {==} - case Tree.NNil{}: - {==} - -# the rule's some argument one is the law's -law unit.any_is: - for as: List<&2, Tree.Node> - {unit.has(UnitRule.any_one(as)) == Laws.unit.any(as) : Bool} - -def unit.any_is(as): - match as: - case Nil{}: - {==} - case Con{+h, +rest}: - Equal.trans(Bool, unit.has(UnitRule.either(UnitRule.any_one(rest), UnitRule.arg_one(h))), - Bool.or(unit.has(UnitRule.any_one(rest)), unit.has(UnitRule.arg_one(h))), - Bool.or(Laws.unit.lone(h), Laws.unit.any(rest)), - unit.either(UnitRule.any_one(rest), UnitRule.arg_one(h)), - Equal.trans(Bool, Bool.or(unit.has(UnitRule.any_one(rest)), unit.has(UnitRule.arg_one(h))), - Bool.or(Laws.unit.any(rest), Laws.unit.lone(h)), Bool.or(Laws.unit.lone(h), Laws.unit.any(rest)), - unit.or2(unit.has(UnitRule.any_one(rest)), Laws.unit.any(rest), unit.has(UnitRule.arg_one(h)), - Laws.unit.lone(h), unit.any_is(rest), unit.arg_is(h)), - unit.comm(Laws.unit.any(rest), Laws.unit.lone(h)))) - -# the rule's last argument one is the law's -law unit.last_is: - for as: List<&2, Tree.Node> - {unit.has(UnitRule.last_one(as)) == Laws.unit.last(as) : Bool} - -def unit.last_is(as): - match as: - case Nil{}: - {==} - case Con{h, t}: - match t: - case Nil{}: - unit.arg_is(h) - case Con{x, y}: - unit.last_is(t) - -# a finding on what the maybe holds, as the law's Bool says -law unit.has_is: - for +mm: Maybe<&2, Lex.Tok> - for -b: Bool - for +path: String - for e: {unit.has(mm) == b : Bool} - {List.length(&2, F.Finding, UnitRule.cite(mm, path)) == Bool.pick(Nat, b, 1n, 0n) : Nat} - -def unit.has_is(mm, b, path, e): - Equal.trans(Nat, List.length(&2, F.Finding, UnitRule.cite(mm, path)), Bool.pick(Nat, unit.has(mm), 1n, 0n), - Bool.pick(Nat, b, 1n, 0n), unit.cite(mm, path), Equal.cong(Bool, Nat, x => Bool.pick(Nat, x, 1n, 0n), unit.has(mm), - b, e)) - -# a divide's finding, when the call is a divide -law unit.div: - for db: Bool - for +as: List<&2, Tree.Node> - for +path: String - {List.length(&2, F.Finding, UnitRule.on_div_is(db, as, path)) == Bool.pick(Nat, Bool.and(db, Laws.unit.last(as)), 1n, - 0n) - : Nat} - -def unit.div(db, as, path): - match db: - case True{}: - unit.has_is(UnitRule.last_one(as), Laws.unit.last(as), path, unit.last_is(as)) - case False{}: - {==} - -# a multiply's finding, else a divide's -law unit.opis: - for mb: Bool - for +tt: String - for +as: List<&2, Tree.Node> - for +path: String - {List.length(&2, F.Finding, UnitRule.on_op_is(mb, tt, as, path)) - == Bool.pick(Nat, Bool.pick(Bool, mb, Laws.unit.any(as), - Bool.and(List.contains(~String, ~String.eq, ["Nat.div", "U32.div", "F32.div"], tt), - Laws.unit.last(as))), 1n, 0n) - : Nat} - -def unit.opis(mb, tt, as, path): - match mb: - case True{}: - unit.has_is(UnitRule.any_one(as), Laws.unit.any(as), path, unit.any_is(as)) - case False{}: - unit.div(List.contains(~String, ~String.eq, ["Nat.div", "U32.div", "F32.div"], tt), as, path) - -# a group's own finding: a `(` call that multiplies or divides by one, when hot -law unit.own: - for call: Bool - for +tt: String - for +kids: Tree.Node - for +path: String - for hot: Bool - {List.length(&2, F.Finding, UnitRule.own_of(call, tt, kids, path, hot)) - == Bool.pick(Nat, Bool.and(call, Bool.and(hot, Laws.unit.call(tt, Calls.args(kids)))), 1n, 0n) : Nat} - -def unit.own(call, tt, kids, path, hot): - match call: - case True{}: - match hot: - case True{}: - unit.opis(List.contains(~String, ~String.eq, ["Nat.mul", "U32.mul", "F32.mul"], tt), tt, - Calls.args(kids), path) - case False{}: - {==} - case False{}: - {==} - -# the name a group calls, to the rule and to the law -law unit.callee: - for mm: Maybe<&2, Lex.Tok> - {UnitRule.callee(mm) == Laws.unit.name(mm) : String} - -def unit.callee(mm): - match mm: - case None{}: - {==} - case Some{tok}: - match tok: - case Lex.Tok{k, t, l, c}: - {==} - -def Laws.unit_walk_counts(nn, prev, self, path, hot): - match nn: - case Tree.NCons{h, +rest}: - match h: - case Tree.Leaf{tok}: - match tok: - case Lex.Tok{k, +t, +l, +c}: - match k: - case Lex.TName{}: - unit.leaf(UnitRule.op_hit(Lex.Tok{Lex.TName{}, t, l, c}, prev, UnitRule.first_tok(rest), path, hot), - UnitRule.walk(rest, Some{Lex.Tok{Lex.TName{}, t, l, c}}, self, path, hot), - Bool.and(hot, False{}), Laws.unit.count(rest, Some{Lex.Tok{Lex.TName{}, t, l, c}}, self, hot), - unit.off(hot, UnitRule.cite(UnitRule.which(t, prev, UnitRule.first_tok(rest)), path)), - Laws.unit_walk_counts(rest, Some{Lex.Tok{Lex.TName{}, t, l, c}}, self, path, hot)) - case Lex.TUpper{}: - unit.leaf(UnitRule.op_hit(Lex.Tok{Lex.TUpper{}, t, l, c}, prev, UnitRule.first_tok(rest), path, hot), - UnitRule.walk(rest, Some{Lex.Tok{Lex.TUpper{}, t, l, c}}, self, path, hot), - Bool.and(hot, False{}), Laws.unit.count(rest, Some{Lex.Tok{Lex.TUpper{}, t, l, c}}, self, hot), - unit.off(hot, UnitRule.cite(UnitRule.which(t, prev, UnitRule.first_tok(rest)), path)), - Laws.unit_walk_counts(rest, Some{Lex.Tok{Lex.TUpper{}, t, l, c}}, self, path, hot)) - case Lex.TDotted{}: - unit.leaf(UnitRule.op_hit(Lex.Tok{Lex.TDotted{}, t, l, c}, prev, UnitRule.first_tok(rest), path, hot), - UnitRule.walk(rest, Some{Lex.Tok{Lex.TDotted{}, t, l, c}}, self, path, hot), - Bool.and(hot, False{}), Laws.unit.count(rest, Some{Lex.Tok{Lex.TDotted{}, t, l, c}}, self, hot), - unit.off(hot, UnitRule.cite(UnitRule.which(t, prev, UnitRule.first_tok(rest)), path)), - Laws.unit_walk_counts(rest, Some{Lex.Tok{Lex.TDotted{}, t, l, c}}, self, path, hot)) - case Lex.TWild{}: - unit.leaf(UnitRule.op_hit(Lex.Tok{Lex.TWild{}, t, l, c}, prev, UnitRule.first_tok(rest), path, hot), - UnitRule.walk(rest, Some{Lex.Tok{Lex.TWild{}, t, l, c}}, self, path, hot), - Bool.and(hot, False{}), Laws.unit.count(rest, Some{Lex.Tok{Lex.TWild{}, t, l, c}}, self, hot), - unit.off(hot, UnitRule.cite(UnitRule.which(t, prev, UnitRule.first_tok(rest)), path)), - Laws.unit_walk_counts(rest, Some{Lex.Tok{Lex.TWild{}, t, l, c}}, self, path, hot)) - case Lex.TKey{}: - unit.leaf(UnitRule.op_hit(Lex.Tok{Lex.TKey{}, t, l, c}, prev, UnitRule.first_tok(rest), path, hot), - UnitRule.walk(rest, Some{Lex.Tok{Lex.TKey{}, t, l, c}}, self, path, hot), - Bool.and(hot, False{}), Laws.unit.count(rest, Some{Lex.Tok{Lex.TKey{}, t, l, c}}, self, hot), - unit.off(hot, UnitRule.cite(UnitRule.which(t, prev, UnitRule.first_tok(rest)), path)), - Laws.unit_walk_counts(rest, Some{Lex.Tok{Lex.TKey{}, t, l, c}}, self, path, hot)) - case Lex.TNum{}: - unit.leaf(UnitRule.op_hit(Lex.Tok{Lex.TNum{}, t, l, c}, prev, UnitRule.first_tok(rest), path, hot), - UnitRule.walk(rest, Some{Lex.Tok{Lex.TNum{}, t, l, c}}, self, path, hot), - Bool.and(hot, False{}), Laws.unit.count(rest, Some{Lex.Tok{Lex.TNum{}, t, l, c}}, self, hot), - unit.off(hot, UnitRule.cite(UnitRule.which(t, prev, UnitRule.first_tok(rest)), path)), - Laws.unit_walk_counts(rest, Some{Lex.Tok{Lex.TNum{}, t, l, c}}, self, path, hot)) - case Lex.TStr{}: - unit.leaf(UnitRule.op_hit(Lex.Tok{Lex.TStr{}, t, l, c}, prev, UnitRule.first_tok(rest), path, hot), - UnitRule.walk(rest, Some{Lex.Tok{Lex.TStr{}, t, l, c}}, self, path, hot), - Bool.and(hot, False{}), Laws.unit.count(rest, Some{Lex.Tok{Lex.TStr{}, t, l, c}}, self, hot), - unit.off(hot, UnitRule.cite(UnitRule.which(t, prev, UnitRule.first_tok(rest)), path)), - Laws.unit_walk_counts(rest, Some{Lex.Tok{Lex.TStr{}, t, l, c}}, self, path, hot)) - case Lex.TChar{}: - unit.leaf(UnitRule.op_hit(Lex.Tok{Lex.TChar{}, t, l, c}, prev, UnitRule.first_tok(rest), path, hot), - UnitRule.walk(rest, Some{Lex.Tok{Lex.TChar{}, t, l, c}}, self, path, hot), - Bool.and(hot, False{}), Laws.unit.count(rest, Some{Lex.Tok{Lex.TChar{}, t, l, c}}, self, hot), - unit.off(hot, UnitRule.cite(UnitRule.which(t, prev, UnitRule.first_tok(rest)), path)), - Laws.unit_walk_counts(rest, Some{Lex.Tok{Lex.TChar{}, t, l, c}}, self, path, hot)) - case Lex.TComment{}: - unit.leaf(UnitRule.op_hit(Lex.Tok{Lex.TComment{}, t, l, c}, prev, UnitRule.first_tok(rest), path, - hot), UnitRule.walk(rest, Some{Lex.Tok{Lex.TComment{}, t, l, c}}, self, path, hot), - Bool.and(hot, False{}), Laws.unit.count(rest, Some{Lex.Tok{Lex.TComment{}, t, l, c}}, self, hot), - unit.off(hot, UnitRule.cite(UnitRule.which(t, prev, UnitRule.first_tok(rest)), path)), - Laws.unit_walk_counts(rest, Some{Lex.Tok{Lex.TComment{}, t, l, c}}, self, path, hot)) - case Lex.TSpace{}: - unit.leaf(UnitRule.op_hit(Lex.Tok{Lex.TSpace{}, t, l, c}, prev, UnitRule.first_tok(rest), path, hot), - UnitRule.walk(rest, Some{Lex.Tok{Lex.TSpace{}, t, l, c}}, self, path, hot), - Bool.and(hot, False{}), Laws.unit.count(rest, Some{Lex.Tok{Lex.TSpace{}, t, l, c}}, self, hot), - unit.off(hot, UnitRule.cite(UnitRule.which(t, prev, UnitRule.first_tok(rest)), path)), - Laws.unit_walk_counts(rest, Some{Lex.Tok{Lex.TSpace{}, t, l, c}}, self, path, hot)) - case Lex.TNewline{}: - unit.leaf(UnitRule.op_hit(Lex.Tok{Lex.TNewline{}, t, l, c}, prev, UnitRule.first_tok(rest), path, - hot), UnitRule.walk(rest, Some{Lex.Tok{Lex.TNewline{}, t, l, c}}, self, path, hot), - Bool.and(hot, False{}), Laws.unit.count(rest, Some{Lex.Tok{Lex.TNewline{}, t, l, c}}, self, hot), - unit.off(hot, UnitRule.cite(UnitRule.which(t, prev, UnitRule.first_tok(rest)), path)), - Laws.unit_walk_counts(rest, Some{Lex.Tok{Lex.TNewline{}, t, l, c}}, self, path, hot)) - case Lex.TOp{}: - unit.leaf(UnitRule.op_hit(Lex.Tok{Lex.TOp{}, t, l, c}, prev, UnitRule.first_tok(rest), path, hot), - UnitRule.walk(rest, None{}, self, path, hot), - Bool.and(hot, Laws.unit.by_one(t, prev, Laws.unit.first(rest))), Laws.unit.count(rest, None{}, self, - hot), - unit.on(hot, UnitRule.cite(UnitRule.which(t, prev, UnitRule.first_tok(rest)), path), - Laws.unit.by_one(t, prev, Laws.unit.first(rest)), unit.opx(t, prev, rest, path)), - Laws.unit_walk_counts(rest, None{}, self, path, hot)) - case Lex.TColon{}: - unit.leaf(UnitRule.op_hit(Lex.Tok{Lex.TColon{}, t, l, c}, prev, UnitRule.first_tok(rest), path, hot), - UnitRule.walk(rest, Some{Lex.Tok{Lex.TColon{}, t, l, c}}, self, path, hot), - Bool.and(hot, False{}), Laws.unit.count(rest, Some{Lex.Tok{Lex.TColon{}, t, l, c}}, self, hot), - unit.off(hot, UnitRule.cite(UnitRule.which(t, prev, UnitRule.first_tok(rest)), path)), - Laws.unit_walk_counts(rest, Some{Lex.Tok{Lex.TColon{}, t, l, c}}, self, path, hot)) - case Lex.TEq{}: - unit.leaf(UnitRule.op_hit(Lex.Tok{Lex.TEq{}, t, l, c}, prev, UnitRule.first_tok(rest), path, hot), - UnitRule.walk(rest, Some{Lex.Tok{Lex.TEq{}, t, l, c}}, self, path, hot), - Bool.and(hot, False{}), Laws.unit.count(rest, Some{Lex.Tok{Lex.TEq{}, t, l, c}}, self, hot), - unit.off(hot, UnitRule.cite(UnitRule.which(t, prev, UnitRule.first_tok(rest)), path)), - Laws.unit_walk_counts(rest, Some{Lex.Tok{Lex.TEq{}, t, l, c}}, self, path, hot)) - case Lex.TBind{}: - unit.leaf(UnitRule.op_hit(Lex.Tok{Lex.TBind{}, t, l, c}, prev, UnitRule.first_tok(rest), path, hot), - UnitRule.walk(rest, Some{Lex.Tok{Lex.TBind{}, t, l, c}}, self, path, hot), - Bool.and(hot, False{}), Laws.unit.count(rest, Some{Lex.Tok{Lex.TBind{}, t, l, c}}, self, hot), - unit.off(hot, UnitRule.cite(UnitRule.which(t, prev, UnitRule.first_tok(rest)), path)), - Laws.unit_walk_counts(rest, Some{Lex.Tok{Lex.TBind{}, t, l, c}}, self, path, hot)) - case Lex.TArrow{}: - unit.leaf(UnitRule.op_hit(Lex.Tok{Lex.TArrow{}, t, l, c}, prev, UnitRule.first_tok(rest), path, hot), - UnitRule.walk(rest, Some{Lex.Tok{Lex.TArrow{}, t, l, c}}, self, path, hot), - Bool.and(hot, False{}), Laws.unit.count(rest, Some{Lex.Tok{Lex.TArrow{}, t, l, c}}, self, hot), - unit.off(hot, UnitRule.cite(UnitRule.which(t, prev, UnitRule.first_tok(rest)), path)), - Laws.unit_walk_counts(rest, Some{Lex.Tok{Lex.TArrow{}, t, l, c}}, self, path, hot)) - case Lex.TLam{}: - unit.leaf(UnitRule.op_hit(Lex.Tok{Lex.TLam{}, t, l, c}, prev, UnitRule.first_tok(rest), path, hot), - UnitRule.walk(rest, Some{Lex.Tok{Lex.TLam{}, t, l, c}}, self, path, hot), - Bool.and(hot, False{}), Laws.unit.count(rest, Some{Lex.Tok{Lex.TLam{}, t, l, c}}, self, hot), - unit.off(hot, UnitRule.cite(UnitRule.which(t, prev, UnitRule.first_tok(rest)), path)), - Laws.unit_walk_counts(rest, Some{Lex.Tok{Lex.TLam{}, t, l, c}}, self, path, hot)) - case Lex.TAll{}: - unit.leaf(UnitRule.op_hit(Lex.Tok{Lex.TAll{}, t, l, c}, prev, UnitRule.first_tok(rest), path, hot), - UnitRule.walk(rest, Some{Lex.Tok{Lex.TAll{}, t, l, c}}, self, path, hot), - Bool.and(hot, False{}), Laws.unit.count(rest, Some{Lex.Tok{Lex.TAll{}, t, l, c}}, self, hot), - unit.off(hot, UnitRule.cite(UnitRule.which(t, prev, UnitRule.first_tok(rest)), path)), - Laws.unit_walk_counts(rest, Some{Lex.Tok{Lex.TAll{}, t, l, c}}, self, path, hot)) - case Lex.TAmp{}: - unit.leaf(UnitRule.op_hit(Lex.Tok{Lex.TAmp{}, t, l, c}, prev, UnitRule.first_tok(rest), path, hot), - UnitRule.walk(rest, Some{Lex.Tok{Lex.TAmp{}, t, l, c}}, self, path, hot), - Bool.and(hot, False{}), Laws.unit.count(rest, Some{Lex.Tok{Lex.TAmp{}, t, l, c}}, self, hot), - unit.off(hot, UnitRule.cite(UnitRule.which(t, prev, UnitRule.first_tok(rest)), path)), - Laws.unit_walk_counts(rest, Some{Lex.Tok{Lex.TAmp{}, t, l, c}}, self, path, hot)) - case Lex.TOpen{}: - unit.leaf(UnitRule.op_hit(Lex.Tok{Lex.TOpen{}, t, l, c}, prev, UnitRule.first_tok(rest), path, hot), - UnitRule.walk(rest, Some{Lex.Tok{Lex.TOpen{}, t, l, c}}, self, path, hot), - Bool.and(hot, False{}), Laws.unit.count(rest, Some{Lex.Tok{Lex.TOpen{}, t, l, c}}, self, hot), - unit.off(hot, UnitRule.cite(UnitRule.which(t, prev, UnitRule.first_tok(rest)), path)), - Laws.unit_walk_counts(rest, Some{Lex.Tok{Lex.TOpen{}, t, l, c}}, self, path, hot)) - case Lex.TClose{}: - unit.leaf(UnitRule.op_hit(Lex.Tok{Lex.TClose{}, t, l, c}, prev, UnitRule.first_tok(rest), path, hot), - UnitRule.walk(rest, Some{Lex.Tok{Lex.TClose{}, t, l, c}}, self, path, hot), - Bool.and(hot, False{}), Laws.unit.count(rest, Some{Lex.Tok{Lex.TClose{}, t, l, c}}, self, hot), - unit.off(hot, UnitRule.cite(UnitRule.which(t, prev, UnitRule.first_tok(rest)), path)), - Laws.unit_walk_counts(rest, Some{Lex.Tok{Lex.TClose{}, t, l, c}}, self, path, hot)) - case Lex.TComma{}: - unit.leaf(UnitRule.op_hit(Lex.Tok{Lex.TComma{}, t, l, c}, prev, UnitRule.first_tok(rest), path, hot), - UnitRule.walk(rest, Some{Lex.Tok{Lex.TComma{}, t, l, c}}, self, path, hot), - Bool.and(hot, False{}), Laws.unit.count(rest, Some{Lex.Tok{Lex.TComma{}, t, l, c}}, self, hot), - unit.off(hot, UnitRule.cite(UnitRule.which(t, prev, UnitRule.first_tok(rest)), path)), - Laws.unit_walk_counts(rest, Some{Lex.Tok{Lex.TComma{}, t, l, c}}, self, path, hot)) - case Tree.Group{open, +kids, close}: - match open: - case Lex.Tok{ok, +o, ol, oc}: - +call = String.eq(o, "(") - +own = UnitRule.own_of(call, UnitRule.callee(prev), kids, path, hot) - M1.fuel.three(UnitRule.walk(kids, None{}, self, path, hot), own, UnitRule.walk(rest, None{}, self, path, - hot), - Laws.unit.count(kids, None{}, self, hot), - Bool.pick(Nat, Bool.and(call, Bool.and(hot, Laws.unit.call(Laws.unit.name(prev), Calls.args(kids)))), - 1n, 0n), - Laws.unit.count(rest, None{}, self, hot), Laws.unit_walk_counts(kids, None{}, self, path, hot), - Equal.trans(Nat, List.length(&2, F.Finding, own), - Bool.pick(Nat, Bool.and(call, Bool.and(hot, Laws.unit.call(UnitRule.callee(prev), Calls.args(kids)))), - 1n, 0n), - Bool.pick(Nat, Bool.and(call, Bool.and(hot, Laws.unit.call(Laws.unit.name(prev), Calls.args(kids)))), - 1n, 0n), - unit.own(call, UnitRule.callee(prev), kids, path, hot), - Equal.cong(String, Nat, x => Bool.pick(Nat, Bool.and(call, Bool.and(hot, Laws.unit.call(x, - Calls.args(kids)))), 1n, - 0n), UnitRule.callee(prev), Laws.unit.name(prev), unit.callee(prev))), - Laws.unit_walk_counts(rest, None{}, self, path, hot)) - case Tree.Stmt{kind, +kids, +body}: - match kind: - case Tree.SDef{}: - M1.fuel.three(UnitRule.walk(kids, None{}, self, path, hot), UnitRule.walk(body, None{}, self, path, hot), - UnitRule.walk(rest, None{}, self, path, hot), Laws.unit.count(kids, None{}, self, hot), - Laws.unit.count(body, None{}, self, hot), Laws.unit.count(rest, None{}, self, hot), - Laws.unit_walk_counts(kids, None{}, self, path, hot), Laws.unit_walk_counts(body, None{}, self, path, - hot), - Laws.unit_walk_counts(rest, None{}, self, path, hot)) - case Tree.SType{}: - M1.fuel.three(UnitRule.walk(kids, None{}, self, path, hot), UnitRule.walk(body, None{}, self, path, hot), - UnitRule.walk(rest, None{}, self, path, hot), Laws.unit.count(kids, None{}, self, hot), - Laws.unit.count(body, None{}, self, hot), Laws.unit.count(rest, None{}, self, hot), - Laws.unit_walk_counts(kids, None{}, self, path, hot), Laws.unit_walk_counts(body, None{}, self, path, - hot), - Laws.unit_walk_counts(rest, None{}, self, path, hot)) - case Tree.SLaw{}: - M1.fuel.three(UnitRule.walk(kids, None{}, self, path, hot), UnitRule.walk(body, None{}, self, path, hot), - UnitRule.walk(rest, None{}, self, path, hot), Laws.unit.count(kids, None{}, self, hot), - Laws.unit.count(body, None{}, self, hot), Laws.unit.count(rest, None{}, self, hot), - Laws.unit_walk_counts(kids, None{}, self, path, hot), Laws.unit_walk_counts(body, None{}, self, path, - hot), - Laws.unit_walk_counts(rest, None{}, self, path, hot)) - case Tree.SImport{}: - M1.fuel.three(UnitRule.walk(kids, None{}, self, path, hot), UnitRule.walk(body, None{}, self, path, hot), - UnitRule.walk(rest, None{}, self, path, hot), Laws.unit.count(kids, None{}, self, hot), - Laws.unit.count(body, None{}, self, hot), Laws.unit.count(rest, None{}, self, hot), - Laws.unit_walk_counts(kids, None{}, self, path, hot), Laws.unit_walk_counts(body, None{}, self, path, - hot), - Laws.unit_walk_counts(rest, None{}, self, path, hot)) - case Tree.SCase{}: - M1.fuel.three(UnitRule.walk(kids, None{}, self, path, False{}), UnitRule.walk(body, None{}, self, path, - Bool.and(hot, Calls.calls(body, self))), - UnitRule.walk(rest, None{}, self, path, hot), Laws.unit.count(kids, None{}, self, False{}), - Laws.unit.count(body, None{}, self, Bool.and(hot, Calls.calls(body, self))), Laws.unit.count(rest, - None{}, self, hot), - Laws.unit_walk_counts(kids, None{}, self, path, False{}), Laws.unit_walk_counts(body, None{}, self, - path, Bool.and(hot, Calls.calls(body, self))), - Laws.unit_walk_counts(rest, None{}, self, path, hot)) - case Tree.SFor{}: - M1.fuel.three(UnitRule.walk(kids, None{}, self, path, hot), UnitRule.walk(body, None{}, self, path, hot), - UnitRule.walk(rest, None{}, self, path, hot), Laws.unit.count(kids, None{}, self, hot), - Laws.unit.count(body, None{}, self, hot), Laws.unit.count(rest, None{}, self, hot), - Laws.unit_walk_counts(kids, None{}, self, path, hot), Laws.unit_walk_counts(body, None{}, self, path, - hot), - Laws.unit_walk_counts(rest, None{}, self, path, hot)) - case Tree.SLet{}: - M1.fuel.three(UnitRule.walk(kids, None{}, self, path, hot), UnitRule.walk(body, None{}, self, path, hot), - UnitRule.walk(rest, None{}, self, path, hot), Laws.unit.count(kids, None{}, self, hot), - Laws.unit.count(body, None{}, self, hot), Laws.unit.count(rest, None{}, self, hot), - Laws.unit_walk_counts(kids, None{}, self, path, hot), Laws.unit_walk_counts(body, None{}, self, path, - hot), - Laws.unit_walk_counts(rest, None{}, self, path, hot)) - case Tree.STerm{}: - M1.fuel.three(UnitRule.walk(kids, None{}, self, path, hot), UnitRule.walk(body, None{}, self, path, hot), - UnitRule.walk(rest, None{}, self, path, hot), Laws.unit.count(kids, None{}, self, hot), - Laws.unit.count(body, None{}, self, hot), Laws.unit.count(rest, None{}, self, hot), - Laws.unit_walk_counts(kids, None{}, self, path, hot), Laws.unit_walk_counts(body, None{}, self, path, - hot), - Laws.unit_walk_counts(rest, None{}, self, path, hot)) - case Tree.NNil{}: - Laws.unit_walk_counts(rest, prev, self, path, hot) - case Tree.NCons{x, y}: - Laws.unit_walk_counts(rest, prev, self, path, hot) - case Tree.Leaf{x}: - {==} - case Tree.Group{x, y, z}: - {==} - case Tree.Stmt{x, y, z}: - {==} - case Tree.NNil{}: - {==} - -# one def's findings: its walk when it calls itself and is not exempt -law unit.stop: - for b: Bool - for +body: Tree.Node - for +name: String - for +path: String - {List.length(&2, F.Finding, Lazy.stop(List<&2, F.Finding>, Bool.not(b), [], - _u => UnitRule.walk(body, None{}, name, path, True{}))) - == Bool.pick(Nat, b, Laws.unit.count(body, None{}, name, True{}), 0n) : Nat} - -def unit.stop(b, body, name, path): - match b: - case True{}: - Laws.unit_walk_counts(body, None{}, name, path, True{}) - case False{}: - {==} - -# unit's def walk: the findings gathered so far, then each def's -law unit.go: - for ds: List<&2, Calls.Def> - for +path: String - for +acc: List<&2, List<&2, F.Finding>> - {List.length(&2, F.Finding, UnitRule.check.go(ds, path, acc)) == M1.pick.sum(acc, Laws.unit.defs(ds, path)) : Nat} - -def unit.go(ds, path, acc): - match ds: - case Nil{}: - M1.pick.rev(acc, Nil{}) - case Con{d, +rest}: - match d: - case Calls.Def{+name, +sig, +body}: - +b = Bool.and(Calls.calls(body, name), Bool.not(Calls.exempt(path, sig))) - +x = Lazy.stop(List<&2, F.Finding>, Bool.not(b), [], _u => UnitRule.walk(body, None{}, name, path, True{})) - +cb = Bool.pick(Nat, b, Laws.unit.count(body, None{}, name, True{}), 0n) - Equal.trans(Nat, List.length(&2, F.Finding, UnitRule.check.go(rest, path, x <> acc)), - M1.pick.sum(acc, Nat.add(List.length(&2, F.Finding, x), Laws.unit.defs(rest, path))), - M1.pick.sum(acc, Nat.add(cb, Laws.unit.defs(rest, path))), - unit.go(rest, path, x <> acc), - Equal.cong(Nat, Nat, n => M1.pick.sum(acc, Nat.add(n, Laws.unit.defs(rest, path))), - List.length(&2, F.Finding, x), cb, unit.stop(b, body, name, path))) - -def Laws.unit_counts(path, _text, _toks, tree, _bound, _items): - unit.go(Calls.defs(tree), path, Nil{}) diff --git a/proofs/UnsafeReportsReachableForeignDefs.bend b/proofs/UnsafeReportsReachableForeignDefs.bend deleted file mode 100644 index 41948e5..0000000 --- a/proofs/UnsafeReportsReachableForeignDefs.bend +++ /dev/null @@ -1,121 +0,0 @@ -# proofs of laws/UnsafeReportsReachableForeignDefs.bend -import Base -import ../src/finding.bend as F -import ../src/rules/laws/unsafe.bend as Unsafe -import ../src/rules/imports.bend as Imports -import ../src/rules/digest.bend as Digest -import ../laws/UnsafeReportsReachableForeignDefs.bend as Laws -import ../laws/defs/Unsafe.bend as D1 -import ./lemmas/ImportClosure.bend as M1 -import ./lemmas/CarriedSearch.bend as CS - -# unsafe (laws/UnsafeReportsReachableForeignDefs.bend) - -# the rule's search of the law files' closures is unsafe.origin -law unsafe.reacher: - for ls: List<&2, Digest.Digest> - for +all: List<&2, Digest.Digest> - for +pp: String - {Unsafe.reacher(Unsafe.reaches(ls, Digest.edges(all), Imports.paths(Digest.edges(all)), - List.length(&2, Digest.Digest, all)), pp) == Laws.unsafe.origin(ls, all, pp) : Maybe<&2, String>} - -def unsafe.reacher(ls, all, pp): - match ls: - case Nil{}: - {==} - case Con{Digest.Digest{+path, +norm, dir, is_laws, is_law_file, exempt, tops, says, deps, unsafes, laws}, +rest}: - match is_law_file: - case True{}: - %unsafe.reacher(rest, all, pp) : - {Unsafe.reacher(Unsafe.Reach{path, D1.unsafe.closure(all, norm)} - <> Unsafe.reaches(rest, Digest.edges(all), Imports.paths(Digest.edges(all)), - List.length(&2, Digest.Digest, all)), pp) - == Bool.pick(Maybe<&2, String>, Imports.has(D1.unsafe.closure(all, norm), pp), Some{path}, _) - : Maybe<&2, String>} - CS.cs.reacher(path, D1.unsafe.closure(all, norm), Unsafe.reaches(rest, Digest.edges(all), - Imports.paths(Digest.edges(all)), List.length(&2, Digest.Digest, all)), pp) - case False{}: - unsafe.reacher(rest, all, pp) - -# the rule's report is unsafe.each -law unsafe.report: - for us: List<&2, Digest.Unsafe> - for +origin: String - for +via: String - for +path: String - {Unsafe.report(us, origin, via, path) == Laws.unsafe.each(us, origin, via, path) : List<&2, F.Finding>} - -def unsafe.report(us, origin, via, path): - match us: - case Nil{}: - {==} - case Con{Digest.Unsafe{+n, +l, +c, fo}, rest}: - match fo: - case True{}: - Equal.cong(List<&2, F.Finding>, List<&2, F.Finding>, - t => F.Finding{path, l, c, 3, "unsafe", Unsafe.say(True{}, n, origin, via)} <> t, - Unsafe.report(rest, origin, via, path), Laws.unsafe.each(rest, origin, via, path), - unsafe.report(rest, origin, via, path)) - case False{}: - Equal.cong(List<&2, F.Finding>, List<&2, F.Finding>, - t => F.Finding{path, l, c, 7, "unsafe", Unsafe.say(False{}, n, origin, via)} <> t, - Unsafe.report(rest, origin, via, path), Laws.unsafe.each(rest, origin, via, path), - unsafe.report(rest, origin, via, path)) - -# the rule's found is unsafe.at -law unsafe.found: - for mm: Maybe<&2, String> - for us: List<&2, Digest.Unsafe> - for +path: String - for +norm: String - for +all: List<&2, Digest.Digest> - {Unsafe.found(mm, us, path, norm, Digest.edges(all), Imports.paths(Digest.edges(all)), - List.length(&2, Digest.Digest, all)) == Laws.unsafe.at(mm, us, path, norm, all) : List<&2, F.Finding>} - -def unsafe.found(mm, us, path, norm, all): - match mm: - case None{}: - {==} - case Some{+origin}: - match us: - case Nil{}: - {==} - case Con{u, rest}: - unsafe.report(u <> rest, origin, Laws.unsafe.via(all, Imports.norm(origin), norm), path) - -# the rule's walk over fs, against the law files of all, as its check runs it -def unsafe.run(fs: List<&2, Digest.Digest>, +all: List<&2, Digest.Digest>) -> List<&2, F.Finding>: - Unsafe.check.go(fs, Unsafe.reaches(all, Digest.edges(all), Imports.paths(Digest.edges(all)), - List.length(&2, Digest.Digest, all)), Digest.edges(all), Imports.paths(Digest.edges(all)), - List.length(&2, Digest.Digest, all)) - -# the rule's walk over the files is unsafe.files -law unsafe.go: - for fs: List<&2, Digest.Digest> - for +all: List<&2, Digest.Digest> - {unsafe.run(fs, all) == Laws.unsafe.files(fs, all) : List<&2, F.Finding>} - -def unsafe.go(fs, all): - match fs: - case Nil{}: - {==} - case Con{Digest.Digest{+path, +norm, dir, is_laws, is_law_file, exempt, tops, says, deps, +unsafes, laws}, rest}: - %unsafe.go(rest, all) : - {unsafe.run(Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, unsafes, laws} - <> rest, all) - == List.append(&2, F.Finding, Laws.unsafe.at(Laws.unsafe.origin(all, all, norm), unsafes, path, norm, all), _) - : List<&2, F.Finding>} - %unsafe.found(Laws.unsafe.origin(all, all, norm), unsafes, path, norm, all) : - {unsafe.run(Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, unsafes, laws} - <> rest, all) - == List.append(&2, F.Finding, _, unsafe.run(rest, all)) : List<&2, F.Finding>} - %unsafe.reacher(all, all, norm) : - {unsafe.run(Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, unsafes, laws} - <> rest, all) - == List.append(&2, F.Finding, Unsafe.found(_, unsafes, path, norm, Digest.edges(all), - Imports.paths(Digest.edges(all)), List.length(&2, Digest.Digest, all)), unsafe.run(rest, all)) - : List<&2, F.Finding>} - {==} - -def Laws.unsafe_reports(ds): - unsafe.go(ds, ds) diff --git a/proofs/lemmas/CarriedSearch.bend b/proofs/lemmas/CarriedSearch.bend index 9609105..894a9d0 100644 --- a/proofs/lemmas/CarriedSearch.bend +++ b/proofs/lemmas/CarriedSearch.bend @@ -6,7 +6,6 @@ import ../../src/syntax/lex.bend as Lex import ../../src/syntax/tree.bend as Tree import ../../src/config.bend as Config import ../../src/rules/digest.bend as Digest -import ../../src/rules/laws/unsafe.bend as Unsafe import ../../src/rules/imports.bend as Imports import ../../src/rules/calls.bend as Calls import ../../src/syntax/bind.bend as Bind @@ -102,51 +101,6 @@ law cs.alias_path: def cs.alias_path(n, p, as, name): cs.alias_path.pick(String.eq(n, name), as, name, p) -# a hit carried into reacher's walk is its answer -law cs.reacher.some: - for rs: List<&2, Unsafe.Reach> - for +pp: String - for +x: String - {Unsafe.reacher.go(rs, pp, Some{x}) == Some{x} : Maybe<&2, String>} - -def cs.reacher.some(rs, _pp, _x): - match rs: - case Nil{}: - {==} - case Con{h, r}: - match h: - case Unsafe.Reach{o, ps}: - {==} - -# reacher's walk past a test: the pick it stands for -law cs.reacher.pick: - for b: Bool - for +rs: List<&2, Unsafe.Reach> - for +pp: String - for +o: String - {Unsafe.reacher.go(rs, pp, Bool.pick(Maybe<&2, String>, b, Some{o}, None{})) - == Bool.pick(Maybe<&2, String>, b, Some{o}, Unsafe.reacher.go(rs, pp, None{})) : Maybe<&2, String>} - -def cs.reacher.pick(b, rs, pp, o): - match b: - case True{}: - cs.reacher.some(rs, pp, o) - case False{}: - {==} - -# reacher on a law file's reach: the file when its closure has the path, else -# the rest's -law cs.reacher: - for +o: String - for +ps: List<&2, String> - for +rs: List<&2, Unsafe.Reach> - for +pp: String - {Unsafe.reacher(Unsafe.Reach{o, ps} <> rs, pp) - == Bool.pick(Maybe<&2, String>, Imports.has(ps, pp), Some{o}, Unsafe.reacher(rs, pp)) : Maybe<&2, String>} - -def cs.reacher(o, ps, rs, pp): - cs.reacher.pick(Imports.has(ps, pp), rs, pp, o) - # a hit carried into find's walk is its answer law cs.find.some: for env: List<&2, Bind.Bind> diff --git a/proofs/lemmas/ImportClosure.bend b/proofs/lemmas/ImportClosure.bend index cebf85d..961b498 100644 --- a/proofs/lemmas/ImportClosure.bend +++ b/proofs/lemmas/ImportClosure.bend @@ -1,10 +1,9 @@ # the fast closure walk, lookups and early stops equal the spec walk import Base import ../../src/lazy/lazy.bend as Lazy -import ../../src/rules/laws/unsafe.bend as Unsafe import ../../src/rules/imports.bend as Imports import ../../src/rules/digest.bend as Digest -import ../../laws/UnsafeReportsReachableForeignDefs.bend as L1 +import ../../laws/ImportReachIsClosed.bend as L1 import ../../laws/defs/Unsafe.bend as D1 import ./RoleProofPicked.bend as M1 import ./UnsafeStringCompare.bend as M2 @@ -394,7 +393,7 @@ def unsafe.deps_imports(ds, pp): match ds: case Nil{}: {==} - case Con{Digest.Digest{path, +norm, dir, is_laws, is_law_file, exempt, tops, says, +deps, unsafes, laws}, +rest}: + case Con{Digest.Digest{path, +norm, dir, is_laws, is_law_file, exempt, tops, says, +deps, laws}, +rest}: %Equal.sym(List<&2, String>, Imports.deps.spec(Digest.edges(rest), pp), D1.unsafe.imports(rest, pp), unsafe.deps_imports(rest, pp)) : {Bool.pick(List<&2, String>, String.eq(norm, pp), deps, _) @@ -411,7 +410,7 @@ def unsafe.read_paths(ds, pp): match ds: case Nil{}: {==} - case Con{Digest.Digest{path, +norm, dir, is_laws, is_law_file, exempt, tops, says, deps, unsafes, laws}, +rest}: + case Con{Digest.Digest{path, +norm, dir, is_laws, is_law_file, exempt, tops, says, deps, laws}, +rest}: %Equal.sym(Bool, Imports.has(norm <> Imports.paths(Digest.edges(rest)), pp), Bool.or(String.eq(norm, pp), Imports.has(Imports.paths(Digest.edges(rest)), pp)), unsafe.has_con(norm, Imports.paths(Digest.edges(rest)), pp)) : @@ -430,7 +429,7 @@ def unsafe.len_paths(ds): match ds: case Nil{}: {==} - case Con{Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, unsafes, laws}, +rest}: + case Con{Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, laws}, +rest}: Equal.cong(Nat, Nat, n => 1n+n, List.length(&2, String, Imports.paths(Digest.edges(rest))), List.length(&2, Digest.Digest, rest), unsafe.len_paths(rest)) @@ -1689,7 +1688,7 @@ def unsafe.unread_imports(ds, pp, h): match ds: case Nil{}: {==} - case Con{Digest.Digest{path, +norm, dir, is_laws, is_law_file, exempt, tops, says, deps, unsafes, laws}, +rest}: + case Con{Digest.Digest{path, +norm, dir, is_laws, is_law_file, exempt, tops, says, deps, laws}, +rest}: unsafe.unread_imports.at(deps, D1.unsafe.imports(rest, pp), D1.unsafe.read(rest, pp), String.eq(norm, pp), h, z => unsafe.unread_imports(rest, pp, z)) diff --git a/proofs/lemmas/LawFileExemptions.bend b/proofs/lemmas/LawFileExemptions.bend index 1f66a38..c84044e 100644 --- a/proofs/lemmas/LawFileExemptions.bend +++ b/proofs/lemmas/LawFileExemptions.bend @@ -14,13 +14,10 @@ import ../../src/rules/correctness/pick.bend as Pick import ../../src/rules/pedantic/tail.bend as Tail import ../../src/rules/suspicious/concat.bend as Concat import ../../src/rules/suspicious/eager.bend as Eager -import ../../src/rules/suspicious/rewalk.bend as Rewalk -import ../../src/rules/suspicious/strict.bend as Strict -import ../../src/rules/suspicious/hoist.bend as Hoist -import ../../src/rules/suspicious/index.bend as Index -import ../../src/rules/suspicious/ring.bend as Ring -import ../../src/rules/suspicious/table.bend as Table -import ../../src/rules/suspicious/unit.bend as UnitRule +import ../../src/rules/pedantic/rewalk.bend as Rewalk +import ../../src/rules/pedantic/strict.bend as Strict +import ../../src/rules/pedantic/index.bend as Index +import ../../src/rules/table.bend as Table import ../../src/rules/suspicious/thunk.bend as Thunk import ../../laws/ExemptPathsYieldNoFindings.bend as L1 import ../../laws/RoleLawFileNamed.bend as L2 @@ -261,30 +258,6 @@ def ex.strict(ds, path, n, e): def L1.strict_exempt(path, _text, _toks, tree, _bound, _items, e): ex.strict(Calls.defs(tree), path, 0n, e) -# hoist's def walk gathers nothing in a law file -law ex.hoist: - for ds: List<&2, Calls.Def> - for +wide: List<&2, String> - for +path: String - for +n: Nat - for +e: {Paths.is_law_file(path) == True{} : Bool} - {Hoist.check.go(ds, wide, path, ex.rep(n)) == Nil{} : List<&2, F.Finding>} - -def ex.hoist(ds, wide, path, n, e): - match ds: - case Nil{}: - ex.flat(n, Nil{}) - case Con{Calls.Def{+name, +sig, +body}, rest}: - ex.push( - Bool.not(Bool.and(Calls.calls(body, name), Bool.not(Calls.exempt(path, sig)))), - ex.guard(Calls.calls(body, name), path, sig, e), - _u => Hoist.walk(body, name, wide, Hoist.carried(sig, body, name), path, True{}), n, - v => Hoist.check.go(rest, wide, path, v), ex.hoist(rest, wide, path, 1n+n, e)) - -def L1.hoist_exempt(path, _text, _toks, tree, _bound, _items, e): - +ds = Calls.defs(tree) - ex.hoist(ds, Hoist.wide_defs(ds, Nil{}), path, 0n, e) - # index's def walk gathers nothing in a law file law ex.index: for ds: List<&2, Calls.Def> @@ -309,74 +282,6 @@ def L1.index_exempt(path, _text, _toks, tree, _bound, _items, e): +ds = Calls.defs(tree) ex.index(ds, Table.modules(ds, Nil{}), path, 0n, e) -# ring's def walk gathers nothing in a law file -law ex.ring: - for ds: List<&2, Calls.Def> - for +path: String - for +n: Nat - for +e: {Paths.is_law_file(path) == True{} : Bool} - {Ring.check.go(ds, path, ex.rep(n)) == Nil{} : List<&2, F.Finding>} - -def ex.ring(ds, path, n, e): - match ds: - case Nil{}: - ex.flat(n, Nil{}) - case Con{Calls.Def{+name, +sig, +body}, rest}: - ex.push( - Bool.not(Bool.and(Calls.calls(body, name), Bool.not(Calls.exempt(path, sig)))), - ex.guard(Calls.calls(body, name), path, sig, e), - _u => Ring.walk(body, name, Calls.names(sig), path, Ring.windows(sig, body)), n, - v => Ring.check.go(rest, path, v), ex.ring(rest, path, 1n+n, e)) - -def L1.ring_exempt(path, _text, _toks, tree, _bound, _items, e): - ex.ring(Calls.defs(tree), path, 0n, e) - -# table's def walk gathers nothing in a law file -law ex.table: - for ds: List<&2, Calls.Def> - for +mods: List<&2, String> - for +path: String - for +n: Nat - for +e: {Paths.is_law_file(path) == True{} : Bool} - {Table.check.go(ds, mods, path, ex.rep(n)) == Nil{} : List<&2, F.Finding>} - -def ex.table(ds, mods, path, n, e): - match ds: - case Nil{}: - ex.flat(n, Nil{}) - case Con{Calls.Def{+name, +sig, +body}, rest}: - ex.push( - Bool.not(Bool.and(Calls.calls(body, name), Bool.not(Calls.exempt(path, sig)))), - ex.guard(Calls.calls(body, name), path, sig, e), - _u => Table.walk(body, path, Table.scope(body, mods)), n, - v => Table.check.go(rest, mods, path, v), ex.table(rest, mods, path, 1n+n, e)) - -def L1.table_exempt(path, _text, _toks, tree, _bound, _items, e): - +ds = Calls.defs(tree) - ex.table(ds, Table.modules(ds, Nil{}), path, 0n, e) - -# unit's def walk gathers nothing in a law file -law ex.unit: - for ds: List<&2, Calls.Def> - for +path: String - for +n: Nat - for +e: {Paths.is_law_file(path) == True{} : Bool} - {UnitRule.check.go(ds, path, ex.rep(n)) == Nil{} : List<&2, F.Finding>} - -def ex.unit(ds, path, n, e): - match ds: - case Nil{}: - ex.flat(n, Nil{}) - case Con{Calls.Def{+name, +sig, +body}, rest}: - ex.push( - Bool.not(Bool.and(Calls.calls(body, name), Bool.not(Calls.exempt(path, sig)))), - ex.guard(Calls.calls(body, name), path, sig, e), - _u => UnitRule.walk(body, None{}, name, path, True{}), n, - v => UnitRule.check.go(rest, path, v), ex.unit(rest, path, 1n+n, e)) - -def L1.unit_exempt(path, _text, _toks, tree, _bound, _items, e): - ex.unit(Calls.defs(tree), path, 0n, e) - # thunk's def walk gathers nothing in a law file law ex.thunk: for ds: List<&2, Calls.Def> diff --git a/proofs/lemmas/PackageRootLookup.bend b/proofs/lemmas/PackageRootLookup.bend index 253b836..3886091 100644 --- a/proofs/lemmas/PackageRootLookup.bend +++ b/proofs/lemmas/PackageRootLookup.bend @@ -6,7 +6,6 @@ import ../../src/rules.bend as Rules import ../../src/role.bend as Role import ../../src/rules/digest.bend as Digest import ../../src/rules/laws/law.bend as Law -import ../../src/rules/laws/unsafe.bend as Unsafe import ../../laws/PackagesScoped.bend as L1 import ../../laws/ScopePackageRootsFound.bend as L2 import ../../laws/defs/ScopeWalkFiles.bend as D1 @@ -89,22 +88,22 @@ def pk.members(ds, rs, pkg): match ds: case Nil{}: {==} - case Con{Digest.Digest{+path, +norm, +dir, +is_laws, +is_law_file, +exempt, +tops, +says, +deps, +unsafes, +laws}, + case Con{Digest.Digest{+path, +norm, +dir, +is_laws, +is_law_file, +exempt, +tops, +says, +deps, +laws}, +tl}: a = L2.package_nearest(rs, dir) %a : {Bool.pick(List<&2, Digest.Digest>, String.eq(Rules.owner(rs, dir), pkg), - Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, unsafes, laws} + Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, laws} <> Rules.members(tl, rs, pkg), Rules.members(tl, rs, pkg)) == Bool.pick(List<&2, Digest.Digest>, String.eq(_, pkg), - Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, unsafes, laws} + Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, laws} <> D1.part(tl, rs, pkg), D1.part(tl, rs, pkg)) : List<&2, Digest.Digest>} e = pk.members(tl, rs, pkg) %e : {Bool.pick(List<&2, Digest.Digest>, String.eq(Rules.owner(rs, dir), pkg), - Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, unsafes, laws} + Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, laws} <> Rules.members(tl, rs, pkg), Rules.members(tl, rs, pkg)) == Bool.pick(List<&2, Digest.Digest>, String.eq(Rules.owner(rs, dir), pkg), - Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, unsafes, laws} <> _, _) + Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, laws} <> _, _) : List<&2, Digest.Digest>} {==} @@ -130,11 +129,11 @@ def pk.covers(ps, ds, rs): def L1.packages_scoped(srcs, rs): +ds = Digest.all.role(Role.marks(srcs), srcs) a = Equal.cong(List<&2, Digest.Digest>, List<&2, F.Finding>, - mm => List.concat(&2, F.Finding, [Law.check(mm), Rules.covers(rs, ds, rs), Unsafe.check(ds)]), + mm => List.concat(&2, F.Finding, [Law.check(mm), Rules.covers(rs, ds, rs)]), Rules.members(ds, rs, ""), D1.part(ds, rs, ""), pk.members(ds, rs, "")) b = Equal.cong(List<&2, F.Finding>, List<&2, F.Finding>, - cc => List.concat(&2, F.Finding, [Law.check(D1.part(ds, rs, "")), cc, Unsafe.check(ds)]), + cc => List.concat(&2, F.Finding, [Law.check(D1.part(ds, rs, "")), cc]), Rules.covers(rs, ds, rs), D1.covered(rs, ds, rs), pk.covers(rs, ds, rs)) Equal.trans(List<&2, F.Finding>, Rules.project(srcs, rs), - List.concat(&2, F.Finding, [Law.check(D1.part(ds, rs, "")), Rules.covers(rs, ds, rs), Unsafe.check(ds)]), + List.concat(&2, F.Finding, [Law.check(D1.part(ds, rs, "")), Rules.covers(rs, ds, rs)]), D1.packed(srcs, rs), a, b) diff --git a/src/README.md b/src/README.md index e4b3d0b..63be254 100644 --- a/src/README.md +++ b/src/README.md @@ -48,13 +48,14 @@ def space() -> String: A group sets every rule in it; a rule set by name wins over its group; an unset group has its default. The groups: -| group | rules | default | -|---------------|----------------------------------------------------------------------------------|---------| -| `correctness` | `hole` `pick` `put` `arms` `escape` `twice` `strings` `chars` `foreign` `setting` | error | -| `suspicious` | `unused` `strict` `eager` `concat` `fuel` `index` `table` `hoist` `ring` `rewalk` `unit` `fromrev` `prefix` (`scan`, `thunk`: opt-in) | warn | -| `style` | `doc` `space` `wrap` `param` `noqa` (`transcong`: opt-in) | warn | -| `laws` | `coverage` `closed` `unsafe` | warn | -| `pedantic` | `tail` | off | +| group | rules | default | +|---------------|------------------------------------------------------------------------|---------| +| `correctness` | `hole` `pick` `put` `arms` `escape` `twice` `setting` | error | +| `suspicious` | `unused` `eager` `concat` `fromrev` `prefix` (`thunk`: opt-in) | warn | +| `perf` | `strings` `chars` | warn | +| `style` | `doc` `space` `wrap` `param` `noqa` (`transcong`: opt-in) | warn | +| `laws` | (`coverage`, `closed`: opt-in) | warn | +| `pedantic` | `foreign` `strict` `fuel` `index` `rewalk` `tail` | off | The stable codes, assigned once (do not renumber): @@ -65,20 +66,26 @@ The stable codes, assigned once (do not renumber): | C003 | `pick` | U003 | `eager` | S003 | `wrap` | | C004 | `put` | U004 | `concat` | S004 | `param` | | C005 | `arms` | U005 | retired | S005 | `noqa` | -| C006 | `escape` | U006 | `fuel` | L001 | `coverage` | -| C007 | `twice` | U007 | `index` | L002 | `closed` | -| C008 | `strings` | U008 | `table` | L003 | `unsafe` | -| C009 | `chars` | U009 | `hoist` | L004 | retired | -| C010 | `foreign` | U010 | `ring` | L005 | retired | -| C011 | `setting` | U011 | `rewalk` | P001 | `tail` | -| | | U012 | `unit` | S006 | `transcong` | -| | | U013 | `scan` | | | +| C006 | `escape` | U006 | `fuel` | S006 | `transcong` | +| C007 | `twice` | U007 | `index` | L001 | `coverage` | +| C008 | `strings` | U008 | retired | L002 | `closed` | +| C009 | `chars` | U009 | retired | L003 | retired | +| C010 | `foreign` | U010 | retired | L004 | retired | +| C011 | `setting` | U011 | `rewalk` | L005 | retired | +| | | U012 | retired | P001 | `tail` | +| | | U013 | retired | | | | | | U014 | retired | | | | | | U015 | `thunk` | | | | | | U016 | `fromrev` | | | | | | U017 | `prefix` | | | -Letters: `C` correctness, `U` suspicious, `S` style, `L` laws, `P` pedantic. +Letters: `C` correctness, `U` suspicious, `S` style, `L` laws, `P` pedantic, +for the group a rule had when its code was assigned. A code names the rule, +not its group, so a rule that moves keeps it: `strings` and `chars` (C008, +C009) are `perf`, what is slow but never wrong; `foreign` (C010), `strict` +(U002), `fuel` (U006), `index` (U007) and `rewalk` (U011) are `pedantic`, +where too few of their findings were defects, or their fix was slower, to +report by default. `pedantic` is advice that is noisy on idiomatic code: off until a project asks for it. L004 was `quantify`, the opt-in strict mode of `closed`; @@ -87,14 +94,24 @@ a check on `IO.args()` readers, retired before its first release: every reader already drops the program, and it could not tell one that does from one that does not. U014 is never reused either. L005 was `trace`, which held a SPEC.md table (now docs/SPEC.md) and the laws' ID tags to each other; a guarantee is -named by its law file now, and L005 is never reused. `scan` and `thunk` are -opt-in: they are in `suspicious`, but no group setting reaches them; only -`def scan()` or `def thunk()` turns each on, and `transcong`, in `style`, only by -`def transcong()`. An unknown level word grades as an error, so a typo shows. A `bolt.bend` is +named by its law file now, and L005 is never reused. U008 `table`, U009 +`hoist`, U010 `ring`, U012 `unit` and U013 `scan` were retired when an audit +of public Bend found none or few of their findings real (`index` still +leaves a get on a fixed table alone, src/rules/table.bend). L003 `unsafe` +was retired since bend 2.0.32 itself fails a proof that reaches an +`@unsafe` or foreign def. None of these codes is reused. A bolt.bend that +still sets one of them gets a `setting` finding (C011); delete the def. + +`thunk` and `transcong` are opt-in: they are in their groups, but no group +setting reaches them; only `def thunk()` or `def transcong()` turns each on. +So are `coverage` and `closed`: what every def must be named by a law, and +whether a law with no binder is allowed, are a project's policy, not a +defect, so a project that keeps laws turns them on by name (`def +coverage() -> String: "warn"`). An unknown level word grades as an error, so a typo shows. A `bolt.bend` is read, never linted. A setting whose name is no rule's slug and no group sets nothing, so `setting` (C011) reports it. Without one, the defaults apply. This repo's -[bolt.bend](../bolt.bend) sets every group to error: the gate must see -`clean`. +[bolt.bend](../bolt.bend) sets every group to error and turns the opt-in +laws rules on: the gate must see `clean`. ## Suppressing a finding @@ -110,7 +127,7 @@ commas (spaces around them allowed), then any text. Codes are the stable ids above, case-sensitive, and a comment may name several (`# noqa: U002, U003`). Only a real comment counts, as the lexer reads it: `noqa` inside a string literal is text. The filter runs once every rule has -run, the project rules (`coverage`, `unsafe`) included, and a +run, the project rule (`coverage`) included, and a silenced finding is neither printed nor counted, so it changes neither the summary nor the exit code. No rule reads the comments: what each rule finds is the same with or without them. The editor applies the same filter. @@ -136,8 +153,7 @@ digests (`rules.bend`'s `project`). The project rules run per package (laws/PackagesScoped.bend). Over the whole tree, a package root is `.` and every other directory the walk reads that holds a `main.bend`, and a file belongs to the nearest root at or above -it. `coverage` runs on one package's files at a time; `unsafe` runs on every -file. Over files named on the line, `.` is the only package. So a repo with +it. `coverage` runs on one package's files at a time. Over files named on the line, `.` is the only package. So a repo with nested packages, each with its own laws, lints in one run from the top, and a law covers only defs of its own package. @@ -158,12 +174,11 @@ its phases use the whole list several times, and a `+` reuse copies what it is given, so handing them the parsed `Src` duplicated every tree (100 files cost 362 s, growing faster than the count; 28 s now). The digest carries only what those rules ask: the paths, the top-level defs and types, what the -laws name, the imports and the `@unsafe` defs. +laws name, the imports and the laws. What the rules share, `rules/calls.bend` (the recursion rules), `rules/tokens.bend`, `rules/imports.bend` and `rules/digest.bend`, sits beside the groups. The cost rules built on `rules/calls.bend` (`pick`, -`strict`, `eager`, `tail`, `concat`, `index`, `table`, `hoist`, `ring`, -`rewalk`, `unit`, `scan`, `thunk`) skip what never runs: a law file, a proof file, and a def +`strict`, `eager`, `tail`, `concat`, `index`, `rewalk`, `thunk`) skip what never runs: a law file, a proof file, and a def that is a proof wherever it is, one that returns a proof (`-> {a == b : T}`) or one written with no type at all (`def f(x, y):`, no `:` among its parameters and no `->`), which is how Bend fills the law named `f`. @@ -245,7 +260,7 @@ parameters and no `->`), which is how Bend fills the law named `f`. thunk (`Lazy.stop(T, hit, x, _u => go(rest))`) stops early but leaves the loop on every step, and measured slower. Law files, proof files and defs that are proofs (they return a proof, or have no type) are exempt. -- `strict` — a self-call inside `Bool.and`/`Bool.or`, or either side of +- `strict` (pedantic) — a self-call inside `Bool.and`/`Bool.or`, or either side of `&&`/`||`, matched by exactly those texts (a qualified `Base.Bool.or` is not seen). They are functions too: both sides always run, so there is no short-circuit (a game's overlap test went 31 -> 55 fps once the call moved @@ -285,61 +300,20 @@ parameters and no `->`), which is how Bend fills the law named `f`. alone (`path ++ "/" ++ name` builds a value used elsewhere, not an accumulator). Prepend each chunk and reverse once at the end (ez's slurp, native: 14.75 s -> 0.36 s at 16 MB). -- `index` — `List.get`/`String.get` at a computed index inside a def that +- `index` (pedantic) — `List.get`/`String.get` at a computed index inside a def that calls itself: the list is walked again each step. Walk the cells instead (one sort phase went 39 s -> 0.9 s). A get anywhere in the def is reported, one in a base arm that runs once included. A literal index of - any size is exempt. A `List.get` on a fixed table is `table`'s; a - `String.get` always stays here. A get that is the def's own self-call (the + any size is exempt, and so is a `List.get` on a fixed table (a literal, a + sized array, or `List.replicate` / `Array.new` / `List.range` with a + constant count: src/rules/table.bend); a `String.get` always stays here. A get that is the def's own self-call (the step of a def named `List.get` or `String.get`, as Base's are) is exempt. -- `table` — `List.get` or `List.set` at a computed index inside a def that - calls itself, when the list is a fixed table (a literal, a sized array, or - `List.replicate` / `Array.new` / `List.range` with a constant count), - inline, as a table def of the file, or held by the let of that name in - scope. A let reaches the statements after it, not a sibling case arm, and a - later let of the name to anything else ends it. Keep it in an `Array`. A - literal index, a growing list, and a data-dependent length stay with - `index` or stay quiet, so one call is one finding. -- `hoist` — a list or array of more than eight constants, or a call that - builds one from inputs that do not change, sits inside a def that calls - itself and is then indexed. The build runs again on every step. Build it - once, outside the recursion. A call builds one when it is a Base - constructor (`List.replicate`, `Array.new`, ...) or a def of the file whose - body is itself a fixed table of more than eight cells; a def whose body is - not in the file does not count. Eight cells or fewer, and a build whose - arguments depend on the step, are left alone. -- `ring` — a self-call replaces a binder with a drop of a constant count and - an append (`List.drop` / `List.tail` / `String.drop` / `String.tail`, then - `List.append` / `String.append` / `++`), passed back in that binder's own - parameter position. Each step copies the window. Keep it in an `Array` and - advance an index. A list the def matches as input (any scrutinee of a - match), a window dropped into another parameter's slot, and a one-shot trim - are left alone. -- `rewalk` — one straight piece of a def calls the same walk twice on the +- `rewalk` (pedantic) — one straight piece of a def calls the same walk twice on the same argument, and one result is used only for a single value (one index, one field, or a let read only that way) while the other result is kept whole. Take the value from that other result. A let of a name the argument uses between the two calls makes them different walks, and so does a different case arm. -- `unit` — a multiply or divide by the literal `1`, `1n` or `1.0` on a - step that recurses, either as `*` / `/` or as `Nat.mul` / `U32.mul` / - `F32.mul` (and `.div`, only when the divisor is one). Drop the operation. - Any other factor or divisor is left alone, and so is a base case: a case - arm that does not call the def. Only a `case` arm can be a base case, so a - `Bool.pick` branch beside a self-call is still the step, and so is a lambda - body inside it. One finding per operation: `Nat.mul(1n, 1n)` is one. -- `scan` (opt-in) — a def that calls itself and, on the same step, hands a - parameter it carries unchanged (every self-call passes it back as the same - lone name in its own position) to a walk: a Base list search - (`List.contains`, `List.find`, `List.filter`, `List.length`, `List.any`, - `List.all`, `List.foldl`, `List.foldr`), or a def of the file that walks - that argument (the one it shrinks, unless a `Nat` or a `String`, or one it - passes to a walk). Every step walks the list again, O(|A| * |B|). Index it - once outside the loop, or merge two sorted walks. A literal table or an - expression in that argument, a lambda's body, a case arm that does not - recurse, a Base walk that rebuilds the list (`List.map`, `List.append`), - and a def of another module are left alone. One finding per call, on its - callee. - `thunk` (opt-in) — a lambda whose body is exactly a self-call and whose parameter the call does not read, passed as the one lambda of a call: a `Unit -> T` thunk such as `Lazy.or_else(hit, _u => go(rest, k))`. The @@ -403,12 +377,12 @@ parameters and no `->`), which is how Bend fills the law named `f`. `Map.put` (`def Map.put`, Base's own source) is exempt. - `escape` — `\0` then a digit in a literal (`"\033"`). Bend has no octal escape: that is NUL followed by the digits. Write `\u{1B}`. -- `strings` — a `match` over string-literal arms totalling more than 64 +- `strings` (perf) — a `match` over string-literal arms totalling more than 64 characters: compile time and memory blow up with the characters (45 chars cost 0.8 s and 0.35 GB here, 480 chars 12 s and 4.8 GB). Map the string to a sum type once. Only the first match column is read, and a literal with no closing quote is not counted. -- `chars` — a `match` with more than eight character-literal arms +- `chars` (perf) — a `match` with more than eight character-literal arms (`case '.':`). `Char` is `Chr{code: U32}`, so each arm is a U32 literal inside a constructor pattern, and the C backend pays about 90 MB for one (eighteen arms cost 1.57 GB and 8.3 s here, 0.10 GB and 0.7 s once @@ -428,7 +402,7 @@ parameters and no `->`), which is how Bend fills the law named `f`. - `arms` — a later `Nat` arm that an earlier `kn+p` already matches, so it is unreachable. `Succ{p}` and `Succ{_}` count as `1n+p`. The checker takes it silently and the answer is wrong: put the narrow arms first. -- `foreign` — a foreign def with a `.c` body and no `.js` body, or the +- `foreign` (pedantic) — a foreign def with a `.c` body and no `.js` body, or the reverse: the missing lane cannot run it. A file headed `# lanes: native` needs no `.js`: that exact line must be one of the comment lines before the file's first non-comment line. @@ -437,11 +411,11 @@ parameters and no `->`), which is how Bend fills the law named `f`. or `shadow`): grading only looks names up, so it sets nothing, and the typo fails open. It is reported once for each bolt.bend that grades a file of the run, at that bolt.bend's path and the setting's `def` line, - after the per-file findings and before `coverage` and `unsafe`. + after the per-file findings and before `coverage`'s. It is graded by that bolt.bend like any rule (`def setting() -> String: "off"` there turns it off). A bolt.bend is read, not linted, so a noqa comment in one silences nothing. bolt runs it; the editor does not. -- `fuel` — a `Nat` literal (digits, then `n`), or `U32.to_nat` of a U32 +- `fuel` (pedantic) — a `Nat` literal (digits, then `n`), or `U32.to_nat` of a U32 literal (`U32.to_nat(100000)`), passed in a call, `name(..)`, to a fuel parameter of a def of the same file. A fuel parameter is known by its name alone, the one before its colon: `fuel`, `gas`, `steps` or `budget`, or @@ -462,21 +436,11 @@ parameters and no `->`), which is how Bend fills the law named `f`. ~64K elements elsewhere). Carry an accumulator. The test is on that parameter's type alone, never on whether the self-call shrinks it. Everything inside a `Bool.pick(..)` is skipped. -- `closed` — a law in a law file with no `for`/`exs` binder. A closed law, +- `closed` (opt-in) — a law in a law file with no `for`/`exs` binder. A closed law, an equality (`{lhs == rhs : T}`, including `IO(T)`) or not, holds for the one input it names: a unit test the checker runs, not a guarantee. Nothing exempts one: quantify it, or delete it. -- `unsafe` (project) — an `@unsafe def` or a foreign def (a body of only - `import "./x.c"` / `import "./x.js"` lines) that a LAWS.bend or PROOF.bend - reaches through its relative imports, over the files bolt read. Since bend - 2.0.32 such a proof fails: bend prints `SOME PROOFS FAIL`, then `Error: N - defs rely on unsafe or foreign code:` and a `- name` list, and exits 1 - (2.0.34), every def of every imported module counted. That list names the - defs, not the import that brought the effect in; the finding names the - import path from the law file (`LAWS.bend -> mid.bend -> eff/io.bend`). Move - the effect into a sibling module no law file imports and pass it in as a - service. Hash imports (`import 0x.../path`) are not followed. -- `coverage` (project) — in a package that states laws (a LAWS.bend among its +- `coverage` (project, opt-in) — in a package that states laws (a LAWS.bend among its files), a def or a type that no law of the package reaches. It is `coverage`, not `law`, because `law` is a Bend keyword: `def law()` is no def, so a bolt.bend could never set it by name. IO is no exemption: a def that @@ -532,6 +496,11 @@ case, but its absence is not held against a rule: most Bend code is written with agents, and a shape nobody's agent flags stays unfixed whether or not it is a defect. +A guard is the one exception to **Seen**: a rule whose shape is rare but +whose miss is a silently wrong result that `bend` never reports (`arms`, +`escape`, `setting`) may ship on fewer sites, as long as it is **Precise**, +its fix is legal, and it is **New**. + A rule is on by default only at near-zero false positives; anything less certain starts opt-in or in `pedantic`. A shipped rule that projects often `# noqa` is demoted or reworked. diff --git a/src/codes.bend b/src/codes.bend index a6fbd84..4892858 100644 --- a/src/codes.bend +++ b/src/codes.bend @@ -4,7 +4,13 @@ # of src/README.md: do not renumber a rule, and do not reuse a retired code # (L004 was `quantify`, now `closed` itself; C001 was `shadow` and U005 was # `nat`, whose failures bend 2.0.25 no longer has; U014 was `argv`, retired -# before its first release, since every reader already drops the program). +# before its first release, since every reader already drops the program; +# U008 `table`, U009 `hoist`, U010 `ring`, U012 `unit` and U013 `scan` were +# retired for want of real hits, and L003 `unsafe` since bend 2.0.32 reports +# an unsafe def a proof reaches itself). A code names the rule, not its +# group: a rule that moves keeps its code (`strings` and `chars` are `perf`, +# `foreign` and the cost rules `fuel`, `index`, `strict` and `rewalk` are +# `pedantic`). # # No law can say this, since it is about versions: a released code is never # renumbered or reused, which review of this table keeps. @@ -24,25 +30,15 @@ def table() -> List<&2, Entry>: Entry{"arms", "C005", "correctness"}, Entry{"escape", "C006", "correctness"}, Entry{"twice", "C007", "correctness"}, - Entry{"strings", "C008", "correctness"}, - Entry{"chars", "C009", "correctness"}, - Entry{"foreign", "C010", "correctness"}, Entry{"setting", "C011", "correctness"}, Entry{"unused", "U001", "suspicious"}, - Entry{"strict", "U002", "suspicious"}, Entry{"eager", "U003", "suspicious"}, Entry{"concat", "U004", "suspicious"}, - Entry{"fuel", "U006", "suspicious"}, - Entry{"index", "U007", "suspicious"}, - Entry{"table", "U008", "suspicious"}, - Entry{"hoist", "U009", "suspicious"}, - Entry{"ring", "U010", "suspicious"}, - Entry{"rewalk", "U011", "suspicious"}, - Entry{"unit", "U012", "suspicious"}, - Entry{"scan", "U013", "suspicious"}, Entry{"thunk", "U015", "suspicious"}, Entry{"fromrev", "U016", "suspicious"}, Entry{"prefix", "U017", "suspicious"}, + Entry{"strings", "C008", "perf"}, + Entry{"chars", "C009", "perf"}, Entry{"doc", "S001", "style"}, Entry{"space", "S002", "style"}, Entry{"wrap", "S003", "style"}, @@ -51,7 +47,11 @@ def table() -> List<&2, Entry>: Entry{"transcong", "S006", "style"}, Entry{"coverage", "L001", "laws"}, Entry{"closed", "L002", "laws"}, - Entry{"unsafe", "L003", "laws"}, + Entry{"foreign", "C010", "pedantic"}, + Entry{"strict", "U002", "pedantic"}, + Entry{"fuel", "U006", "pedantic"}, + Entry{"index", "U007", "pedantic"}, + Entry{"rewalk", "U011", "pedantic"}, Entry{"tail", "P001", "pedantic"}] # this row is the slug diff --git a/src/config.bend b/src/config.bend index c80cffc..281b123 100644 --- a/src/config.bend +++ b/src/config.bend @@ -283,9 +283,10 @@ def default(+group: String) -> Level: # a rule only its own setting turns on: a group setting, or the group's # default, would put new findings on projects that never asked for it -# (`thunk`, `scan` and `transcong`) +# (`thunk`, `transcong`, and the laws' `coverage` and `closed`, which are a +# project's policy rather than defects) def opt_in(+rule: String) -> Bool: - List.contains(~String, ~String.eq, ["thunk", "scan", "transcong"], rule) + List.contains(~String, ~String.eq, ["thunk", "transcong", "coverage", "closed"], rule) # the rule's own setting, else its group's, else the group's default; an # opt-in rule's own setting, else off diff --git a/src/rules.bend b/src/rules.bend index 09a0343..9015ba6 100644 --- a/src/rules.bend +++ b/src/rules.bend @@ -2,7 +2,7 @@ # rules// with `check(src) -> List`, run over each file read # once (src/src.bend); a project rule has `check(ds) -> List` and # sees many files at once, as the digests of rules/digest.bend: `coverage` -# one package's files, `unsafe` every file (`project` below). The directory +# one package's files at a time (`project` below). The directory # names the group, and codes.bend says it again for the config. # Adding one is adding it here and a row in codes.bend. One rule is not # listed here, `noqa` (rules/style/noqa.bend): it reads the other rules' @@ -11,9 +11,9 @@ # grades the run, not a source, so the planner runs it between the per-file # and the project rules. # The groups are what a bolt.bend switches at once: correctness (what will -# fail or blow up), suspicious (what is probably a slip), style, laws, and -# pedantic (advice that is noisy on idiomatic code: off unless a project -# asks for it). +# fail or blow up), suspicious (what is probably a slip), perf (what is +# slow but right), style, laws, and pedantic (advice that is noisy on +# idiomatic code: off unless a project asks for it). import Base import ./finding.bend as F import ./codes.bend as Codes @@ -32,30 +32,24 @@ import ./rules/style/param.bend as Param import ./rules/style/transcong.bend as Transcong import ./rules/correctness/pick.bend as Pick import ./rules/laws/law.bend as Law -import ./rules/suspicious/strict.bend as Strict +import ./rules/pedantic/strict.bend as Strict import ./rules/suspicious/eager.bend as Eager import ./rules/pedantic/tail.bend as Tail import ./rules/suspicious/concat.bend as Concat -import ./rules/suspicious/index.bend as Index -import ./rules/suspicious/table.bend as Table -import ./rules/suspicious/hoist.bend as Hoist -import ./rules/suspicious/ring.bend as Ring -import ./rules/suspicious/rewalk.bend as Rewalk -import ./rules/suspicious/unit.bend as Unit -import ./rules/suspicious/scan.bend as Scan +import ./rules/pedantic/index.bend as Index +import ./rules/pedantic/rewalk.bend as Rewalk import ./rules/suspicious/thunk.bend as Thunk import ./rules/suspicious/fromrev.bend as Fromrev import ./rules/suspicious/prefix.bend as Prefix import ./rules/correctness/put.bend as Put import ./rules/correctness/escape.bend as Escape -import ./rules/correctness/strings.bend as Strings -import ./rules/correctness/chars.bend as Chars +import ./rules/perf/strings.bend as Strings +import ./rules/perf/chars.bend as Chars import ./rules/correctness/twice.bend as Twice import ./rules/correctness/arms.bend as Arms -import ./rules/correctness/foreign.bend as Foreign -import ./rules/suspicious/fuel.bend as Fuel +import ./rules/pedantic/foreign.bend as Foreign +import ./rules/pedantic/fuel.bend as Fuel import ./rules/laws/closed.bend as Closed -import ./rules/laws/unsafe.bend as Unsafe # the file a Src was read from def on.path(ss: Src.Src) -> String: @@ -72,9 +66,9 @@ def on.at(+path: String, fs: List<&2, F.Finding>) -> List<&2, F.Finding>: F.Finding{path, line, col, len, rule, msg} <> on.at(path, rest) # every per-file rule on a file, told its role (src/role.bend): the rules a -# law or proof file changes (doc, hole, param, scan, fromrev, prefix, -# closed) read the role, never the path, and the cost rules check nothing -# in a law or proof file +# law or proof file changes (doc, hole, param, fromrev, prefix, closed) read +# the role, never the path, and the cost rules check nothing in a law or +# proof file def on.rules.role(+rr: Role.Role, +ss: Src.Src) -> List<&2, F.Finding>: List.concat(&2, F.Finding, [ Doc.check.role(rr, ss), @@ -91,12 +85,7 @@ def on.rules.role(+rr: Role.Role, +ss: Src.Src) -> List<&2, F.Finding>: Role.skip(rr, _u => Concat.check(ss)), Role.skip(rr, _u => Concat.cont(ss)), Role.skip(rr, _u => Index.check(ss)), - Role.skip(rr, _u => Table.check(ss)), - Role.skip(rr, _u => Hoist.check(ss)), - Role.skip(rr, _u => Ring.check(ss)), Role.skip(rr, _u => Rewalk.check(ss)), - Role.skip(rr, _u => Unit.check(ss)), - Scan.check.role(rr, ss), Role.skip(rr, _u => Thunk.check(ss)), Fromrev.check.role(rr, ss), Prefix.check.role(rr, ss), @@ -171,10 +160,10 @@ def members(ds: List<&2, Digest.Digest>, +roots: List<&2, String>, +pkg: String) match ds: case Nil{}: Nil{} - case Con{Digest.Digest{path, norm, +dir, is_laws, is_law_file, exempt, tops, says, deps, unsafes, laws}, rest}: + case Con{Digest.Digest{path, norm, +dir, is_laws, is_law_file, exempt, tops, says, deps, laws}, rest}: +more = members(rest, roots, pkg) Bool.pick(List<&2, Digest.Digest>, String.eq(owner(roots, dir), pkg), - Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, unsafes, laws} <> more, more) + Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, laws} <> more, more) # `coverage` on each package but `.` def covers(rs: List<&2, String>, +ds: List<&2, Digest.Digest>, +roots: List<&2, String>) -> List<&2, F.Finding>: @@ -187,10 +176,10 @@ def covers(rs: List<&2, String>, +ds: List<&2, Digest.Digest>, +roots: List<&2, # every project rule's findings on the files and the package roots but `.` # (each a directory as Digest.dir names one): each file is read down to its # digest once (rules/digest.bend); `coverage` runs on each package's files, -# `.` first, and `unsafe` on every file +# `.` first def project(+srcs: List<&2, Src.Src>, +rs: List<&2, String>) -> List<&2, F.Finding>: +ds = Digest.all.role(Role.marks(srcs), srcs) - List.concat(&2, F.Finding, [Law.check(members(ds, rs, "")), covers(rs, ds, rs), Unsafe.check(ds)]) + List.concat(&2, F.Finding, [Law.check(members(ds, rs, "")), covers(rs, ds, rs)]) # the group a rule is in; an unknown slug grades with correctness def group(+rule: String) -> String: diff --git a/src/rules/digest.bend b/src/rules/digest.bend index 6ad13a0..9976f2c 100644 --- a/src/rules/digest.bend +++ b/src/rules/digest.bend @@ -4,8 +4,8 @@ # file's text, tokens and tree, and the pair of project rules grew faster than # the file count (100 files cost 362 s that way). So each file is read down # once to what those rules ask of it -- its path, its top-level defs and types -# by name and line, what its laws name, what it imports, and its `@unsafe` -# and foreign defs -- and the phases run over these, which are strings and cheap to copy. +# by name and line, what its laws name, what it imports, and its laws -- and +# the phases run over these, which are strings and cheap to copy. import Base import ../src.bend as Src import ../syntax/lex.bend as Lex @@ -17,7 +17,6 @@ import ./imports.bend as Imports import ./laws/closed.bend as Closed import ../paths.bend as Paths import ../role.bend as Role -import ./correctness/foreign.bend as Foreign import ./calls.bend as Calls # what a file holds @@ -34,12 +33,6 @@ type Mention is Data: type Top is Data: Top{kind: Outline.ItemKind, name: String, line: U32, ctors: List<&2, String>, calls: List<&2, Mention>} -# a def a proof may not reach (bend 2.0.32): an `@unsafe def`, where its -# `@` is, or a foreign def (its body only `import "./x.c"` / `import -# "./x.js"` lines), where its header starts; foreign says which -type Unsafe is Data: - Unsafe{name: String, line: U32, col: U32, foreign: Bool} - # a law of a LAWS.bend: its name, the line it starts on, whether it binds # (a `for` or `exs` line), and the lines of its comment block, `# ` dropped type Law is Data: @@ -48,10 +41,10 @@ type Law is Data: # one file as the project rules see it: its path as given and normalized, its # directory, whether it states laws (LAWS.bend) or proves them too # (PROOF.bend), whether the coverage rule lets it off, its top-level items, what -# its own laws name, the files it imports, its `@unsafe` and foreign defs +# its own laws name, the files it imports, and its laws type Digest is Data: Digest{path: String, norm: String, dir: String, is_laws: Bool, is_law_file: Bool, exempt: Bool, - tops: List<&2, Top>, says: List<&2, Mention>, deps: List<&2, String>, unsafes: List<&2, Unsafe>, + tops: List<&2, Top>, says: List<&2, Mention>, deps: List<&2, String>, laws: List<&2, Law>} # what the laws name @@ -260,65 +253,6 @@ def ranged( def is_exempt(+pp: String) -> Bool: Bool.or(Paths.is_law_file(pp), Paths.is_test(pp)) -# the significant tokens: no space, newline or comment -def significant(toks: List<&2, Lex.Tok>) -> List<&2, Lex.Tok>: - match toks: - case Nil{}: - Nil{} - case Con{Lex.Tok{+k, t, l, c}, rest}: - +more = significant(rest) - Bool.pick(List<&2, Lex.Tok>, Lex.significant(k), Lex.Tok{k, t, l, c} <> more, more) - -# the name after `unsafe def`, when the tokens run so -def unsafe_name(toks: List<&2, Lex.Tok>) -> Maybe<&2, String>: - match toks: - case Con{Lex.Tok{k1, +u, l1, c1}, Con{Lex.Tok{k2, +d, l2, c2}, Con{Lex.Tok{k3, n, l3, c3}, rest}}}: - Bool.pick(Maybe<&2, String>, Bool.and(String.eq(u, "unsafe"), String.eq(d, "def")), Some{n}, None{}) - case other: - None{} - -# an `@` that heads an unsafe def, onto the others -def push(mm: Maybe<&2, String>, +ll: U32, +cc: U32, more: List<&2, Unsafe>) -> List<&2, Unsafe>: - match mm: - case None{}: - more - case Some{n}: - Unsafe{n, ll, cc, False{}} <> more - -# every `@ unsafe def name` run of tokens, on one line or two -def unsafe_defs(toks: List<&2, Lex.Tok>) -> List<&2, Unsafe>: - match toks: - case Nil{}: - Nil{} - case Con{Lex.Tok{k, +at, +l, +c}, +rest}: - +more = unsafe_defs(rest) - Bool.pick(List<&2, Unsafe>, String.eq(at, "@"), push(unsafe_name(rest), l, c, more), more) - -# a foreign def, when its body is one (Foreign.lanes), onto the others -def foreign.push( - mm: Maybe<&2, Foreign.Lanes>, - +name: String, - +ll: U32, - +cc: U32, - more: List<&2, Unsafe> -) -> List<&2, Unsafe>: - match mm: - case None{}: - more - case Some{_lanes}: - Unsafe{name, ll, cc, True{}} <> more - -# every top-level foreign def, where its header starts -def foreign_defs(root: Tree.Node) -> List<&2, Unsafe>: - match root: - case Tree.NCons{Tree.Stmt{Tree.SDef{}, +kids, body}, rest}: - foreign.push(Foreign.lanes(body), Foreign.name.of(Bind.declared(kids)), Tree.line(kids), Tree.col(kids), - foreign_defs(rest)) - case Tree.NCons{h, rest}: - foreign_defs(rest) - case other: - Nil{} - # the laws # -------- @@ -395,7 +329,6 @@ def of(s2: Src.Src) -> Digest: Digest{path, p, Imports.dir_of(path), Paths.is_laws(path), Paths.is_law_file(path), is_exempt(p), ranged(items, uses.of(bound), as, p, proofs(Calls.defs(tree))), stated(Paths.is_laws(path), tree, bound, as, p), Imports.targets(items, p), - List.append(&2, Unsafe, unsafe_defs(significant(toks)), foreign_defs(tree)), laws.in(Paths.is_laws(path), tree, items)} # one parsed file read down to its digest, told its role (src/role.bend): a @@ -408,7 +341,6 @@ def of.role(+rr: Role.Role, s2: Src.Src) -> Digest: Digest{path, p, Imports.dir_of(path), laws, Role.is_law_file(rr), Bool.or(Role.is_law_file(rr), Paths.is_test(p)), ranged(items, uses.of(bound), as, p, proofs(Calls.defs(tree))), stated(laws, tree, bound, as, p), Imports.targets(items, p), - List.append(&2, Unsafe, unsafe_defs(significant(toks)), foreign_defs(tree)), laws.in(laws, tree, items)} # one parsed file's digest, told its role among the marks (src/role.bend) @@ -430,5 +362,5 @@ def edges(ds: List<&2, Digest>) -> List<&2, Imports.Edge>: match ds: case Nil{}: Nil{} - case Con{Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, unsafes, laws}, rest}: + case Con{Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, laws}, rest}: Imports.Edge{norm, deps} <> edges(rest) diff --git a/src/rules/imports.bend b/src/rules/imports.bend index dfee61d..8792c4b 100644 --- a/src/rules/imports.bend +++ b/src/rules/imports.bend @@ -172,43 +172,3 @@ def walk( # the closure of the file at from, a path normalized already, over the graph def closure.go(es: List<&2, Edge>, +read: List<&2, String>, +fuel: Nat, +from: String) -> List<&2, String>: List.reverse(&2, String, walk(fuel, [from], [from], es, read)) - -# a file the trail's walk has queued, and the files it was reached through, -# itself first and the start last -type Step is Data: - Step{at: String, back: List<&2, String>} - -# each file queued, reached through back -def steps(ds: List<&2, String>, +back: List<&2, String>) -> List<&2, Step>: - match ds: - case Nil{}: - Nil{} - case Con{+d, rest}: - Step{d, d <> back} <> steps(rest, back) - -# the walk `walk` takes, breadth first, each queued file carrying how it was -# reached, until it takes the file at to: the files from the start to it, in -# order; none when the walk never takes it -def trail.go( - fuel: Nat, - todo: List<&2, Step>, - +seen: List<&2, String>, - +es: List<&2, Edge>, - +read: List<&2, String>, - +to: String -) -> List<&2, String>: - match fuel todo: - case 0n t: - Nil{} - case 1n+f Nil{}: - Nil{} - case 1n+f Con{Step{+at, +back}, rest}: - +found = fresh.fast(deps.first(es, at), seen, read) - Lazy.stop(List<&2, String>, String.eq(at, to), List.reverse(&2, String, back), - _u => trail.go(f, List.append(&2, Step, rest, steps(found, back)), - List.append(&2, String, List.reverse(&2, String, found), seen), es, read, to)) - -# a shortest chain of relative imports from the file at from to the file at -# to, both normalized: the files in order, from first and to last -def trail(es: List<&2, Edge>, +read: List<&2, String>, +fuel: Nat, +from: String, +to: String) -> List<&2, String>: - trail.go(fuel, [Step{from, [from]}], [from], es, read, to) diff --git a/src/rules/laws/law.bend b/src/rules/laws/law.bend index 3323ba2..b1f81b3 100644 --- a/src/rules/laws/law.bend +++ b/src/rules/laws/law.bend @@ -50,7 +50,7 @@ def named(ds: List<&2, Digest.Digest>) -> List<&2, Digest.Mention>: match ds: case Nil{}: Nil{} - case Con{Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, unsafes, laws}, rest}: + case Con{Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, laws}, rest}: named.put(is_laws, says, named(rest)) # the graph @@ -95,7 +95,7 @@ def graph.items(ds: List<&2, Digest.Digest>) -> List<&2, Imports.Edge>: match ds: case Nil{}: Nil{} - case Con{Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, unsafes, laws}, rest}: + case Con{Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, laws}, rest}: nodes(tops, norm, graph.items(rest)) # the graph: the empty key first, leading to what the laws name, then the @@ -119,7 +119,7 @@ def law_dirs(ds: List<&2, Digest.Digest>) -> List<&2, String>: match ds: case Nil{}: Nil{} - case Con{Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, unsafes, laws}, rest}: + case Con{Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, laws}, rest}: +more = law_dirs(rest) Bool.pick(List<&2, String>, is_laws, dir <> more, more) @@ -146,7 +146,7 @@ def check.go(ds: List<&2, Digest.Digest>, +cl: List<&2, String>) -> List<&2, F.F match ds: case Nil{}: Nil{} - case Con{Digest.Digest{path, +norm, dir, is_laws, is_law_file, exempt, tops, says, deps, unsafes, laws}, rest}: + case Con{Digest.Digest{path, +norm, dir, is_laws, is_law_file, exempt, tops, says, deps, laws}, rest}: +more = check.go(rest, cl) Lazy.stop(List<&2, F.Finding>, exempt, more, _u => List.append(&2, F.Finding, gaps(tops, cl, norm, path), more)) diff --git a/src/rules/laws/unsafe.bend b/src/rules/laws/unsafe.bend deleted file mode 100644 index f0c0aa8..0000000 --- a/src/rules/laws/unsafe.bend +++ /dev/null @@ -1,138 +0,0 @@ -# rule unsafe (project-wide): an `@unsafe def` or a foreign def (its body -# only `import "./x.c"` / `import "./x.js"` lines, what rule foreign reads) -# that a LAWS.bend or PROOF.bend reaches by relative imports, over the files -# bolt read. Since bend 2.0.32 a proof passes only when no def it loads, -# imports included, is `@unsafe` or foreign or names one that is: every def -# of every imported module counts, named by a law or not. Otherwise `bend` -# prints `SOME PROOFS FAIL`, then `Error: N defs rely on unsafe or foreign -# code:` and a `- name` list, and exits 1 (2.0.34; before 2.0.32 it printed -# "All terms check, but ..." and exited 0). So the gate is red, and the -# message names the defs that lean on the effect, not the import that -# brought it in: this rule names that path, law file first. `@unsafe` skips -# the termination check (a def that never returns proves anything): recurse -# on a shrinking argument or on fuel, and drop it. A foreign effect is fine -# in the program, never under a law: move it into a sibling module no law -# file imports, and hand the laws' code the effect as a service record -# (AGENTS.md; snap, ezhttp, bolt and ez were restructured that way). A def no -# law file reaches is not this rule's business. Hash imports (`import -# 0x.../path`, resolved through BEND_LIB) are not followed: bolt reads only -# the files of the run. The rule reads each file's digest -# (src/rules/digest.bend), and takes every closure over one import graph. -import Base -import ../../finding.bend as F -import ../digest.bend as Digest -import ../imports.bend as Imports - -# a law file and what it reaches -type Reach is Data: - Reach{origin: String, files: List<&2, String>} - -# every LAWS.bend and PROOF.bend with its closure over the graph -def reaches( - ds: List<&2, Digest.Digest>, - +es: List<&2, Imports.Edge>, - +read: List<&2, String>, - +fuel: Nat -) -> List<&2, Reach>: - match ds: - case Nil{}: - Nil{} - case Con{Digest.Digest{path, norm, dir, is_laws, is_law_file, exempt, tops, says, deps, unsafes, laws}, rest}: - +more = reaches(rest, es, read, fuel) - Bool.pick(List<&2, Reach>, is_law_file, Reach{path, Imports.closure.go(es, read, fuel, norm)} <> more, more) - -# the first law file that reaches the path; hit: the one before rs did, so -# the search stops there -def reacher.go(rs: List<&2, Reach>, +pp: String, hit: Maybe<&2, String>) -> Maybe<&2, String>: - match rs: - case Nil{}: - hit - case Con{Reach{origin, ps}, rest}: - match hit: - case Some{x}: - Some{x} - case None{}: - reacher.go(rest, pp, Bool.pick(Maybe<&2, String>, Imports.has(ps, pp), Some{origin}, None{})) - -# the first law file that reaches the path -def reacher(rs: List<&2, Reach>, +pp: String) -> Maybe<&2, String>: - reacher.go(rs, pp, None{}) - -# what a def reached from a law file along the import path via is, and what -# to do about it -def say(foreign: Bool, +name: String, +origin: String, +via: String) -> String: - match foreign: - case True{}: - "foreign def " ++ name ++ " is reachable from " ++ origin ++ " (" ++ via - ++ "), so bend fails that proof: SOME PROOFS FAIL, defs rely on unsafe or foreign code; move the effect into a sibling module no law file imports, and pass it in as a service." - case False{}: - "@unsafe def " ++ name ++ " is reachable from " ++ origin ++ " (" ++ via - ++ "), so bend fails that proof: SOME PROOFS FAIL, defs rely on unsafe or foreign code; recurse on a shrinking argument or on fuel and drop @unsafe, or move it into a sibling module no law file imports." - -# each unsafe or foreign def as a finding, at its `@unsafe` or its `def`, -# reached from the law file along the import path via -def report(us: List<&2, Digest.Unsafe>, +origin: String, +via: String, +path: String) -> List<&2, F.Finding>: - match us: - case Nil{}: - Nil{} - case Con{Digest.Unsafe{n, l, c, +fo}, rest}: - F.Finding{path, l, c, Bool.pick(U32, fo, 3, 7), "unsafe", say(fo, n, origin, via)} <> report(rest, origin, via, path) - -# the import path from the file at from to the file at to, both normalized, -# as `a -> b -> c` -def via(es: List<&2, Imports.Edge>, +read: List<&2, String>, +fuel: Nat, +from: String, +to: String) -> String: - String.join(Imports.trail(es, read, fuel, from, to), " -> ") - -# the unsafe and foreign defs of the file at norm, reached from the law file -# at origin: the import path is looked for only when there is one -def found.some( - us: List<&2, Digest.Unsafe>, - +origin: String, - +path: String, - +norm: String, - +es: List<&2, Imports.Edge>, - +read: List<&2, String>, - +fuel: Nat -) -> List<&2, F.Finding>: - match us: - case Nil{}: - Nil{} - case Con{u, rest}: - report(u <> rest, origin, via(es, read, fuel, Imports.norm(origin), norm), path) - -# the unsafe and foreign defs of a file, when a law file reaches it -def found( - mm: Maybe<&2, String>, - us: List<&2, Digest.Unsafe>, - +path: String, - +norm: String, - +es: List<&2, Imports.Edge>, - +read: List<&2, String>, - +fuel: Nat -) -> List<&2, F.Finding>: - match mm: - case None{}: - Nil{} - case Some{origin}: - found.some(us, origin, path, norm, es, read, fuel) - -def check.go( - ds: List<&2, Digest.Digest>, - +rs: List<&2, Reach>, - +es: List<&2, Imports.Edge>, - +read: List<&2, String>, - +fuel: Nat -) -> List<&2, F.Finding>: - match ds: - case Nil{}: - Nil{} - case Con{Digest.Digest{path, +norm, dir, is_laws, is_law_file, exempt, tops, says, deps, unsafes, laws}, rest}: - List.append(&2, F.Finding, found(reacher(rs, norm), unsafes, path, norm, es, read, fuel), - check.go(rest, rs, es, read, fuel)) - -# the rule, over every file the linter read -def check(+ds: List<&2, Digest.Digest>) -> List<&2, F.Finding>: - +es = Digest.edges(ds) - +read = Imports.paths(es) - +fuel = List.length(&2, Digest.Digest, ds) - check.go(ds, reaches(ds, es, read, fuel), es, read, fuel) diff --git a/src/rules/correctness/foreign.bend b/src/rules/pedantic/foreign.bend similarity index 100% rename from src/rules/correctness/foreign.bend rename to src/rules/pedantic/foreign.bend diff --git a/src/rules/suspicious/fuel.bend b/src/rules/pedantic/fuel.bend similarity index 100% rename from src/rules/suspicious/fuel.bend rename to src/rules/pedantic/fuel.bend diff --git a/src/rules/suspicious/index.bend b/src/rules/pedantic/index.bend similarity index 94% rename from src/rules/suspicious/index.bend rename to src/rules/pedantic/index.bend index 4059826..21b3b49 100644 --- a/src/rules/suspicious/index.bend +++ b/src/rules/pedantic/index.bend @@ -7,8 +7,8 @@ # (`List.get(.., xs, 0n)`, `5000n`) is exempt, as are laws and proofs (a def # with no type at all fills a law: it is a proof). A # `List.get` on a fixed table (a literal, a sized array, a constant -# `List.replicate`) is `table`'s finding instead, so the two rules do not -# both report that call. `String.get` is never `table`'s: it stays here. A +# `List.replicate`: src/rules/table.bend) is left alone; `String.get` always +# stays here. A # get that is the def's own self-call is exempt: the step of a def named # `List.get` or `String.get` itself (Base's), which walks one cell per call. import Base @@ -18,7 +18,7 @@ import ../../syntax/lex.bend as Lex import ../../syntax/tree.bend as Tree import ../calls.bend as Calls import ../../lazy/lazy.bend as Lazy -import ./table.bend as Table +import ../table.bend as Table # is the chain exactly one number? def literal(nn: Tree.Node) -> Bool: @@ -28,7 +28,7 @@ def literal(nn: Tree.Node) -> Bool: case other: False{} -# a fixed table is reported by `table`, not here +# a fixed table (src/rules/table.bend) is left alone def held(+list: Bool, coll: Tree.Node, +fixed: List<&2, String>) -> Bool: match list: case False{}: diff --git a/src/rules/suspicious/rewalk.bend b/src/rules/pedantic/rewalk.bend similarity index 100% rename from src/rules/suspicious/rewalk.bend rename to src/rules/pedantic/rewalk.bend diff --git a/src/rules/suspicious/strict.bend b/src/rules/pedantic/strict.bend similarity index 100% rename from src/rules/suspicious/strict.bend rename to src/rules/pedantic/strict.bend diff --git a/src/rules/correctness/chars.bend b/src/rules/perf/chars.bend similarity index 100% rename from src/rules/correctness/chars.bend rename to src/rules/perf/chars.bend diff --git a/src/rules/correctness/strings.bend b/src/rules/perf/strings.bend similarity index 100% rename from src/rules/correctness/strings.bend rename to src/rules/perf/strings.bend diff --git a/src/rules/style/noqa.bend b/src/rules/style/noqa.bend index 0029b4d..53096cc 100644 --- a/src/rules/style/noqa.bend +++ b/src/rules/style/noqa.bend @@ -7,7 +7,7 @@ # It reads the graded findings of the whole run, so it is no per-file rule: # the planner runs it after every other rule and after the filter, and # what it reports no noqa comment silences. A project rule's code -# (`coverage`, `unsafe`) is judged only when the run is the whole +# (`coverage`) is judged only when the run is the whole # tree (`whole`): over files named, and in the editor, those rules see only # part of the laws, so their silence proves nothing. import Base @@ -18,7 +18,7 @@ import ../../noqa.bend as Noqa # a project rule's code def project(+code: String) -> Bool: - Lazy.or_else(String.eq(code, Codes.code("coverage")), _u => String.eq(code, Codes.code("unsafe"))) + String.eq(code, Codes.code("coverage")) # a code this run can judge: any, over the whole tree; a per-file rule's # (or no rule's) otherwise diff --git a/src/rules/suspicious/hoist.bend b/src/rules/suspicious/hoist.bend deleted file mode 100644 index f119de4..0000000 --- a/src/rules/suspicious/hoist.bend +++ /dev/null @@ -1,653 +0,0 @@ -# rule hoist: a fixed table built inside a def that calls itself, from inputs -# that do not change, and then indexed. The build runs again on every step. -# Build it once, outside the recursion. A build is a list or array literal, -# `List.replicate` / `List.range` / `List.map` / `Array.new` / `Array.map`, or -# a call to a def of this file whose body is itself one fixed table of more -# than eight cells; a def whose body cannot be seen (another module's) is not -# a build. A table of eight cells or fewer is left alone, and so is a build -# whose arguments depend on the step. A name that is not a carried parameter -# (a pattern binder, a value computed on the step) keeps the build; a case arm -# that does not recurse is cold; laws and proofs do not run (a def with no -# type at all fills a law: it is a proof). `List.get` itself stays with -# `index`. -import Base -import ../../src.bend as Src -import ../../finding.bend as F -import ../../syntax/lex.bend as Lex -import ../../syntax/tree.bend as Tree -import ../calls.bend as Calls -import ../../lazy/lazy.bend as Lazy -import ./table.bend as Table - -# where an accessor reads its collection, when it is one -type Slot is Data: - Slot{on: Bool, at: Nat} - -# a `name = expr` whose left side is one plain name -type Got is Data: - Got{nm: String, expr: Tree.Node} - -# which argument a get or set indexes -def slot_pick(+list: Bool, +str: Bool, +arr: Bool) -> Slot: - match list str arr: - case True{} b c: - Slot{True{}, 2n} - case False{} True{} c: - Slot{True{}, 0n} - case False{} False{} True{}: - Slot{True{}, 1n} - case False{} False{} False{}: - Slot{False{}, 0n} - -# List.get / List.set read argument 2, String.get argument 0, Array.get / Array.set argument 1 -def slot_of(+tt: String) -> Slot: - slot_pick( - Bool.or(String.eq(tt, "List.get"), String.eq(tt, "List.set")), - String.eq(tt, "String.get"), - Bool.or(String.eq(tt, "Array.get"), String.eq(tt, "Array.set"))) - -# the self-calls' argument lists -def pushes(nn: Tree.Node, +self: String) -> List<&2, List<&2, Tree.Node>>: - match nn: - case Tree.NCons{Tree.Leaf{Lex.Tok{k, +t, l, c}}, Tree.NCons{Tree.Group{Lex.Tok{_, +o, _, _}, +kids, _}, rest}}: - +me = Bool.and(String.eq(o, "("), String.eq(t, self)) - +more = List.concat(&2, List<&2, Tree.Node>, [pushes(kids, self), pushes(rest, self)]) - Bool.pick(List<&2, List<&2, Tree.Node>>, me, Calls.args(kids) <> more, more) - case Tree.NCons{Tree.Group{open, +kids, close}, rest}: - List.concat(&2, List<&2, Tree.Node>, [pushes(kids, self), pushes(rest, self)]) - case Tree.NCons{Tree.Stmt{kind, +kids, body}, rest}: - List.concat(&2, List<&2, Tree.Node>, [pushes(kids, self), pushes(body, self), pushes(rest, self)]) - case Tree.NCons{h, rest}: - pushes(rest, self) - case other: - Nil{} - -# is the chain exactly this name? -def lone_eq(nn: Tree.Node, +name: String) -> Bool: - match nn: - case Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TName{}, +t, l, c}}, Tree.NNil{}}: - String.eq(t, name) - case other: - False{} - -# every self-call passes this same name at this argument -def all_same(cs: List<&2, List<&2, Tree.Node>>, +ii: Nat, +name: String) -> Bool: - match cs: - case Nil{}: - True{} - case Con{as, rest}: - +here = lone_eq(Calls.arg(as, ii), name) - +more = all_same(rest, ii, name) - Bool.and(here, more) - -# parameters every self-call passes unchanged, when there is a self-call -def carried.go( - ps: List<&2, Tree.Node>, - +cs: List<&2, List<&2, Tree.Node>>, - +ii: Nat, - +acc: List<&2, String> -) -> List<&2, String>: - match ps: - case Nil{}: - List.reverse(&2, String, acc) - case Con{p, rest}: - +nm = Calls.param_name(p) - +keep = Bool.and(Bool.not(String.is_empty(nm)), all_same(cs, ii, nm)) - carried.go(rest, cs, (ii + 1n : Nat), Bool.pick(List<&2, String>, keep, nm <> acc, acc)) - -# parameters passed through unchanged; empty when nothing recurses -def carried_of(cs: List<&2, List<&2, Tree.Node>>, +sig: Tree.Node) -> List<&2, String>: - match cs: - case Nil{}: - Nil{} - case Con{h, t}: - carried.go(Calls.params(sig), h <> t, 0n, []) - -# parameters passed through unchanged; empty when nothing recurses -def carried(sig: Tree.Node, body: Tree.Node, +self: String) -> List<&2, String>: - carried_of(pushes(body, self), sig) - -# a plain name is closed only when it is a carried parameter; a type or a dotted name is -def name_ok(kk: Lex.TokKind, +tt: String, +carried: List<&2, String>) -> Bool: - match kk: - case Lex.TName{}: - List.contains(~String, ~String.eq, carried, tt) - case other: - True{} - -# a token after an operator: a template argument is not a value, anything else is -def leaf_ok(+skip: Bool, tok: Lex.Tok, +carried: List<&2, String>) -> Bool: - match skip: - case True{}: - True{} - case False{}: - Lex.Tok{+k, +t, l, c} = tok - name_ok(k, t, carried) - -# an application is closed in its arguments; a bracketed name is a value -def value_ok(+app: Bool, kk: Lex.TokKind, +tt: String, +carried: List<&2, String>) -> Bool: - match app: - case True{}: - True{} - case False{}: - name_ok(kk, tt, carried) - -# every value name under the node is a carried parameter (callees and templates are not values) -def closed(nn: Tree.Node, +carried: List<&2, String>) -> Bool: - match nn: - case Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TOp{}, +op, l, c}}, Tree.NCons{Tree.Leaf{+tok}, tail}}: - +ok = leaf_ok(String.eq(op, "~"), tok, carried) - +aft = closed(tail, carried) - Bool.and(ok, aft) - case Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TOp{}, op, l, c}}, rest}: - closed(rest, carried) - case Tree.NCons{Tree.Leaf{Lex.Tok{+k, +t, l, c}}, Tree.NCons{Tree.Group{Lex.Tok{_, +o, _, _}, +kids, _}, rest}}: - +ok = value_ok(String.eq(o, "("), k, t, carried) - +inn = closed(kids, carried) - +aft = closed(rest, carried) - Bool.and(ok, Bool.and(inn, aft)) - case Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TName{}, +t, l, c}}, rest}: - +ok = List.contains(~String, ~String.eq, carried, t) - +aft = closed(rest, carried) - Bool.and(ok, aft) - case Tree.NCons{Tree.Leaf{tok}, rest}: - closed(rest, carried) - case Tree.NCons{Tree.Group{open, +kids, close}, rest}: - +inn = closed(kids, carried) - +aft = closed(rest, carried) - Bool.and(inn, aft) - case Tree.NCons{Tree.Stmt{kind, +kids, body}, rest}: - +inn = closed(kids, carried) - +bod = closed(body, carried) - +aft = closed(rest, carried) - Bool.and(inn, Bool.and(bod, aft)) - case Tree.NCons{h, rest}: - closed(rest, carried) - case Tree.Leaf{Lex.Tok{Lex.TName{}, +t, l, c}}: - List.contains(~String, ~String.eq, carried, t) - case other: - True{} - -# a top-level token of this text, not one nested in a group -def top_mark(nn: Tree.Node, +want: String) -> Bool: - match nn: - case Tree.NCons{Tree.Group{open, kids, close}, rest}: - top_mark(rest, want) - case Tree.NCons{Tree.Leaf{Lex.Tok{k, +t, l, c}}, rest}: - +more = top_mark(rest, want) - Bool.or(String.eq(t, want), more) - case Tree.NCons{h, rest}: - top_mark(rest, want) - case other: - False{} - -# `[v : T*n]` or `[v : T^d]`: an array, not a list -def is_array(+kids: Tree.Node) -> Bool: - +col = top_mark(kids, ":") - +star = top_mark(kids, "*") - +hat = top_mark(kids, "^") - Bool.and(col, Bool.or(star, hat)) - -# how many top-level copies of this token -def top_count(nn: Tree.Node, +want: String, +got: Nat) -> Nat: - match nn: - case Tree.NCons{Tree.Group{open, kids, close}, rest}: - top_count(rest, want, got) - case Tree.NCons{Tree.Leaf{Lex.Tok{k, +t, l, c}}, rest}: - +next = Bool.pick(Nat, String.eq(t, want), (got + 1n : Nat), got) - top_count(rest, want, next) - case Tree.NCons{h, rest}: - top_count(rest, want, got) - case other: - got - -# a number literal of eight or fewer -def small_num(+tt: String) -> Bool: - List.contains(~String, ~String.eq, - ["0", "1", "2", "3", "4", "5", "6", "7", "8", "0n", "1n", "2n", "3n", "4n", "5n", "6n", "7n", "8n"], tt) - -# the size after `*` is a literal of eight or fewer, or the depth after `^` (2^d slots) three or fewer -def small_size(nn: Tree.Node) -> Bool: - match nn: - case Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TOp{}, +op, l, c}}, - Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TNum{}, +t, l2, c2}}, rest}}: - +here = Bool.or(Bool.and(String.eq(op, "*"), small_num(t)), - Bool.and(String.eq(op, "^"), List.contains(~String, ~String.eq, ["0", "1", "2", "3", "0n", "1n", "2n", "3n"], t))) - +more = small_size(rest) - Bool.or(here, more) - case Tree.NCons{Tree.Group{open, kids, close}, rest}: - small_size(rest) - case Tree.NCons{h, rest}: - small_size(rest) - case other: - False{} - -# more than eight cells: nine or more list elements, or an array whose size is not that small -def wide_shape(+arr: Bool, +kids: Tree.Node) -> Bool: - match arr: - case True{}: - Bool.not(small_size(kids)) - case False{}: - Nat.is_gt(top_count(kids, ",", 0n), 7n) - -# constructors that build a fixed table rather than walk one -def allowed(+tt: String) -> Bool: - List.contains(~String, ~String.eq, - ["List.replicate", "List.range", "List.map", "Array.new", "Array.map"], tt) - -# a Base constructor on the allow list, or a def of this file whose body is -# itself a wide table (`wides`); a def whose body cannot be seen is not one -def builder(kk: Lex.TokKind, +tt: String, +self: String, +wides: List<&2, String>) -> Bool: - match kk: - case Lex.TName{}: - Bool.and(Bool.not(String.eq(tt, self)), List.contains(~String, ~String.eq, wides, tt)) - case Lex.TDotted{}: - Bool.or(allowed(tt), List.contains(~String, ~String.eq, wides, tt)) - case other: - False{} - -# a bracket group that is a closed list or array of more than eight cells -def group_yes(+arr: Bool, +kids: Tree.Node, +carried: List<&2, String>) -> Bool: - +shape = Bool.or(top_mark(kids, ","), arr) - +ok = closed(kids, carried) - +wide = wide_shape(arr, kids) - Bool.and(shape, Bool.and(ok, wide)) - -# a bracket group that is a closed list or array -def table_group(+brack: Bool, +kids: Tree.Node, +carried: List<&2, String>) -> Bool: - match brack: - case False{}: - False{} - case True{}: - group_yes(is_array(kids), kids, carried) - -# the chain is exactly one number; its text, or empty -def num_text(nn: Tree.Node) -> String: - match nn: - case Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TNum{}, +t, l, c}}, Tree.NNil{}}: - t - case other: - "" - -# a count is wide when it is not a literal of eight or fewer (a name may be large) -def count_wide(aa: Tree.Node) -> Bool: - Bool.not(small_num(num_text(aa))) - -# the last argument -def last_node(as: List<&2, Tree.Node>) -> Tree.Node: - match as: - case Nil{}: - Tree.NNil{} - case Con{h, Nil{}}: - h - case Con{h, rest}: - last_node(rest) - -# `List.replicate` / `Array.new` take argument 2; anything else is wide unless it is `List.range` -def call_arm(rep: Bool, +as: List<&2, Tree.Node>, +end: Tree.Node, +range: Bool) -> Bool: - match rep: - case True{}: - count_wide(Calls.arg(as, 2n)) - case False{}: - Bool.pick(Bool, range, count_wide(end), True{}) - -# `List.replicate` / `Array.new` / `List.range` are wide unless their count is a small literal -def call_wide(+tt: String, kids: Tree.Node) -> Bool: - +as = Calls.args(kids) - call_arm(Bool.or(String.eq(tt, "List.replicate"), String.eq(tt, "Array.new")), as, last_node(as), String.eq(tt, "List.range")) - -# a call that builds a table from closed arguments, and is not a small literal count -def table_built(+ok: Bool, +wide: Bool, kids: Tree.Node, +carried: List<&2, String>) -> Bool: - match ok wide: - case True{} True{}: - closed(kids, carried) - case True{} False{}: - False{} - case False{} b: - False{} - -# a call that builds a table from closed arguments -def table_call( - +app: Bool, - kk: Lex.TokKind, - +tt: String, - +kids: Tree.Node, - +self: String, - +wides: List<&2, String>, - +carried: List<&2, String> -) -> Bool: - match app: - case False{}: - False{} - case True{}: - table_built(builder(kk, tt, self, wides), call_wide(tt, kids), kids, carried) - -# a fixed table of more than eight cells, whatever its cells read -def wide_of(nn: Tree.Node) -> Bool: - match nn: - case Tree.NCons{Tree.Group{Lex.Tok{k, +o, l, c}, +kids, close}, Tree.NNil{}}: - wide_shape(is_array(kids), kids) - case Tree.NCons{Tree.Leaf{Lex.Tok{k, +t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{_, +o, _, _}, +kids, _}, Tree.NNil{}}}: - call_wide(t, kids) - case other: - False{} - -# a def whose body is one wide fixed table (`Table.built`, and more than eight cells) -def wide_one(body: Tree.Node, +name: String, +acc: List<&2, String>) -> List<&2, String>: - match body: - case Tree.NCons{Tree.Stmt{kind, +kids, inn}, Tree.NNil{}}: - Bool.pick(List<&2, String>, Bool.and(Table.built(kids), wide_of(kids)), name <> acc, acc) - case other: - acc - -# defs of this file whose body is one wide fixed table -def wide_defs(ds: List<&2, Calls.Def>, acc: List<&2, String>) -> List<&2, String>: - match ds: - case Nil{}: - acc - case Con{Calls.Def{+name, sig, +body}, rest}: - wide_defs(rest, wide_one(body, name, acc)) - -# a list, an array, or a builder call, closed over carried parameters -def is_table(nn: Tree.Node, +self: String, +wides: List<&2, String>, +carried: List<&2, String>) -> Bool: - match nn: - case Tree.NCons{Tree.Group{Lex.Tok{k, +o, l, c}, +kids, close}, Tree.NNil{}}: - table_group(String.eq(o, "["), kids, carried) - case Tree.NCons{Tree.Leaf{Lex.Tok{+k, +t, l, c}}, - Tree.NCons{Tree.Group{Lex.Tok{_, +o, _, _}, +kids, _}, Tree.NNil{}}}: - table_call(String.eq(o, "("), k, t, kids, self, wides, carried) - case other: - False{} - -# drop the right-hand side -def skip_expr(nn: Tree.Node) -> Maybe<&2, Got>: - match nn: - case other: - None{} - -# the chain after `=`, when the left side is one plain name and nothing else -def got_bad(+bad: Bool, +nm: String, expr: Tree.Node) -> Maybe<&2, Got>: - match bad: - case True{}: - skip_expr(expr) - case False{}: - Some{Got{nm, expr}} - -# the chain after `=`, when exactly one plain name stands before it -def got_at(+seen: Bool, +bad: Bool, +nm: String, expr: Tree.Node) -> Maybe<&2, Got>: - match seen: - case False{}: - skip_expr(expr) - case True{}: - got_bad(bad, nm, expr) - -# a second name, a constructor or a group on the left is not a plain binder -def eat(nn: Tree.Node, +seen: Bool, +bad: Bool, +nm: String) -> Maybe<&2, Got>: - match nn: - case Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TEq{}, t, l, c}}, rest}: - got_at(seen, bad, nm, rest) - case Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TName{}, +t, l, c}}, rest}: - eat(rest, True{}, Bool.or(bad, seen), t) - case Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TUpper{}, t, l, c}}, rest}: - eat(rest, seen, True{}, nm) - case Tree.NCons{Tree.Group{open, kids, close}, rest}: - eat(rest, seen, True{}, nm) - case Tree.NCons{h, rest}: - eat(rest, seen, bad, nm) - case other: - None{} - -# a finding on the table expression -def cite(+nn: Tree.Node, +path: String) -> List<&2, F.Finding>: - [F.Finding{path, Tree.line(nn), Tree.col(nn), U32.from_nat(String.length(Tree.text(nn))), "hoist", - "This table is rebuilt on every step from inputs that do not change; build it once, outside the recursion."}] - -# a name, and nothing after it -def wrapped_inside(nn: Tree.Node, +binder: String) -> Bool: - match nn: - case Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TName{}, +t, l, c}}, Tree.NNil{}}: - String.eq(t, binder) - case other: - False{} - -# inside one pair of parentheses -def wrapped_open(+paren: Bool, kids: Tree.Node, +binder: String) -> Bool: - match paren: - case True{}: - wrapped_inside(kids, binder) - case False{}: - False{} - -# the chain is exactly this name, or that name in parentheses -def wrapped(nn: Tree.Node, +binder: String) -> Bool: - match nn: - case Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TName{}, +t, l, c}}, Tree.NNil{}}: - String.eq(t, binder) - case Tree.NCons{Tree.Group{Lex.Tok{k, +o, l, c}, +kids, close}, Tree.NNil{}}: - wrapped_open(String.eq(o, "("), kids, binder) - case other: - False{} - -# the collection argument is this name -def uses_arg(ss: Slot, +as: List<&2, Tree.Node>, +binder: String) -> Bool: - Slot{on, at} = ss - match on: - case False{}: - False{} - case True{}: - wrapped(Calls.arg(as, at), binder) - -# `name[` is an index; a call is an index when its collection argument is the name -def uses_here_call(+call: Bool, +tt: String, kids: Tree.Node, +binder: String) -> Bool: - match call: - case False{}: - False{} - case True{}: - uses_arg(slot_of(tt), Calls.args(kids), binder) - -# `name[` is an index; a call is an index when its collection argument is the name -def uses_here(+brack: Bool, +call: Bool, +tt: String, kids: Tree.Node, +binder: String) -> Bool: - match brack: - case True{}: - String.eq(tt, binder) - case False{}: - uses_here_call(call, tt, kids, binder) - -# the name is indexed later: `name[`, or the collection of a get or set -def uses_name(nn: Tree.Node, +binder: String) -> Bool: - match nn: - case Tree.NCons{Tree.Leaf{Lex.Tok{k, +t, l, c}}, Tree.NCons{Tree.Group{Lex.Tok{_, +o, _, _}, +kids, _}, rest}}: - +here = uses_here(String.eq(o, "["), String.eq(o, "("), t, kids, binder) - +inn = uses_name(kids, binder) - +aft = uses_name(rest, binder) - Bool.or(here, Bool.or(inn, aft)) - case Tree.NCons{Tree.Group{open, +kids, close}, rest}: - +inn = uses_name(kids, binder) - +aft = uses_name(rest, binder) - Bool.or(inn, aft) - case Tree.NCons{Tree.Stmt{kind, +kids, body}, rest}: - +inn = uses_name(kids, binder) - +bod = uses_name(body, binder) - +aft = uses_name(rest, binder) - Bool.or(inn, Bool.or(bod, aft)) - case Tree.NCons{h, rest}: - uses_name(rest, binder) - case other: - False{} - -# indexed in the let's body or in a following statement -def uses_or(body: Tree.Node, rest: Tree.Node, +binder: String) -> Bool: - +inn = uses_name(body, binder) - +aft = uses_name(rest, binder) - Bool.or(inn, aft) - -# a finding when the table is also indexed -def let_used(+used: Bool, +expr: Tree.Node, +path: String) -> List<&2, F.Finding>: - match used: - case True{}: - cite(expr, path) - case False{}: - Nil{} - -# a finding when the right-hand side is a closed table that is indexed -def let_use(+tab: Bool, +used: Bool, +expr: Tree.Node, +path: String) -> List<&2, F.Finding>: - match tab: - case False{}: - Nil{} - case True{}: - let_used(used, expr, path) - -# a plain let of a table -def let_of( - mm: Maybe<&2, Got>, - +body: Tree.Node, - +rest: Tree.Node, - +self: String, - +wides: List<&2, String>, - +carried: List<&2, String>, - +path: String -) -> List<&2, F.Finding>: - match mm: - case None{}: - Nil{} - case Some{Got{+nm, +expr}}: - let_use(is_table(expr, self, wides, carried), uses_or(body, rest, nm), expr, path) - -# a hot plain let of a table that is indexed afterwards -def let_hit( - +hot: Bool, - kids: Tree.Node, - +body: Tree.Node, - +rest: Tree.Node, - +self: String, - +wides: List<&2, String>, - +carried: List<&2, String>, - +path: String -) -> List<&2, F.Finding>: - match hot: - case False{}: - Nil{} - case True{}: - let_of(eat(kids, False{}, False{}, ""), body, rest, self, wides, carried, path) - -# a finding when this argument is itself a closed table -def one_yes(+yes: Bool, +aa: Tree.Node, +path: String) -> List<&2, F.Finding>: - match yes: - case True{}: - cite(aa, path) - case False{}: - Nil{} - -# the collection argument, when this call indexes one -def arg_table( - ss: Slot, - +as: List<&2, Tree.Node>, - +self: String, - +wides: List<&2, String>, - +carried: List<&2, String>, - +path: String -) -> List<&2, F.Finding>: - Slot{on, at} = ss - match on: - case False{}: - Nil{} - case True{}: - +aa = Calls.arg(as, at) - one_yes(is_table(aa, self, wides, carried), aa, path) - -# an inline table passed straight to a get or set, when the group is a call -def call_open( - +open: Bool, - +tt: String, - kids: Tree.Node, - +self: String, - +wides: List<&2, String>, - +carried: List<&2, String>, - +path: String -) -> List<&2, F.Finding>: - match open: - case False{}: - Nil{} - case True{}: - arg_table(slot_of(tt), Calls.args(kids), self, wides, carried, path) - -# an inline table passed straight to a get or set -def call_table( - +hot: Bool, - +open: Bool, - +tt: String, - kids: Tree.Node, - +self: String, - +wides: List<&2, String>, - +carried: List<&2, String>, - +path: String -) -> List<&2, F.Finding>: - match hot: - case False{}: - Nil{} - case True{}: - call_open(open, tt, kids, self, wides, carried, path) - -# a `=>` cools the rest of its chain: a lambda's body runs when it is called, not per step -def warm(+hot: Bool, kk: Lex.TokKind) -> Bool: - match kk: - case Lex.TLam{}: - False{} - case other: - hot - -# tables built in a hot region -def walk( - nn: Tree.Node, - +self: String, - +wides: List<&2, String>, - +carried: List<&2, String>, - +path: String, - +hot: Bool -) -> List<&2, F.Finding>: - match nn: - case Tree.NCons{Tree.Leaf{Lex.Tok{k, +t, l, c}}, Tree.NCons{Tree.Group{Lex.Tok{_, +o, _, _}, +kids, _}, rest}}: - +heat = warm(hot, k) - +own = call_table(heat, String.eq(o, "("), t, kids, self, wides, carried, path) - +inn = walk(kids, self, wides, carried, path, heat) - +aft = walk(rest, self, wides, carried, path, heat) - List.concat(&2, F.Finding, [own, inn, aft]) - case Tree.NCons{Tree.Leaf{Lex.Tok{k, t, l, c}}, rest}: - walk(rest, self, wides, carried, path, warm(hot, k)) - case Tree.NCons{Tree.Group{open, +kids, close}, rest}: - List.concat(&2, F.Finding, - [walk(kids, self, wides, carried, path, hot), walk(rest, self, wides, carried, path, hot)]) - case Tree.NCons{Tree.Stmt{Tree.SCase{}, kids, +body}, rest}: - +arm = Bool.and(hot, Calls.calls(body, self)) - List.concat(&2, F.Finding, [walk(kids, self, wides, carried, path, False{}), - walk(body, self, wides, carried, path, arm), walk(rest, self, wides, carried, path, hot)]) - case Tree.NCons{Tree.Stmt{kind, +kids, +body}, +rest}: - +own = let_hit(hot, kids, body, rest, self, wides, carried, path) - List.concat(&2, F.Finding, [own, walk(kids, self, wides, carried, path, hot), - walk(body, self, wides, carried, path, hot), walk(rest, self, wides, carried, path, hot)]) - case Tree.NCons{h, rest}: - walk(rest, self, wides, carried, path, hot) - case other: - Nil{} - -def check.go( - ds: List<&2, Calls.Def>, - +wide: List<&2, String>, - +path: String, - acc: List<&2, List<&2, F.Finding>> -) -> List<&2, F.Finding>: - match ds: - case Nil{}: - List.concat(&2, F.Finding, List.reverse(&2, List<&2, F.Finding>, acc)) - case Con{Calls.Def{+name, +sig, +body}, rest}: - check.go(rest, wide, path, - Lazy.stop(List<&2, F.Finding>, - Bool.not(Bool.and(Calls.calls(body, name), Bool.not(Calls.exempt(path, sig)))), [], - _u => walk(body, name, wide, carried(sig, body, name), path, True{})) <> acc) - -# the rule -def check(ss: Src.Src) -> List<&2, F.Finding>: - Src.Src{path, text, toks, tree, bound, items} = ss - +ds = Calls.defs(tree) - check.go(ds, wide_defs(ds, []), path, []) diff --git a/src/rules/suspicious/ring.bend b/src/rules/suspicious/ring.bend deleted file mode 100644 index e01b7ae..0000000 --- a/src/rules/suspicious/ring.bend +++ /dev/null @@ -1,313 +0,0 @@ -# rule ring: a self-call replaces a binder with `List.append` of `List.drop` -# (or `String.append` / `++`, or `tail`) by a constant count, passing it back -# in the binder's own parameter position. Each step copies that fixed window. -# Keep it in an Array and advance an index. A list the def matches and walks -# (as any scrutinee of a match) is variable-length input, a window dropped -# into another parameter's slot is not carried, and a one-shot trim is not a -# loop: all stay quiet. Growing an accumulator by append, with no drop of -# that same value, is `concat`. Laws and proofs do not run (a def with no -# type at all fills a law: it is a proof). -import Base -import ../../src.bend as Src -import ../../finding.bend as F -import ../../syntax/lex.bend as Lex -import ../../syntax/tree.bend as Tree -import ../calls.bend as Calls -import ../../lazy/lazy.bend as Lazy - -# the first of two maybes -def or_tok(aa: Maybe<&2, Lex.Tok>, +bb: Maybe<&2, Lex.Tok>) -> Maybe<&2, Lex.Tok>: - match aa: - case Some{+t}: - Some{t} - case None{}: - bb - -# is the chain exactly one number? -def is_num(nn: Tree.Node) -> Bool: - match nn: - case Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TNum{}, t, l, c}}, Tree.NNil{}}: - True{} - case other: - False{} - -# the chain is exactly one lowercase name; its text, or empty -def name_text(nn: Tree.Node) -> String: - match nn: - case Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TName{}, +t, l, c}}, Tree.NNil{}}: - t - case other: - "" - -# this argument is a window binder -def win_name(nn: Tree.Node, +wins: List<&2, String>) -> Bool: - +tt = name_text(nn) - Bool.and(Bool.not(String.is_empty(tt)), List.contains(~String, ~String.eq, wins, tt)) - -# does any argument name the window? -def has_name(as: List<&2, Tree.Node>, +wins: List<&2, String>) -> Bool: - match as: - case Nil{}: - False{} - case Con{h, rest}: - +more = has_name(rest, wins) - Bool.or(win_name(h, wins), more) - -# is the last argument a number? -def last_num(as: List<&2, Tree.Node>) -> Bool: - match as: - case Nil{}: - False{} - case Con{h, Nil{}}: - is_num(h) - case Con{h, rest}: - last_num(rest) - -# a fixed drop or tail of a named window -def drop_ok(+tt: String, +as: List<&2, Tree.Node>, +wins: List<&2, String>) -> Bool: - +kind = List.contains(~String, ~String.eq, ["List.drop", "String.drop", "List.tail", "String.tail"], tt) - +tail = Bool.or(String.eq(tt, "List.tail"), String.eq(tt, "String.tail")) - Bool.and(kind, Bool.and(has_name(as, wins), Bool.or(tail, last_num(as)))) - -# the callee token when this call is a fixed drop -def drop_tok( - kk: Lex.TokKind, - +tt: String, - +line: U32, - +col: U32, - kids: Tree.Node, - +wins: List<&2, String> -) -> Maybe<&2, Lex.Tok>: - Bool.pick(Maybe<&2, Lex.Tok>, drop_ok(tt, Calls.args(kids), wins), Some{Lex.Tok{kk, tt, line, col}}, None{}) - -# the drop this call is, when the group is an application -def here_of( - +open: Bool, - kk: Lex.TokKind, - +tt: String, - +line: U32, - +col: U32, - kids: Tree.Node, - +wins: List<&2, String> -) -> Maybe<&2, Lex.Tok>: - match open: - case False{}: - None{} - case True{}: - drop_tok(kk, tt, line, col, kids, wins) - -# a drop anywhere under the node -def drop_in(nn: Tree.Node, +wins: List<&2, String>) -> Maybe<&2, Lex.Tok>: - match nn: - case Tree.NCons{Tree.Leaf{Lex.Tok{+k, +t, +l, +c}}, Tree.NCons{Tree.Group{Lex.Tok{_, +o, _, _}, +kids, _}, rest}}: - +here = here_of(String.eq(o, "("), k, t, l, c, kids, wins) - +inn = drop_in(kids, wins) - +aft = drop_in(rest, wins) - or_tok(here, or_tok(inn, aft)) - case Tree.NCons{Tree.Group{open, +kids, close}, rest}: - or_tok(drop_in(kids, wins), drop_in(rest, wins)) - case Tree.NCons{Tree.Stmt{kind, +kids, body}, rest}: - or_tok(drop_in(kids, wins), or_tok(drop_in(body, wins), drop_in(rest, wins))) - case Tree.NCons{h, rest}: - drop_in(rest, wins) - case other: - None{} - -# which list an append extends -def receiver_is(+list: Bool, +str: Bool, +as: List<&2, Tree.Node>, +wins: List<&2, String>) -> Maybe<&2, Lex.Tok>: - match list str: - case True{} b: - drop_in(Calls.arg(as, 2n), wins) - case False{} True{}: - drop_in(Calls.arg(as, 0n), wins) - case False{} False{}: - None{} - -# the drop an append copies onto, when this call is List.append or String.append -def receiver(+tt: String, kids: Tree.Node, +wins: List<&2, String>) -> Maybe<&2, Lex.Tok>: - receiver_is(String.eq(tt, "List.append"), String.eq(tt, "String.append"), Calls.args(kids), wins) - -# a `++` whose left already dropped: that drop; otherwise nothing -def pp_found(+is_pp: Bool, seen: Maybe<&2, Lex.Tok>) -> Maybe<&2, Lex.Tok>: - match is_pp seen: - case True{} Some{+t}: - Some{t} - case True{} None{}: - None{} - case False{} s: - None{} - -# the drop still in play after this operator (`++` starts a new left side) -def pp_seen(+is_pp: Bool, +seen: Maybe<&2, Lex.Tok>) -> Maybe<&2, Lex.Tok>: - match is_pp: - case True{}: - None{} - case False{}: - seen - -# an application's append-receiver drop; a bracket is not an application -def call_recv(+open: Bool, +tt: String, kids: Tree.Node, +wins: List<&2, String>) -> Maybe<&2, Lex.Tok>: - match open: - case False{}: - None{} - case True{}: - receiver(tt, kids, wins) - -# a drop this node itself is: the call when it is an application, else under a bracket -def call_drop( - +open: Bool, - +kk: Lex.TokKind, - +tt: String, - +line: U32, - +col: U32, - +kids: Tree.Node, - +wins: List<&2, String> -) -> Maybe<&2, Lex.Tok>: - match open: - case False{}: - drop_in(kids, wins) - case True{}: - drop_tok(kk, tt, line, col, kids, wins) - -# a drop then `++`, or an append onto a drop, in this chain -def plus_scan(nn: Tree.Node, +seen: Maybe<&2, Lex.Tok>, +wins: List<&2, String>) -> Maybe<&2, Lex.Tok>: - match nn: - case Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TOp{}, +op, l, c}}, rest}: - +is_pp = String.eq(op, "++") - or_tok(pp_found(is_pp, seen), plus_scan(rest, pp_seen(is_pp, seen), wins)) - case Tree.NCons{Tree.Leaf{Lex.Tok{+k, +t, +l, +c}}, Tree.NCons{Tree.Group{Lex.Tok{_, +o, _, _}, +kids, _}, rest}}: - +open = String.eq(o, "(") - +recv = call_recv(open, t, kids, wins) - +inn = plus_scan(kids, None{}, wins) - +drop = call_drop(open, k, t, l, c, kids, wins) - +aft = plus_scan(rest, or_tok(seen, drop), wins) - or_tok(recv, or_tok(inn, aft)) - case Tree.NCons{Tree.Group{open, +kids, close}, rest}: - +inn = plus_scan(kids, None{}, wins) - +aft = plus_scan(rest, or_tok(seen, drop_in(kids, wins)), wins) - or_tok(inn, aft) - case Tree.NCons{Tree.Stmt{kind, +kids, body}, rest}: - or_tok(plus_scan(kids, None{}, wins), or_tok(plus_scan(body, None{}, wins), plus_scan(rest, seen, wins))) - case Tree.NCons{h, rest}: - plus_scan(rest, seen, wins) - case other: - None{} - -# a finding at the drop the scan found -def slide_at(mm: Maybe<&2, Lex.Tok>, +path: String) -> List<&2, F.Finding>: - match mm: - case None{}: - Nil{} - case Some{Lex.Tok{k, +t, l, c}}: - [F.Finding{path, l, c, U32.from_nat(String.length(t)), "ring", - "Each step drops from and appends to a fixed-size window, which copies it; keep the window in an Array and advance an index."}] - -# a finding at the drop, when the argument slides a window -def slide(aa: Tree.Node, +path: String, +wins: List<&2, String>) -> List<&2, F.Finding>: - slide_at(plus_scan(aa, None{}, wins), path) - -# the windows an argument in this parameter's slot may slide: the parameter -# itself when it is a window, else none -def slot_wins(+slot: String, +wins: List<&2, String>) -> List<&2, String>: - Bool.pick(List<&2, String>, List.contains(~String, ~String.eq, wins, slot), [slot], []) - -# every self-call argument that slides the window of its own slot (params, -# from the argument's position on) -def slides( - as: List<&2, Tree.Node>, - +params: List<&2, String>, - +path: String, - +wins: List<&2, String> -) -> List<&2, F.Finding>: - match as: - case Nil{}: - Nil{} - case Con{h, rest}: - +slot = Maybe.default(&2, String, List.head(&2, String, params), "") - List.append(&2, F.Finding, slide(h, path, slot_wins(slot, wins)), - slides(rest, List.tail(&2, String, params), path, wins)) - -# every self-call's sliding arguments -def walk( - nn: Tree.Node, - +self: String, - +params: List<&2, String>, - +path: String, - +wins: List<&2, String> -) -> List<&2, F.Finding>: - match nn: - case Tree.NCons{Tree.Leaf{Lex.Tok{k, +t, l, c}}, Tree.NCons{Tree.Group{Lex.Tok{_, +o, _, _}, +kids, _}, rest}}: - +me = Bool.and(String.eq(o, "("), String.eq(t, self)) - +own = Lazy.stop(List<&2, F.Finding>, Bool.not(me), [], _u => slides(Calls.args(kids), params, path, wins)) - List.concat(&2, F.Finding, [own, walk(kids, self, params, path, wins), walk(rest, self, params, path, wins)]) - case Tree.NCons{Tree.Group{open, +kids, close}, rest}: - List.concat(&2, F.Finding, [walk(kids, self, params, path, wins), walk(rest, self, params, path, wins)]) - case Tree.NCons{Tree.Stmt{kind, +kids, body}, rest}: - List.concat(&2, F.Finding, [walk(kids, self, params, path, wins), walk(body, self, params, path, wins), - walk(rest, self, params, path, wins)]) - case Tree.NCons{h, rest}: - walk(rest, self, params, path, wins) - case other: - Nil{} - -# the top-level names of a chain, onto acc -def names_in(nn: Tree.Node, acc: List<&2, String>) -> List<&2, String>: - match nn: - case Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TName{}, t, l, c}}, rest}: - t <> names_in(rest, acc) - case Tree.NCons{h, rest}: - names_in(rest, acc) - case other: - acc - -# every scrutinee of `match a b ..:`, onto acc (acc alone for any other chain) -def match_names(kids: Tree.Node, +acc: List<&2, String>) -> List<&2, String>: - match kids: - case Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TKey{}, +kw, l, c}}, +rest}: - Lazy.stop(List<&2, String>, Bool.not(String.eq(kw, "match")), acc, _u => names_in(rest, acc)) - case other: - acc - -# names the def matches, as any scrutinee: variable-length input, not a window -def matched(nn: Tree.Node, +acc: List<&2, String>) -> List<&2, String>: - match nn: - case Tree.NCons{Tree.Stmt{Tree.STerm{}, +kids, body}, rest}: - +here = match_names(kids, acc) - matched(rest, matched(body, matched(kids, here))) - case Tree.NCons{Tree.Stmt{kind, +kids, body}, rest}: - matched(rest, matched(body, matched(kids, acc))) - case Tree.NCons{Tree.Group{open, +kids, close}, rest}: - matched(rest, matched(kids, acc)) - case Tree.NCons{h, rest}: - matched(rest, acc) - case other: - acc - -# parameters that are not matched as input -def windows.go(ps: List<&2, String>, +bad: List<&2, String>, +acc: List<&2, String>) -> List<&2, String>: - match ps: - case Nil{}: - List.reverse(&2, String, acc) - case Con{+p, rest}: - +known = List.contains(~String, ~String.eq, bad, p) - +keep = Bool.and(Bool.not(String.is_empty(p)), Bool.not(known)) - windows.go(rest, bad, Bool.pick(List<&2, String>, keep, p <> acc, acc)) - -# parameters that are not matched as input -def windows(sig: Tree.Node, body: Tree.Node) -> List<&2, String>: - windows.go(Calls.names(sig), matched(body, []), []) - -def check.go(ds: List<&2, Calls.Def>, +path: String, acc: List<&2, List<&2, F.Finding>>) -> List<&2, F.Finding>: - match ds: - case Nil{}: - List.concat(&2, F.Finding, List.reverse(&2, List<&2, F.Finding>, acc)) - case Con{Calls.Def{+name, +sig, +body}, rest}: - check.go(rest, path, - Lazy.stop(List<&2, F.Finding>, - Bool.not(Bool.and(Calls.calls(body, name), Bool.not(Calls.exempt(path, sig)))), [], - _u => walk(body, name, Calls.names(sig), path, windows(sig, body))) <> acc) - -# the rule -def check(ss: Src.Src) -> List<&2, F.Finding>: - Src.Src{path, text, toks, tree, bound, items} = ss - check.go(Calls.defs(tree), path, []) diff --git a/src/rules/suspicious/scan.bend b/src/rules/suspicious/scan.bend deleted file mode 100644 index 38123ce..0000000 --- a/src/rules/suspicious/scan.bend +++ /dev/null @@ -1,347 +0,0 @@ -# rule scan: a def that calls itself and, on the same step, hands a -# parameter it carries unchanged to a walk. Every step walks that list again: -# O(|A| * |B|), A what the def walks and B what it carries. Index it once -# outside the loop, or merge two sorted walks. A parameter is carried when -# every self-call passes it back as that same lone name in its own position -# (hoist's notion). A call is a name token (plain or dotted), then a `(` -# group, and what it walks is some of its arguments, counted from 0. A Base -# list search walks its list: argument 2 of `List.contains`, `List.find`, -# `List.filter` and `List.length`, 3 of `List.any` and `List.all`, 4 of -# `List.foldl` and `List.foldr`; a Base walk that rebuilds the list -# (`List.map`, `List.append`, ...) makes output, not a search, and is left -# alone. A def of this file walks, when it calls itself, its first live -# parameter that some self-call does not pass back unchanged (the one it -# shrinks), unless that parameter is a `Nat` or a `String` (a count, such as -# fuel, or a text); and each parameter its body, anywhere, passes as the lone -# name of an argument a call walks, of a Base search or of a def listed -# before it. A def of another module is not seen. A call reports when it is -# not a self-call and an argument it walks is exactly the lone name of a -# carried parameter: anything else there (a literal table, an expression, a -# name the step binds) is left alone. The step is what hoist reads as hot: -# the def's body, a case arm's body only when it calls the def, never a case -# pattern, and never the rest of a chain after `=>` (a lambda's body, a -# `Lazy` thunk, runs when it is called). Laws and proofs do not run (a def -# with no type at all fills a law: it is a proof), and a law file is skipped -# whole. One finding per call, on its callee. -import Base -import ../../src.bend as Src -import ../../paths.bend as Paths -import ../../role.bend as Role -import ../../finding.bend as F -import ../../syntax/lex.bend as Lex -import ../../syntax/tree.bend as Tree -import ../calls.bend as Calls -import ../../lazy/lazy.bend as Lazy -import ./hoist.bend as Hoist - -# a def of this file, and one argument it walks -type Walk is Data: - Walk{name: String, at: Nat} - -# the answer as a list: none or one -def opt(mm: Maybe<&2, Nat>) -> List<&2, Nat>: - match mm: - case None{}: - Nil{} - case Some{at}: - [at] - -# the list argument of a Base list search -def base_at(+tt: String) -> List<&2, Nat>: - +two = List.contains(~String, ~String.eq, ["List.contains", "List.find", "List.filter", "List.length"], tt) - +three = List.contains(~String, ~String.eq, ["List.any", "List.all"], tt) - +four = List.contains(~String, ~String.eq, ["List.foldl", "List.foldr"], tt) - Bool.pick(List<&2, Nat>, two, [2n], Bool.pick(List<&2, Nat>, three, [3n], Bool.pick(List<&2, Nat>, four, [4n], []))) - -# the arguments the defs of this file named tt walk -def file_at(ws: List<&2, Walk>, +tt: String) -> List<&2, Nat>: - match ws: - case Nil{}: - Nil{} - case Con{Walk{+nm, +at}, rest}: - +more = file_at(rest, tt) - Bool.pick(List<&2, Nat>, String.eq(nm, tt), at <> more, more) - -# the arguments a callee walks: a Base walk's list, and those the defs of this file record -def slots(+tt: String, +ws: List<&2, Walk>) -> List<&2, Nat>: - List.append(&2, Nat, base_at(tt), file_at(ws, tt)) - -# a leaf that is a plain name: its text -def name_tok(tok: Lex.Tok) -> Maybe<&2, String>: - match tok: - case Lex.Tok{Lex.TName{}, t, l, c}: - Some{t} - case other: - None{} - -# the argument, when it is exactly one plain name -def name_of(nn: Tree.Node) -> Maybe<&2, String>: - match nn: - case Tree.NCons{Tree.Leaf{tok}, Tree.NNil{}}: - name_tok(tok) - case other: - None{} - -# the name, when the list holds it -def keep_in(mm: Maybe<&2, String>, +names: List<&2, String>) -> Maybe<&2, String>: - match mm: - case None{}: - None{} - case Some{+nm}: - Bool.pick(Maybe<&2, String>, List.contains(~String, ~String.eq, names, nm), Some{nm}, None{}) - -# the first of two answers that has one -def first(aa: Maybe<&2, String>, bb: Maybe<&2, String>) -> Maybe<&2, String>: - match aa: - case None{}: - bb - case Some{nm}: - Some{nm} - -# the first walked argument that is the lone name of one of the names -def found(ss: List<&2, Nat>, +as: List<&2, Tree.Node>, +names: List<&2, String>) -> Maybe<&2, String>: - match ss: - case Nil{}: - None{} - case Con{at, rest}: - +more = found(rest, as, names) - first(keep_in(name_of(Calls.arg(as, at)), names), more) - -# where a name sits among the parameter names -def index_of(ps: List<&2, String>, +nm: String, +ii: Nat) -> Maybe<&2, Nat>: - match ps: - case Nil{}: - None{} - case Con{+p, rest}: - Lazy.stop(Maybe<&2, Nat>, String.eq(p, nm), Some{ii}, _u => index_of(rest, nm, 1n+ii)) - -# where the name sits among the parameter names, when there is a name -def index_in(mm: Maybe<&2, String>, +ps: List<&2, String>) -> Maybe<&2, Nat>: - match mm: - case None{}: - None{} - case Some{+nm}: - index_of(ps, nm, 0n) - -# the parameters that sit, as lone names, at walked arguments -def params_at(ss: List<&2, Nat>, +as: List<&2, Tree.Node>, +ps: List<&2, String>) -> List<&2, Nat>: - match ss: - case Nil{}: - Nil{} - case Con{at, rest}: - +more = params_at(rest, as, ps) - List.append(&2, Nat, opt(index_in(name_of(Calls.arg(as, at)), ps)), more) - -# a call's walked parameters, when its callee is a name and it is a call -def pass_hit( - kk: Lex.TokKind, - +open: Bool, - +tt: String, - kids: Tree.Node, - +ps: List<&2, String>, - +ws: List<&2, Walk> -) -> List<&2, Nat>: - match kk: - case Lex.TName{}: - Lazy.stop(List<&2, Nat>, Bool.not(open), [], _u => params_at(slots(tt, ws), Calls.args(kids), ps)) - case Lex.TDotted{}: - Lazy.stop(List<&2, Nat>, Bool.not(open), [], _u => params_at(slots(tt, ws), Calls.args(kids), ps)) - case other: - Nil{} - -# the parameters a body passes, anywhere, as an argument some call walks -def passes(nn: Tree.Node, +ps: List<&2, String>, +ws: List<&2, Walk>) -> List<&2, Nat>: - match nn: - case Tree.NCons{Tree.Leaf{Lex.Tok{+k, +t, l, c}}, Tree.NCons{Tree.Group{Lex.Tok{_, +o, _, _}, +kids, _}, rest}}: - +own = pass_hit(k, String.eq(o, "("), t, kids, ps, ws) - List.concat(&2, Nat, [own, passes(kids, ps, ws), passes(rest, ps, ws)]) - case Tree.NCons{Tree.Group{open, +kids, close}, rest}: - List.concat(&2, Nat, [passes(kids, ps, ws), passes(rest, ps, ws)]) - case Tree.NCons{Tree.Stmt{kind, +kids, body}, rest}: - List.concat(&2, Nat, [passes(kids, ps, ws), passes(body, ps, ws), passes(rest, ps, ws)]) - case Tree.NCons{h, rest}: - passes(rest, ps, ws) - case other: - Nil{} - -# a live parameter that some self-call does not pass back unchanged -def moves(+pp: Tree.Node, +carried: List<&2, String>) -> Bool: - Bool.and(Calls.is_live(pp), Bool.not(List.contains(~String, ~String.eq, carried, Calls.param_name(pp)))) - -# a count or a text: a loop down one scans no list -def scalar(+pp: Tree.Node) -> Bool: - +hh = Calls.type_head(pp, False{}) - Bool.or(String.eq(hh, "Nat"), String.eq(hh, "String")) - -# where the parameter a def shrinks sits, unless it is a count or a text -def place(+pp: Tree.Node, +ii: Nat) -> Maybe<&2, Nat>: - Bool.pick(Maybe<&2, Nat>, scalar(pp), None{}, Some{ii}) - -def own.go(ps: List<&2, Tree.Node>, +carried: List<&2, String>, +ii: Nat) -> Maybe<&2, Nat>: - match ps: - case Nil{}: - None{} - case Con{+p, rest}: - +here = moves(p, carried) - Lazy.stop(Maybe<&2, Nat>, here, place(p, ii), _u => own.go(rest, carried, 1n+ii)) - -# where the parameter a def shrinks sits, when it calls itself and it is no count or text -def own(+sig: Tree.Node, +body: Tree.Node, +name: String) -> Maybe<&2, Nat>: - Lazy.stop(Maybe<&2, Nat>, Bool.not(Calls.calls(body, name)), None{}, - _u => own.go(Calls.params(sig), Hoist.carried(sig, body, name), 0n)) - -# one walk of the def for each argument -def tag(ns: List<&2, Nat>, +name: String, +ws: List<&2, Walk>) -> List<&2, Walk>: - match ns: - case Nil{}: - ws - case Con{at, rest}: - Walk{name, at} <> tag(rest, name, ws) - -def walks.go(ds: List<&2, Calls.Def>, +ws: List<&2, Walk>) -> List<&2, Walk>: - match ds: - case Nil{}: - ws - case Con{Calls.Def{+name, +sig, +body}, rest}: - +mine = List.append(&2, Nat, opt(own(sig, body, name)), passes(body, Calls.names(sig), ws)) - walks.go(rest, tag(mine, name, ws)) - -# the defs of this file that walk, each with an argument it walks, in order -def walks(ds: List<&2, Calls.Def>) -> List<&2, Walk>: - walks.go(ds, []) - -# the name of the parameter a def shrinks, "" for none -def lead.go(ps: List<&2, Tree.Node>, +carried: List<&2, String>) -> String: - match ps: - case Nil{}: - "" - case Con{+p, rest}: - +here = moves(p, carried) - Lazy.stop(String, here, Calls.param_name(p), _u => lead.go(rest, carried)) - -# a finding on the callee, naming the carried list -def cite( - mm: Maybe<&2, String>, - +tt: String, - +line: U32, - +col: U32, - +self: String, - +lead: String, - +path: String -) -> List<&2, F.Finding>: - match mm: - case None{}: - Nil{} - case Some{+pp}: - [F.Finding{path, line, col, U32.from_nat(String.length(tt)), "scan", - tt ++ " walks " ++ pp ++ " on every step of " ++ self ++ ", which carries it unchanged: that is O(|" ++ lead - ++ "| * |" ++ pp ++ "|). Index it once outside the loop, or merge two sorted walks."}] - -# a hot call of a walk other than the def, a walked argument carried -def on_name( - +hot: Bool, - +open: Bool, - +tt: String, - +line: U32, - +col: U32, - kids: Tree.Node, - +self: String, - +ws: List<&2, Walk>, - +carried: List<&2, String>, - +lead: String, - +path: String -) -> List<&2, F.Finding>: - Lazy.stop(List<&2, F.Finding>, Bool.not(Bool.and(hot, Bool.and(open, Bool.not(String.eq(tt, self))))), [], - _u => cite(found(slots(tt, ws), Calls.args(kids), carried), tt, line, col, self, lead, path)) - -# a call's finding, when its callee is a name -def call_hit( - kk: Lex.TokKind, - +hot: Bool, - +open: Bool, - +tt: String, - +line: U32, - +col: U32, - kids: Tree.Node, - +self: String, - +ws: List<&2, Walk>, - +carried: List<&2, String>, - +lead: String, - +path: String -) -> List<&2, F.Finding>: - match kk: - case Lex.TName{}: - on_name(hot, open, tt, line, col, kids, self, ws, carried, lead, path) - case Lex.TDotted{}: - on_name(hot, open, tt, line, col, kids, self, ws, carried, lead, path) - case other: - Nil{} - -# the walks of carried lists on a hot path -def walk( - nn: Tree.Node, - +self: String, - +ws: List<&2, Walk>, - +carried: List<&2, String>, - +lead: String, - +path: String, - +hot: Bool -) -> List<&2, F.Finding>: - match nn: - case Tree.NCons{Tree.Leaf{Lex.Tok{+k, +t, +l, +c}}, Tree.NCons{Tree.Group{Lex.Tok{_, +o, _, _}, +kids, _}, rest}}: - +heat = Hoist.warm(hot, k) - +own = call_hit(k, heat, String.eq(o, "("), t, l, c, kids, self, ws, carried, lead, path) - +inn = walk(kids, self, ws, carried, lead, path, heat) - +aft = walk(rest, self, ws, carried, lead, path, heat) - List.concat(&2, F.Finding, [own, inn, aft]) - case Tree.NCons{Tree.Leaf{Lex.Tok{k, t, l, c}}, rest}: - walk(rest, self, ws, carried, lead, path, Hoist.warm(hot, k)) - case Tree.NCons{Tree.Group{open, +kids, close}, rest}: - List.concat(&2, F.Finding, - [walk(kids, self, ws, carried, lead, path, hot), walk(rest, self, ws, carried, lead, path, hot)]) - case Tree.NCons{Tree.Stmt{Tree.SCase{}, kids, +body}, rest}: - +arm = Bool.and(hot, Calls.calls(body, self)) - List.concat(&2, F.Finding, [walk(kids, self, ws, carried, lead, path, False{}), - walk(body, self, ws, carried, lead, path, arm), walk(rest, self, ws, carried, lead, path, hot)]) - case Tree.NCons{Tree.Stmt{kind, kids, body}, rest}: - List.concat(&2, F.Finding, [walk(kids, self, ws, carried, lead, path, hot), - walk(body, self, ws, carried, lead, path, hot), walk(rest, self, ws, carried, lead, path, hot)]) - case Tree.NCons{h, rest}: - walk(rest, self, ws, carried, lead, path, hot) - case other: - Nil{} - -# one def's walk, its carried parameters and the one it shrinks read once -def run(+sig: Tree.Node, +body: Tree.Node, +name: String, +ws: List<&2, Walk>, +path: String) -> List<&2, F.Finding>: - +carried = Hoist.carried(sig, body, name) - walk(body, name, ws, carried, lead.go(Calls.params(sig), carried), path, True{}) - -def check.go( - ds: List<&2, Calls.Def>, - +ws: List<&2, Walk>, - +path: String, - acc: List<&2, List<&2, F.Finding>> -) -> List<&2, F.Finding>: - match ds: - case Nil{}: - List.concat(&2, F.Finding, List.reverse(&2, List<&2, F.Finding>, acc)) - case Con{Calls.Def{+name, +sig, +body}, rest}: - check.go(rest, ws, path, - Lazy.stop(List<&2, F.Finding>, - Bool.not(Bool.and(Calls.calls(body, name), Bool.not(Calls.exempt(path, sig)))), [], - _u => run(sig, body, name, ws, path)) <> acc) - -# the findings over a file's defs, their walks read once -def check.on(+ds: List<&2, Calls.Def>, +path: String) -> List<&2, F.Finding>: - check.go(ds, walks(ds), path, []) - -# the rule; a law file is skipped whole, before its walks are read -def check(ss: Src.Src) -> List<&2, F.Finding>: - Src.Src{+path, text, toks, tree, bound, items} = ss - Lazy.stop(List<&2, F.Finding>, Paths.is_law_file(path), [], _u => check.on(Calls.defs(tree), path)) - -# the rule, told the file's role (src/role.bend): a law or proof file is -# skipped whole -def check.role(rr: Role.Role, ss: Src.Src) -> List<&2, F.Finding>: - Src.Src{path, text, toks, tree, bound, items} = ss - Lazy.stop(List<&2, F.Finding>, Role.is_law_file(rr), [], _u => check.on(Calls.defs(tree), path)) diff --git a/src/rules/suspicious/unit.bend b/src/rules/suspicious/unit.bend deleted file mode 100644 index 94655a2..0000000 --- a/src/rules/suspicious/unit.bend +++ /dev/null @@ -1,227 +0,0 @@ -# rule unit: a multiply or divide by the literal one (`1`, `1n`, `1.0`) on a -# step that actually recurses. `*` and `/` inside `(e : T)` are `T.mul` and -# `T.div`; bare, they are `Nat`. Either form, and a direct `Nat.mul` / -# `U32.mul` / `F32.mul` or `.div`, does nothing when an operand is one -# (`/` only when the divisor is). Drop the operation. Any other factor or -# divisor is left alone, and so is a base case or a def that does not call -# itself. A base case is a case arm that does not call the def; the arms are -# judged per `case` only, so a `Bool.pick` branch beside a self-call is still -# the recursive step, and so is a lambda body inside it (a `Lazy.stop` -# thunk or a `List.map` callback runs on the step). One finding per -# operation, on the literal: `Nat.mul(1n, 1n)` is one. Laws and proofs do not -# run (a def with no type at all fills a law: it is a proof). -import Base -import ../../src.bend as Src -import ../../finding.bend as F -import ../../syntax/lex.bend as Lex -import ../../syntax/tree.bend as Tree -import ../calls.bend as Calls -import ../../lazy/lazy.bend as Lazy - -# is the text the literal one? -def one_text(+tt: String) -> Bool: - Bool.or(String.eq(tt, "1"), Bool.or(String.eq(tt, "1n"), String.eq(tt, "1.0"))) - -# is the kind a number token? -def num_kind(kk: Lex.TokKind) -> Bool: - match kk: - case Lex.TNum{}: - True{} - case other: - False{} - -# a token that is the literal one -def one_tok(tok: Lex.Tok) -> Bool: - Lex.Tok{+k, +t, l, c} = tok - Bool.and(num_kind(k), one_text(t)) - -# is the kind an operator's? -def op_kind(kk: Lex.TokKind) -> Bool: - match kk: - case Lex.TOp{}: - True{} - case other: - False{} - -# the maybe, when it holds the literal one -def one_of(mm: Maybe<&2, Lex.Tok>) -> Maybe<&2, Lex.Tok>: - match mm: - case None{}: - None{} - case Some{+tok}: - Bool.pick(Maybe<&2, Lex.Tok>, one_tok(tok), Some{tok}, None{}) - -# the first maybe when it holds a token, else the second -def either(aa: Maybe<&2, Lex.Tok>, bb: Maybe<&2, Lex.Tok>) -> Maybe<&2, Lex.Tok>: - match aa: - case None{}: - bb - case Some{tok}: - Some{tok} - -# the literal an operator's finding points at: for `*` the factor after it -# when it is one, else the one before; for `/` the divisor when it is one -def which(+op: String, prev: Maybe<&2, Lex.Tok>, +nxt: Maybe<&2, Lex.Tok>) -> Maybe<&2, Lex.Tok>: - +right = one_of(nxt) - Bool.pick(Maybe<&2, Lex.Tok>, String.eq(op, "*"), either(right, one_of(prev)), - Bool.pick(Maybe<&2, Lex.Tok>, String.eq(op, "/"), right, None{})) - -# a finding on that literal -def cite(mm: Maybe<&2, Lex.Tok>, +path: String) -> List<&2, F.Finding>: - match mm: - case None{}: - Nil{} - case Some{Lex.Tok{k, +t, l, c}}: - [F.Finding{path, l, c, U32.from_nat(String.length(t)), "unit", - "Multiplying or dividing by " ++ t ++ " has no effect; drop the operation."}] - -# an argument that is exactly the literal one -def arg_one(aa: Tree.Node) -> Maybe<&2, Lex.Tok>: - match aa: - case Tree.NCons{Tree.Leaf{tok}, Tree.NNil{}}: - one_of(Some{tok}) - case other: - None{} - -# the last argument, when it is the literal one -def last_one(as: List<&2, Tree.Node>) -> Maybe<&2, Lex.Tok>: - match as: - case Nil{}: - None{} - case Con{h, Nil{}}: - arg_one(h) - case Con{h, rest}: - last_one(rest) - -# the last argument that is the literal one -def any_one(as: List<&2, Tree.Node>) -> Maybe<&2, Lex.Tok>: - match as: - case Nil{}: - None{} - case Con{h, rest}: - +more = any_one(rest) - either(more, arg_one(h)) - -# a divide's divisor, when the call is a divide -def on_div_is(+div: Bool, +as: List<&2, Tree.Node>, +path: String) -> List<&2, F.Finding>: - match div: - case True{}: - cite(last_one(as), path) - case False{}: - Nil{} - -# a divide's divisor, when the call is a divide -def on_div(+tt: String, +as: List<&2, Tree.Node>, +path: String) -> List<&2, F.Finding>: - on_div_is(List.contains(~String, ~String.eq, ["Nat.div", "U32.div", "F32.div"], tt), as, path) - -# a multiply (either factor) or a divide (the divisor only) -def on_op_is(+mul: Bool, +tt: String, +as: List<&2, Tree.Node>, +path: String) -> List<&2, F.Finding>: - match mul: - case True{}: - cite(any_one(as), path) - case False{}: - on_div(tt, as, path) - -# a multiply (either factor) or a divide (the divisor only) -def on_op(+tt: String, +as: List<&2, Tree.Node>, +path: String) -> List<&2, F.Finding>: - on_op_is(List.contains(~String, ~String.eq, ["Nat.mul", "U32.mul", "F32.mul"], tt), tt, as, path) - -# the call's own identity operands, when this step recurses -def on_hot(hot: Bool, +tt: String, +as: List<&2, Tree.Node>, +path: String) -> List<&2, F.Finding>: - match hot: - case False{}: - Nil{} - case True{}: - on_op(tt, as, path) - -# the call's own identity operands, when this step recurses -def on_call(+tt: String, kids: Tree.Node, +path: String, +hot: Bool) -> List<&2, F.Finding>: - on_hot(hot, tt, Calls.args(kids), path) - -# the call's findings when the group is an application -def own_of( - call: Bool, - +tt: String, - +kids: Tree.Node, - +path: String, - +hot: Bool -) -> List<&2, F.Finding>: - match call: - case True{}: - on_call(tt, kids, path, hot) - case False{}: - Nil{} - -# the first leaf, when the chain starts with one -def first_tok(nn: Tree.Node) -> Maybe<&2, Lex.Tok>: - match nn: - case Tree.NCons{Tree.Leaf{tok}, rest}: - Some{tok} - case other: - None{} - -# the token a leaf leaves behind for what follows it: none past an operator -def after(tok: Lex.Tok) -> Maybe<&2, Lex.Tok>: - match tok: - case Lex.Tok{+k, +t, +l, +c}: - Bool.pick(Maybe<&2, Lex.Tok>, op_kind(k), None{}, Some{Lex.Tok{k, t, l, c}}) - -# a leaf's own finding: an operator by one, in a hot region -def op_hit( - tok: Lex.Tok, - +prev: Maybe<&2, Lex.Tok>, - +nxt: Maybe<&2, Lex.Tok>, - +path: String, - +hot: Bool -) -> List<&2, F.Finding>: - match tok: - case Lex.Tok{k, +t, l, c}: - Bool.pick(List<&2, F.Finding>, Bool.and(hot, op_kind(k)), cite(which(t, prev, nxt), path), []) - -# the name a group is called by: the token right before it ("" for none) -def callee(mm: Maybe<&2, Lex.Tok>) -> String: - match mm: - case None{}: - "" - case Some{Lex.Tok{k, t, l, c}}: - t - -# `*` / `/` by one, and the same through calls, in a hot region. prev is the -# leaf right before, none past an operator, a group or a statement; a `(` -# group right after a leaf is a call of it -def walk(nn: Tree.Node, +prev: Maybe<&2, Lex.Tok>, +self: String, +path: String, +hot: Bool) -> List<&2, F.Finding>: - match nn: - case Tree.NCons{Tree.Leaf{+tok}, +rest}: - +more = walk(rest, after(tok), self, path, hot) - List.append(&2, F.Finding, op_hit(tok, prev, first_tok(rest), path, hot), more) - case Tree.NCons{Tree.Group{Lex.Tok{_, +o, _, _}, +kids, _}, rest}: - +own = own_of(String.eq(o, "("), callee(prev), kids, path, hot) - +inner = walk(kids, None{}, self, path, hot) - +later = walk(rest, None{}, self, path, hot) - List.concat(&2, F.Finding, [inner, own, later]) - case Tree.NCons{Tree.Stmt{Tree.SCase{}, kids, +body}, rest}: - +arm = Bool.and(hot, Calls.calls(body, self)) - List.concat(&2, F.Finding, [walk(kids, None{}, self, path, False{}), - walk(body, None{}, self, path, arm), walk(rest, None{}, self, path, hot)]) - case Tree.NCons{Tree.Stmt{kind, kids, body}, rest}: - List.concat(&2, F.Finding, [walk(kids, None{}, self, path, hot), - walk(body, None{}, self, path, hot), walk(rest, None{}, self, path, hot)]) - case Tree.NCons{h, rest}: - walk(rest, prev, self, path, hot) - case other: - Nil{} - -def check.go(ds: List<&2, Calls.Def>, +path: String, acc: List<&2, List<&2, F.Finding>>) -> List<&2, F.Finding>: - match ds: - case Nil{}: - List.concat(&2, F.Finding, List.reverse(&2, List<&2, F.Finding>, acc)) - case Con{Calls.Def{+name, +sig, +body}, rest}: - check.go(rest, path, - Lazy.stop(List<&2, F.Finding>, - Bool.not(Bool.and(Calls.calls(body, name), Bool.not(Calls.exempt(path, sig)))), [], - _u => walk(body, None{}, name, path, True{})) <> acc) - -# the rule -def check(ss: Src.Src) -> List<&2, F.Finding>: - Src.Src{path, text, toks, tree, bound, items} = ss - check.go(Calls.defs(tree), path, []) diff --git a/src/rules/suspicious/table.bend b/src/rules/table.bend similarity index 77% rename from src/rules/suspicious/table.bend rename to src/rules/table.bend index 6a2ffdf..ce35010 100644 --- a/src/rules/suspicious/table.bend +++ b/src/rules/table.bend @@ -1,21 +1,17 @@ -# rule table: `List.get` or `List.set` at a computed index inside a def that -# calls itself, when the list is a fixed table: a literal, an array, or -# `List.replicate` / `Array.new` / `List.range` with a constant count, written -# inline, defined beside the loop, or held by the let of that name in scope. -# The index walks that table on every step. Keep it in an Array. A let reaches -# the statements after it and the blocks under them, not a sibling case arm, -# and a later let of the name to anything else ends it. A literal index (a -# fixed slot, including 0 and 1), a growing or data-dependent list, a one-shot -# outside the recursion, and `List.head` / `List.tail` do not run. `index` -# leaves a table this rule owns, so one call is one finding. Laws and proofs -# do not run (a def with no type at all fills a law: it is a proof). +# src/rules/table: what a fixed table is, for `index`. A fixed table is a +# literal list, an array, or `List.replicate` / `Array.new` / `List.range` +# with a constant count, written inline, defined beside the loop, or held by +# the let of that name in scope; a let reaches the statements after it and +# the blocks under them, not a sibling case arm, and a later let of the name +# to anything else ends it. `index` leaves a get of one alone. This was the +# `table` rule (U008), retired: it had no public hits; `hit` still builds the +# finding that rule gave, which `index` reads only as "a fixed table". Pure. import Base -import ../../src.bend as Src -import ../../finding.bend as F -import ../../syntax/lex.bend as Lex -import ../../syntax/tree.bend as Tree -import ../calls.bend as Calls -import ../../lazy/lazy.bend as Lazy +import ../finding.bend as F +import ../syntax/lex.bend as Lex +import ../syntax/tree.bend as Tree +import ./calls.bend as Calls +import ../lazy/lazy.bend as Lazy # is the chain exactly one number? def literal(nn: Tree.Node) -> Bool: @@ -254,39 +250,3 @@ def hit( +fire = Bool.and(op, Bool.and(Bool.not(literal(Calls.arg(as, 3n))), owns(Calls.arg(as, 2n), fixed))) Bool.pick(List<&2, F.Finding>, fire, [cite(get, path, line, col, U32.from_nat(String.length(tt)))], []) - -# every fixed-table get or set -def walk(nn: Tree.Node, +path: String, +fixed: List<&2, String>) -> List<&2, F.Finding>: - match nn: - case Tree.NCons{Tree.Leaf{Lex.Tok{k, +t, +l, +c}}, Tree.NCons{Tree.Group{Lex.Tok{_, +o, _, _}, +kids, _}, rest}}: - +own = hit(String.eq(o, "("), t, kids, fixed, path, l, c) - List.concat(&2, F.Finding, [own, walk(kids, path, fixed), walk(rest, path, fixed)]) - case Tree.NCons{Tree.Group{open, +kids, close}, rest}: - List.concat(&2, F.Finding, [walk(kids, path, fixed), walk(rest, path, fixed)]) - case Tree.NCons{Tree.Stmt{kind, +kids, body}, rest}: - List.concat(&2, F.Finding, [walk(kids, path, fixed), walk(body, path, fixed), walk(rest, path, fixed)]) - case Tree.NCons{h, rest}: - walk(rest, path, fixed) - case other: - Nil{} - -def check.go( - ds: List<&2, Calls.Def>, - +mods: List<&2, String>, - +path: String, - acc: List<&2, List<&2, F.Finding>> -) -> List<&2, F.Finding>: - match ds: - case Nil{}: - List.concat(&2, F.Finding, List.reverse(&2, List<&2, F.Finding>, acc)) - case Con{Calls.Def{+name, +sig, +body}, rest}: - check.go(rest, mods, path, - Lazy.stop(List<&2, F.Finding>, - Bool.not(Bool.and(Calls.calls(body, name), Bool.not(Calls.exempt(path, sig)))), [], - _u => walk(body, path, scope(body, mods))) <> acc) - -# the rule -def check(ss: Src.Src) -> List<&2, F.Finding>: - Src.Src{path, text, toks, tree, bound, items} = ss - +ds = Calls.defs(tree) - check.go(ds, modules(ds, []), path, []) diff --git a/src/syntax/tree.bend b/src/syntax/tree.bend index 7c62bbe..d612696 100644 --- a/src/syntax/tree.bend +++ b/src/syntax/tree.bend @@ -65,17 +65,6 @@ def first(nn: Node) -> Maybe<&2, Lex.Tok>: case NCons{h, t}: first(h) -def text.of(mm: Maybe<&2, Lex.Tok>) -> String: - match mm: - case None{}: - "" - case Some{Lex.Tok{k, t, l, c}}: - t - -# the text of a node's first token ("" for none) -def text(nn: Node) -> String: - text.of(first(nn)) - def line.of(mm: Maybe<&2, Lex.Tok>) -> U32: match mm: case None{}: From 918d358d8800399c111ca918884635823c5380fc Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 1 Nov 2026 15:10:53 +0000 Subject: [PATCH 2/7] fix(role): read a file's role from what it holds; no import walk over named files Two bugs put the wrong role on files, so findings were made up or hidden. A file was on the laws' side only when a root LAWS.bend or PROOF.bend in the run reached it. A guarantee kept one per file under laws/ and proofs/, linted without those root files (files named on the line, a run from inside laws/, the editor), was linted as code: most of fromrev's false positives and some of strict's, eager's, concat's and pick's. A file now has a role of its own (Role.own): a LAWS.bend or PROOF.bend by name; else, but for a package's main.bend, a proof file when it fills a law and a law file when it states one and fills none. Over the whole tree that seeds the walk, a file that imports a law file joins the laws' side, and code still wins: a file a main.bend reaches is code, laws and all. A run over a LAWS.bend without the package's main.bend took the code that LAWS.bend imports for law files and dropped its findings (bend2craft's entities.bend: 174 findings alone, none beside its LAWS.bend). Over files named on the line the planner no longer walks the imports: each file is told its own role, as the editor tells it, so the LSP's findings are the CLI's by construction (laws/LspDiagnosticsMatchCli.bend now states the named run's per-file findings, and its proof no longer needs a one-file walk). Laws: laws/RoleOwn.bend states own; the planner's per-file findings are on_run's (the marks over the whole tree, each own role over named files). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_0186KEnw5sg7heUQZTJJU89N --- LAWS.bend | 1 + PROOF.bend | 1 + laws/LspDiagnosticsMatchCli.bend | 3 +- laws/RoleOwn.bend | 44 +++++++++ laws/defs/GradedFindingCounts.bend | 34 ++++++- proofs/LspDiagnosticsMatchCli.bend | 70 +++----------- proofs/RoleOwn.bend | 36 +++++++ proofs/lemmas/PlannerPerFileFindings.bend | 77 ++++++++++++--- src/README.md | 23 +++-- src/lint/plan.bend | 24 ++++- src/role.bend | 110 ++++++++++++++++++++-- src/rules.bend | 6 +- 12 files changed, 334 insertions(+), 95 deletions(-) create mode 100644 laws/RoleOwn.bend create mode 100644 proofs/RoleOwn.bend diff --git a/LAWS.bend b/LAWS.bend index 21ccfe8..92340fd 100644 --- a/LAWS.bend +++ b/LAWS.bend @@ -102,6 +102,7 @@ import ./laws/RoleIndex.bend as RoleIndex import ./laws/RoleLawFileNamed.bend as RoleLawFileNamed import ./laws/RoleLawsNamed.bend as RoleLawsNamed import ./laws/RoleNamedFirst.bend as RoleNamedFirst +import ./laws/RoleOwn.bend as RoleOwn import ./laws/RoleParam.bend as RoleParam import ./laws/RoleParamExempt.bend as RoleParamExempt import ./laws/RolePick.bend as RolePick diff --git a/PROOF.bend b/PROOF.bend index 881c2d4..7f5475b 100644 --- a/PROOF.bend +++ b/PROOF.bend @@ -87,6 +87,7 @@ import ./proofs/RoleIndex.bend as PRoleIndex import ./proofs/RoleLawFileNamed.bend as PRoleLawFileNamed import ./proofs/RoleLawsNamed.bend as PRoleLawsNamed import ./proofs/RoleNamedFirst.bend as PRoleNamedFirst +import ./proofs/RoleOwn.bend as PRoleOwn import ./proofs/RoleParam.bend as PRoleParam import ./proofs/RoleParamExempt.bend as PRoleParamExempt import ./proofs/RolePick.bend as PRolePick diff --git a/laws/LspDiagnosticsMatchCli.bend b/laws/LspDiagnosticsMatchCli.bend index 370468f..0c5e548 100644 --- a/laws/LspDiagnosticsMatchCli.bend +++ b/laws/LspDiagnosticsMatchCli.bend @@ -17,7 +17,8 @@ import ../src/noqa.bend as Noqa def pub.cli(+world: W.World) -> List<&2, F.Graded>: +ss = Plan.srcs_of(Plan.gots(Plan.inputs(world), world)) +cs = Plan.confs(world) - Plan.after(Noqa.of_srcs(ss), False{}, Plan.grade(cs, world, Plan.per_file(ss)), fs => Plan.grade(cs, world, fs)) + Plan.after(Noqa.of_srcs(ss), False{}, Plan.grade(cs, world, Plan.per_file.run(W.paths.of(world), ss)), + fs => Plan.grade(cs, world, fs)) # LAW: an open document's lint is the CLI's: over every file system and # working directory (a World), for a document at a path whose text the CLI diff --git a/laws/RoleOwn.bend b/laws/RoleOwn.bend new file mode 100644 index 0000000..0c26148 --- /dev/null +++ b/laws/RoleOwn.bend @@ -0,0 +1,44 @@ +# A file's own role (src/role.bend's own): a LAWS.bend or a PROOF.bend +# keeps its role by name; any other package's main.bend is code whatever it +# holds; any other file that fills a law (a def that is a proof) is a proof +# file, one that states a law and fills none a law file, and one that does +# neither is code. So a guarantee kept one per file under laws/ and proofs/ +# is read as laws and proofs with no LAWS.bend or PROOF.bend in the run, and +# a run over files named on the line, which takes each file's own role with +# no import walk (laws/defs/GradedFindingCounts.bend's on_run), never takes +# the code a law imports for a law. +import Base +import ../src/role.bend as Role + +# LAW: a LAWS.bend or a PROOF.bend keeps its role by name +law own_named: + for +named: Role.Role + for entry: Bool + for fills: Bool + for states: Bool + for e: {Role.is_law_file(named) == True{} : Bool} + {Role.own.of(named, entry, fills, states) == named : Role.Role} + +# LAW: any other main.bend is code, whatever it holds +law own_entry: + for +named: Role.Role + for fills: Bool + for states: Bool + for e: {Role.is_law_file(named) == False{} : Bool} + {Role.own.of(named, True{}, fills, states) == Role.Plain{} : Role.Role} + +# LAW: any other file that fills a law is a proof file +law own_fills: + for +named: Role.Role + for states: Bool + for e: {Role.is_law_file(named) == False{} : Bool} + {Role.own.of(named, False{}, True{}, states) == Role.Proof{} : Role.Role} + +# LAW: any other file that states a law and fills none is a law file, and +# one that does neither is code +law own_states: + for +named: Role.Role + for states: Bool + for e: {Role.is_law_file(named) == False{} : Bool} + {Role.own.of(named, False{}, False{}, states) == Bool.pick(Role.Role, states, Role.Laws{}, Role.Plain{}) + : Role.Role} diff --git a/laws/defs/GradedFindingCounts.bend b/laws/defs/GradedFindingCounts.bend index b1b14c5..e5efd7b 100644 --- a/laws/defs/GradedFindingCounts.bend +++ b/laws/defs/GradedFindingCounts.bend @@ -65,6 +65,34 @@ def on_all(+ms: List<&2, Role.Mark>, +world: W.World, ps: List<&2, String>) -> L case Con{+p, rest}: List.append(&2, F.Finding, on_at(ms, p, W.text(world, p)), on_all(ms, world, rest)) +# the per-file rules on one source, told its own role (src/role.bend), when +# the World holds a text for it +def own_at(+path: String, mm: Maybe<&2, String>) -> List<&2, F.Finding>: + match mm: + case None{}: + [] + case Some{text}: + Rules.on(Src.of(path, text)) + +# the per-file rules on each source, each told its own role, in file-list +# order +def own_all(+world: W.World, ps: List<&2, String>) -> List<&2, F.Finding>: + match ps: + case Nil{}: + [] + case Con{+p, rest}: + List.append(&2, F.Finding, own_at(p, W.text(world, p)), own_all(world, rest)) + +# the per-file rules on each source of a run: over the whole tree (no paths +# named) each told its role among the marks of every source; over files +# named on the line each told its own role, with no walk +def on_run(named: List<&2, String>, +world: W.World, +ps: List<&2, String>) -> List<&2, F.Finding>: + match named: + case Nil{}: + on_all(Role.marks(Plan.srcs_of(Plan.gots(ps, world))), world, ps) + case Con{_n, _rest}: + own_all(world, ps) + # the path of the first of the paths the World read a text for, as a list of # one; none when it read none def read_first.at(+path: String, mm: Maybe<&2, W.Answer>, more: List<&2, String>) -> List<&2, String>: @@ -149,13 +177,13 @@ def pkgs(rs: List<&2, String>) -> List<&2, String>: Imports.dir_of(r ++ "/main.bend") <> pkgs(rest) # every finding of a run, ungraded: the read failures, then each source's -# per-file findings, each rule seeing that one source and its role among the -# run's law and proof files (src/role.bend), then `setting`'s, +# per-file findings, each rule seeing that one source and its role (on_run), +# then `setting`'s, # seeing each bolt.bend grading reads, then the project rules over every # source in the run, by package def in_scope(+world: W.World) -> List<&2, F.Finding>: +ps = Plan.inputs(world) - List.concat(&2, F.Finding, [unread_all(world, ps), on_all(Role.marks(Plan.srcs_of(Plan.gots(ps, world))), world, ps), + List.concat(&2, F.Finding, [unread_all(world, ps), on_run(W.paths.of(world), world, ps), settings_found(world), D2.packed(parsed_all(world, ps), pkgs(Plan.roots(world)))]) diff --git a/proofs/LspDiagnosticsMatchCli.bend b/proofs/LspDiagnosticsMatchCli.bend index 8bce0d5..cb652dd 100644 --- a/proofs/LspDiagnosticsMatchCli.bend +++ b/proofs/LspDiagnosticsMatchCli.bend @@ -8,13 +8,9 @@ import ../src/rules.bend as Rules import ../src/lint/world.bend as W import ../src/lint/plan.bend as Plan import ../src/src.bend as Src -import ../src/role.bend as Role -import ../src/rules/imports.bend as Imports import ../src/noqa.bend as Noqa import ../src/rules/style/noqa.bend as NoqaRule import ../src/syntax/lex.bend as Lex -import ../src/paths.bend as Paths -import ../src/syntax/outline.bend as Outline import ../laws/LspDiagnosticsMatchCli.bend as Laws import ./lemmas/PublishComparisons.bend as M1 @@ -86,18 +82,6 @@ def pub.find_head(d, rest, world): == Plan.conf.near(world, d) : Config.Config} {==} -# `bolt ` runs over that path alone, unless it is a bolt.bend -law pub.cli.named: - for +world: W.World - for +path: String - for h: {W.paths.of(world) == [path] : List<&2, String>} - {Plan.inputs(world) == Plan.sources([path]) : List<&2, String>} - -def pub.cli.named(world, path, h): - e = Equal.sym(List<&2, String>, W.paths.of(world), [path], h) - %e : {Plan.sources(Plan.files.named(_, world)) == Plan.sources([path]) : List<&2, String>} - {==} - # findings put at a path are graded by the CLI as by the config nearest it law pub.grade_near: for +world: W.World @@ -129,43 +113,12 @@ law pub.gs: for +text: String {Rules.graded(Config.nearest(Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world)), Rules.all(path, text)) == Plan.grade(Plan.confs.at(Plan.conf.dirs([path]), world), world, - Plan.per_file(Plan.srcs_of([Plan.Got{path, Some{text}}]))) + Plan.per_file.run([path], Plan.srcs_of([Plan.Got{path, Some{text}}]))) : List<&2, F.Graded>} -# a run of one file gives it its role by name: a LAWS.bend or PROOF.bend -# keeps it, and any other file is reached by no law, so it is neither -law pub.role_one: - for +named: Role.Role - for +nn: String - for deps: List<&2, String> - for +entry: Bool - {Role.role.of(named, Role.of.norm(Role.marks.of([Role.Fact{nn, deps, named, entry}], - Role.reach([Role.Fact{nn, deps, named, entry}], Role.roots([Role.Fact{nn, deps, named, entry}])), - Role.reach([Role.Fact{nn, deps, named, entry}], Role.lawful([Role.Fact{nn, deps, named, entry}])), - Role.reach([Role.Fact{nn, deps, named, entry}], Role.code([Role.Fact{nn, deps, named, entry}], - Role.reach([Role.Fact{nn, deps, named, entry}], Role.roots([Role.Fact{nn, deps, named, entry}]))))), nn)) - == named : Role.Role} - -def pub.role_one(named, _nn, _deps, _entry): - match named: - case Role.Plain{}: - {==} - case Role.Laws{}: - {==} - case Role.Proof{}: - {==} - def pub.gs(world, path, text): - +s = Src.of(path, text) - +r = Equal.cong(Role.Role, List<&2, F.Finding>, z => List.append(&2, F.Finding, Rules.on.role(z, s), []), - Role.of(Role.marks([s]), path), Role.of_path(path), - pub.role_one(Role.of_path(path), Imports.norm(path), - Imports.targets(Outline.of_tokens(text, Lex.tokens(text)), Imports.norm(path)), - String.eq(Paths.base(path), "main.bend"))) - a = Equal.sym(List<&2, F.Finding>, Plan.per_file(Plan.srcs_of([Plan.Got{path, Some{text}}])), Rules.all(path, text), - Equal.trans(List<&2, F.Finding>, Plan.per_file(Plan.srcs_of([Plan.Got{path, Some{text}}])), - List.append(&2, F.Finding, Rules.all(path, text), []), Rules.all(path, text), r, - pub.app_nil(F.Finding, Rules.all(path, text)))) + a = Equal.sym(List<&2, F.Finding>, Plan.per_file.run([path], Plan.srcs_of([Plan.Got{path, Some{text}}])), + Rules.all(path, text), pub.app_nil(F.Finding, Rules.all(path, text))) %a : {Rules.graded(Config.nearest(Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world)), Rules.all(path, text)) == Plan.grade(Plan.confs.at(Plan.conf.dirs([path]), world), world, _) @@ -216,7 +169,7 @@ law pub.cli.case: {Server.pub.lint(path, text, Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world)) == Plan.after(Noqa.of_srcs(Plan.srcs_of(Plan.gots(Bool.pick(List<&2, String>, bb, [], [path]), world))), False{}, Plan.grade(Plan.confs.at(Plan.conf.dirs(Bool.pick(List<&2, String>, bb, [], [path])), world), world, - Plan.per_file(Plan.srcs_of(Plan.gots(Bool.pick(List<&2, String>, bb, [], [path]), world)))), + Plan.per_file.run([path], Plan.srcs_of(Plan.gots(Bool.pick(List<&2, String>, bb, [], [path]), world)))), fs => Plan.grade(Plan.confs.at(Plan.conf.dirs(Bool.pick(List<&2, String>, bb, [], [path])), world), world, fs)) : List<&2, F.Graded>} @@ -240,7 +193,7 @@ def pub.cli.case(bb, world, path, text, hc, ht): fs => Rules.graded(Config.nearest(Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world)), fs))) == Plan.after(Noqa.of_srcs(Plan.srcs_of(Plan.gots([path], world))), False{}, Plan.grade(Plan.confs.at(Plan.conf.dirs([path]), world), world, - Plan.per_file(Plan.srcs_of(Plan.gots([path], world)))), + Plan.per_file.run([path], Plan.srcs_of(Plan.gots([path], world)))), fs => Plan.grade(Plan.confs.at(Plan.conf.dirs([path]), world), world, fs)) : List<&2, F.Graded>} t = Equal.sym(Maybe<&2, String>, W.text(world, path), Some{text}, ht) @@ -250,7 +203,7 @@ def pub.cli.case(bb, world, path, text, hc, ht): fs => Rules.graded(Config.nearest(Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world)), fs)) == Plan.after(Noqa.of_srcs(Plan.srcs_of([Plan.Got{path, _}])), False{}, Plan.grade(Plan.confs.at(Plan.conf.dirs([path]), world), world, - Plan.per_file(Plan.srcs_of([Plan.Got{path, _}]))), + Plan.per_file.run([path], Plan.srcs_of([Plan.Got{path, _}]))), fs => Plan.grade(Plan.confs.at(Plan.conf.dirs([path]), world), world, fs)) : List<&2, F.Graded>} g = pub.gs(world, path, text) @@ -279,10 +232,11 @@ def pub.cli.case(bb, world, path, text, hc, ht): Rules.all(path, text)))) def Laws.lint_is_cli(world, path, text, h, ht): - n = Equal.sym(List<&2, String>, Plan.inputs(world), Plan.sources([path]), pub.cli.named(world, path, h)) - %n : {Server.pub.lint(path, text, Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world)) - == Plan.after(Noqa.of_srcs(Plan.srcs_of(Plan.gots(_, world))), False{}, - Plan.grade(Plan.confs.at(Plan.conf.dirs(_), world), world, Plan.per_file(Plan.srcs_of(Plan.gots(_, world)))), - fs => Plan.grade(Plan.confs.at(Plan.conf.dirs(_), world), world, fs)) + q = Equal.sym(List<&2, String>, W.paths.of(world), [path], h) + %q : {Server.pub.lint(path, text, Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world)) + == Plan.after(Noqa.of_srcs(Plan.srcs_of(Plan.gots(Plan.sources(Plan.files.named(_, world)), world))), False{}, + Plan.grade(Plan.confs.at(Plan.conf.dirs(Plan.sources(Plan.files.named(_, world))), world), world, + Plan.per_file.run(_, Plan.srcs_of(Plan.gots(Plan.sources(Plan.files.named(_, world)), world)))), + fs => Plan.grade(Plan.confs.at(Plan.conf.dirs(Plan.sources(Plan.files.named(_, world))), world), world, fs)) : List<&2, F.Graded>} pub.cli.case(Plan.is_config(path), world, path, text, {==}, ht) diff --git a/proofs/RoleOwn.bend b/proofs/RoleOwn.bend new file mode 100644 index 0000000..279fef7 --- /dev/null +++ b/proofs/RoleOwn.bend @@ -0,0 +1,36 @@ +# proofs of laws/RoleOwn.bend +import Base +import ../src/role.bend as Role +import ../laws/RoleOwn.bend as Laws + +def Laws.own_named(named, entry, fills, states, e): + s = Equal.sym(Bool, Role.is_law_file(named), True{}, e) + %s : {Bool.pick(Role.Role, _, named, + Bool.pick(Role.Role, entry, Role.Plain{}, Bool.pick(Role.Role, fills, Role.Proof{}, + Bool.pick(Role.Role, states, Role.Laws{}, Role.Plain{})))) + == named : Role.Role} + {==} + +def Laws.own_entry(named, fills, states, e): + s = Equal.sym(Bool, Role.is_law_file(named), False{}, e) + %s : {Bool.pick(Role.Role, _, named, + Bool.pick(Role.Role, True{}, Role.Plain{}, Bool.pick(Role.Role, fills, Role.Proof{}, + Bool.pick(Role.Role, states, Role.Laws{}, Role.Plain{})))) + == Role.Plain{} : Role.Role} + {==} + +def Laws.own_fills(named, states, e): + s = Equal.sym(Bool, Role.is_law_file(named), False{}, e) + %s : {Bool.pick(Role.Role, _, named, + Bool.pick(Role.Role, False{}, Role.Plain{}, Bool.pick(Role.Role, True{}, Role.Proof{}, + Bool.pick(Role.Role, states, Role.Laws{}, Role.Plain{})))) + == Role.Proof{} : Role.Role} + {==} + +def Laws.own_states(named, states, e): + s = Equal.sym(Bool, Role.is_law_file(named), False{}, e) + %s : {Bool.pick(Role.Role, _, named, + Bool.pick(Role.Role, False{}, Role.Plain{}, Bool.pick(Role.Role, False{}, Role.Proof{}, + Bool.pick(Role.Role, states, Role.Laws{}, Role.Plain{})))) + == Bool.pick(Role.Role, states, Role.Laws{}, Role.Plain{}) : Role.Role} + {==} diff --git a/proofs/lemmas/PlannerPerFileFindings.bend b/proofs/lemmas/PlannerPerFileFindings.bend index bbe9592..2c982e6 100644 --- a/proofs/lemmas/PlannerPerFileFindings.bend +++ b/proofs/lemmas/PlannerPerFileFindings.bend @@ -115,6 +115,55 @@ def lint.on(ms, ps, world): case Con{+p, +tl}: lint.on.step(ms, p, W.text(world, p), Plan.gots(tl, world), D1.on_all(ms, world, tl), lint.on(ms, tl, world)) +# one source onto the per-file findings, each told its own role +law lint.own.step: + for +path: String + for mm: Maybe<&2, String> + for +gs: List<&2, Plan.Got> + for -os: List<&2, F.Finding> + for ih: {Plan.per_file.own(Plan.srcs_of(gs)) == os : List<&2, F.Finding>} + {Plan.per_file.own(Plan.srcs_of(Plan.Got{path, mm} <> gs)) + == List.append(&2, F.Finding, D1.own_at(path, mm), os) : List<&2, F.Finding>} + +def lint.own.step(path, mm, gs, os, ih): + match mm: + case None{}: + ih + case Some{text}: + Equal.cong(List<&2, F.Finding>, List<&2, F.Finding>, + rr => List.append(&2, F.Finding, Rules.on(Src.of(path, text)), rr), + Plan.per_file.own(Plan.srcs_of(gs)), os, ih) + +# the planner's per-file findings over named files are each source's with +# its own role, in order +law lint.own: + for ps: List<&2, String> + for +world: W.World + {Plan.per_file.own(Plan.srcs_of(Plan.gots(ps, world))) == D1.own_all(world, ps) : List<&2, F.Finding>} + +def lint.own(ps, world): + match ps: + case Nil{}: + {==} + case Con{+p, +tl}: + lint.own.step(p, W.text(world, p), Plan.gots(tl, world), D1.own_all(world, tl), lint.own(tl, world)) + +# the planner's per-file findings in a run are the spec's: the marks over +# the whole tree, each source's own role over files named +law lint.run: + for named: List<&2, String> + for +ps: List<&2, String> + for +world: W.World + {Plan.per_file.run(named, Plan.srcs_of(Plan.gots(ps, world))) == D1.on_run(named, world, ps) + : List<&2, F.Finding>} + +def lint.run(named, ps, world): + match named: + case Nil{}: + lint.on(Role.marks(Plan.srcs_of(Plan.gots(ps, world))), ps, world) + case Con{_n, _rest}: + lint.own(ps, world) + def L3.scope_findings(world): n = Equal.sym(List<&2, F.Finding>, List.append(&2, F.Finding, Rules.project(Plan.srcs_of(Plan.gots(Plan.inputs(world), world)), @@ -123,42 +172,42 @@ def L3.scope_findings(world): M3.lint.app_nil(F.Finding, Rules.project(Plan.srcs_of(Plan.gots(Plan.inputs(world), world)), Plan.packages(Plan.roots(world))))) %n : {List.append(&2, F.Finding, Plan.unread(Plan.gots(Plan.inputs(world), world)), - List.append(&2, F.Finding, Plan.per_file(Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), + List.append(&2, F.Finding, + Plan.per_file.run(W.paths.of(world), Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), List.append(&2, F.Finding, Plan.settings(world), _))) == D1.in_scope(world) : List<&2, F.Finding>} a = Equal.sym(List<&2, F.Finding>, Plan.unread(Plan.gots(Plan.inputs(world), world)), D1.unread_all(world, Plan.inputs(world)), lint.unread(Plan.inputs(world), world)) %a : {List.append(&2, F.Finding, _, - List.append(&2, F.Finding, Plan.per_file(Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), + List.append(&2, F.Finding, + Plan.per_file.run(W.paths.of(world), Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), List.append(&2, F.Finding, Plan.settings(world), Rules.project(Plan.srcs_of(Plan.gots(Plan.inputs(world), world)), Plan.packages(Plan.roots(world)))))) == D1.in_scope(world) : List<&2, F.Finding>} - b = Equal.sym(List<&2, F.Finding>, Plan.per_file(Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), - D1.on_all(Role.marks(Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), world, Plan.inputs(world)), - lint.on(Role.marks(Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), Plan.inputs(world), world)) + b = Equal.sym(List<&2, F.Finding>, + Plan.per_file.run(W.paths.of(world), Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), + D1.on_run(W.paths.of(world), world, Plan.inputs(world)), + lint.run(W.paths.of(world), Plan.inputs(world), world)) %b : {List.append(&2, F.Finding, D1.unread_all(world, Plan.inputs(world)), List.append(&2, F.Finding, _, List.append(&2, F.Finding, Plan.settings(world), Rules.project(Plan.srcs_of(Plan.gots(Plan.inputs(world), world)), Plan.packages(Plan.roots(world)))))) == D1.in_scope(world) : List<&2, F.Finding>} t = Equal.sym(List<&2, F.Finding>, Plan.settings(world), D1.settings_found(world), L4.setting_reports(world)) %t : {List.append(&2, F.Finding, D1.unread_all(world, Plan.inputs(world)), - List.append(&2, F.Finding, D1.on_all(Role.marks(Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), world, - Plan.inputs(world)), List.append(&2, F.Finding, _, + List.append(&2, F.Finding, D1.on_run(W.paths.of(world), world, Plan.inputs(world)), List.append(&2, F.Finding, _, Rules.project(Plan.srcs_of(Plan.gots(Plan.inputs(world), world)), Plan.packages(Plan.roots(world)))))) == D1.in_scope(world) : List<&2, F.Finding>} c = Equal.sym(List<&2, Src.Src>, Plan.srcs_of(Plan.gots(Plan.inputs(world), world)), D1.parsed_all(world, Plan.inputs(world)), lint.parsed(Plan.inputs(world), world)) %c : {List.append(&2, F.Finding, D1.unread_all(world, Plan.inputs(world)), - List.append(&2, F.Finding, D1.on_all(Role.marks(Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), world, - Plan.inputs(world)), + List.append(&2, F.Finding, D1.on_run(W.paths.of(world), world, Plan.inputs(world)), List.append(&2, F.Finding, D1.settings_found(world), Rules.project(_, Plan.packages(Plan.roots(world)))))) == D1.in_scope(world) : List<&2, F.Finding>} k = Equal.sym(List<&2, String>, Plan.packages(Plan.roots(world)), D1.pkgs(Plan.roots(world)), L1.package_dirs(Plan.roots(world))) %k : {List.append(&2, F.Finding, D1.unread_all(world, Plan.inputs(world)), - List.append(&2, F.Finding, D1.on_all(Role.marks(Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), world, - Plan.inputs(world)), + List.append(&2, F.Finding, D1.on_run(W.paths.of(world), world, Plan.inputs(world)), List.append(&2, F.Finding, D1.settings_found(world), Rules.project(D1.parsed_all(world, Plan.inputs(world)), _)))) == D1.in_scope(world) : List<&2, F.Finding>} @@ -167,14 +216,12 @@ def L3.scope_findings(world): D2.packed(D1.parsed_all(world, Plan.inputs(world)), D1.pkgs(Plan.roots(world))), L2.packages_scoped(D1.parsed_all(world, Plan.inputs(world)), D1.pkgs(Plan.roots(world)))) %q : {List.append(&2, F.Finding, D1.unread_all(world, Plan.inputs(world)), - List.append(&2, F.Finding, D1.on_all(Role.marks(Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), world, - Plan.inputs(world)), + List.append(&2, F.Finding, D1.on_run(W.paths.of(world), world, Plan.inputs(world)), List.append(&2, F.Finding, D1.settings_found(world), _))) == D1.in_scope(world) : List<&2, F.Finding>} z = M3.lint.app_nil(F.Finding, D2.packed(D1.parsed_all(world, Plan.inputs(world)), D1.pkgs(Plan.roots(world)))) %z : {List.append(&2, F.Finding, D1.unread_all(world, Plan.inputs(world)), - List.append(&2, F.Finding, D1.on_all(Role.marks(Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), world, - Plan.inputs(world)), + List.append(&2, F.Finding, D1.on_run(W.paths.of(world), world, Plan.inputs(world)), List.append(&2, F.Finding, D1.settings_found(world), _))) == D1.in_scope(world) : List<&2, F.Finding>} {==} diff --git a/src/README.md b/src/README.md index 63be254..2bfc734 100644 --- a/src/README.md +++ b/src/README.md @@ -157,13 +157,24 @@ it. `coverage` runs on one package's files at a time. Over files named on the li nested packages, each with its own laws, lints in one run from the top, and a law covers only defs of its own package. -Rules read a file's role from the imports, never from where it sits. A law -file is a `LAWS.bend` or a file one reaches through relative imports; a -proof file is a `PROOF.bend` or a file only a `PROOF.bend` reaches. A file a -package's `main.bend` reaches, or one no `LAWS.bend` or `PROOF.bend` -reaches, is code, even when a law imports it to state laws about it. So a +Rules read a file's role from its name, what it holds and the imports, +never from where it sits. A file's own role is by its name, a `LAWS.bend` +or a `PROOF.bend`, else by what it holds: a file that fills a law (a def +that is a proof: it returns one, or is written with no type) is a proof +file, one that states a law and fills none a law file, but a package's +`main.bend` is code whatever it holds. Over the whole tree the imports +widen that: a file a law file reaches through relative imports is a law +file, one only proof files reach a proof file, and a file that imports a +law file is on the laws' side too (a proof of imports alone). A file a +package's `main.bend` reaches, or one the laws do not reach, is code, even +when a law imports it to state laws about it, laws of its own and all. So a `LAWS.bend` of nothing but `import ./laws/.bend` lines makes those -files law files, and a proof may state lemma laws of its own. +files law files, one guarantee per file under `laws/` and `proofs/` reads +right with no `LAWS.bend` at all, and a proof may state lemma laws of its +own. Over files named on the line, and in the editor, each file has its own +role and the imports are not walked: the code that imports a file may not be +in the run, and a walk without it would take that code for laws and hide +its findings. A rule is handed a [`Src`](src.bend), not the text: the path, the text, and the file's tokens, tree, binder and outline, each read once for the whole diff --git a/src/lint/plan.bend b/src/lint/plan.bend index f31b9dc..a215bb8 100644 --- a/src/lint/plan.bend +++ b/src/lint/plan.bend @@ -364,6 +364,28 @@ def per_file.role(+ms: List<&2, Role.Mark>, ss: List<&2, Src.Src>) -> List<&2, F def per_file(+ss: List<&2, Src.Src>) -> List<&2, F.Finding>: per_file.role(Role.marks(ss), ss) +# every source's findings from the per-file rules, each told its own role +# (src/role.bend's own: by its name, else by what it holds), as the editor +# tells it +def per_file.own(ss: List<&2, Src.Src>) -> List<&2, F.Finding>: + match ss: + case Nil{}: + Nil{} + case Con{s, rest}: + List.append(&2, F.Finding, Rules.on(s), per_file.own(rest)) + +# every source's findings from the per-file rules in a run: over the whole +# tree (no paths named), each told its role among the marks, the import walk +# over every package's files; over files named on the line, each told its +# own role, since the code that imports a file, a package's main.bend, may +# not be in the run, and a walk without it would take that code for laws +def per_file.run(paths: List<&2, String>, +ss: List<&2, Src.Src>) -> List<&2, F.Finding>: + match paths: + case Nil{}: + per_file(ss) + case Con{_p, _rest}: + per_file.own(ss) + # the path of the first candidate the World holds a text for, as a list of # one; none when it holds none def conf.first(gs: List<&2, Got>) -> List<&2, String>: @@ -409,7 +431,7 @@ def settings(+world: W.World) -> List<&2, F.Finding>: # then `setting`'s over each bolt.bend grading reads, then the project rules' # over every source and the other packages def found.at(+world: W.World, +gs: List<&2, Got>, +ss: List<&2, Src.Src>) -> List<&2, F.Finding>: - List.concat(&2, F.Finding, [unread(gs), per_file(ss), settings(world), + List.concat(&2, F.Finding, [unread(gs), per_file.run(W.paths.of(world), ss), settings(world), Rules.project(ss, packages(roots(world)))]) # every finding of every rule, ungraded diff --git a/src/role.bend b/src/role.bend index 549f61d..bfe8280 100644 --- a/src/role.bend +++ b/src/role.bend @@ -8,12 +8,27 @@ # proof may state laws of its own. So `LAWS.bend` can be nothing but # `import ./laws/x.bend as X` lines, the files it reaches are held to what a # law file is held to, and the code they state laws about, which main.bend -# imports, is still code. Pure. +# imports, is still code. +# +# A file needs no LAWS.bend above it to be on the laws' side: what it holds +# says so too (`own`). A file that fills a law (a def that is a proof: it +# returns one, or is written with no type) is a proof file, and one that +# states a law and fills none a law file, unless it is a package's +# main.bend, which is code whatever it holds; a file that imports a law file +# of the run is on the laws' side as well (a proof made of imports alone). +# So one guarantee per file under laws/ and proofs/ is read right with no +# root file, and over the whole tree the code still wins: a file the code +# imports is code, laws and all. Over files named on the line the walk does +# not run, since the code that imports a file may not be in the run: each +# file has its own role (src/lint/plan.bend). Pure. import Base import ./paths.bend as Paths import ./src.bend as Src import ./lazy/lazy.bend as Lazy +import ./syntax/tree.bend as Tree import ./rules/imports.bend as Imports +import ./rules/calls.bend as Calls +import ./syntax/outline.bend as Outline import ./finding.bend as F # what a file is to the laws @@ -58,21 +73,75 @@ def skip(+rr: Role, run: Unit -> List<&2, F.Finding>) -> List<&2, F.Finding>: # reading a file # -------------- +# is the item a law? +def law_item(kk: Outline.ItemKind) -> Bool: + match kk: + case Outline.ILaw{}: + True{} + case other: + False{} + +# does the outline state a law? hit: the item before did +def states.go(its: List<&2, Outline.Item>, hit: Bool) -> Bool: + match its: + case Nil{}: + hit + case Con{Outline.Item{k, _name, _line, _sig, _doc, _path}, rest}: + match hit: + case True{}: + True{} + case False{}: + states.go(rest, law_item(k)) + +# does the outline state a law? +def states(its: List<&2, Outline.Item>) -> Bool: + states.go(its, False{}) + +# does one of the defs fill a law (Calls.proof)? hit: the def before did +def fills.go(ds: List<&2, Calls.Def>, hit: Bool) -> Bool: + match ds: + case Nil{}: + hit + case Con{Calls.Def{_name, +sig, _body}, rest}: + match hit: + case True{}: + True{} + case False{}: + fills.go(rest, Calls.proof(sig)) + +# does a def of the tree fill a law? +def fills(tree: Tree.Node) -> Bool: + fills.go(Calls.defs(tree), False{}) + +# a file's role by itself: a LAWS.bend or PROOF.bend by its name; else, but +# for a package's main.bend (entry), a proof file when it fills a law and a +# law file when it states one; else neither +def own.of(+named: Role, entry: Bool, fills: Bool, states: Bool) -> Role: + Bool.pick(Role, is_law_file(named), named, + Bool.pick(Role, entry, Plain{}, Bool.pick(Role, fills, Proof{}, Bool.pick(Role, states, Laws{}, Plain{})))) + +# a parsed file's role by itself (own.of) +def own(ss: Src.Src) -> Role: + Src.Src{+path, _text, _toks, tree, _bound, items} = ss + own.of(of_path(path), String.eq(Paths.base(path), "main.bend"), fills(tree), states(items)) + # what a file on the laws' side is: a law file when a LAWS.bend reaches it, # else a proof file def reached(laws: Bool) -> Role: Bool.pick(Role, laws, Laws{}, Proof{}) # one file as the walk reads it: its normalized path, what it imports, its -# role by name, and whether it is a package's entry (a main.bend) +# own role (by name, else by what it holds), and whether it is a package's +# entry (a main.bend) type Fact is Data: Fact{norm: String, deps: List<&2, String>, named: Role, entry: Bool} # a parsed file read down to its fact def fact(ss: Src.Src) -> Fact: - Src.Src{+path, _text, _toks, _tree, _bound, items} = ss + Src.Src{+path, text, toks, tree, bound, +items} = ss +nn = Imports.norm(path) - Fact{nn, Imports.targets(items, nn), of_path(path), String.eq(Paths.base(path), "main.bend")} + Fact{nn, Imports.targets(items, nn), own(Src.Src{path, text, toks, tree, bound, items}), + String.eq(Paths.base(path), "main.bend")} # every file's fact, in order def facts(ss: List<&2, Src.Src>) -> List<&2, Fact>: @@ -105,7 +174,7 @@ def paths(fs: List<&2, Fact>) -> List<&2, String>: case Con{Fact{norm, _deps, _named, _entry}, rest}: norm <> paths(rest) -# the roots of the laws: every LAWS.bend and PROOF.bend +# the roots of the laws: every file that is a law or proof file by itself def roots(fs: List<&2, Fact>) -> List<&2, String>: match fs: case Nil{}: @@ -114,7 +183,7 @@ def roots(fs: List<&2, Fact>) -> List<&2, String>: +more = roots(rest) Bool.pick(List<&2, String>, is_law_file(named), norm <> more, more) -# the roots of the law files: every LAWS.bend +# the roots of the law files: every file that is a law file by itself def lawful(fs: List<&2, Fact>) -> List<&2, String>: match fs: case Nil{}: @@ -123,6 +192,30 @@ def lawful(fs: List<&2, Fact>) -> List<&2, String>: +more = lawful(rest) Bool.pick(List<&2, String>, is_laws(named), norm <> more, more) +# do the imports hold one of the paths? Over the paths first, so none to +# look for answers at once; hit: the path before was held +def imports.any(ls: List<&2, String>, +deps: List<&2, String>, hit: Bool) -> Bool: + match ls: + case Nil{}: + hit + case Con{l, rest}: + match hit: + case True{}: + True{} + case False{}: + imports.any(rest, deps, Imports.has(deps, l)) + +# the files no role of their own puts on the laws' side that import a law +# file of the run (lawful): a proof made of imports alone +def feeds(fs: List<&2, Fact>, +lawful: List<&2, String>) -> List<&2, String>: + match fs: + case Nil{}: + Nil{} + case Con{Fact{norm, +deps, +named, _entry}, rest}: + +more = feeds(rest, lawful) + +hit = Bool.and(Bool.not(is_law_file(named)), imports.any(lawful, deps, False{})) + Bool.pick(List<&2, String>, hit, norm <> more, more) + # what the walk from some files reaches, through every file of the run def reach(+fs: List<&2, Fact>, +from: List<&2, String>) -> List<&2, String>: Imports.walk(List.length(&2, Fact, fs), from, from, edges(fs), paths(fs)) @@ -162,8 +255,9 @@ def marks.of( # roots at once def marks(ss: List<&2, Src.Src>) -> List<&2, Mark>: +fs = facts(ss) - +ls = reach(fs, roots(fs)) - marks.of(fs, ls, reach(fs, lawful(fs)), reach(fs, code(fs, ls))) + +lw = lawful(fs) + +ls = reach(fs, List.append(&2, String, roots(fs), feeds(fs, lw))) + marks.of(fs, ls, reach(fs, lw), reach(fs, code(fs, ls))) # a normalized path's role among the marks: neither when it is not marked def of.norm(ms: List<&2, Mark>, +nn: String) -> Role: diff --git a/src/rules.bend b/src/rules.bend index 9015ba6..1afdefb 100644 --- a/src/rules.bend +++ b/src/rules.bend @@ -100,10 +100,10 @@ def on.rules.role(+rr: Role.Role, +ss: Src.Src) -> List<&2, F.Finding>: Closed.check.role(rr, ss), Transcong.check(ss)]) -# every rule's findings on a file read once, as each rule gives them, its -# role read from its name +# every rule's findings on a file read once, as each rule gives them, told +# its own role (by its name, else by what it holds: src/role.bend) def on.rules(+ss: Src.Src) -> List<&2, F.Finding>: - on.rules.role(Role.of_path(on.path(ss)), ss) + on.rules.role(Role.own(ss), ss) # every rule's findings on a file read once, each at that file def on(+ss: Src.Src) -> List<&2, F.Finding>: From 951a582e3e695828bceb5a7ac0b26594cea8cf11 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 1 Nov 2026 15:11:33 +0000 Subject: [PATCH 3/7] Revert "fix(role): read a file's role from what it holds; no import walk over named files" This reverts commit 918d358d8800399c111ca918884635823c5380fc. Role by content and no walk over named files were the wrong fix: few projects keep the laws/ and proofs/ layout, and roles must follow the imports. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_0186KEnw5sg7heUQZTJJU89N --- LAWS.bend | 1 - PROOF.bend | 1 - laws/LspDiagnosticsMatchCli.bend | 3 +- laws/RoleOwn.bend | 44 --------- laws/defs/GradedFindingCounts.bend | 34 +------ proofs/LspDiagnosticsMatchCli.bend | 70 +++++++++++--- proofs/RoleOwn.bend | 36 ------- proofs/lemmas/PlannerPerFileFindings.bend | 77 +++------------ src/README.md | 23 ++--- src/lint/plan.bend | 24 +---- src/role.bend | 110 ++-------------------- src/rules.bend | 6 +- 12 files changed, 95 insertions(+), 334 deletions(-) delete mode 100644 laws/RoleOwn.bend delete mode 100644 proofs/RoleOwn.bend diff --git a/LAWS.bend b/LAWS.bend index 92340fd..21ccfe8 100644 --- a/LAWS.bend +++ b/LAWS.bend @@ -102,7 +102,6 @@ import ./laws/RoleIndex.bend as RoleIndex import ./laws/RoleLawFileNamed.bend as RoleLawFileNamed import ./laws/RoleLawsNamed.bend as RoleLawsNamed import ./laws/RoleNamedFirst.bend as RoleNamedFirst -import ./laws/RoleOwn.bend as RoleOwn import ./laws/RoleParam.bend as RoleParam import ./laws/RoleParamExempt.bend as RoleParamExempt import ./laws/RolePick.bend as RolePick diff --git a/PROOF.bend b/PROOF.bend index 7f5475b..881c2d4 100644 --- a/PROOF.bend +++ b/PROOF.bend @@ -87,7 +87,6 @@ import ./proofs/RoleIndex.bend as PRoleIndex import ./proofs/RoleLawFileNamed.bend as PRoleLawFileNamed import ./proofs/RoleLawsNamed.bend as PRoleLawsNamed import ./proofs/RoleNamedFirst.bend as PRoleNamedFirst -import ./proofs/RoleOwn.bend as PRoleOwn import ./proofs/RoleParam.bend as PRoleParam import ./proofs/RoleParamExempt.bend as PRoleParamExempt import ./proofs/RolePick.bend as PRolePick diff --git a/laws/LspDiagnosticsMatchCli.bend b/laws/LspDiagnosticsMatchCli.bend index 0c5e548..370468f 100644 --- a/laws/LspDiagnosticsMatchCli.bend +++ b/laws/LspDiagnosticsMatchCli.bend @@ -17,8 +17,7 @@ import ../src/noqa.bend as Noqa def pub.cli(+world: W.World) -> List<&2, F.Graded>: +ss = Plan.srcs_of(Plan.gots(Plan.inputs(world), world)) +cs = Plan.confs(world) - Plan.after(Noqa.of_srcs(ss), False{}, Plan.grade(cs, world, Plan.per_file.run(W.paths.of(world), ss)), - fs => Plan.grade(cs, world, fs)) + Plan.after(Noqa.of_srcs(ss), False{}, Plan.grade(cs, world, Plan.per_file(ss)), fs => Plan.grade(cs, world, fs)) # LAW: an open document's lint is the CLI's: over every file system and # working directory (a World), for a document at a path whose text the CLI diff --git a/laws/RoleOwn.bend b/laws/RoleOwn.bend deleted file mode 100644 index 0c26148..0000000 --- a/laws/RoleOwn.bend +++ /dev/null @@ -1,44 +0,0 @@ -# A file's own role (src/role.bend's own): a LAWS.bend or a PROOF.bend -# keeps its role by name; any other package's main.bend is code whatever it -# holds; any other file that fills a law (a def that is a proof) is a proof -# file, one that states a law and fills none a law file, and one that does -# neither is code. So a guarantee kept one per file under laws/ and proofs/ -# is read as laws and proofs with no LAWS.bend or PROOF.bend in the run, and -# a run over files named on the line, which takes each file's own role with -# no import walk (laws/defs/GradedFindingCounts.bend's on_run), never takes -# the code a law imports for a law. -import Base -import ../src/role.bend as Role - -# LAW: a LAWS.bend or a PROOF.bend keeps its role by name -law own_named: - for +named: Role.Role - for entry: Bool - for fills: Bool - for states: Bool - for e: {Role.is_law_file(named) == True{} : Bool} - {Role.own.of(named, entry, fills, states) == named : Role.Role} - -# LAW: any other main.bend is code, whatever it holds -law own_entry: - for +named: Role.Role - for fills: Bool - for states: Bool - for e: {Role.is_law_file(named) == False{} : Bool} - {Role.own.of(named, True{}, fills, states) == Role.Plain{} : Role.Role} - -# LAW: any other file that fills a law is a proof file -law own_fills: - for +named: Role.Role - for states: Bool - for e: {Role.is_law_file(named) == False{} : Bool} - {Role.own.of(named, False{}, True{}, states) == Role.Proof{} : Role.Role} - -# LAW: any other file that states a law and fills none is a law file, and -# one that does neither is code -law own_states: - for +named: Role.Role - for states: Bool - for e: {Role.is_law_file(named) == False{} : Bool} - {Role.own.of(named, False{}, False{}, states) == Bool.pick(Role.Role, states, Role.Laws{}, Role.Plain{}) - : Role.Role} diff --git a/laws/defs/GradedFindingCounts.bend b/laws/defs/GradedFindingCounts.bend index e5efd7b..b1b14c5 100644 --- a/laws/defs/GradedFindingCounts.bend +++ b/laws/defs/GradedFindingCounts.bend @@ -65,34 +65,6 @@ def on_all(+ms: List<&2, Role.Mark>, +world: W.World, ps: List<&2, String>) -> L case Con{+p, rest}: List.append(&2, F.Finding, on_at(ms, p, W.text(world, p)), on_all(ms, world, rest)) -# the per-file rules on one source, told its own role (src/role.bend), when -# the World holds a text for it -def own_at(+path: String, mm: Maybe<&2, String>) -> List<&2, F.Finding>: - match mm: - case None{}: - [] - case Some{text}: - Rules.on(Src.of(path, text)) - -# the per-file rules on each source, each told its own role, in file-list -# order -def own_all(+world: W.World, ps: List<&2, String>) -> List<&2, F.Finding>: - match ps: - case Nil{}: - [] - case Con{+p, rest}: - List.append(&2, F.Finding, own_at(p, W.text(world, p)), own_all(world, rest)) - -# the per-file rules on each source of a run: over the whole tree (no paths -# named) each told its role among the marks of every source; over files -# named on the line each told its own role, with no walk -def on_run(named: List<&2, String>, +world: W.World, +ps: List<&2, String>) -> List<&2, F.Finding>: - match named: - case Nil{}: - on_all(Role.marks(Plan.srcs_of(Plan.gots(ps, world))), world, ps) - case Con{_n, _rest}: - own_all(world, ps) - # the path of the first of the paths the World read a text for, as a list of # one; none when it read none def read_first.at(+path: String, mm: Maybe<&2, W.Answer>, more: List<&2, String>) -> List<&2, String>: @@ -177,13 +149,13 @@ def pkgs(rs: List<&2, String>) -> List<&2, String>: Imports.dir_of(r ++ "/main.bend") <> pkgs(rest) # every finding of a run, ungraded: the read failures, then each source's -# per-file findings, each rule seeing that one source and its role (on_run), -# then `setting`'s, +# per-file findings, each rule seeing that one source and its role among the +# run's law and proof files (src/role.bend), then `setting`'s, # seeing each bolt.bend grading reads, then the project rules over every # source in the run, by package def in_scope(+world: W.World) -> List<&2, F.Finding>: +ps = Plan.inputs(world) - List.concat(&2, F.Finding, [unread_all(world, ps), on_run(W.paths.of(world), world, ps), + List.concat(&2, F.Finding, [unread_all(world, ps), on_all(Role.marks(Plan.srcs_of(Plan.gots(ps, world))), world, ps), settings_found(world), D2.packed(parsed_all(world, ps), pkgs(Plan.roots(world)))]) diff --git a/proofs/LspDiagnosticsMatchCli.bend b/proofs/LspDiagnosticsMatchCli.bend index cb652dd..8bce0d5 100644 --- a/proofs/LspDiagnosticsMatchCli.bend +++ b/proofs/LspDiagnosticsMatchCli.bend @@ -8,9 +8,13 @@ import ../src/rules.bend as Rules import ../src/lint/world.bend as W import ../src/lint/plan.bend as Plan import ../src/src.bend as Src +import ../src/role.bend as Role +import ../src/rules/imports.bend as Imports import ../src/noqa.bend as Noqa import ../src/rules/style/noqa.bend as NoqaRule import ../src/syntax/lex.bend as Lex +import ../src/paths.bend as Paths +import ../src/syntax/outline.bend as Outline import ../laws/LspDiagnosticsMatchCli.bend as Laws import ./lemmas/PublishComparisons.bend as M1 @@ -82,6 +86,18 @@ def pub.find_head(d, rest, world): == Plan.conf.near(world, d) : Config.Config} {==} +# `bolt ` runs over that path alone, unless it is a bolt.bend +law pub.cli.named: + for +world: W.World + for +path: String + for h: {W.paths.of(world) == [path] : List<&2, String>} + {Plan.inputs(world) == Plan.sources([path]) : List<&2, String>} + +def pub.cli.named(world, path, h): + e = Equal.sym(List<&2, String>, W.paths.of(world), [path], h) + %e : {Plan.sources(Plan.files.named(_, world)) == Plan.sources([path]) : List<&2, String>} + {==} + # findings put at a path are graded by the CLI as by the config nearest it law pub.grade_near: for +world: W.World @@ -113,12 +129,43 @@ law pub.gs: for +text: String {Rules.graded(Config.nearest(Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world)), Rules.all(path, text)) == Plan.grade(Plan.confs.at(Plan.conf.dirs([path]), world), world, - Plan.per_file.run([path], Plan.srcs_of([Plan.Got{path, Some{text}}]))) + Plan.per_file(Plan.srcs_of([Plan.Got{path, Some{text}}]))) : List<&2, F.Graded>} +# a run of one file gives it its role by name: a LAWS.bend or PROOF.bend +# keeps it, and any other file is reached by no law, so it is neither +law pub.role_one: + for +named: Role.Role + for +nn: String + for deps: List<&2, String> + for +entry: Bool + {Role.role.of(named, Role.of.norm(Role.marks.of([Role.Fact{nn, deps, named, entry}], + Role.reach([Role.Fact{nn, deps, named, entry}], Role.roots([Role.Fact{nn, deps, named, entry}])), + Role.reach([Role.Fact{nn, deps, named, entry}], Role.lawful([Role.Fact{nn, deps, named, entry}])), + Role.reach([Role.Fact{nn, deps, named, entry}], Role.code([Role.Fact{nn, deps, named, entry}], + Role.reach([Role.Fact{nn, deps, named, entry}], Role.roots([Role.Fact{nn, deps, named, entry}]))))), nn)) + == named : Role.Role} + +def pub.role_one(named, _nn, _deps, _entry): + match named: + case Role.Plain{}: + {==} + case Role.Laws{}: + {==} + case Role.Proof{}: + {==} + def pub.gs(world, path, text): - a = Equal.sym(List<&2, F.Finding>, Plan.per_file.run([path], Plan.srcs_of([Plan.Got{path, Some{text}}])), - Rules.all(path, text), pub.app_nil(F.Finding, Rules.all(path, text))) + +s = Src.of(path, text) + +r = Equal.cong(Role.Role, List<&2, F.Finding>, z => List.append(&2, F.Finding, Rules.on.role(z, s), []), + Role.of(Role.marks([s]), path), Role.of_path(path), + pub.role_one(Role.of_path(path), Imports.norm(path), + Imports.targets(Outline.of_tokens(text, Lex.tokens(text)), Imports.norm(path)), + String.eq(Paths.base(path), "main.bend"))) + a = Equal.sym(List<&2, F.Finding>, Plan.per_file(Plan.srcs_of([Plan.Got{path, Some{text}}])), Rules.all(path, text), + Equal.trans(List<&2, F.Finding>, Plan.per_file(Plan.srcs_of([Plan.Got{path, Some{text}}])), + List.append(&2, F.Finding, Rules.all(path, text), []), Rules.all(path, text), r, + pub.app_nil(F.Finding, Rules.all(path, text)))) %a : {Rules.graded(Config.nearest(Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world)), Rules.all(path, text)) == Plan.grade(Plan.confs.at(Plan.conf.dirs([path]), world), world, _) @@ -169,7 +216,7 @@ law pub.cli.case: {Server.pub.lint(path, text, Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world)) == Plan.after(Noqa.of_srcs(Plan.srcs_of(Plan.gots(Bool.pick(List<&2, String>, bb, [], [path]), world))), False{}, Plan.grade(Plan.confs.at(Plan.conf.dirs(Bool.pick(List<&2, String>, bb, [], [path])), world), world, - Plan.per_file.run([path], Plan.srcs_of(Plan.gots(Bool.pick(List<&2, String>, bb, [], [path]), world)))), + Plan.per_file(Plan.srcs_of(Plan.gots(Bool.pick(List<&2, String>, bb, [], [path]), world)))), fs => Plan.grade(Plan.confs.at(Plan.conf.dirs(Bool.pick(List<&2, String>, bb, [], [path])), world), world, fs)) : List<&2, F.Graded>} @@ -193,7 +240,7 @@ def pub.cli.case(bb, world, path, text, hc, ht): fs => Rules.graded(Config.nearest(Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world)), fs))) == Plan.after(Noqa.of_srcs(Plan.srcs_of(Plan.gots([path], world))), False{}, Plan.grade(Plan.confs.at(Plan.conf.dirs([path]), world), world, - Plan.per_file.run([path], Plan.srcs_of(Plan.gots([path], world)))), + Plan.per_file(Plan.srcs_of(Plan.gots([path], world)))), fs => Plan.grade(Plan.confs.at(Plan.conf.dirs([path]), world), world, fs)) : List<&2, F.Graded>} t = Equal.sym(Maybe<&2, String>, W.text(world, path), Some{text}, ht) @@ -203,7 +250,7 @@ def pub.cli.case(bb, world, path, text, hc, ht): fs => Rules.graded(Config.nearest(Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world)), fs)) == Plan.after(Noqa.of_srcs(Plan.srcs_of([Plan.Got{path, _}])), False{}, Plan.grade(Plan.confs.at(Plan.conf.dirs([path]), world), world, - Plan.per_file.run([path], Plan.srcs_of([Plan.Got{path, _}]))), + Plan.per_file(Plan.srcs_of([Plan.Got{path, _}]))), fs => Plan.grade(Plan.confs.at(Plan.conf.dirs([path]), world), world, fs)) : List<&2, F.Graded>} g = pub.gs(world, path, text) @@ -232,11 +279,10 @@ def pub.cli.case(bb, world, path, text, hc, ht): Rules.all(path, text)))) def Laws.lint_is_cli(world, path, text, h, ht): - q = Equal.sym(List<&2, String>, W.paths.of(world), [path], h) - %q : {Server.pub.lint(path, text, Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world)) - == Plan.after(Noqa.of_srcs(Plan.srcs_of(Plan.gots(Plan.sources(Plan.files.named(_, world)), world))), False{}, - Plan.grade(Plan.confs.at(Plan.conf.dirs(Plan.sources(Plan.files.named(_, world))), world), world, - Plan.per_file.run(_, Plan.srcs_of(Plan.gots(Plan.sources(Plan.files.named(_, world)), world)))), - fs => Plan.grade(Plan.confs.at(Plan.conf.dirs(Plan.sources(Plan.files.named(_, world))), world), world, fs)) + n = Equal.sym(List<&2, String>, Plan.inputs(world), Plan.sources([path]), pub.cli.named(world, path, h)) + %n : {Server.pub.lint(path, text, Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world)) + == Plan.after(Noqa.of_srcs(Plan.srcs_of(Plan.gots(_, world))), False{}, + Plan.grade(Plan.confs.at(Plan.conf.dirs(_), world), world, Plan.per_file(Plan.srcs_of(Plan.gots(_, world)))), + fs => Plan.grade(Plan.confs.at(Plan.conf.dirs(_), world), world, fs)) : List<&2, F.Graded>} pub.cli.case(Plan.is_config(path), world, path, text, {==}, ht) diff --git a/proofs/RoleOwn.bend b/proofs/RoleOwn.bend deleted file mode 100644 index 279fef7..0000000 --- a/proofs/RoleOwn.bend +++ /dev/null @@ -1,36 +0,0 @@ -# proofs of laws/RoleOwn.bend -import Base -import ../src/role.bend as Role -import ../laws/RoleOwn.bend as Laws - -def Laws.own_named(named, entry, fills, states, e): - s = Equal.sym(Bool, Role.is_law_file(named), True{}, e) - %s : {Bool.pick(Role.Role, _, named, - Bool.pick(Role.Role, entry, Role.Plain{}, Bool.pick(Role.Role, fills, Role.Proof{}, - Bool.pick(Role.Role, states, Role.Laws{}, Role.Plain{})))) - == named : Role.Role} - {==} - -def Laws.own_entry(named, fills, states, e): - s = Equal.sym(Bool, Role.is_law_file(named), False{}, e) - %s : {Bool.pick(Role.Role, _, named, - Bool.pick(Role.Role, True{}, Role.Plain{}, Bool.pick(Role.Role, fills, Role.Proof{}, - Bool.pick(Role.Role, states, Role.Laws{}, Role.Plain{})))) - == Role.Plain{} : Role.Role} - {==} - -def Laws.own_fills(named, states, e): - s = Equal.sym(Bool, Role.is_law_file(named), False{}, e) - %s : {Bool.pick(Role.Role, _, named, - Bool.pick(Role.Role, False{}, Role.Plain{}, Bool.pick(Role.Role, True{}, Role.Proof{}, - Bool.pick(Role.Role, states, Role.Laws{}, Role.Plain{})))) - == Role.Proof{} : Role.Role} - {==} - -def Laws.own_states(named, states, e): - s = Equal.sym(Bool, Role.is_law_file(named), False{}, e) - %s : {Bool.pick(Role.Role, _, named, - Bool.pick(Role.Role, False{}, Role.Plain{}, Bool.pick(Role.Role, False{}, Role.Proof{}, - Bool.pick(Role.Role, states, Role.Laws{}, Role.Plain{})))) - == Bool.pick(Role.Role, states, Role.Laws{}, Role.Plain{}) : Role.Role} - {==} diff --git a/proofs/lemmas/PlannerPerFileFindings.bend b/proofs/lemmas/PlannerPerFileFindings.bend index 2c982e6..bbe9592 100644 --- a/proofs/lemmas/PlannerPerFileFindings.bend +++ b/proofs/lemmas/PlannerPerFileFindings.bend @@ -115,55 +115,6 @@ def lint.on(ms, ps, world): case Con{+p, +tl}: lint.on.step(ms, p, W.text(world, p), Plan.gots(tl, world), D1.on_all(ms, world, tl), lint.on(ms, tl, world)) -# one source onto the per-file findings, each told its own role -law lint.own.step: - for +path: String - for mm: Maybe<&2, String> - for +gs: List<&2, Plan.Got> - for -os: List<&2, F.Finding> - for ih: {Plan.per_file.own(Plan.srcs_of(gs)) == os : List<&2, F.Finding>} - {Plan.per_file.own(Plan.srcs_of(Plan.Got{path, mm} <> gs)) - == List.append(&2, F.Finding, D1.own_at(path, mm), os) : List<&2, F.Finding>} - -def lint.own.step(path, mm, gs, os, ih): - match mm: - case None{}: - ih - case Some{text}: - Equal.cong(List<&2, F.Finding>, List<&2, F.Finding>, - rr => List.append(&2, F.Finding, Rules.on(Src.of(path, text)), rr), - Plan.per_file.own(Plan.srcs_of(gs)), os, ih) - -# the planner's per-file findings over named files are each source's with -# its own role, in order -law lint.own: - for ps: List<&2, String> - for +world: W.World - {Plan.per_file.own(Plan.srcs_of(Plan.gots(ps, world))) == D1.own_all(world, ps) : List<&2, F.Finding>} - -def lint.own(ps, world): - match ps: - case Nil{}: - {==} - case Con{+p, +tl}: - lint.own.step(p, W.text(world, p), Plan.gots(tl, world), D1.own_all(world, tl), lint.own(tl, world)) - -# the planner's per-file findings in a run are the spec's: the marks over -# the whole tree, each source's own role over files named -law lint.run: - for named: List<&2, String> - for +ps: List<&2, String> - for +world: W.World - {Plan.per_file.run(named, Plan.srcs_of(Plan.gots(ps, world))) == D1.on_run(named, world, ps) - : List<&2, F.Finding>} - -def lint.run(named, ps, world): - match named: - case Nil{}: - lint.on(Role.marks(Plan.srcs_of(Plan.gots(ps, world))), ps, world) - case Con{_n, _rest}: - lint.own(ps, world) - def L3.scope_findings(world): n = Equal.sym(List<&2, F.Finding>, List.append(&2, F.Finding, Rules.project(Plan.srcs_of(Plan.gots(Plan.inputs(world), world)), @@ -172,42 +123,42 @@ def L3.scope_findings(world): M3.lint.app_nil(F.Finding, Rules.project(Plan.srcs_of(Plan.gots(Plan.inputs(world), world)), Plan.packages(Plan.roots(world))))) %n : {List.append(&2, F.Finding, Plan.unread(Plan.gots(Plan.inputs(world), world)), - List.append(&2, F.Finding, - Plan.per_file.run(W.paths.of(world), Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), + List.append(&2, F.Finding, Plan.per_file(Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), List.append(&2, F.Finding, Plan.settings(world), _))) == D1.in_scope(world) : List<&2, F.Finding>} a = Equal.sym(List<&2, F.Finding>, Plan.unread(Plan.gots(Plan.inputs(world), world)), D1.unread_all(world, Plan.inputs(world)), lint.unread(Plan.inputs(world), world)) %a : {List.append(&2, F.Finding, _, - List.append(&2, F.Finding, - Plan.per_file.run(W.paths.of(world), Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), + List.append(&2, F.Finding, Plan.per_file(Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), List.append(&2, F.Finding, Plan.settings(world), Rules.project(Plan.srcs_of(Plan.gots(Plan.inputs(world), world)), Plan.packages(Plan.roots(world)))))) == D1.in_scope(world) : List<&2, F.Finding>} - b = Equal.sym(List<&2, F.Finding>, - Plan.per_file.run(W.paths.of(world), Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), - D1.on_run(W.paths.of(world), world, Plan.inputs(world)), - lint.run(W.paths.of(world), Plan.inputs(world), world)) + b = Equal.sym(List<&2, F.Finding>, Plan.per_file(Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), + D1.on_all(Role.marks(Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), world, Plan.inputs(world)), + lint.on(Role.marks(Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), Plan.inputs(world), world)) %b : {List.append(&2, F.Finding, D1.unread_all(world, Plan.inputs(world)), List.append(&2, F.Finding, _, List.append(&2, F.Finding, Plan.settings(world), Rules.project(Plan.srcs_of(Plan.gots(Plan.inputs(world), world)), Plan.packages(Plan.roots(world)))))) == D1.in_scope(world) : List<&2, F.Finding>} t = Equal.sym(List<&2, F.Finding>, Plan.settings(world), D1.settings_found(world), L4.setting_reports(world)) %t : {List.append(&2, F.Finding, D1.unread_all(world, Plan.inputs(world)), - List.append(&2, F.Finding, D1.on_run(W.paths.of(world), world, Plan.inputs(world)), List.append(&2, F.Finding, _, + List.append(&2, F.Finding, D1.on_all(Role.marks(Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), world, + Plan.inputs(world)), List.append(&2, F.Finding, _, Rules.project(Plan.srcs_of(Plan.gots(Plan.inputs(world), world)), Plan.packages(Plan.roots(world)))))) == D1.in_scope(world) : List<&2, F.Finding>} c = Equal.sym(List<&2, Src.Src>, Plan.srcs_of(Plan.gots(Plan.inputs(world), world)), D1.parsed_all(world, Plan.inputs(world)), lint.parsed(Plan.inputs(world), world)) %c : {List.append(&2, F.Finding, D1.unread_all(world, Plan.inputs(world)), - List.append(&2, F.Finding, D1.on_run(W.paths.of(world), world, Plan.inputs(world)), + List.append(&2, F.Finding, D1.on_all(Role.marks(Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), world, + Plan.inputs(world)), List.append(&2, F.Finding, D1.settings_found(world), Rules.project(_, Plan.packages(Plan.roots(world)))))) == D1.in_scope(world) : List<&2, F.Finding>} k = Equal.sym(List<&2, String>, Plan.packages(Plan.roots(world)), D1.pkgs(Plan.roots(world)), L1.package_dirs(Plan.roots(world))) %k : {List.append(&2, F.Finding, D1.unread_all(world, Plan.inputs(world)), - List.append(&2, F.Finding, D1.on_run(W.paths.of(world), world, Plan.inputs(world)), + List.append(&2, F.Finding, D1.on_all(Role.marks(Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), world, + Plan.inputs(world)), List.append(&2, F.Finding, D1.settings_found(world), Rules.project(D1.parsed_all(world, Plan.inputs(world)), _)))) == D1.in_scope(world) : List<&2, F.Finding>} @@ -216,12 +167,14 @@ def L3.scope_findings(world): D2.packed(D1.parsed_all(world, Plan.inputs(world)), D1.pkgs(Plan.roots(world))), L2.packages_scoped(D1.parsed_all(world, Plan.inputs(world)), D1.pkgs(Plan.roots(world)))) %q : {List.append(&2, F.Finding, D1.unread_all(world, Plan.inputs(world)), - List.append(&2, F.Finding, D1.on_run(W.paths.of(world), world, Plan.inputs(world)), + List.append(&2, F.Finding, D1.on_all(Role.marks(Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), world, + Plan.inputs(world)), List.append(&2, F.Finding, D1.settings_found(world), _))) == D1.in_scope(world) : List<&2, F.Finding>} z = M3.lint.app_nil(F.Finding, D2.packed(D1.parsed_all(world, Plan.inputs(world)), D1.pkgs(Plan.roots(world)))) %z : {List.append(&2, F.Finding, D1.unread_all(world, Plan.inputs(world)), - List.append(&2, F.Finding, D1.on_run(W.paths.of(world), world, Plan.inputs(world)), + List.append(&2, F.Finding, D1.on_all(Role.marks(Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), world, + Plan.inputs(world)), List.append(&2, F.Finding, D1.settings_found(world), _))) == D1.in_scope(world) : List<&2, F.Finding>} {==} diff --git a/src/README.md b/src/README.md index 2bfc734..63be254 100644 --- a/src/README.md +++ b/src/README.md @@ -157,24 +157,13 @@ it. `coverage` runs on one package's files at a time. Over files named on the li nested packages, each with its own laws, lints in one run from the top, and a law covers only defs of its own package. -Rules read a file's role from its name, what it holds and the imports, -never from where it sits. A file's own role is by its name, a `LAWS.bend` -or a `PROOF.bend`, else by what it holds: a file that fills a law (a def -that is a proof: it returns one, or is written with no type) is a proof -file, one that states a law and fills none a law file, but a package's -`main.bend` is code whatever it holds. Over the whole tree the imports -widen that: a file a law file reaches through relative imports is a law -file, one only proof files reach a proof file, and a file that imports a -law file is on the laws' side too (a proof of imports alone). A file a -package's `main.bend` reaches, or one the laws do not reach, is code, even -when a law imports it to state laws about it, laws of its own and all. So a +Rules read a file's role from the imports, never from where it sits. A law +file is a `LAWS.bend` or a file one reaches through relative imports; a +proof file is a `PROOF.bend` or a file only a `PROOF.bend` reaches. A file a +package's `main.bend` reaches, or one no `LAWS.bend` or `PROOF.bend` +reaches, is code, even when a law imports it to state laws about it. So a `LAWS.bend` of nothing but `import ./laws/.bend` lines makes those -files law files, one guarantee per file under `laws/` and `proofs/` reads -right with no `LAWS.bend` at all, and a proof may state lemma laws of its -own. Over files named on the line, and in the editor, each file has its own -role and the imports are not walked: the code that imports a file may not be -in the run, and a walk without it would take that code for laws and hide -its findings. +files law files, and a proof may state lemma laws of its own. A rule is handed a [`Src`](src.bend), not the text: the path, the text, and the file's tokens, tree, binder and outline, each read once for the whole diff --git a/src/lint/plan.bend b/src/lint/plan.bend index a215bb8..f31b9dc 100644 --- a/src/lint/plan.bend +++ b/src/lint/plan.bend @@ -364,28 +364,6 @@ def per_file.role(+ms: List<&2, Role.Mark>, ss: List<&2, Src.Src>) -> List<&2, F def per_file(+ss: List<&2, Src.Src>) -> List<&2, F.Finding>: per_file.role(Role.marks(ss), ss) -# every source's findings from the per-file rules, each told its own role -# (src/role.bend's own: by its name, else by what it holds), as the editor -# tells it -def per_file.own(ss: List<&2, Src.Src>) -> List<&2, F.Finding>: - match ss: - case Nil{}: - Nil{} - case Con{s, rest}: - List.append(&2, F.Finding, Rules.on(s), per_file.own(rest)) - -# every source's findings from the per-file rules in a run: over the whole -# tree (no paths named), each told its role among the marks, the import walk -# over every package's files; over files named on the line, each told its -# own role, since the code that imports a file, a package's main.bend, may -# not be in the run, and a walk without it would take that code for laws -def per_file.run(paths: List<&2, String>, +ss: List<&2, Src.Src>) -> List<&2, F.Finding>: - match paths: - case Nil{}: - per_file(ss) - case Con{_p, _rest}: - per_file.own(ss) - # the path of the first candidate the World holds a text for, as a list of # one; none when it holds none def conf.first(gs: List<&2, Got>) -> List<&2, String>: @@ -431,7 +409,7 @@ def settings(+world: W.World) -> List<&2, F.Finding>: # then `setting`'s over each bolt.bend grading reads, then the project rules' # over every source and the other packages def found.at(+world: W.World, +gs: List<&2, Got>, +ss: List<&2, Src.Src>) -> List<&2, F.Finding>: - List.concat(&2, F.Finding, [unread(gs), per_file.run(W.paths.of(world), ss), settings(world), + List.concat(&2, F.Finding, [unread(gs), per_file(ss), settings(world), Rules.project(ss, packages(roots(world)))]) # every finding of every rule, ungraded diff --git a/src/role.bend b/src/role.bend index bfe8280..549f61d 100644 --- a/src/role.bend +++ b/src/role.bend @@ -8,27 +8,12 @@ # proof may state laws of its own. So `LAWS.bend` can be nothing but # `import ./laws/x.bend as X` lines, the files it reaches are held to what a # law file is held to, and the code they state laws about, which main.bend -# imports, is still code. -# -# A file needs no LAWS.bend above it to be on the laws' side: what it holds -# says so too (`own`). A file that fills a law (a def that is a proof: it -# returns one, or is written with no type) is a proof file, and one that -# states a law and fills none a law file, unless it is a package's -# main.bend, which is code whatever it holds; a file that imports a law file -# of the run is on the laws' side as well (a proof made of imports alone). -# So one guarantee per file under laws/ and proofs/ is read right with no -# root file, and over the whole tree the code still wins: a file the code -# imports is code, laws and all. Over files named on the line the walk does -# not run, since the code that imports a file may not be in the run: each -# file has its own role (src/lint/plan.bend). Pure. +# imports, is still code. Pure. import Base import ./paths.bend as Paths import ./src.bend as Src import ./lazy/lazy.bend as Lazy -import ./syntax/tree.bend as Tree import ./rules/imports.bend as Imports -import ./rules/calls.bend as Calls -import ./syntax/outline.bend as Outline import ./finding.bend as F # what a file is to the laws @@ -73,75 +58,21 @@ def skip(+rr: Role, run: Unit -> List<&2, F.Finding>) -> List<&2, F.Finding>: # reading a file # -------------- -# is the item a law? -def law_item(kk: Outline.ItemKind) -> Bool: - match kk: - case Outline.ILaw{}: - True{} - case other: - False{} - -# does the outline state a law? hit: the item before did -def states.go(its: List<&2, Outline.Item>, hit: Bool) -> Bool: - match its: - case Nil{}: - hit - case Con{Outline.Item{k, _name, _line, _sig, _doc, _path}, rest}: - match hit: - case True{}: - True{} - case False{}: - states.go(rest, law_item(k)) - -# does the outline state a law? -def states(its: List<&2, Outline.Item>) -> Bool: - states.go(its, False{}) - -# does one of the defs fill a law (Calls.proof)? hit: the def before did -def fills.go(ds: List<&2, Calls.Def>, hit: Bool) -> Bool: - match ds: - case Nil{}: - hit - case Con{Calls.Def{_name, +sig, _body}, rest}: - match hit: - case True{}: - True{} - case False{}: - fills.go(rest, Calls.proof(sig)) - -# does a def of the tree fill a law? -def fills(tree: Tree.Node) -> Bool: - fills.go(Calls.defs(tree), False{}) - -# a file's role by itself: a LAWS.bend or PROOF.bend by its name; else, but -# for a package's main.bend (entry), a proof file when it fills a law and a -# law file when it states one; else neither -def own.of(+named: Role, entry: Bool, fills: Bool, states: Bool) -> Role: - Bool.pick(Role, is_law_file(named), named, - Bool.pick(Role, entry, Plain{}, Bool.pick(Role, fills, Proof{}, Bool.pick(Role, states, Laws{}, Plain{})))) - -# a parsed file's role by itself (own.of) -def own(ss: Src.Src) -> Role: - Src.Src{+path, _text, _toks, tree, _bound, items} = ss - own.of(of_path(path), String.eq(Paths.base(path), "main.bend"), fills(tree), states(items)) - # what a file on the laws' side is: a law file when a LAWS.bend reaches it, # else a proof file def reached(laws: Bool) -> Role: Bool.pick(Role, laws, Laws{}, Proof{}) # one file as the walk reads it: its normalized path, what it imports, its -# own role (by name, else by what it holds), and whether it is a package's -# entry (a main.bend) +# role by name, and whether it is a package's entry (a main.bend) type Fact is Data: Fact{norm: String, deps: List<&2, String>, named: Role, entry: Bool} # a parsed file read down to its fact def fact(ss: Src.Src) -> Fact: - Src.Src{+path, text, toks, tree, bound, +items} = ss + Src.Src{+path, _text, _toks, _tree, _bound, items} = ss +nn = Imports.norm(path) - Fact{nn, Imports.targets(items, nn), own(Src.Src{path, text, toks, tree, bound, items}), - String.eq(Paths.base(path), "main.bend")} + Fact{nn, Imports.targets(items, nn), of_path(path), String.eq(Paths.base(path), "main.bend")} # every file's fact, in order def facts(ss: List<&2, Src.Src>) -> List<&2, Fact>: @@ -174,7 +105,7 @@ def paths(fs: List<&2, Fact>) -> List<&2, String>: case Con{Fact{norm, _deps, _named, _entry}, rest}: norm <> paths(rest) -# the roots of the laws: every file that is a law or proof file by itself +# the roots of the laws: every LAWS.bend and PROOF.bend def roots(fs: List<&2, Fact>) -> List<&2, String>: match fs: case Nil{}: @@ -183,7 +114,7 @@ def roots(fs: List<&2, Fact>) -> List<&2, String>: +more = roots(rest) Bool.pick(List<&2, String>, is_law_file(named), norm <> more, more) -# the roots of the law files: every file that is a law file by itself +# the roots of the law files: every LAWS.bend def lawful(fs: List<&2, Fact>) -> List<&2, String>: match fs: case Nil{}: @@ -192,30 +123,6 @@ def lawful(fs: List<&2, Fact>) -> List<&2, String>: +more = lawful(rest) Bool.pick(List<&2, String>, is_laws(named), norm <> more, more) -# do the imports hold one of the paths? Over the paths first, so none to -# look for answers at once; hit: the path before was held -def imports.any(ls: List<&2, String>, +deps: List<&2, String>, hit: Bool) -> Bool: - match ls: - case Nil{}: - hit - case Con{l, rest}: - match hit: - case True{}: - True{} - case False{}: - imports.any(rest, deps, Imports.has(deps, l)) - -# the files no role of their own puts on the laws' side that import a law -# file of the run (lawful): a proof made of imports alone -def feeds(fs: List<&2, Fact>, +lawful: List<&2, String>) -> List<&2, String>: - match fs: - case Nil{}: - Nil{} - case Con{Fact{norm, +deps, +named, _entry}, rest}: - +more = feeds(rest, lawful) - +hit = Bool.and(Bool.not(is_law_file(named)), imports.any(lawful, deps, False{})) - Bool.pick(List<&2, String>, hit, norm <> more, more) - # what the walk from some files reaches, through every file of the run def reach(+fs: List<&2, Fact>, +from: List<&2, String>) -> List<&2, String>: Imports.walk(List.length(&2, Fact, fs), from, from, edges(fs), paths(fs)) @@ -255,9 +162,8 @@ def marks.of( # roots at once def marks(ss: List<&2, Src.Src>) -> List<&2, Mark>: +fs = facts(ss) - +lw = lawful(fs) - +ls = reach(fs, List.append(&2, String, roots(fs), feeds(fs, lw))) - marks.of(fs, ls, reach(fs, lw), reach(fs, code(fs, ls))) + +ls = reach(fs, roots(fs)) + marks.of(fs, ls, reach(fs, lawful(fs)), reach(fs, code(fs, ls))) # a normalized path's role among the marks: neither when it is not marked def of.norm(ms: List<&2, Mark>, +nn: String) -> Role: diff --git a/src/rules.bend b/src/rules.bend index 1afdefb..9015ba6 100644 --- a/src/rules.bend +++ b/src/rules.bend @@ -100,10 +100,10 @@ def on.rules.role(+rr: Role.Role, +ss: Src.Src) -> List<&2, F.Finding>: Closed.check.role(rr, ss), Transcong.check(ss)]) -# every rule's findings on a file read once, as each rule gives them, told -# its own role (by its name, else by what it holds: src/role.bend) +# every rule's findings on a file read once, as each rule gives them, its +# role read from its name def on.rules(+ss: Src.Src) -> List<&2, F.Finding>: - on.rules.role(Role.own(ss), ss) + on.rules.role(Role.of_path(on.path(ss)), ss) # every rule's findings on a file read once, each at that file def on(+ss: Src.Src) -> List<&2, F.Finding>: From 9afb693f93830333d8f005bcbde7bf23d9b03484 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 1 Nov 2026 15:23:18 +0000 Subject: [PATCH 4/7] fix(role): over named files, follow the imports of the whole tree A file's role follows the imports: a file a LAWS.bend reaches is a law file unless a package's main.bend reaches it too. The walk only saw the files of the run, so naming a LAWS.bend without the main.bend that imports the same code took that code for laws and hid its findings (bend2craft's world/entities.bend: 174 findings alone, none beside world/LAWS.bend), and naming laws/.bend files without their LAWS.bend linted them as code. Over files named on the line the planner now lists the tree under the working directory, as a whole-tree run does, and reads every .bend file in it for its imports alone (Role.fact.text: lexed and outlined, never parsed); the marks are taken over that graph (Plan.marks.run), and only the named files are linted. A whole-tree run is unchanged. Laws: the planner's per-file findings are each source's told its role among Plan.marks.run's marks (laws/defs/GradedFindingCounts.bend). The editor reads one file and not the tree, so laws/LspDiagnosticsMatchCli.bend now compares it with `bolt ` as that command really runs, for a file the tree's imports leave at the role its name gives it. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_0186KEnw5sg7heUQZTJJU89N --- laws/LspDiagnosticsMatchCli.bend | 15 +++- laws/defs/GradedFindingCounts.bend | 6 +- proofs/LspDiagnosticsMatchCli.bend | 85 +++++++---------------- proofs/lemmas/PlannerPerFileFindings.bend | 33 ++++++--- src/README.md | 8 +++ src/lint/plan.bend | 54 +++++++++----- src/role.bend | 19 ++++- 7 files changed, 124 insertions(+), 96 deletions(-) diff --git a/laws/LspDiagnosticsMatchCli.bend b/laws/LspDiagnosticsMatchCli.bend index 370468f..55c4928 100644 --- a/laws/LspDiagnosticsMatchCli.bend +++ b/laws/LspDiagnosticsMatchCli.bend @@ -1,13 +1,16 @@ # Diagnostics for an open document equal the CLI's per-file findings for that # file and text under the same bolt.bend, less what its noqa comments silence # (laws/NoqaSilencesNamedFindings.bend), then `noqa`'s findings on them, which judge no project rule's -# code. +# code, for a file whose role the tree's imports leave at its name's. The +# editor reads one file and not the imports of the tree around it, so a file +# a LAWS.bend reaches, a law file to `bolt `, is linted as code there. import Base import ../src/lsp/server.bend as Server import ../src/finding.bend as F import ../src/lint/world.bend as W import ../src/lint/plan.bend as Plan import ../src/noqa.bend as Noqa +import ../src/role.bend as Role # the CLI's per-file findings in a run, graded the CLI's way: each source's # per-file rules, each finding at the level the bolt.bend nearest its path @@ -17,7 +20,9 @@ import ../src/noqa.bend as Noqa def pub.cli(+world: W.World) -> List<&2, F.Graded>: +ss = Plan.srcs_of(Plan.gots(Plan.inputs(world), world)) +cs = Plan.confs(world) - Plan.after(Noqa.of_srcs(ss), False{}, Plan.grade(cs, world, Plan.per_file(ss)), fs => Plan.grade(cs, world, fs)) + Plan.after(Noqa.of_srcs(ss), False{}, + Plan.grade(cs, world, Plan.per_file.role(Plan.marks.run(W.paths.of(world), world, ss), ss)), + fs => Plan.grade(cs, world, fs)) # LAW: an open document's lint is the CLI's: over every file system and # working directory (a World), for a document at a path whose text the CLI @@ -26,12 +31,16 @@ def pub.cli(+world: W.World) -> List<&2, F.Graded>: # directory, are exactly what `bolt ` grades as that file's per-file # findings, the same noqa comments silencing the same findings and `noqa` # reporting what the CLI reports but for a project rule's code, which the -# editor cannot judge (none for a bolt.bend, which neither lints) +# editor cannot judge (none for a bolt.bend, which neither lints), whenever +# the imports of the tree (the graph `bolt ` reads its roles from) give +# the document no role but the one its name gives it law lint_is_cli: for +world: W.World for +path: String for +text: String for h: {W.paths.of(world) == [path] : List<&2, String>} for ht: {W.text(world, path) == Some{text} : Maybe<&2, String>} + for hr: {Role.of(Role.marks.facts(Plan.facts.of(Plan.gots(Plan.graph(world), world))), path) == Role.of_path(path) + : Role.Role} {Server.pub.lint(path, text, Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world)) == pub.cli(world) : List<&2, F.Graded>} diff --git a/laws/defs/GradedFindingCounts.bend b/laws/defs/GradedFindingCounts.bend index b1b14c5..0857df0 100644 --- a/laws/defs/GradedFindingCounts.bend +++ b/laws/defs/GradedFindingCounts.bend @@ -150,12 +150,14 @@ def pkgs(rs: List<&2, String>) -> List<&2, String>: # every finding of a run, ungraded: the read failures, then each source's # per-file findings, each rule seeing that one source and its role among the -# run's law and proof files (src/role.bend), then `setting`'s, +# law and proof files (src/role.bend) of the run, or of the whole tree when +# files were named (Plan.marks.run), then `setting`'s, # seeing each bolt.bend grading reads, then the project rules over every # source in the run, by package def in_scope(+world: W.World) -> List<&2, F.Finding>: +ps = Plan.inputs(world) - List.concat(&2, F.Finding, [unread_all(world, ps), on_all(Role.marks(Plan.srcs_of(Plan.gots(ps, world))), world, ps), + List.concat(&2, F.Finding, [unread_all(world, ps), + on_all(Plan.marks.run(W.paths.of(world), world, Plan.srcs_of(Plan.gots(ps, world))), world, ps), settings_found(world), D2.packed(parsed_all(world, ps), pkgs(Plan.roots(world)))]) diff --git a/proofs/LspDiagnosticsMatchCli.bend b/proofs/LspDiagnosticsMatchCli.bend index 8bce0d5..8a87a05 100644 --- a/proofs/LspDiagnosticsMatchCli.bend +++ b/proofs/LspDiagnosticsMatchCli.bend @@ -9,12 +9,9 @@ import ../src/lint/world.bend as W import ../src/lint/plan.bend as Plan import ../src/src.bend as Src import ../src/role.bend as Role -import ../src/rules/imports.bend as Imports import ../src/noqa.bend as Noqa import ../src/rules/style/noqa.bend as NoqaRule import ../src/syntax/lex.bend as Lex -import ../src/paths.bend as Paths -import ../src/syntax/outline.bend as Outline import ../laws/LspDiagnosticsMatchCli.bend as Laws import ./lemmas/PublishComparisons.bend as M1 @@ -86,18 +83,6 @@ def pub.find_head(d, rest, world): == Plan.conf.near(world, d) : Config.Config} {==} -# `bolt ` runs over that path alone, unless it is a bolt.bend -law pub.cli.named: - for +world: W.World - for +path: String - for h: {W.paths.of(world) == [path] : List<&2, String>} - {Plan.inputs(world) == Plan.sources([path]) : List<&2, String>} - -def pub.cli.named(world, path, h): - e = Equal.sym(List<&2, String>, W.paths.of(world), [path], h) - %e : {Plan.sources(Plan.files.named(_, world)) == Plan.sources([path]) : List<&2, String>} - {==} - # findings put at a path are graded by the CLI as by the config nearest it law pub.grade_near: for +world: W.World @@ -127,43 +112,20 @@ law pub.gs: for +world: W.World for +path: String for +text: String + for -ms: List<&2, Role.Mark> + for hr: {Role.of(ms, path) == Role.of_path(path) : Role.Role} {Rules.graded(Config.nearest(Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world)), Rules.all(path, text)) == Plan.grade(Plan.confs.at(Plan.conf.dirs([path]), world), world, - Plan.per_file(Plan.srcs_of([Plan.Got{path, Some{text}}]))) + Plan.per_file.role(ms, Plan.srcs_of([Plan.Got{path, Some{text}}]))) : List<&2, F.Graded>} -# a run of one file gives it its role by name: a LAWS.bend or PROOF.bend -# keeps it, and any other file is reached by no law, so it is neither -law pub.role_one: - for +named: Role.Role - for +nn: String - for deps: List<&2, String> - for +entry: Bool - {Role.role.of(named, Role.of.norm(Role.marks.of([Role.Fact{nn, deps, named, entry}], - Role.reach([Role.Fact{nn, deps, named, entry}], Role.roots([Role.Fact{nn, deps, named, entry}])), - Role.reach([Role.Fact{nn, deps, named, entry}], Role.lawful([Role.Fact{nn, deps, named, entry}])), - Role.reach([Role.Fact{nn, deps, named, entry}], Role.code([Role.Fact{nn, deps, named, entry}], - Role.reach([Role.Fact{nn, deps, named, entry}], Role.roots([Role.Fact{nn, deps, named, entry}]))))), nn)) - == named : Role.Role} - -def pub.role_one(named, _nn, _deps, _entry): - match named: - case Role.Plain{}: - {==} - case Role.Laws{}: - {==} - case Role.Proof{}: - {==} - -def pub.gs(world, path, text): +def pub.gs(world, path, text, ms, hr): +s = Src.of(path, text) - +r = Equal.cong(Role.Role, List<&2, F.Finding>, z => List.append(&2, F.Finding, Rules.on.role(z, s), []), - Role.of(Role.marks([s]), path), Role.of_path(path), - pub.role_one(Role.of_path(path), Imports.norm(path), - Imports.targets(Outline.of_tokens(text, Lex.tokens(text)), Imports.norm(path)), - String.eq(Paths.base(path), "main.bend"))) - a = Equal.sym(List<&2, F.Finding>, Plan.per_file(Plan.srcs_of([Plan.Got{path, Some{text}}])), Rules.all(path, text), - Equal.trans(List<&2, F.Finding>, Plan.per_file(Plan.srcs_of([Plan.Got{path, Some{text}}])), + r = Equal.cong(Role.Role, List<&2, F.Finding>, z => List.append(&2, F.Finding, Rules.on.role(z, s), []), + Role.of(ms, path), Role.of_path(path), hr) + a = Equal.sym(List<&2, F.Finding>, Plan.per_file.role(ms, Plan.srcs_of([Plan.Got{path, Some{text}}])), + Rules.all(path, text), + Equal.trans(List<&2, F.Finding>, Plan.per_file.role(ms, Plan.srcs_of([Plan.Got{path, Some{text}}])), List.append(&2, F.Finding, Rules.all(path, text), []), Rules.all(path, text), r, pub.app_nil(F.Finding, Rules.all(path, text)))) %a : {Rules.graded(Config.nearest(Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world)), @@ -211,16 +173,18 @@ law pub.cli.case: for +world: W.World for +path: String for +text: String + for -ms: List<&2, Role.Mark> for hc: {Plan.is_config(path) == bb : Bool} for ht: {W.text(world, path) == Some{text} : Maybe<&2, String>} + for hr: {Role.of(ms, path) == Role.of_path(path) : Role.Role} {Server.pub.lint(path, text, Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world)) == Plan.after(Noqa.of_srcs(Plan.srcs_of(Plan.gots(Bool.pick(List<&2, String>, bb, [], [path]), world))), False{}, Plan.grade(Plan.confs.at(Plan.conf.dirs(Bool.pick(List<&2, String>, bb, [], [path])), world), world, - Plan.per_file(Plan.srcs_of(Plan.gots(Bool.pick(List<&2, String>, bb, [], [path]), world)))), + Plan.per_file.role(ms, Plan.srcs_of(Plan.gots(Bool.pick(List<&2, String>, bb, [], [path]), world)))), fs => Plan.grade(Plan.confs.at(Plan.conf.dirs(Bool.pick(List<&2, String>, bb, [], [path])), world), world, fs)) : List<&2, F.Graded>} -def pub.cli.case(bb, world, path, text, hc, ht): +def pub.cli.case(bb, world, path, text, ms, hc, ht, hr): match bb: case True{}: e = Equal.sym(Bool, Plan.is_config(path), True{}, hc) @@ -240,7 +204,7 @@ def pub.cli.case(bb, world, path, text, hc, ht): fs => Rules.graded(Config.nearest(Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world)), fs))) == Plan.after(Noqa.of_srcs(Plan.srcs_of(Plan.gots([path], world))), False{}, Plan.grade(Plan.confs.at(Plan.conf.dirs([path]), world), world, - Plan.per_file(Plan.srcs_of(Plan.gots([path], world)))), + Plan.per_file.role(ms, Plan.srcs_of(Plan.gots([path], world)))), fs => Plan.grade(Plan.confs.at(Plan.conf.dirs([path]), world), world, fs)) : List<&2, F.Graded>} t = Equal.sym(Maybe<&2, String>, W.text(world, path), Some{text}, ht) @@ -250,10 +214,10 @@ def pub.cli.case(bb, world, path, text, hc, ht): fs => Rules.graded(Config.nearest(Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world)), fs)) == Plan.after(Noqa.of_srcs(Plan.srcs_of([Plan.Got{path, _}])), False{}, Plan.grade(Plan.confs.at(Plan.conf.dirs([path]), world), world, - Plan.per_file(Plan.srcs_of([Plan.Got{path, _}]))), + Plan.per_file.role(ms, Plan.srcs_of([Plan.Got{path, _}]))), fs => Plan.grade(Plan.confs.at(Plan.conf.dirs([path]), world), world, fs)) : List<&2, F.Graded>} - g = pub.gs(world, path, text) + g = pub.gs(world, path, text, ms, hr) %g : {Plan.after([Noqa.file(path, Lex.tokens(text))], False{}, Rules.graded(Config.nearest(Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world)), Rules.all(path, text)), @@ -278,11 +242,14 @@ def pub.cli.case(bb, world, path, text, hc, ht): Rules.graded(Config.nearest(Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world)), Rules.all(path, text)))) -def Laws.lint_is_cli(world, path, text, h, ht): - n = Equal.sym(List<&2, String>, Plan.inputs(world), Plan.sources([path]), pub.cli.named(world, path, h)) - %n : {Server.pub.lint(path, text, Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world)) - == Plan.after(Noqa.of_srcs(Plan.srcs_of(Plan.gots(_, world))), False{}, - Plan.grade(Plan.confs.at(Plan.conf.dirs(_), world), world, Plan.per_file(Plan.srcs_of(Plan.gots(_, world)))), - fs => Plan.grade(Plan.confs.at(Plan.conf.dirs(_), world), world, fs)) +def Laws.lint_is_cli(world, path, text, h, ht, hr): + q = Equal.sym(List<&2, String>, W.paths.of(world), [path], h) + %q : {Server.pub.lint(path, text, Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world)) + == Plan.after(Noqa.of_srcs(Plan.srcs_of(Plan.gots(Plan.sources(Plan.files.named(_, world)), world))), False{}, + Plan.grade(Plan.confs.at(Plan.conf.dirs(Plan.sources(Plan.files.named(_, world))), world), world, + Plan.per_file.role(Plan.marks.run(_, world, Plan.srcs_of(Plan.gots(Plan.sources(Plan.files.named(_, world)), + world))), Plan.srcs_of(Plan.gots(Plan.sources(Plan.files.named(_, world)), world)))), + fs => Plan.grade(Plan.confs.at(Plan.conf.dirs(Plan.sources(Plan.files.named(_, world))), world), world, fs)) : List<&2, F.Graded>} - pub.cli.case(Plan.is_config(path), world, path, text, {==}, ht) + pub.cli.case(Plan.is_config(path), world, path, text, + Role.marks.facts(Plan.facts.of(Plan.gots(Plan.graph(world), world))), {==}, ht, hr) diff --git a/proofs/lemmas/PlannerPerFileFindings.bend b/proofs/lemmas/PlannerPerFileFindings.bend index bbe9592..be6290f 100644 --- a/proofs/lemmas/PlannerPerFileFindings.bend +++ b/proofs/lemmas/PlannerPerFileFindings.bend @@ -123,33 +123,43 @@ def L3.scope_findings(world): M3.lint.app_nil(F.Finding, Rules.project(Plan.srcs_of(Plan.gots(Plan.inputs(world), world)), Plan.packages(Plan.roots(world))))) %n : {List.append(&2, F.Finding, Plan.unread(Plan.gots(Plan.inputs(world), world)), - List.append(&2, F.Finding, Plan.per_file(Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), + List.append(&2, F.Finding, Plan.per_file.role(Plan.marks.run(W.paths.of(world), world, + Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), + Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), List.append(&2, F.Finding, Plan.settings(world), _))) == D1.in_scope(world) : List<&2, F.Finding>} a = Equal.sym(List<&2, F.Finding>, Plan.unread(Plan.gots(Plan.inputs(world), world)), D1.unread_all(world, Plan.inputs(world)), lint.unread(Plan.inputs(world), world)) %a : {List.append(&2, F.Finding, _, - List.append(&2, F.Finding, Plan.per_file(Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), + List.append(&2, F.Finding, Plan.per_file.role(Plan.marks.run(W.paths.of(world), world, + Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), + Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), List.append(&2, F.Finding, Plan.settings(world), Rules.project(Plan.srcs_of(Plan.gots(Plan.inputs(world), world)), Plan.packages(Plan.roots(world)))))) == D1.in_scope(world) : List<&2, F.Finding>} - b = Equal.sym(List<&2, F.Finding>, Plan.per_file(Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), - D1.on_all(Role.marks(Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), world, Plan.inputs(world)), - lint.on(Role.marks(Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), Plan.inputs(world), world)) + b = Equal.sym(List<&2, F.Finding>, Plan.per_file.role(Plan.marks.run(W.paths.of(world), world, + Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), + Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), + D1.on_all(Plan.marks.run(W.paths.of(world), world, Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), + world, Plan.inputs(world)), + lint.on(Plan.marks.run(W.paths.of(world), world, Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), + Plan.inputs(world), world)) %b : {List.append(&2, F.Finding, D1.unread_all(world, Plan.inputs(world)), List.append(&2, F.Finding, _, List.append(&2, F.Finding, Plan.settings(world), Rules.project(Plan.srcs_of(Plan.gots(Plan.inputs(world), world)), Plan.packages(Plan.roots(world)))))) == D1.in_scope(world) : List<&2, F.Finding>} t = Equal.sym(List<&2, F.Finding>, Plan.settings(world), D1.settings_found(world), L4.setting_reports(world)) %t : {List.append(&2, F.Finding, D1.unread_all(world, Plan.inputs(world)), - List.append(&2, F.Finding, D1.on_all(Role.marks(Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), world, + List.append(&2, F.Finding, D1.on_all(Plan.marks.run(W.paths.of(world), world, + Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), world, Plan.inputs(world)), List.append(&2, F.Finding, _, Rules.project(Plan.srcs_of(Plan.gots(Plan.inputs(world), world)), Plan.packages(Plan.roots(world)))))) == D1.in_scope(world) : List<&2, F.Finding>} c = Equal.sym(List<&2, Src.Src>, Plan.srcs_of(Plan.gots(Plan.inputs(world), world)), D1.parsed_all(world, Plan.inputs(world)), lint.parsed(Plan.inputs(world), world)) %c : {List.append(&2, F.Finding, D1.unread_all(world, Plan.inputs(world)), - List.append(&2, F.Finding, D1.on_all(Role.marks(Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), world, + List.append(&2, F.Finding, D1.on_all(Plan.marks.run(W.paths.of(world), world, + Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), world, Plan.inputs(world)), List.append(&2, F.Finding, D1.settings_found(world), Rules.project(_, Plan.packages(Plan.roots(world)))))) @@ -157,7 +167,8 @@ def L3.scope_findings(world): k = Equal.sym(List<&2, String>, Plan.packages(Plan.roots(world)), D1.pkgs(Plan.roots(world)), L1.package_dirs(Plan.roots(world))) %k : {List.append(&2, F.Finding, D1.unread_all(world, Plan.inputs(world)), - List.append(&2, F.Finding, D1.on_all(Role.marks(Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), world, + List.append(&2, F.Finding, D1.on_all(Plan.marks.run(W.paths.of(world), world, + Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), world, Plan.inputs(world)), List.append(&2, F.Finding, D1.settings_found(world), Rules.project(D1.parsed_all(world, Plan.inputs(world)), _)))) @@ -167,13 +178,15 @@ def L3.scope_findings(world): D2.packed(D1.parsed_all(world, Plan.inputs(world)), D1.pkgs(Plan.roots(world))), L2.packages_scoped(D1.parsed_all(world, Plan.inputs(world)), D1.pkgs(Plan.roots(world)))) %q : {List.append(&2, F.Finding, D1.unread_all(world, Plan.inputs(world)), - List.append(&2, F.Finding, D1.on_all(Role.marks(Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), world, + List.append(&2, F.Finding, D1.on_all(Plan.marks.run(W.paths.of(world), world, + Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), world, Plan.inputs(world)), List.append(&2, F.Finding, D1.settings_found(world), _))) == D1.in_scope(world) : List<&2, F.Finding>} z = M3.lint.app_nil(F.Finding, D2.packed(D1.parsed_all(world, Plan.inputs(world)), D1.pkgs(Plan.roots(world)))) %z : {List.append(&2, F.Finding, D1.unread_all(world, Plan.inputs(world)), - List.append(&2, F.Finding, D1.on_all(Role.marks(Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), world, + List.append(&2, F.Finding, D1.on_all(Plan.marks.run(W.paths.of(world), world, + Plan.srcs_of(Plan.gots(Plan.inputs(world), world))), world, Plan.inputs(world)), List.append(&2, F.Finding, D1.settings_found(world), _))) == D1.in_scope(world) : List<&2, F.Finding>} diff --git a/src/README.md b/src/README.md index 63be254..59cffb4 100644 --- a/src/README.md +++ b/src/README.md @@ -165,6 +165,14 @@ reaches, is code, even when a law imports it to state laws about it. So a `LAWS.bend` of nothing but `import ./laws/.bend` lines makes those files law files, and a proof may state lemma laws of its own. +The walk is over the whole tree under the working directory, even over files +named on the line: bolt reads every `.bend` file there for its imports +(lexed, never parsed) and lints only the files named. So a file named beside +a `LAWS.bend` that imports it is still code when a `main.bend` the run does +not name imports it, and a file under `laws/` named without its `LAWS.bend` +is still a law file. The editor reads the open file alone, so there a file +has only the role its name gives it. + A rule is handed a [`Src`](src.bend), not the text: the path, the text, and the file's tokens, tree, binder and outline, each read once for the whole set. A tree parse of a 156 KB file costs 1.5 s here, so twenty rules that diff --git a/src/lint/plan.bend b/src/lint/plan.bend index f31b9dc..de2fbaa 100644 --- a/src/lint/plan.bend +++ b/src/lint/plan.bend @@ -166,6 +166,13 @@ def sources(ps: List<&2, String>) -> List<&2, String>: def inputs(+world: W.World) -> List<&2, String>: sources(files(world)) +# the files a run reads its roles from: its sources, then every source the +# walk of the tree finds. A file's role follows the imports, and the file +# that makes a named file code, a package's main.bend that imports it, may +# be named or not +def graph(+world: W.World) -> List<&2, String>: + List.append(&2, String, inputs(world), sources(walk.found(tree(world)))) + # --------------------------------------------------------------------------- # what the plan reads @@ -266,24 +273,17 @@ def asks.texts(ps: List<&2, String>) -> List<&2, W.Ask>: case Con{p, rest}: W.Contents{p} <> asks.texts(rest) -# the directories the walk lists: every one it reads, none when files were -# named -def lists.named(paths: List<&2, String>, +world: W.World) -> List<&2, W.Ask>: - match paths: - case Nil{}: - asks.entries(walk.read(tree(world))) - case Con{_p, _rest}: - Nil{} - -# the listings the plan reads +# the listings the plan reads: every directory the walk of the tree reads, +# whether files were named or not, since a named file's role is read from the +# imports of the whole tree (graph) def lists(+world: W.World) -> List<&2, W.Ask>: - lists.named(W.paths.of(world), world) + asks.entries(walk.read(tree(world))) -# the texts the plan reads: each source and every bolt.bend candidate of their -# directories, each once +# the texts the plan reads: each source, every file of the graph, and every +# bolt.bend candidate of the sources' directories, each once def texts(+world: W.World) -> List<&2, W.Ask>: +ps = inputs(world) - asks.texts(uniq(List.concat(&2, String, [ps, conf.cands(conf.dirs(ps), world)]), + asks.texts(uniq(List.concat(&2, String, [ps, graph(world), conf.cands(conf.dirs(ps), world)]), [])) # every question the plan reads the World through: the listings, then the @@ -359,10 +359,26 @@ def per_file.role(+ms: List<&2, Role.Mark>, ss: List<&2, Src.Src>) -> List<&2, F List.concat(&2, F.Finding, [Rules.on.role(Role.of(ms, path), Src.Src{path, text, toks, tree, bound, items}), per_file.role(ms, rest)]) -# every source's findings from the per-file rules: a file a LAWS.bend or a -# PROOF.bend imports is a law or proof file to them as those are -def per_file(+ss: List<&2, Src.Src>) -> List<&2, F.Finding>: - per_file.role(Role.marks(ss), ss) +# the facts (src/role.bend) of each file the World read, from its text +def facts.of(gs: List<&2, Got>) -> List<&2, Role.Fact>: + match gs: + case Nil{}: + Nil{} + case Con{Got{_path, None{}}, rest}: + facts.of(rest) + case Con{Got{path, Some{text}}, rest}: + Role.fact.text(path, text) <> facts.of(rest) + +# the marks a run's roles are read from: over the whole tree, the sources' +# own; over files named on the line, the graph's, every file of the tree +# read for its imports, so the main.bend that imports a named file makes it +# code even when it is not named +def marks.run(paths: List<&2, String>, +world: W.World, +ss: List<&2, Src.Src>) -> List<&2, Role.Mark>: + match paths: + case Nil{}: + Role.marks(ss) + case Con{_p, _rest}: + Role.marks.facts(facts.of(gots(graph(world), world))) # the path of the first candidate the World holds a text for, as a list of # one; none when it holds none @@ -409,7 +425,7 @@ def settings(+world: W.World) -> List<&2, F.Finding>: # then `setting`'s over each bolt.bend grading reads, then the project rules' # over every source and the other packages def found.at(+world: W.World, +gs: List<&2, Got>, +ss: List<&2, Src.Src>) -> List<&2, F.Finding>: - List.concat(&2, F.Finding, [unread(gs), per_file(ss), settings(world), + List.concat(&2, F.Finding, [unread(gs), per_file.role(marks.run(W.paths.of(world), world, ss), ss), settings(world), Rules.project(ss, packages(roots(world)))]) # every finding of every rule, ungraded diff --git a/src/role.bend b/src/role.bend index 549f61d..21cc447 100644 --- a/src/role.bend +++ b/src/role.bend @@ -14,6 +14,8 @@ import ./paths.bend as Paths import ./src.bend as Src import ./lazy/lazy.bend as Lazy import ./rules/imports.bend as Imports +import ./syntax/lex.bend as Lex +import ./syntax/outline.bend as Outline import ./finding.bend as F # what a file is to the laws @@ -74,6 +76,13 @@ def fact(ss: Src.Src) -> Fact: +nn = Imports.norm(path) Fact{nn, Imports.targets(items, nn), of_path(path), String.eq(Paths.base(path), "main.bend")} +# a file's fact read from its text alone: lexed and outlined for its imports, +# never parsed, so a file read only for its place in the graph costs little +def fact.text(+path: String, +text: String) -> Fact: + +nn = Imports.norm(path) + Fact{nn, Imports.targets(Outline.of_tokens(text, Lex.tokens(text)), nn), of_path(path), + String.eq(Paths.base(path), "main.bend")} + # every file's fact, in order def facts(ss: List<&2, Src.Src>) -> List<&2, Fact>: match ss: @@ -157,13 +166,17 @@ def marks.of( Bool.pick(List<&2, Mark>, Bool.and(Imports.has(laws, norm), Bool.not(Imports.has(code, norm))), Mark{norm, role.of(named, reached(Imports.has(lawful, norm)))} <> more, more) +# the role of every file on the laws' side of a graph given as facts +def marks.facts(+fs: List<&2, Fact>) -> List<&2, Mark>: + +ls = reach(fs, roots(fs)) + marks.of(fs, ls, reach(fs, lawful(fs)), reach(fs, code(fs, ls))) + # the role of every file on the laws' side. Both walks are the one every # project rule takes over imports (src/rules/imports.bend), each from all its # roots at once def marks(ss: List<&2, Src.Src>) -> List<&2, Mark>: - +fs = facts(ss) - +ls = reach(fs, roots(fs)) - marks.of(fs, ls, reach(fs, lawful(fs)), reach(fs, code(fs, ls))) + marks.facts(facts(ss)) + # a normalized path's role among the marks: neither when it is not marked def of.norm(ms: List<&2, Mark>, +nn: String) -> Role: From 9fcc10bc2476b4f500ec5844092948924437c96b Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 1 Nov 2026 15:46:30 +0000 Subject: [PATCH 5/7] fix(plan): read each file of the role graph once The graph a named run reads its roles from was the run's sources, then every source the tree's walk finds, so a named file under the working directory was in it twice (`a.bend` and `./a.bend`) and read twice. It is now the walk's sources, then each source of the run whose normalized path the walk did not find: one named outside the working directory. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_0186KEnw5sg7heUQZTJJU89N --- src/README.md | 4 +++- src/lint/plan.bend | 29 ++++++++++++++++++++++++----- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/src/README.md b/src/README.md index 59cffb4..77ac124 100644 --- a/src/README.md +++ b/src/README.md @@ -167,7 +167,9 @@ files law files, and a proof may state lemma laws of its own. The walk is over the whole tree under the working directory, even over files named on the line: bolt reads every `.bend` file there for its imports -(lexed, never parsed) and lints only the files named. So a file named beside +(lexed, never parsed), and each named file the walk does not find (one +outside the working directory), each file once, and lints only the files +named. So a file named beside a `LAWS.bend` that imports it is still code when a `main.bend` the run does not name imports it, and a file under `laws/` named without its `LAWS.bend` is still a law file. The editor reads the open file alone, so there a file diff --git a/src/lint/plan.bend b/src/lint/plan.bend index de2fbaa..2a4af8d 100644 --- a/src/lint/plan.bend +++ b/src/lint/plan.bend @@ -166,12 +166,31 @@ def sources(ps: List<&2, String>) -> List<&2, String>: def inputs(+world: W.World) -> List<&2, String>: sources(files(world)) -# the files a run reads its roles from: its sources, then every source the -# walk of the tree finds. A file's role follows the imports, and the file -# that makes a named file code, a package's main.bend that imports it, may -# be named or not +# each path normalized (src/rules/imports.bend) +def norms(ps: List<&2, String>) -> List<&2, String>: + match ps: + case Nil{}: + Nil{} + case Con{p, rest}: + Imports.norm(p) <> norms(rest) + +# the paths whose normalized form is not among the normalized paths seen +def unseen(ps: List<&2, String>, +seen: List<&2, String>) -> List<&2, String>: + match ps: + case Nil{}: + Nil{} + case Con{+p, rest}: + +more = unseen(rest, seen) + Bool.pick(List<&2, String>, Imports.has(seen, Imports.norm(p)), more, p <> more) + +# the files a run reads its roles from: every source the walk of the tree +# finds, then each source of the run the walk does not find (one named +# outside the working directory), so each file is read once. A file's role +# follows the imports, and the file that makes a named file code, a +# package's main.bend that imports it, may be named or not def graph(+world: W.World) -> List<&2, String>: - List.append(&2, String, inputs(world), sources(walk.found(tree(world)))) + +found = sources(walk.found(tree(world))) + List.append(&2, String, found, unseen(inputs(world), norms(found))) # --------------------------------------------------------------------------- # what the plan reads From 25ced75620873f609c59f279db75b9a52515e89f Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 1 Nov 2026 16:02:52 +0000 Subject: [PATCH 6/7] feat(lsp): the editor reads roles from the tree's imports, as the CLI does `bolt ` takes a file's role from the imports of the whole tree, but the editor linted the one open file by its name alone, so a file a LAWS.bend reaches was a law file to the CLI and code in the editor, and laws/LspDiagnosticsMatchCli.bend had to assume the two agreed. On open and save, beside the checker's run, the server now gathers the World `bolt ` reads (src/lint/plan.bend's questions answered through the Files service, which can now list a directory) and reads the role marks from it (Plan.marks.named); the document keeps them, and each change is linted with the marks of the last open or save. The editor law now states the editor's lint, told those marks, is the CLI's, with no assumption on the file's role. laws/LspKeepsRoleMarks.bend states that open and save keep the marks they read and a change keeps them as they were. The graph is now walked over resolved paths, so a file named by an absolute path (as the editor names it) or as `./a.bend` is matched to its file in the tree; `bolt /w/laws/x.bend` from /w took it for code before. src/lsp/tests/checker.bend runs native only: each open now reads every file of the tree, and bolt's proofs are past the JS lane's stack. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_0186KEnw5sg7heUQZTJJU89N --- LAWS.bend | 1 + PROOF.bend | 1 + laws/LspDiagnosticsMatchCli.bend | 19 ++-- laws/LspKeepsRoleMarks.bend | 48 +++++++++ laws/LspPublishesOnDocumentEvents.bend | 31 +++--- proofs/LspDiagnosticsMatchCli.bend | 61 ++++++------ proofs/LspKeepsRoleMarks.bend | 33 +++++++ proofs/LspPublishesOnDocumentEvents.bend | 48 +++++---- src/README.md | 9 +- src/lint/plan.bend | 51 +++++++--- src/lsp/docs.bend | 37 ++++--- src/lsp/files/disk.bend | 6 +- src/lsp/files/service.bend | 22 +++-- src/lsp/server.bend | 121 ++++++++++++++++++++--- src/lsp/tests/checker.bend | 4 + src/rules.bend | 13 --- 16 files changed, 368 insertions(+), 137 deletions(-) create mode 100644 laws/LspKeepsRoleMarks.bend create mode 100644 proofs/LspKeepsRoleMarks.bend diff --git a/LAWS.bend b/LAWS.bend index 21ccfe8..94fb8d5 100644 --- a/LAWS.bend +++ b/LAWS.bend @@ -129,6 +129,7 @@ import ./laws/LspFramesCountBytes.bend as LspFramesCountBytes import ./laws/LspNavigationUsesBinder.bend as LspNavigationUsesBinder import ./laws/LspPositionsUseNegotiatedEncoding.bend as LspPositionsUseNegotiatedEncoding import ./laws/LspPublishesOnDocumentEvents.bend as LspPublishesOnDocumentEvents +import ./laws/LspKeepsRoleMarks.bend as LspKeepsRoleMarks import ./laws/LspUnitsMatchCharsBelowAstral.bend as LspUnitsMatchCharsBelowAstral import ./laws/ArrowTerm.bend as ArrowTerm import ./laws/AttributeDef.bend as AttributeDef diff --git a/PROOF.bend b/PROOF.bend index 881c2d4..5a1364c 100644 --- a/PROOF.bend +++ b/PROOF.bend @@ -114,6 +114,7 @@ import ./proofs/LspFramesCountBytes.bend as PLspFramesCountBytes import ./proofs/LspNavigationUsesBinder.bend as PLspNavigationUsesBinder import ./proofs/LspPositionsUseNegotiatedEncoding.bend as PLspPositionsUseNegotiatedEncoding import ./proofs/LspPublishesOnDocumentEvents.bend as PLspPublishesOnDocumentEvents +import ./proofs/LspKeepsRoleMarks.bend as PLspKeepsRoleMarks import ./proofs/LspUnitsMatchCharsBelowAstral.bend as PLspUnitsMatchCharsBelowAstral import ./proofs/ArrowTerm.bend as PArrowTerm import ./proofs/AttributeDef.bend as PAttributeDef diff --git a/laws/LspDiagnosticsMatchCli.bend b/laws/LspDiagnosticsMatchCli.bend index 55c4928..73f7287 100644 --- a/laws/LspDiagnosticsMatchCli.bend +++ b/laws/LspDiagnosticsMatchCli.bend @@ -1,16 +1,15 @@ # Diagnostics for an open document equal the CLI's per-file findings for that # file and text under the same bolt.bend, less what its noqa comments silence # (laws/NoqaSilencesNamedFindings.bend), then `noqa`'s findings on them, which judge no project rule's -# code, for a file whose role the tree's imports leave at its name's. The -# editor reads one file and not the imports of the tree around it, so a file -# a LAWS.bend reaches, a law file to `bolt `, is linted as code there. +# code. The editor is told the roles the imports of the tree give the files, +# read as `bolt ` reads them, so a file a LAWS.bend reaches is a law +# file in both. import Base import ../src/lsp/server.bend as Server import ../src/finding.bend as F import ../src/lint/world.bend as W import ../src/lint/plan.bend as Plan import ../src/noqa.bend as Noqa -import ../src/role.bend as Role # the CLI's per-file findings in a run, graded the CLI's way: each source's # per-file rules, each finding at the level the bolt.bend nearest its path @@ -31,16 +30,14 @@ def pub.cli(+world: W.World) -> List<&2, F.Graded>: # directory, are exactly what `bolt ` grades as that file's per-file # findings, the same noqa comments silencing the same findings and `noqa` # reporting what the CLI reports but for a project rule's code, which the -# editor cannot judge (none for a bolt.bend, which neither lints), whenever -# the imports of the tree (the graph `bolt ` reads its roles from) give -# the document no role but the one its name gives it +# editor cannot judge (none for a bolt.bend, which neither lints), the editor +# told the role marks the imports of the tree give the files, as `bolt +# ` reads them from that same file system and directory law lint_is_cli: for +world: W.World for +path: String for +text: String for h: {W.paths.of(world) == [path] : List<&2, String>} for ht: {W.text(world, path) == Some{text} : Maybe<&2, String>} - for hr: {Role.of(Role.marks.facts(Plan.facts.of(Plan.gots(Plan.graph(world), world))), path) == Role.of_path(path) - : Role.Role} - {Server.pub.lint(path, text, Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world)) == pub.cli(world) - : List<&2, F.Graded>} + {Server.pub.lint(path, text, Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world), Server.pub.marks(world)) + == pub.cli(world) : List<&2, F.Graded>} diff --git a/laws/LspKeepsRoleMarks.bend b/laws/LspKeepsRoleMarks.bend new file mode 100644 index 0000000..faa2514 --- /dev/null +++ b/laws/LspKeepsRoleMarks.bend @@ -0,0 +1,48 @@ +# The editor reads the roles the imports of the tree give the files (src/role.bend's +# marks, as `bolt ` reads them) on open and save, the checker's two +# runs, and keeps them with the document: after open or save the documents +# hold the marks read then, and a change keeps the marks as they were, so +# every change is linted with the roles of the last open or save +# (laws/LspPublishesOnDocumentEvents.bend says what each publishes). +import Base +import ../src/lsp/server.bend as Server +import ../src/lsp/docs.bend as Docs +import ../src/lsp/report.bend as Rep +import ../src/lsp/enc.bend as Enc +import ../src/role.bend as Role + +# the open documents after a method's work +def keep.docs(ww: Server.Work) -> List<&2, Docs.Doc>: + Server.Work{_notes, _answer, docs} = ww + docs + +# LAW: after open the documents hold the marks read with it +law open_keeps_marks: + for +ee: Enc.Enc + for +uri: String + for +text: String + for +ds: List<&2, Rep.Diag> + for +rs: List<&2, Maybe<&2, String>> + for +ms: List<&2, Role.Mark> + for +docs: List<&2, Docs.Doc> + {Docs.marks(keep.docs(Server.pub.open(ee, uri, text, ds, rs, ms, docs)), uri) == ms : List<&2, Role.Mark>} + +# LAW: after save the documents hold the marks read with it +law save_keeps_marks: + for +ee: Enc.Enc + for +uri: String + for +ds: List<&2, Rep.Diag> + for +rs: List<&2, Maybe<&2, String>> + for +ms: List<&2, Role.Mark> + for +docs: List<&2, Docs.Doc> + {Docs.marks(keep.docs(Server.pub.save(ee, uri, ds, rs, ms, docs)), uri) == ms : List<&2, Role.Mark>} + +# LAW: a change keeps the marks as they were +law change_keeps_marks: + for +ee: Enc.Enc + for +uri: String + for +text: String + for +rs: List<&2, Maybe<&2, String>> + for +docs: List<&2, Docs.Doc> + {Docs.marks(keep.docs(Server.pub.change(ee, uri, text, rs, docs)), uri) == Docs.marks(docs, uri) + : List<&2, Role.Mark>} diff --git a/laws/LspPublishesOnDocumentEvents.bend b/laws/LspPublishesOnDocumentEvents.bend index 4f7b7c5..da34110 100644 --- a/laws/LspPublishesOnDocumentEvents.bend +++ b/laws/LspPublishesOnDocumentEvents.bend @@ -1,5 +1,6 @@ -# Open and save publish checker plus lint; change publishes lint plus the last -# checker result; close publishes an empty list; an open bolt.bend gets no +# Open and save publish checker plus lint, each file told its role among the +# marks read with the checker's run; change publishes lint, told the marks +# kept, plus the last checker result; close publishes an empty list; an open bolt.bend gets no # lint. import Base import ../src/lsp/server.bend as Server @@ -9,6 +10,7 @@ import ../src/lsp/proto.bend as Proto import ../src/finding.bend as F import ../src/lint/plan.bend as Plan import ../src/lsp/enc.bend as Enc +import ../src/role.bend as Role # publishing # ---------- @@ -30,9 +32,11 @@ law open_publishes: for +text: String for +ds: List<&2, Rep.Diag> for +rs: List<&2, Maybe<&2, String>> + for +ms: List<&2, Role.Mark> for +docs: List<&2, Docs.Doc> - {pub.notes(Server.pub.open(ee, uri, text, ds, rs, docs)) - == [Server.pub.note(Enc.cols(ee, text), uri, ds, Server.pub.lint(Proto.path(uri), text, rs))] : List<&2, String>} + {pub.notes(Server.pub.open(ee, uri, text, ds, rs, ms, docs)) + == [Server.pub.note(Enc.cols(ee, text), uri, ds, Server.pub.lint(Proto.path(uri), text, rs, ms))] + : List<&2, String>} # LAW: save publishes the checker's new errors plus the lint of the text the # editor shows @@ -41,10 +45,12 @@ law save_publishes: for +uri: String for +ds: List<&2, Rep.Diag> for +rs: List<&2, Maybe<&2, String>> + for +ms: List<&2, Role.Mark> for +docs: List<&2, Docs.Doc> - {pub.notes(Server.pub.save(ee, uri, ds, rs, docs)) + {pub.notes(Server.pub.save(ee, uri, ds, rs, ms, docs)) == [Server.pub.note(Enc.cols(ee, Maybe.default(&2, String, Docs.get(docs, uri), "")), uri, ds, - Server.pub.lint(Proto.path(uri), Maybe.default(&2, String, Docs.get(docs, uri), ""), rs))] : List<&2, String>} + Server.pub.lint(Proto.path(uri), Maybe.default(&2, String, Docs.get(docs, uri), ""), rs, ms))] + : List<&2, String>} # LAW: change publishes the lint of the new text plus the checker's errors # the documents hold for it @@ -56,7 +62,7 @@ law change_publishes: for +docs: List<&2, Docs.Doc> {pub.notes(Server.pub.change(ee, uri, text, rs, docs)) == [Server.pub.note(Enc.cols(ee, text), uri, Docs.errors(docs, uri), - Server.pub.lint(Proto.path(uri), text, rs))] : List<&2, String>} + Server.pub.lint(Proto.path(uri), text, rs, Docs.marks(docs, uri)))] : List<&2, String>} # LAW: after open the documents hold the errors it published and the text # opened @@ -66,9 +72,10 @@ law open_keeps: for +text: String for +ds: List<&2, Rep.Diag> for +rs: List<&2, Maybe<&2, String>> + for +ms: List<&2, Role.Mark> for +docs: List<&2, Docs.Doc> - {Docs.errors(pub.docs(Server.pub.open(ee, uri, text, ds, rs, docs)), uri) == ds : List<&2, Rep.Diag>} - & {Docs.get(pub.docs(Server.pub.open(ee, uri, text, ds, rs, docs)), uri) == Some{text} : Maybe<&2, String>} + {Docs.errors(pub.docs(Server.pub.open(ee, uri, text, ds, rs, ms, docs)), uri) == ds : List<&2, Rep.Diag>} + & {Docs.get(pub.docs(Server.pub.open(ee, uri, text, ds, rs, ms, docs)), uri) == Some{text} : Maybe<&2, String>} # LAW: after save the documents hold the errors it published law save_keeps: @@ -76,8 +83,9 @@ law save_keeps: for +uri: String for +ds: List<&2, Rep.Diag> for +rs: List<&2, Maybe<&2, String>> + for +ms: List<&2, Role.Mark> for +docs: List<&2, Docs.Doc> - {Docs.errors(pub.docs(Server.pub.save(ee, uri, ds, rs, docs)), uri) == ds : List<&2, Rep.Diag>} + {Docs.errors(pub.docs(Server.pub.save(ee, uri, ds, rs, ms, docs)), uri) == ds : List<&2, Rep.Diag>} # LAW: change keeps the checker's errors as they were, so every change # publishes the last checker result, and keeps the new text @@ -105,4 +113,5 @@ law config_unlinted: for h: {Plan.is_config(path) == True{} : Bool} for text: String for rs: List<&2, Maybe<&2, String>> - {Server.pub.lint(path, text, rs) == [] : List<&2, F.Graded>} + for ms: List<&2, Role.Mark> + {Server.pub.lint(path, text, rs, ms) == [] : List<&2, F.Graded>} diff --git a/proofs/LspDiagnosticsMatchCli.bend b/proofs/LspDiagnosticsMatchCli.bend index 8a87a05..9ccdc4a 100644 --- a/proofs/LspDiagnosticsMatchCli.bend +++ b/proofs/LspDiagnosticsMatchCli.bend @@ -112,31 +112,28 @@ law pub.gs: for +world: W.World for +path: String for +text: String - for -ms: List<&2, Role.Mark> - for hr: {Role.of(ms, path) == Role.of_path(path) : Role.Role} - {Rules.graded(Config.nearest(Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world)), Rules.all(path, text)) + for +ms: List<&2, Role.Mark> + {Rules.graded(Config.nearest(Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world)), + Rules.on.role(Role.of(ms, path), Src.of(path, text))) == Plan.grade(Plan.confs.at(Plan.conf.dirs([path]), world), world, Plan.per_file.role(ms, Plan.srcs_of([Plan.Got{path, Some{text}}]))) : List<&2, F.Graded>} -def pub.gs(world, path, text, ms, hr): - +s = Src.of(path, text) - r = Equal.cong(Role.Role, List<&2, F.Finding>, z => List.append(&2, F.Finding, Rules.on.role(z, s), []), - Role.of(ms, path), Role.of_path(path), hr) - a = Equal.sym(List<&2, F.Finding>, Plan.per_file.role(ms, Plan.srcs_of([Plan.Got{path, Some{text}}])), - Rules.all(path, text), - Equal.trans(List<&2, F.Finding>, Plan.per_file.role(ms, Plan.srcs_of([Plan.Got{path, Some{text}}])), - List.append(&2, F.Finding, Rules.all(path, text), []), Rules.all(path, text), r, - pub.app_nil(F.Finding, Rules.all(path, text)))) +def pub.gs(world, path, text, ms): + a = Equal.sym(List<&2, F.Finding>, List.append(&2, F.Finding, Rules.on.role(Role.of(ms, path), Src.of(path, + text)), []), + Rules.on.role(Role.of(ms, path), Src.of(path, text)), + pub.app_nil(F.Finding, Rules.on.role(Role.of(ms, path), Src.of(path, text)))) %a : {Rules.graded(Config.nearest(Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world)), - Rules.all(path, text)) + Rules.on.role(Role.of(ms, path), Src.of(path, text))) == Plan.grade(Plan.confs.at(Plan.conf.dirs([path]), world), world, _) : List<&2, F.Graded>} Equal.sym(List<&2, F.Graded>, - Plan.grade(Plan.confs.at(Plan.conf.dirs([path]), world), world, Rules.all(path, text)), + Plan.grade(Plan.confs.at(Plan.conf.dirs([path]), world), world, Rules.on.role(Role.of(ms, path), + Src.of(path, text))), Rules.graded(Config.nearest(Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world)), - Rules.all(path, text)), - pub.grade_near(world, path, Rules.on.rules(Src.of(path, text)))) + Rules.on.role(Role.of(ms, path), Src.of(path, text))), + pub.grade_near(world, path, Rules.on.rules.role(Role.of(ms, path), Src.of(path, text)))) # the `noqa` findings of the LSP's lint, graded by the config nearest the # path, are graded alike by the CLI @@ -173,25 +170,24 @@ law pub.cli.case: for +world: W.World for +path: String for +text: String - for -ms: List<&2, Role.Mark> + for +ms: List<&2, Role.Mark> for hc: {Plan.is_config(path) == bb : Bool} for ht: {W.text(world, path) == Some{text} : Maybe<&2, String>} - for hr: {Role.of(ms, path) == Role.of_path(path) : Role.Role} - {Server.pub.lint(path, text, Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world)) + {Server.pub.lint(path, text, Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world), ms) == Plan.after(Noqa.of_srcs(Plan.srcs_of(Plan.gots(Bool.pick(List<&2, String>, bb, [], [path]), world))), False{}, Plan.grade(Plan.confs.at(Plan.conf.dirs(Bool.pick(List<&2, String>, bb, [], [path])), world), world, Plan.per_file.role(ms, Plan.srcs_of(Plan.gots(Bool.pick(List<&2, String>, bb, [], [path]), world)))), fs => Plan.grade(Plan.confs.at(Plan.conf.dirs(Bool.pick(List<&2, String>, bb, [], [path])), world), world, fs)) : List<&2, F.Graded>} -def pub.cli.case(bb, world, path, text, ms, hc, ht, hr): +def pub.cli.case(bb, world, path, text, ms, hc, ht): match bb: case True{}: e = Equal.sym(Bool, Plan.is_config(path), True{}, hc) %e : {Bool.pick(List<&2, F.Graded>, _, [], Plan.after([Noqa.file(path, Lex.tokens(text))], False{}, Rules.graded(Config.nearest(Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world)), - Rules.all(path, text)), + Rules.on.role(Role.of(ms, path), Src.of(path, text))), fs => Rules.graded(Config.nearest(Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world)), fs))) == [] : List<&2, F.Graded>} {==} @@ -200,7 +196,7 @@ def pub.cli.case(bb, world, path, text, ms, hc, ht, hr): %e : {Bool.pick(List<&2, F.Graded>, _, [], Plan.after([Noqa.file(path, Lex.tokens(text))], False{}, Rules.graded(Config.nearest(Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world)), - Rules.all(path, text)), + Rules.on.role(Role.of(ms, path), Src.of(path, text))), fs => Rules.graded(Config.nearest(Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world)), fs))) == Plan.after(Noqa.of_srcs(Plan.srcs_of(Plan.gots([path], world))), False{}, Plan.grade(Plan.confs.at(Plan.conf.dirs([path]), world), world, @@ -210,17 +206,17 @@ def pub.cli.case(bb, world, path, text, ms, hc, ht, hr): t = Equal.sym(Maybe<&2, String>, W.text(world, path), Some{text}, ht) %t : {Plan.after([Noqa.file(path, Lex.tokens(text))], False{}, Rules.graded(Config.nearest(Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world)), - Rules.all(path, text)), + Rules.on.role(Role.of(ms, path), Src.of(path, text))), fs => Rules.graded(Config.nearest(Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world)), fs)) == Plan.after(Noqa.of_srcs(Plan.srcs_of([Plan.Got{path, _}])), False{}, Plan.grade(Plan.confs.at(Plan.conf.dirs([path]), world), world, Plan.per_file.role(ms, Plan.srcs_of([Plan.Got{path, _}]))), fs => Plan.grade(Plan.confs.at(Plan.conf.dirs([path]), world), world, fs)) : List<&2, F.Graded>} - g = pub.gs(world, path, text, ms, hr) + g = pub.gs(world, path, text, ms) %g : {Plan.after([Noqa.file(path, Lex.tokens(text))], False{}, Rules.graded(Config.nearest(Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world)), - Rules.all(path, text)), + Rules.on.role(Role.of(ms, path), Src.of(path, text))), fs => Rules.graded(Config.nearest(Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world)), fs)) == Plan.after([Noqa.file(path, Lex.tokens(text))], False{}, _, fs => Plan.grade(Plan.confs.at(Plan.conf.dirs([path]), world), world, fs)) @@ -229,22 +225,23 @@ def pub.cli.case(bb, world, path, text, ms, hc, ht, hr): rr => List.append(&2, F.Graded, Noqa.keep([Noqa.file(path, Lex.tokens(text))], Rules.graded(Config.nearest(Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world)), - Rules.all(path, text))), rr), + Rules.on.role(Role.of(ms, path), Src.of(path, text)))), rr), Rules.graded(Config.nearest(Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world)), Plan.noqa([Noqa.file(path, Lex.tokens(text))], False{}, Rules.graded(Config.nearest(Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world)), - Rules.all(path, text)))), + Rules.on.role(Role.of(ms, path), Src.of(path, text))))), Plan.grade(Plan.confs.at(Plan.conf.dirs([path]), world), world, Plan.noqa([Noqa.file(path, Lex.tokens(text))], False{}, Rules.graded(Config.nearest(Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world)), - Rules.all(path, text)))), + Rules.on.role(Role.of(ms, path), Src.of(path, text))))), pub.noqa_near(world, path, text, Rules.graded(Config.nearest(Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world)), - Rules.all(path, text)))) + Rules.on.role(Role.of(ms, path), Src.of(path, text))))) -def Laws.lint_is_cli(world, path, text, h, ht, hr): +def Laws.lint_is_cli(world, path, text, h, ht): q = Equal.sym(List<&2, String>, W.paths.of(world), [path], h) - %q : {Server.pub.lint(path, text, Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world)) + %q : {Server.pub.lint(path, text, Plan.texts.at(Server.pub.cands(W.cwd.of(world), path), world), + Server.pub.marks(world)) == Plan.after(Noqa.of_srcs(Plan.srcs_of(Plan.gots(Plan.sources(Plan.files.named(_, world)), world))), False{}, Plan.grade(Plan.confs.at(Plan.conf.dirs(Plan.sources(Plan.files.named(_, world))), world), world, Plan.per_file.role(Plan.marks.run(_, world, Plan.srcs_of(Plan.gots(Plan.sources(Plan.files.named(_, world)), @@ -252,4 +249,4 @@ def Laws.lint_is_cli(world, path, text, h, ht, hr): fs => Plan.grade(Plan.confs.at(Plan.conf.dirs(Plan.sources(Plan.files.named(_, world))), world), world, fs)) : List<&2, F.Graded>} pub.cli.case(Plan.is_config(path), world, path, text, - Role.marks.facts(Plan.facts.of(Plan.gots(Plan.graph(world), world))), {==}, ht, hr) + Server.pub.marks(world), {==}, ht) diff --git a/proofs/LspKeepsRoleMarks.bend b/proofs/LspKeepsRoleMarks.bend new file mode 100644 index 0000000..975abd0 --- /dev/null +++ b/proofs/LspKeepsRoleMarks.bend @@ -0,0 +1,33 @@ +# proofs of laws/LspKeepsRoleMarks.bend +import Base +import ../src/lazy/lazy.bend as Lazy +import ../src/lsp/docs.bend as Docs +import ../src/lsp/report.bend as Rep +import ../src/role.bend as Role +import ../laws/LspKeepsRoleMarks.bend as Laws +import ./lemmas/PublishComparisons.bend as M1 + +# the document just put at the head of the list is the one a lookup of its +# URI finds: its marks +law keep.marks_head: + for +uri: String + for -text: String + for -ds: List<&2, Rep.Diag> + for -ms: List<&2, Role.Mark> + for -rest: List<&2, Docs.Doc> + {Docs.marks(Docs.Doc{uri, text, ds, ms} <> rest, uri) == ms : List<&2, Role.Mark>} + +def keep.marks_head(uri, _text, _ds, ms, rest): + e = Equal.sym(Bool, String.eq(uri, uri), True{}, M1.pub.seq_refl(uri)) + %e : {Lazy.stop(List<&2, Role.Mark>, _, ms, _u => Docs.marks(rest, uri)) == ms : List<&2, Role.Mark>} + {==} + +def Laws.open_keeps_marks(_ee, uri, text, ds, _rs, ms, docs): + keep.marks_head(uri, Maybe.default(&2, String, Docs.get(Docs.set(docs, uri, text), uri), ""), ds, ms, + Docs.del(Docs.set(docs, uri, text), uri)) + +def Laws.save_keeps_marks(_ee, uri, ds, _rs, ms, docs): + keep.marks_head(uri, Maybe.default(&2, String, Docs.get(docs, uri), ""), ds, ms, Docs.del(docs, uri)) + +def Laws.change_keeps_marks(_ee, uri, text, _rs, docs): + keep.marks_head(uri, text, Docs.errors(docs, uri), Docs.marks(docs, uri), Docs.del(docs, uri)) diff --git a/proofs/LspPublishesOnDocumentEvents.bend b/proofs/LspPublishesOnDocumentEvents.bend index 86aa97d..62014ba 100644 --- a/proofs/LspPublishesOnDocumentEvents.bend +++ b/proofs/LspPublishesOnDocumentEvents.bend @@ -2,6 +2,8 @@ import Base import ../src/lazy/lazy.bend as Lazy import ../src/lsp/docs.bend as Docs +import ../src/role.bend as Role +import ../src/src.bend as Src import ../src/lsp/report.bend as Rep import ../src/finding.bend as F import ../src/config.bend as Config @@ -18,10 +20,11 @@ law pub.errors_head: for +uri: String for -text: String for -ds: List<&2, Rep.Diag> + for -ms: List<&2, Role.Mark> for -rest: List<&2, Docs.Doc> - {Docs.errors(Docs.Doc{uri, text, ds} <> rest, uri) == ds : List<&2, Rep.Diag>} + {Docs.errors(Docs.Doc{uri, text, ds, ms} <> rest, uri) == ds : List<&2, Rep.Diag>} -def pub.errors_head(uri, _text, ds, rest): +def pub.errors_head(uri, _text, ds, _ms, rest): e = Equal.sym(Bool, String.eq(uri, uri), True{}, M1.pub.seq_refl(uri)) %e : {Lazy.stop(List<&2, Rep.Diag>, _, ds, _u => Docs.errors(rest, uri)) == ds : List<&2, Rep.Diag>} {==} @@ -31,10 +34,11 @@ law pub.get_head: for +uri: String for -text: String for -ds: List<&2, Rep.Diag> + for -ms: List<&2, Role.Mark> for -rest: List<&2, Docs.Doc> - {Docs.get(Docs.Doc{uri, text, ds} <> rest, uri) == Some{text} : Maybe<&2, String>} + {Docs.get(Docs.Doc{uri, text, ds, ms} <> rest, uri) == Some{text} : Maybe<&2, String>} -def pub.get_head(uri, text, _ds, rest): +def pub.get_head(uri, text, _ds, _ms, rest): e = Equal.sym(Bool, String.eq(uri, uri), True{}, M1.pub.seq_refl(uri)) %e : {Lazy.stop(Maybe<&2, String>, _, Some{text}, _u => Docs.get(rest, uri)) == Some{text} : Maybe<&2, String>} {==} @@ -46,12 +50,14 @@ law pub.del.step: for +uri: String for -t: String for -e: List<&2, Rep.Diag> + for -m: List<&2, Role.Mark> for +rest: List<&2, Docs.Doc> for h: {String.eq(u, uri) == bb : Bool} for ih: {Docs.errors(rest, uri) == [] : List<&2, Rep.Diag>} - {Docs.errors(Bool.pick(List<&2, Docs.Doc>, bb, rest, Docs.Doc{u, t, e} <> rest), uri) == [] : List<&2, Rep.Diag>} + {Docs.errors(Bool.pick(List<&2, Docs.Doc>, bb, rest, Docs.Doc{u, t, e, m} <> rest), uri) == [] + : List<&2, Rep.Diag>} -def pub.del.step(bb, u, uri, _t, e, rest, h, ih): +def pub.del.step(bb, u, uri, _t, e, _m, rest, h, ih): match bb: case True{}: ih @@ -70,45 +76,45 @@ def pub.del(docs, uri): match docs: case Nil{}: {==} - case Con{Docs.Doc{+u, t, e}, +r}: - pub.del.step(String.eq(u, uri), u, uri, t, e, Docs.del(r, uri), {==}, pub.del(r, uri)) + case Con{Docs.Doc{+u, t, e, m}, +r}: + pub.del.step(String.eq(u, uri), u, uri, t, e, m, Docs.del(r, uri), {==}, pub.del(r, uri)) -def Laws.open_publishes(_ee, _uri, _text, _ds, _rs, _docs): +def Laws.open_publishes(_ee, _uri, _text, _ds, _rs, _ms, _docs): {==} -def Laws.save_publishes(_ee, _uri, _ds, _rs, _docs): +def Laws.save_publishes(_ee, _uri, _ds, _rs, _ms, _docs): {==} def Laws.change_publishes(_ee, _uri, _text, _rs, _docs): {==} -def Laws.open_keeps(_ee, uri, text, ds, _rs, docs): - (pub.errors_head(uri, Maybe.default(&2, String, Docs.get(Docs.set(docs, uri, text), uri), ""), ds, +def Laws.open_keeps(_ee, uri, text, ds, _rs, ms, docs): + (pub.errors_head(uri, Maybe.default(&2, String, Docs.get(Docs.set(docs, uri, text), uri), ""), ds, ms, Docs.del(Docs.set(docs, uri, text), uri)), Equal.trans(Maybe<&2, String>, - Docs.get(Docs.set_errors(Docs.set(docs, uri, text), uri, ds), uri), + Docs.get(Docs.set_checked(Docs.set(docs, uri, text), uri, ds, ms), uri), Some{Maybe.default(&2, String, Docs.get(Docs.set(docs, uri, text), uri), "")}, Some{text}, - pub.get_head(uri, Maybe.default(&2, String, Docs.get(Docs.set(docs, uri, text), uri), ""), ds, + pub.get_head(uri, Maybe.default(&2, String, Docs.get(Docs.set(docs, uri, text), uri), ""), ds, ms, Docs.del(Docs.set(docs, uri, text), uri)), Equal.cong(Maybe<&2, String>, Maybe<&2, String>, m => Some{Maybe.default(&2, String, m, "")}, Docs.get(Docs.set(docs, uri, text), uri), Some{text}, - pub.get_head(uri, text, Docs.errors(docs, uri), Docs.del(docs, uri))))) + pub.get_head(uri, text, Docs.errors(docs, uri), Docs.marks(docs, uri), Docs.del(docs, uri))))) -def Laws.save_keeps(_ee, uri, ds, _rs, docs): - pub.errors_head(uri, Maybe.default(&2, String, Docs.get(docs, uri), ""), ds, Docs.del(docs, uri)) +def Laws.save_keeps(_ee, uri, ds, _rs, ms, docs): + pub.errors_head(uri, Maybe.default(&2, String, Docs.get(docs, uri), ""), ds, ms, Docs.del(docs, uri)) def Laws.change_keeps(_ee, uri, text, _rs, docs): - (pub.errors_head(uri, text, Docs.errors(docs, uri), Docs.del(docs, uri)), - pub.get_head(uri, text, Docs.errors(docs, uri), Docs.del(docs, uri))) + (pub.errors_head(uri, text, Docs.errors(docs, uri), Docs.marks(docs, uri), Docs.del(docs, uri)), + pub.get_head(uri, text, Docs.errors(docs, uri), Docs.marks(docs, uri), Docs.del(docs, uri))) def Laws.close_publishes(_ee, uri, docs): ({==}, pub.del(docs, uri)) -def Laws.config_unlinted(path, h, _text, _rs): +def Laws.config_unlinted(path, h, _text, _rs, _ms): e = Equal.sym(Bool, Plan.is_config(path), True{}, h) %e : {Bool.pick(List<&2, F.Graded>, _, [], Plan.after([Noqa.file(path, Lex.tokens(_text))], False{}, - Rules.graded(Config.nearest(_rs), Rules.all(path, _text)), + Rules.graded(Config.nearest(_rs), Rules.on.role(Role.of(_ms, path), Src.of(path, _text))), fs => Rules.graded(Config.nearest(_rs), fs))) == [] : List<&2, F.Graded>} {==} diff --git a/src/README.md b/src/README.md index 77ac124..c3654dd 100644 --- a/src/README.md +++ b/src/README.md @@ -169,11 +169,14 @@ The walk is over the whole tree under the working directory, even over files named on the line: bolt reads every `.bend` file there for its imports (lexed, never parsed), and each named file the walk does not find (one outside the working directory), each file once, and lints only the files -named. So a file named beside +named. A path is matched to its file however it is spelled (`a.bend`, +`./a.bend`, or absolute under the working directory). So a file named beside a `LAWS.bend` that imports it is still code when a `main.bend` the run does not name imports it, and a file under `laws/` named without its `LAWS.bend` -is still a law file. The editor reads the open file alone, so there a file -has only the role its name gives it. +is still a law file. The editor reads the same graph as `bolt ` when a +document is opened or saved, from the directory it runs in, and keeps those +roles for the edits in between (laws/LspKeepsRoleMarks.bend), so a change +that adds or drops an import changes a file's role at the next save. A rule is handed a [`Src`](src.bend), not the text: the path, the text, and the file's tokens, tree, binder and outline, each read once for the whole diff --git a/src/lint/plan.bend b/src/lint/plan.bend index 2a4af8d..2290abf 100644 --- a/src/lint/plan.bend +++ b/src/lint/plan.bend @@ -166,22 +166,29 @@ def sources(ps: List<&2, String>) -> List<&2, String>: def inputs(+world: W.World) -> List<&2, String>: sources(files(world)) -# each path normalized (src/rules/imports.bend) -def norms(ps: List<&2, String>) -> List<&2, String>: +# a path as the one file it names wherever it is spelled from: resolved +# against the working directory when relative, then normalized +# (src/rules/imports.bend), so `a.bend`, `./a.bend` and `/w/a.bend` run +# from `/w` are one +def abs(+cwd: String, +path: String) -> String: + Imports.norm(Bool.pick(String, String.starts_with(path, "/"), path, cwd ++ "/" ++ path)) + +# each path resolved (abs) +def norms(ps: List<&2, String>, +cwd: String) -> List<&2, String>: match ps: case Nil{}: Nil{} case Con{p, rest}: - Imports.norm(p) <> norms(rest) + abs(cwd, p) <> norms(rest, cwd) -# the paths whose normalized form is not among the normalized paths seen -def unseen(ps: List<&2, String>, +seen: List<&2, String>) -> List<&2, String>: +# the paths that resolve to none of the paths seen +def unseen(ps: List<&2, String>, +cwd: String, +seen: List<&2, String>) -> List<&2, String>: match ps: case Nil{}: Nil{} case Con{+p, rest}: - +more = unseen(rest, seen) - Bool.pick(List<&2, String>, Imports.has(seen, Imports.norm(p)), more, p <> more) + +more = unseen(rest, cwd, seen) + Bool.pick(List<&2, String>, Imports.has(seen, abs(cwd, p)), more, p <> more) # the files a run reads its roles from: every source the walk of the tree # finds, then each source of the run the walk does not find (one named @@ -190,7 +197,8 @@ def unseen(ps: List<&2, String>, +seen: List<&2, String>) -> List<&2, String>: # package's main.bend that imports it, may be named or not def graph(+world: W.World) -> List<&2, String>: +found = sources(walk.found(tree(world))) - List.append(&2, String, found, unseen(inputs(world), norms(found))) + +cwd = W.cwd.of(world) + List.append(&2, String, found, unseen(inputs(world), cwd, norms(found, cwd))) # --------------------------------------------------------------------------- # what the plan reads @@ -378,15 +386,32 @@ def per_file.role(+ms: List<&2, Role.Mark>, ss: List<&2, Src.Src>) -> List<&2, F List.concat(&2, F.Finding, [Rules.on.role(Role.of(ms, path), Src.Src{path, text, toks, tree, bound, items}), per_file.role(ms, rest)]) -# the facts (src/role.bend) of each file the World read, from its text -def facts.of(gs: List<&2, Got>) -> List<&2, Role.Fact>: +# the facts (src/role.bend) of each file the World read, from its text, each +# at its path resolved against the working directory (abs) +def facts.of(gs: List<&2, Got>, +cwd: String) -> List<&2, Role.Fact>: match gs: case Nil{}: Nil{} case Con{Got{_path, None{}}, rest}: - facts.of(rest) + facts.of(rest, cwd) case Con{Got{path, Some{text}}, rest}: - Role.fact.text(path, text) <> facts.of(rest) + Role.fact.text(abs(cwd, path), text) <> facts.of(rest, cwd) + +# each path's mark as the path is spelled: its role among marks taken over +# resolved paths (abs), neither when it has none there +def marks.key(ps: List<&2, String>, +cwd: String, +ms: List<&2, Role.Mark>) -> List<&2, Role.Mark>: + match ps: + case Nil{}: + Nil{} + case Con{+p, rest}: + Role.Mark{Imports.norm(p), Role.of.norm(ms, abs(cwd, p))} <> marks.key(rest, cwd, ms) + +# the marks of a run over files named on the line: every file of the graph +# read for its imports, the walk taken over their resolved paths, and each +# named file's role looked up there as it is named +def marks.named(+world: W.World) -> List<&2, Role.Mark>: + +cwd = W.cwd.of(world) + marks.key(inputs(world), cwd, Role.marks.facts(facts.of(gots(graph(world), world), cwd))) # the marks a run's roles are read from: over the whole tree, the sources' # own; over files named on the line, the graph's, every file of the tree @@ -397,7 +422,7 @@ def marks.run(paths: List<&2, String>, +world: W.World, +ss: List<&2, Src.Src>) case Nil{}: Role.marks(ss) case Con{_p, _rest}: - Role.marks.facts(facts.of(gots(graph(world), world))) + marks.named(world) # the path of the first candidate the World holds a text for, as a list of # one; none when it holds none diff --git a/src/lsp/docs.bend b/src/lsp/docs.bend index 06d22e2..4c14269 100644 --- a/src/lsp/docs.bend +++ b/src/lsp/docs.bend @@ -4,41 +4,56 @@ import Base import ../lazy/lazy.bend as Lazy import ./report.bend as Rep +import ../role.bend as Role # an open document: its URI, the text the editor shows, the checker's errors -# from its last run +# from its last run, and the role marks read with them type Doc is Data: - Doc{uri: String, text: String, errors: List<&2, Rep.Diag>} + Doc{uri: String, text: String, errors: List<&2, Rep.Diag>, marks: List<&2, Role.Mark>} # the documents without the one at a URI def del(docs: List<&2, Doc>, +uri: String) -> List<&2, Doc>: match docs: case Nil{}: Nil{} - case Con{Doc{+u, t, e}, r}: + case Con{Doc{+u, t, e, m}, r}: +rest = del(r, uri) - Bool.pick(List<&2, Doc>, String.eq(u, uri), rest, Doc{u, t, e} <> rest) + Bool.pick(List<&2, Doc>, String.eq(u, uri), rest, Doc{u, t, e, m} <> rest) # the checker's errors for a document, none when it is not open def errors(docs: List<&2, Doc>, +uri: String) -> List<&2, Rep.Diag>: match docs: case Nil{}: Nil{} - case Con{Doc{u, t, e}, r}: + case Con{Doc{u, t, e, m}, r}: Lazy.stop(List<&2, Rep.Diag>, String.eq(u, uri), e, _u => errors(r, uri)) -# a document's text set (opened, or changed); its errors stay +# the role marks kept for a document, none when it is not open +def marks(docs: List<&2, Doc>, +uri: String) -> List<&2, Role.Mark>: + match docs: + case Nil{}: + Nil{} + case Con{Doc{u, t, e, m}, r}: + Lazy.stop(List<&2, Role.Mark>, String.eq(u, uri), m, _u => marks(r, uri)) + +# a document's text set (opened, or changed); its errors and marks stay def set(+docs: List<&2, Doc>, +uri: String, text: String) -> List<&2, Doc>: - Doc{uri, text, errors(docs, uri)} <> del(docs, uri) + Doc{uri, text, errors(docs, uri), marks(docs, uri)} <> del(docs, uri) # the text of an open document, when it is open def get(docs: List<&2, Doc>, +uri: String) -> Maybe<&2, String>: match docs: case Nil{}: None{} - case Con{Doc{u, t, e}, r}: + case Con{Doc{u, t, e, m}, r}: Lazy.stop(Maybe<&2, String>, String.eq(u, uri), Some{t}, _u => get(r, uri)) -# a document's errors set, from a run of the checker -def set_errors(+docs: List<&2, Doc>, +uri: String, errors: List<&2, Rep.Diag>) -> List<&2, Doc>: - Doc{uri, Maybe.default(&2, String, get(docs, uri), ""), errors} <> del(docs, uri) +# a document's errors set, from a run of the checker, with the marks read +# beside it +def set_checked( + +docs: List<&2, Doc>, + +uri: String, + errors: List<&2, Rep.Diag>, + ms: List<&2, Role.Mark> +) -> List<&2, Doc>: + Doc{uri, Maybe.default(&2, String, get(docs, uri), ""), errors, ms} <> del(docs, uri) diff --git a/src/lsp/files/disk.bend b/src/lsp/files/disk.bend index 3126cfc..4e07986 100644 --- a/src/lsp/files/disk.bend +++ b/src/lsp/files/disk.bend @@ -85,6 +85,10 @@ def base_path(_u: Unit) -> IO(String): # noqa: L001 disk effect def cwd(_u: Unit) -> IO(String): # noqa: L001 disk effect WalkDisk.cwd() +# a directory's entries, as the lint's walk lists them +def entries(dir: String) -> IO(List<&2, String>): # noqa: L001 disk effect + WalkDisk.entries(dir) + # the real file system def new() -> S.Files: # noqa: L001 effect record - S.Files{read, base_path, cwd} + S.Files{read, base_path, cwd, entries} diff --git a/src/lsp/files/service.bend b/src/lsp/files/service.bend index 2467191..e314a44 100644 --- a/src/lsp/files/service.bend +++ b/src/lsp/files/service.bend @@ -1,26 +1,34 @@ -# files: an effect service: a file's text by path, where Base lives, and -# the working directory a relative path is read against. +# files: an effect service: a file's text by path, where Base lives, the +# working directory a relative path is read against, and a directory's +# entries (a sub-directory's name ending in "/", as src/walk/ lists them). import Base -# effect service: a file's text, where Base is, and the working directory +# effect service: a file's text, where Base is, the working directory, and a +# directory's entries type Files is Type: - Files{read: String -> IO(Maybe<&2, String>), base_path: Unit -> IO(String), cwd: Unit -> IO(String)} + Files{read: String -> IO(Maybe<&2, String>), base_path: Unit -> IO(String), cwd: Unit -> IO(String), + entries: String -> IO(List<&2, String>)} # None when the file cannot be read def read(ff: Files, path: String) -> IO(Maybe<&2, String>): - Files{r, _b, _c} = ff + Files{r, _b, _c, _e} = ff r(path) # the path of Base, from a files service def base_path(ff: Files) -> IO(String): - Files{_r, b, _c} = ff + Files{_r, b, _c, _e} = ff b(Unit{}) # the working directory, from the root; "" when it cannot be named def cwd(ff: Files) -> IO(String): # noqa: L001 effect accessor - Files{_r, _b, c} = ff + Files{_r, _b, c, _e} = ff c(Unit{}) +# a directory's entries, none when it cannot be opened +def entries(ff: Files, dir: String) -> IO(List<&2, String>): # noqa: L001 effect accessor + Files{_r, _b, _c, e} = ff + e(dir) + # every path read, in order (a missing file is a None) def read_all(~ff: Files, paths: List<&2, String>) -> IO(List<&2, Maybe<&2, String>>): # noqa: L001 effect accessor match paths: diff --git a/src/lsp/server.bend b/src/lsp/server.bend index 053ac8c..abbf735 100644 --- a/src/lsp/server.bend +++ b/src/lsp/server.bend @@ -3,7 +3,10 @@ # in (transport/, files/, checker/answer.bend's Checker; checker/bend.bend's # service is the real checker). The open documents are the # loop's state. The checker runs on open and save, against the file on disk; -# the linter on every open, change and save, on the text the editor shows. +# the linter on every open, change and save, on the text the editor shows, +# each file told the role the imports of the tree give it, read as `bolt +# ` reads them (src/lint/plan.bend's graph) on open and save and kept +# for the changes between. import Base import 0xa81e161008ecc2f297c0a3065cb9b188/main.bend as Ezjson import 0xa81e161008ecc2f297c0a3065cb9b188/src/value.bend as J @@ -15,6 +18,9 @@ import ../rules.bend as Lint import ../finding.bend as Finding import ../config.bend as Config import ../lint/plan.bend as Plan +import ../lint/world.bend as W +import ../role.bend as Role +import ../src.bend as Src import ../noqa.bend as Noqa import ./transport/service.bend as T import ./checker/answer.bend as C @@ -223,16 +229,27 @@ def pub.note(cc: Enc.Cols, uri: String, ds: List<&2, Rep.Diag>, gs: List<&2, Fin def pub.cands(cwd: String, path: String) -> List<&2, String>: Config.chain(Config.home(cwd, Config.dir_of(path))) -# the linter's findings on the text the editor shows, at the levels the -# nearest bolt.bend read gives them, less what the text's noqa comments +# the linter's findings on the text the editor shows, each rule told the +# file's role among the marks, at the levels the nearest bolt.bend read gives +# them, less what the text's noqa comments # silence, then the `noqa` rule's, which judges no project rule's code (the # editor runs none); none for a bolt.bend, which is bolt's configuration, # not source -def pub.lint(+path: String, +text: String, rs: List<&2, Maybe<&2, String>>) -> List<&2, Finding.Graded>: +def pub.lint( + +path: String, + +text: String, + rs: List<&2, Maybe<&2, String>>, + ms: List<&2, Role.Mark> +) -> List<&2, Finding.Graded>: +cfg = Config.nearest(rs) Bool.pick(List<&2, Finding.Graded>, Plan.is_config(path), [], - Plan.after([Noqa.file(path, Lex.tokens(text))], False{}, Lint.graded(cfg, Lint.all(path, text)), - fs => Lint.graded(cfg, fs))) + Plan.after([Noqa.file(path, Lex.tokens(text))], False{}, + Lint.graded(cfg, Lint.on.role(Role.of(ms, path), Src.of(path, text))), fs => Lint.graded(cfg, fs))) + +# the role marks `bolt ` reads from a World: the imports of every file +# of the tree (Plan.graph), each file read for its imports alone +def pub.marks(+world: W.World) -> List<&2, Role.Mark>: + Plan.marks.named(world) # open or save: the checker's errors with the lint of the text, the errors # kept for the changes after @@ -242,9 +259,11 @@ def pub.checked( +text: String, +ds: List<&2, Rep.Diag>, rs: List<&2, Maybe<&2, String>>, + +ms: List<&2, Role.Mark>, +docs: List<&2, Docs.Doc> ) -> Work: - noted([pub.note(Enc.cols(ee, text), uri, ds, pub.lint(Proto.path(uri), text, rs))], Docs.set_errors(docs, uri, ds)) + noted([pub.note(Enc.cols(ee, text), uri, ds, pub.lint(Proto.path(uri), text, rs, ms))], + Docs.set_checked(docs, uri, ds, ms)) # open: the document's text kept, then published with the checker's errors def pub.open( @@ -253,9 +272,10 @@ def pub.open( +text: String, ds: List<&2, Rep.Diag>, rs: List<&2, Maybe<&2, String>>, + +ms: List<&2, Role.Mark>, +docs: List<&2, Docs.Doc> ) -> Work: - pub.checked(ee, uri, text, ds, rs, Docs.set(docs, uri, text)) + pub.checked(ee, uri, text, ds, rs, ms, Docs.set(docs, uri, text)) # save: the text the editor shows published with the checker's new errors def pub.save( @@ -263,12 +283,13 @@ def pub.save( +uri: String, ds: List<&2, Rep.Diag>, rs: List<&2, Maybe<&2, String>>, + +ms: List<&2, Role.Mark>, +docs: List<&2, Docs.Doc> ) -> Work: - pub.checked(ee, uri, Maybe.default(&2, String, Docs.get(docs, uri), ""), ds, rs, docs) + pub.checked(ee, uri, Maybe.default(&2, String, Docs.get(docs, uri), ""), ds, rs, ms, docs) # change: the new text kept, and its lint published with the checker's last -# errors +# errors and the marks read with them def pub.change( ee: Enc.Enc, +uri: String, @@ -276,8 +297,8 @@ def pub.change( rs: List<&2, Maybe<&2, String>>, +docs: List<&2, Docs.Doc> ) -> Work: - noted([pub.note(Enc.cols(ee, text), uri, Docs.errors(docs, uri), pub.lint(Proto.path(uri), text, rs))], - Docs.set(docs, uri, text)) + noted([pub.note(Enc.cols(ee, text), uri, Docs.errors(docs, uri), + pub.lint(Proto.path(uri), text, rs, Docs.marks(docs, uri)))], Docs.set(docs, uri, text)) # close: the document forgotten, and an empty list published def pub.close(ee: Enc.Enc, +uri: String, docs: List<&2, Docs.Doc>) -> Work: @@ -289,6 +310,76 @@ def pub.configs(~fs: F.Files, +uri: String) -> IO(List<&2, Maybe<&2, String>>): cwd : String <- F.cwd(fs) F.read_all(~fs, pub.cands(cwd, Proto.path(uri))) +# a file's text as an answer +def pub.read.answer(mm: Maybe<&2, String>) -> W.Answer: + match mm: + case None{}: + W.Unread{} + case Some{text}: + W.Read{text} + +# one question of the planner's, answered from the files: a directory's +# entries, or a file's text (a directory reads as no text) +def pub.answer(~fs: F.Files, ask: W.Ask) -> IO(W.Answer): + match ask: + case W.Entries{dir}: + do IO: + es : List<&2, String> <- F.entries(fs, dir) + return W.Listed{es} + case W.Contents{path}: + do IO: + mm : Maybe<&2, String> <- F.read(fs, path) + return pub.read.answer(mm) + +# every question answered, in order +def pub.answers(~fs: F.Files, asks: List<&2, W.Ask>) -> IO(List<&2, W.Reply>): + match asks: + case Nil{}: + IO.pure(List<&2, W.Reply>, []) + case Con{+a, rest}: + do IO>: + r : W.Answer <- pub.answer(~fs, a) + more : List<&2, W.Reply> <- pub.answers(~fs, rest) + return W.Reply{a, r} <> more + +# the World with more answers in it +def pub.world.more(world: W.World, rs: List<&2, W.Reply>) -> W.World: + W.World{paths, cwd, replies} = world + W.World{paths, cwd, List.append(&2, W.Reply, replies, rs)} + +# the planner's questions answered and the World gone round again, or the +# World as it is once the planner asks none +def pub.world.step( + ~fs: F.Files, + asks: List<&2, W.Ask>, + +world: W.World, + go: W.World -> IO(W.World) +) -> IO(W.World): + match asks: + case Nil{}: + IO.pure(W.World, world) + case Con{a, rest}: + do IO: + rs : List<&2, W.Reply> <- pub.answers(~fs, a <> rest) + go(pub.world.more(world, rs)) + +# the World `bolt ` reads, gathered as src/lint.bend gathers it: the +# planner's questions answered until it asks none, under the same fuel +def pub.world.go(~fs: F.Files, fuel: Nat, +world: W.World) -> IO(W.World): + match fuel: + case 0n: + IO.pure(W.World, world) + case 1n+f: + pub.world.step(~fs, Plan.wants(world), world, ww => pub.world.go(~fs, f, ww)) + +# the role marks the tree's imports give a document, as `bolt ` reads +# them +def pub.seen(~fs: F.Files, +uri: String) -> IO(List<&2, Role.Mark>): + do IO>: + cwd : String <- F.cwd(fs) + ww : W.World <- pub.world.go(~fs, Nat.add(Plan.limit(), 2n), W.World{[Proto.path(uri)], cwd, []}) + return pub.marks(ww) + # open, on the checker's run on the file on disk def pub.opened( ~fs: F.Files, @@ -301,14 +392,16 @@ def pub.opened( do IO: aa : C.Answer <- C.check(ck, Proto.path(uri)) rs : List<&2, Maybe<&2, String>> <- pub.configs(~fs, uri) - return pub.open(ee, uri, text, C.errors(aa), rs, docs) + ms : List<&2, Role.Mark> <- pub.seen(~fs, uri) + return pub.open(ee, uri, text, C.errors(aa), rs, ms, docs) # save, on the checker's run on the file on disk def pub.saved(~fs: F.Files, ~ck: C.Checker, ee: Enc.Enc, +uri: String, docs: List<&2, Docs.Doc>) -> IO(Work): do IO: aa : C.Answer <- C.check(ck, Proto.path(uri)) rs : List<&2, Maybe<&2, String>> <- pub.configs(~fs, uri) - return pub.save(ee, uri, C.errors(aa), rs, docs) + ms : List<&2, Role.Mark> <- pub.seen(~fs, uri) + return pub.save(ee, uri, C.errors(aa), rs, ms, docs) # change: the linter only def pub.changed(~fs: F.Files, ee: Enc.Enc, +uri: String, text: String, docs: List<&2, Docs.Doc>) -> IO(Work): diff --git a/src/lsp/tests/checker.bend b/src/lsp/tests/checker.bend index 0c3f900..097408e 100644 --- a/src/lsp/tests/checker.bend +++ b/src/lsp/tests/checker.bend @@ -5,6 +5,10 @@ # check clean, and `unproven/` has none at all, which is the same verdict. # A fixture holding an *incomplete* PROOF.bend cannot live in this tree: the # gate checks every PROOF.bend it finds, and that one is meant to fail. +# Each open reads every file of the tree for its imports (the roles `bolt +# ` gives it), and this tree holds proofs past the JS lane's stack, so +# the test runs native only: +# lanes: native import Base import ../server.bend as Server import ../transport/script.bend as Script diff --git a/src/rules.bend b/src/rules.bend index 9015ba6..7959b65 100644 --- a/src/rules.bend +++ b/src/rules.bend @@ -100,23 +100,10 @@ def on.rules.role(+rr: Role.Role, +ss: Src.Src) -> List<&2, F.Finding>: Closed.check.role(rr, ss), Transcong.check(ss)]) -# every rule's findings on a file read once, as each rule gives them, its -# role read from its name -def on.rules(+ss: Src.Src) -> List<&2, F.Finding>: - on.rules.role(Role.of_path(on.path(ss)), ss) - -# every rule's findings on a file read once, each at that file -def on(+ss: Src.Src) -> List<&2, F.Finding>: - on.at(on.path(ss), on.rules(ss)) - # every rule's findings on a file read once, told its role, each at that file def on.role(+rr: Role.Role, +ss: Src.Src) -> List<&2, F.Finding>: on.at(on.path(ss), on.rules.role(rr, ss)) -# every rule's findings on a file -def all(+path: String, +text: String) -> List<&2, F.Finding>: - on(Src.of(path, text)) - # Packages (laws/ScopePackageRootsFound.bend). A package root other than `.` is a directory # holding a main.bend; a file belongs to the nearest root at or above it, and # `coverage` runs on each package's files alone. A directory is From 9ffbe5cde72dcbcfdb1d6bd1edb8d97c0b21d660 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 1 Nov 2026 16:19:54 +0000 Subject: [PATCH 7/7] ci: check on macos-15; GitHub retires macos-14 on 2026-11-02 The shared nix-flake-check workflow defaults to macos-14, which GitHub retires on 2026-11-02 and fails during scheduled brownouts before then: the job is cancelled before any runner is assigned, so `check / check`, the check main requires, went red with no step run. Pass the runner list with macos-15, also arm64, so aarch64-darwin is still checked. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_0186KEnw5sg7heUQZTJJU89N --- .github/workflows/ci.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index aab0d90..c63bee1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,3 +12,8 @@ on: jobs: check: uses: Emerging-Patterns/actions/.github/workflows/nix-flake-check.yml@5b0aa4f0eaee72662e06479ecebcfd15e9762fe7 + with: + # the shared default still names macos-14, which GitHub retires on + # 2026-11-02 and fails in brownouts before then (no runner is assigned); + # macos-15 is arm64 too, so aarch64-darwin is still checked + runners: '["ubuntu-latest", "ubuntu-24.04-arm", "macos-15"]'