Files
tricu/docs/view-contract-syntax.md
James Eversole fdebb6c13d Tricu 2.0.0
Sorry for squashing all of this but 🤷
2026-05-25 12:44:24 -05:00

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 T bodies;
  • expected Fn requirements for lambda literals and curried application spines;
  • application argument requirements when the callee has a known Fn view;
  • expected constructor flow for unshadowed stdlib constructors:
    • pair with expected Pair A B;
    • just and nothing with expected Maybe A;
    • ok and err with expected Result 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:

  1. Binder annotations introduce binders and argument views.
  2. Phantom annotations introduce argument views only.
  3. Phantom annotations may only appear after all binders.
  4. Unannotated binders in contract-bearing heads contribute Any.
  5. Missing return annotations in contract-bearing heads default to Any.
  6. Nullary =@T definitions are value contracts, not zero-arity functions.
  7. Compound annotation types must be parenthesized.
  8. Lowering emits ordinary typed-program nodes for the existing checker.