Attach contracts to definitions

Contracts now live directly on definitions via @ / =@ annotations and
travel automatically with exported values.

- Remove !export from lexer/parser/AST/evaluator/manifest/resolver and CLI.
- Simplify workspace module export logic: export all top-level local
  definitions by default.
- Update Frontend.ContractDesugar:
  - Named binder annotations (x@nat?) expand to per-argument withContract.
  - Phantom annotations (@nat?) expand to a local raw helper plus a wrapper,
    keeping fixed points shared and only depending on withContract.
- Merge lib/guardedBase.tri into lib/base.tri and annotate partial/sensitive
  base functions: head, tail, last, add, sub, mul, div, mod, pow, min,
  max, length, sum, product.
- Add check contract helper to lib/base.tri.
- Update demos/contractBasics.tri and README to reflect @/=@-only design.
- Update test suite: remove guardedBase import, replace explicit !export
  test with a test verifying that contract annotations on an exported
  definition are enforced on import.
- Fix remaining base.tri definitions (div/mod/pow) to stay point-free.
This commit is contained in:
2026-09-01 10:33:54 -05:00
parent 229ba34af4
commit e595763f91
15 changed files with 105 additions and 198 deletions

View File

@@ -1,8 +1,6 @@
!import "prelude" !Local
-- A custom 'and' combinator written directly against base.matchResult.
-- It succeeds only when *both* contracts succeed, threading the checked value
-- from the first into the second. This makes the Result pair structure explicit.
-- Custom contract combinators built directly on matchResult.
myAndC = (c1 c2 value rest :
matchResult
(msg _ : contractErr msg rest)
@@ -13,29 +11,15 @@ myAndC = (c1 c2 value rest :
natural? = guardC "natural" (n : gte? n 0)
nonZero? = guardC "non-zero" (n : not? (isZero? n))
-- Safe wrappers around partial base / list functions.
-- The frontend desugars @ and =@ into runtime withContract applications.
safeDiv a@natural? b@(myAndC natural? nonZero?) =@natural? div a b
-- Phantom annotations let point-free definitions carry their own contracts.
-- The base library now uses the same syntax, so head/tail/div etc. are
-- guarded by default.
myHead @(nonEmptyListOf anyC) =@anyC head
myTail @(listOf anyC) =@(listOf anyC) tail
myDiv @natural? @(myAndC natural? nonZero?) =@natural? div
safeHead xs@(nonEmptyListOf anyC) =@anyC head xs
safeTail xs@(nonEmptyListOf anyC) =@(listOf anyC) tail xs
-- The `check` helper applies a contract to any value and returns the
-- checked value (or the diagnostic message on failure).
checkedSuccessor = check natural? (add 1 2)
-- A higher-order wrapper: the supplied function must satisfy a contract,
-- the input list must satisfy a contract, and the result list is guaranteed.
checkedMap f@(fnContract anyC natural?) xs@(listOf anyC) =@(listOf natural?) map f xs
-- Advertise the safe wrappers in the module manifest with their own contracts.
!export safeDiv : fn2 natural? nonZero? natural?
!export safeHead : fnContract (nonEmptyListOf anyC) anyC
!export checkedMap : fn2 (fnContract anyC natural?) (listOf anyC) (listOf natural?)
-- A small interaction-tree pipeline that uses contracts as recoverable effects.
pipeline = (input :
do bindM
scaled <- checkM natural? (mul input 2)
half <- handleM "contract"
(_ : pureM 1)
(checkM nonZero? (sub scaled 4))
pureM (div scaled half))
main = runM (pipeline 5)
main = pair checkedSuccessor (myDiv 10 2)