11 KiB
View Contract Syntax Design
1. Purpose
This document specifies source-level syntax sugar for emitting View Contract
metadata from annotated tricu definitions.
The syntax is frontend sugar. It lowers to ordinary typed-program nodes consumed
by the portable checker in lib/view.tri and catalog helpers in
lib/views/catalog.tri.
The checker remains independent of source syntax.
2. Definition Annotations
A definition may carry argument and return view annotations directly in its head.
name arg1@Type1 arg2@Type2 =@ReturnType body
This declares:
name : Fn [Type1 Type2] ReturnType
arg1 : Type1
arg2 : Type2
and lowers to View Contract metadata:
typedDeclareFn nameSym [(Type1) (Type2)] ReturnType t
typedValue arg1Sym Type1 t
typedValue arg2Sym Type2 t
If body flow metadata is emitted, the body result is required to satisfy the appropriate residual view.
3. Syntax Forms
3.1 Binder annotation
x@Bool
xs@(List Bool)
f@(Fn [Bool] String)
A binder annotation introduces a normal term binder and contributes an argument view to the function contract.
3.2 Phantom argument annotation
name @A @B =@C body
A phantom argument annotation contributes an argument view to the function contract but introduces no term binder.
This is useful for point-free and combinator-heavy definitions.
name @A @B =@C body
declares:
name : Fn [A B] C
The body itself must satisfy the residual function view:
Fn [A B] C
3.3 Binder prefix with phantom tail
Phantom annotations may appear after binder annotations:
name x@A @B =@C body
This declares:
name : Fn [A B] C
x : A
The body must satisfy:
Fn [B] C
This allows a named binder prefix with a point-free tail.
3.4 Return annotation
name x@A =@B body
name =@B body
=@B contributes the result view.
A definition with no arguments and a return annotation is a value contract, not a zero-arity function contract:
name =@Bool body
lowers to:
typedValue nameSym viewBool t
not:
typedDeclareFn nameSym [] viewBool t
4. Ordering Rule
Phantom argument annotations may only appear at the end of the argument list.
Valid:
foo x@A y@B =@C body
foo @A @B =@C body
foo x@A @B =@C body
foo x y@B @C =@D body
Invalid:
foo x@A @B z@C =@D body
foo @A x@B =@C body
Once a phantom @Type item appears, no later named binder may appear.
5. Contract-Bearing Definitions
A definition is contract-bearing if its head contains any of:
binder@Type
@Type
=@Type
Ordinary unannotated definitions do not emit View Contract metadata.
foo x y = body
emits no contract metadata.
6. Unannotated Binders in Contract-Bearing Heads
In a contract-bearing definition, an unannotated binder contributes Any.
foo x y@Bool =@String body
means:
foo : Fn [Any Bool] String
x : Any
y : Bool
This keeps mixed annotation lightweight without emitting contracts for fully unannotated definitions.
7. Missing Return Annotation
If a contract-bearing definition has argument annotations but no return
annotation, the return view defaults to Any.
foo x@Bool = body
means:
foo : Fn [Bool] Any
x : Bool
8. Type Annotation Grammar
Annotations are intentionally small at the attachment site.
After @ or =@, the parser accepts either a single atomic view expression or
a parenthesized compound view expression.
Valid:
x@Bool
x@(List Bool)
f@(Fn [Bool] String)
r@(Result String Bool)
name =@Bool body
name =@(List Bool) body
These are not structural annotations:
x@List Bool
f@Fn [Bool] String
name =@List Bool body
They are parsed according to normal definition-head rules. For example,
x@List Bool means binder x has the atomic view expression List, followed by
an unannotated binder named Bool. Use parentheses when the annotation itself is
an application.
9. Type Grammar
View expressions are ordinary value-level expressions in a restricted annotation grammar:
ViewExpr
= name
| integer
| [ViewExpr...]
| ViewExpr ViewExpr
| (ViewExpr)
Built-in names lower to standard view values:
Any -> viewAny
Bool -> viewBool
String -> viewString
Byte -> viewByte
Unit -> viewUnit
Atomic refs lower explicitly. String refs are the preferred user-facing form; numeric refs remain available for low-level/generated code:
Ref "Nat" -> viewRef "Nat"
Ref 10 -> viewRef 10
Additional named views and view constructors are ordinary tricu values:
Nat = viewRef "Nat"
Box a = viewPair (viewRef "Box") a
idNat x@Nat =@Nat x
idBox x@(Box String) =@(Box String) x
The frontend resolves names and evaluates view expressions, but well-formedness
is judged by the self-hosted checker (wellFormedView? in lib/view.tri).
Malformed view values are rejected when checked or published.
10. List Syntax in Types
Function argument lists use the source type grammar:
Fn [Bool String] Unit
Fn [(List Bool) (Maybe String)] Unit
The lowered typed program must still respect ordinary tricu list syntax, where
each list element is parenthesized when needed:
viewFn [(viewBool) (viewString)] viewUnit
11. Residual Body View
For a contract-bearing definition, the full definition view is always:
Fn [allArgumentViews...] returnView
except for nullary value annotations, which use the return view directly.
The body obligation depends on how many argument views are represented by named binders in the definition head.
Let:
argViews = [A B C]
returnView = R
binderCount = number of named binders before the phantom tail
remaining = drop binderCount argViews
Then:
bodyRequiredView = residual(remaining, returnView)
where:
residual([], R) = R
residual([A ...], R) = Fn [A ...] R
Examples:
foo x@A y@B =@C body
Body required view:
C
foo @A @B =@C body
Body required view:
Fn [A B] C
foo x@A @B =@C body
Body required view:
Fn [B] C
12. Lowering Examples
12.1 Fully annotated binders
Source:
foo x@Bool xs@(List Bool) =@String body
Definition contract:
typedDeclareFn fooSym [(viewBool) (viewList viewBool)] viewString t
typedValue xSym viewBool t
typedValue xsSym (viewList viewBool) t
Body obligation:
typedRequire bodySym viewString t
12.2 Pure phantom signature
Source:
foo @Bool @(List Bool) =@String body
Definition contract:
typedDeclareFn fooSym [(viewBool) (viewList viewBool)] viewString t
Body obligation:
typedRequire bodySym (viewFn [(viewBool) (viewList viewBool)] viewString) t
12.3 Binder prefix with phantom tail
Source:
foo x@Bool @(List Bool) =@String body
Definition contract:
typedDeclareFn fooSym [(viewBool) (viewList viewBool)] viewString t
typedValue xSym viewBool t
Body obligation:
typedRequire bodySym (viewFn [(viewList viewBool)] viewString) t
12.4 Value annotation
Source:
message =@String "hello"
Definition contract:
typedValue messageSym viewString t
Body obligation:
typedRequire bodySym viewString t
13. tricu check
tricu check consumes an annotated program, lowers annotations to typed program
metadata, runs the checker, and reports either ok or rendered diagnostics.
Initial behavior:
tricu check path/to/program.tri
outputs checker success or errors. Diagnostics are rendered by the portable checker, then annotated by the frontend with source/debug labels when available:
id x@String =@Bool x
reports:
symbol 1 (x) expected Bool but got String
Application result labels include the application head when known:
xs =@(List String) [(g "hi")]
g y@String =@Bool y
reports:
symbol 3 (g application result) expected String but got Bool
These labels are presentation-only metadata. The checker still judges only the emitted typed-program evidence.
Future behavior may include:
tricu check --out path/to/executable.arboricx path/to/program.tri
which checks an annotated source program and emits an executable Arboricx bundle.
The checker library remains available independently of the CLI workflow.
14. Frontend Lowering Boundaries
The annotation syntax is frontend sugar. The canonical checker input remains a
plain typed program: ordinary typedValue, typedDeclareFn,
typedRequire, and typedApply nodes represented as portable tricu
data.
The frontend may emit richer evidence from source forms, but it does not decide semantic compatibility. In short:
Haskell emits evidence.
tricu judges evidence.
Current source-driven evidence includes:
- literal views for strings, bytes, unit, and homogeneous list literals;
- expected element requirements for
List Tbodies; - expected
Fnrequirements for lambda literals and curried application spines; - application argument requirements when the callee has a known
Fnview; - expected constructor flow for unshadowed stdlib constructors:
pairwith expectedPair A B;justandnothingwith expectedMaybe A;okanderrwith expectedResult E A.
Constructor lowering only applies when the constructor name is not shadowed by a
local binder or top-level definition in the checked source. If a program defines
its own pair, just, nothing, ok, or err, checking falls back to normal
application evidence.
For tooling and regression tests, the frontend exposes a lowering-only API that returns emitted typed program text without invoking the checker:
lowerSource :: String -> Either String String
It also exposes debug labels for symbols:
lowerSourceWithDebug :: String -> Either String (String, Map Integer String)
Debug labels are presentation metadata only. They are not part of checker
semantics and are not consumed by lib/view.tri.
do blocks have no separate View Contract semantics. The parser lowers them
through their explicit bind operator:
do bind
x <- action
next x
becomes ordinary application/lambda structure. Checking then follows the known
Fn view of the bind operator, including the callback argument view when it is
available.
15. Summary
The annotation syntax is:
name arg@A arg2@B =@C body
name @A @B =@C body
name arg@A @B =@C body
name =@C body
Core rules:
- Binder annotations introduce binders and argument views.
- Phantom annotations introduce argument views only.
- Phantom annotations may only appear after all binders.
- Unannotated binders in contract-bearing heads contribute
Any. - Missing return annotations in contract-bearing heads default to
Any. - Nullary
=@Tdefinitions are value contracts, not zero-arity functions. - Compound annotation types must be parenthesized.
- Lowering emits ordinary typed-program nodes for the existing checker.