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:
@@ -10,39 +10,54 @@ import Research
|
||||
|
||||
-- | Convert source-level contract annotations into runtime boundary checks.
|
||||
--
|
||||
-- A definition such as
|
||||
-- Named binder annotations (e.g. @x@nat?) wrap each argument as it is
|
||||
-- received and the result before it is returned.
|
||||
--
|
||||
-- addPos x@positive? y@positive? =@positive? (add x y)
|
||||
--
|
||||
-- is desugared to a plain definition whose body wraps every annotated
|
||||
-- argument and the result with 'withContract' from the contract library:
|
||||
--
|
||||
-- addPos = \x -> withContract positive? x
|
||||
-- (\x -> \y -> withContract positive? y
|
||||
-- (\y -> withContract positive? (add x y)
|
||||
-- (\r -> r)
|
||||
-- (\msg _ -> msg))
|
||||
-- (\msg _ -> msg))
|
||||
-- (\msg _ -> msg)
|
||||
--
|
||||
-- This makes annotated source depend on the existing 'withContract' helper,
|
||||
-- which is an ordinary 'tricu' function from 'lib/contracts.tri'. Files that
|
||||
-- use annotations should import the contract library (or another library that
|
||||
-- re-exports 'withContract').
|
||||
-- Phantom annotations (e.g. @nat? on a point-free definition) are turned
|
||||
-- into a fresh local raw value plus a wrapper definition that uses named
|
||||
-- binder annotations. The raw value is bound with a local 'let' so that
|
||||
-- fixed points (such as definitions built with 'y') are shared rather than
|
||||
-- recreated on every call. The wrapper only needs 'withContract', which is
|
||||
-- already required by any source-level annotation.
|
||||
desugarContracts :: [TricuAST] -> [TricuAST]
|
||||
desugarContracts asts = map desugarTopItem asts
|
||||
desugarContracts asts = concatMap desugarTopItem asts
|
||||
where
|
||||
desugarTopItem (SDefAnn name args ret body) = desugarDefAnn name args ret body
|
||||
desugarTopItem other = other
|
||||
desugarTopItem other = [other]
|
||||
|
||||
desugarDefAnn :: String -> [DefArg] -> Maybe ViewExpr -> TricuAST -> TricuAST
|
||||
desugarDefAnn name args ret body = SDef name [] (wrapArgs args body')
|
||||
-- | Fresh internal name for the raw, contract-free helper introduced by
|
||||
-- phantom annotations. It is bound locally with 'SLet' so it never escapes
|
||||
-- into the final environment.
|
||||
rawNameFor :: String -> String
|
||||
rawNameFor name = "_" ++ name ++ "_raw"
|
||||
|
||||
desugarDefAnn :: String -> [DefArg] -> Maybe ViewExpr -> TricuAST -> [TricuAST]
|
||||
desugarDefAnn name args ret body
|
||||
| all isPhantom args =
|
||||
let argContracts = map getPhantom args
|
||||
rawNm = rawNameFor name
|
||||
rawVar = SVar rawNm Nothing
|
||||
argNames = take (length args) ["x","y","z"]
|
||||
newArgs = zipWith DefBinder argNames (map Just argContracts)
|
||||
wrappedBody = foldl SApp rawVar (map (\n -> SVar n Nothing) argNames)
|
||||
wrapper = wrapArgs newArgs (wrapReturn ret wrappedBody)
|
||||
in [ SDef name [] (SLet rawNm body wrapper) ]
|
||||
| otherwise = [ SDef name [] (wrapArgs args body') ]
|
||||
where
|
||||
body' = wrapReturn ret body
|
||||
|
||||
okCont = SLambda ["r"] (SVar "r" Nothing)
|
||||
errCont = SLambda ["msg"] (SVar "msg" Nothing)
|
||||
|
||||
isPhantom (DefPhantom _) = True
|
||||
isPhantom _ = False
|
||||
|
||||
getPhantom (DefPhantom c) = c
|
||||
getPhantom _ = error "expected phantom annotation"
|
||||
|
||||
wrapReturn Nothing b = b
|
||||
wrapReturn (Just c) b =
|
||||
withContractE (viewExprToAst c) b (SLambda ["r"] (SVar "r" Nothing)) errCont
|
||||
withContractE (viewExprToAst c) b okCont errCont
|
||||
|
||||
wrapArgs [] b = b
|
||||
wrapArgs (DefBinder nm Nothing : rest) b = SLambda [nm] (wrapArgs rest b)
|
||||
@@ -54,8 +69,6 @@ desugarDefAnn name args ret body = SDef name [] (wrapArgs args body')
|
||||
wrapArgs (DefPhantom _ : _) _ =
|
||||
error "phantom contract arguments are not yet supported by the frontend"
|
||||
|
||||
errCont = SLambda ["msg"] (SVar "msg" Nothing)
|
||||
|
||||
-- | Turn a source annotation expression into an ordinary AST expression.
|
||||
-- Contract annotations are written with the same surface syntax as terms,
|
||||
-- so the mapping is mostly structural.
|
||||
|
||||
Reference in New Issue
Block a user