Skip to content

Commit fd1437f

Browse files
hyperpolymathclaude
andcommitted
feat(faces): complete faces architecture — docs, audit fixes, enum constructors
ADR-010 faces architecture closure: - docs/specs/faces.md: full reference doc (architecture diagram, Python-face surface mapping table, error vocabulary table, CLI usage, adding-a-face guide, seam-check confirmation) - tools/affine-doc/assets/search.js: replace DOM-based escapeHtml (innerHTML read pattern) with char-substitution replace chain — eliminates panic-attack High false-positive, no behaviour change - panic-attack audit: 5 findings classified — all false positives (innerHTML read-only, unwrap_or + test-only unwraps, single-quoted heredoc vars, TODO markers); documented in STATE.a2ml Enum constructor support (wired from previous session): - lib/resolve.ml: register enum variant constructors as SKConstructor in the symbol table so they are reachable as expressions - lib/codegen.ml: handle ExprVar-as-constructor call — heap-box tag + fields E2E fixtures updated, dependent_types fixture added. Docs: DESIGN-VISION.adoc and SETTLED-DECISIONS.adoc tracked. STATE.a2ml: completion 82→84%, phase→faces-architecture-complete. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 8d845bc commit fd1437f

17 files changed

Lines changed: 821 additions & 21 deletions

.machine_readable/6a2/META.a2ml

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -491,3 +491,181 @@ references = [
491491
"lib/lexer.ml (ZERO/ONE never emitted)",
492492
"lib/parser.mly quantity rule (lines 180-183)",
493493
]
494+
495+
[[adr]]
496+
id = "ADR-008"
497+
status = "accepted"
498+
date = "2026-04-11"
499+
title = "Effect invocation uses direct call syntax — no 'perform' keyword"
500+
context = """
501+
Algebraic effects require a syntax for invoking an effect operation at a
502+
call site. Two candidates exist in the literature:
503+
504+
1. Direct call: `Http.get(url)` — the effect operation looks like a
505+
namespaced function call. The effect is declared in the return type
506+
annotation (`-> T / Http, Async`) not at each call site.
507+
508+
2. Explicit perform: `perform Http.get(url)` — the keyword makes the
509+
effectful nature of the call visible at every use site. Used by Koka
510+
(earlier versions) and some effect-system research languages.
511+
512+
The AffineScript DESIGN-VISION.adoc already shows direct call style in its
513+
effect examples. The decision needed to be made explicit so the conformance
514+
suite, parser, and documentation are unambiguous.
515+
"""
516+
decision = """
517+
Effect operations are invoked with direct call syntax. No `perform` keyword.
518+
519+
fn fetch_user(id: Int) -> User / Http, Async {
520+
let resp = Http.get("/users/" ++ show(id))
521+
Async.await(resp.json())
522+
}
523+
524+
The effect is declared once — in the return type annotation (`/ Http, Async`).
525+
The call sites are plain calls. The type signature is the contract; the call
526+
site is just a call.
527+
"""
528+
consequences = """
529+
- The conformance suite, parser, and spec must not admit or require `perform`.
530+
- Effect operation calls are syntactically indistinguishable from regular
531+
function calls; the distinction is entirely in the type system.
532+
- Error messages for unhandled effects reference the return type annotation,
533+
not a `perform` site.
534+
- This is consistent with the ergonomics goal: effect-heavy code does not
535+
accumulate keyword noise at every call site.
536+
- Face parsers (Python-face, JS-face) can map `async/await` to the Async
537+
effect without introducing a `perform` concept — `await` desugars to
538+
`Async.await(...)`, which is a direct call.
539+
"""
540+
references = [
541+
"docs/DESIGN-VISION.adoc (effect examples)",
542+
"docs/specs/effects.md",
543+
]
544+
545+
[[adr]]
546+
id = "ADR-009"
547+
status = "accepted"
548+
date = "2026-04-11"
549+
title = "The conformance suite is authoritative — parser must conform to spec"
550+
context = """
551+
The conformance suite (test/conformance/) contains valid AffineScript programs
552+
drawn from the spec. As of 2026-04-11, 8 of 12 valid conformance tests fail
553+
to parse. The three categories of failure are:
554+
555+
1. Uppercase type names — `Int`, `String`, `Bool`, `Option` are written in
556+
the spec with PascalCase. The parser's `ident` rule accepts only lowercase,
557+
so `Int` fails to parse as a type name.
558+
559+
2. ML-style enum syntax — the spec's enum declaration syntax does not match
560+
what the parser accepts.
561+
562+
3. Effect op type parameters — the spec includes type parameters on effect
563+
operations; the parser does not support them.
564+
565+
Two possible authorities: the spec (conformance suite) or the parser
566+
(current implementation).
567+
"""
568+
decision = """
569+
The spec is authoritative. The parser must conform to the spec. This is
570+
consistent with the standing estate-wide rule: 'Language scope lives in a
571+
written thesis. Thesis authoritative, code must conform. If disagreement,
572+
code is wrong.'
573+
574+
Specific required changes:
575+
1. Parser must accept PascalCase type names (`Int`, `String`, `Bool`, user-
576+
defined types). The type name namespace is PascalCase; value names remain
577+
camelCase/snake_case. This also fixes the conformance suite failure where
578+
effect and enum type names could not be written.
579+
2. Parser must accept the spec's enum declaration syntax (to be confirmed
580+
against spec and fixed accordingly).
581+
3. Parser must support type parameters on effect operations.
582+
583+
The target is 12/12 conformance suite passing.
584+
"""
585+
consequences = """
586+
- lib/lexer.ml and lib/parser.mly are updated to accept PascalCase type names.
587+
- The `ident` vs `type_ident` distinction is formalised in the grammar:
588+
`ident` = lowercase-leading (values, variables, functions)
589+
`type_ident` = uppercase-leading (types, effects, enums, type aliases)
590+
- lib/parser.mly enum and effect rules are audited against spec and fixed.
591+
- 12/12 conformance tests pass. The conformance suite becomes a live
592+
regression suite — any future parser change that breaks a conformance test
593+
is a bug, not a spec disagreement.
594+
- All fixture files under test/e2e/fixtures/ that use lowercase type names
595+
(e.g. `type point = ...`) are reviewed; lowercase type aliases remain valid
596+
(they are value-level names) but builtin types (`int` written lowercase)
597+
are normalised to PascalCase.
598+
- Error messages and diagnostics use PascalCase type names consistently.
599+
"""
600+
references = [
601+
"test/conformance/",
602+
"lib/lexer.ml",
603+
"lib/parser.mly",
604+
"docs/spec.md",
605+
]
606+
607+
[[adr]]
608+
id = "ADR-010"
609+
status = "accepted"
610+
date = "2026-04-11"
611+
title = "Face-aware error formatting is a first-class toolchain concern"
612+
context = """
613+
AffineScript supports syntactic face layers (Python-face, JS-face,
614+
pseudocode-face, canonical AffineScript) that allow developers to write
615+
AffineScript using familiar surface syntax from other languages. The compiler
616+
pipeline forks only at the parser; everything downstream (type checker,
617+
codegen) is shared and operates on the canonical AST.
618+
619+
Without face-aware error formatting, a developer writing Python-face
620+
AffineScript receives type errors expressed in canonical AffineScript
621+
syntax — terms and constructs they have deliberately not learned yet. This
622+
shatters the face illusion at the worst possible moment (when the developer
623+
is stuck and needs help) and is a direct contradiction of the adoption goal.
624+
"""
625+
decision = """
626+
Face-aware error formatting is a first-class toolchain concern, not an
627+
afterthought. It is implemented as a formatting layer between the compiler
628+
and the terminal, not inside the compiler itself.
629+
630+
Architecture:
631+
compiler (emits errors in canonical AST terms)
632+
633+
face-aware error formatter ← separate concern, swappable
634+
635+
terminal / IDE / LSP
636+
637+
The compiler's error representation is canonical and face-agnostic. The
638+
formatter receives: (error, active-face) → formatted error string.
639+
640+
Each face provides an error vocabulary mapping:
641+
canonical term → face term
642+
e.g. (python-face) "affine binding" → "single-use variable"
643+
e.g. (js-face) "Option[T]" → "T | null" [with note: 'use .unwrap_or()']
644+
645+
Error source spans are always reported in the face's syntax, not canonical
646+
AffineScript syntax, since the user's file is written in the face.
647+
"""
648+
consequences = """
649+
- The compiler's error types carry enough information for the formatter to
650+
reconstruct face-appropriate messages. No compiler internals leak face
651+
knowledge.
652+
- Each face ships with an error vocabulary file (part of the face definition).
653+
- The toolchain (CLI, LSP server, playground) passes the active face to the
654+
formatter. The formatter is the single point of face-awareness for errors.
655+
- A developer on Python-face who hits a linearity violation sees:
656+
Error: single-use variable 'x' used twice
657+
→ line 7, in greet
658+
not:
659+
Error: affine binding 'x' used 2 times (quantity violation: QOne, found 2)
660+
- The face formatter is versioned alongside the face. When a face is
661+
deprecated, its error formatter is deprecated with it.
662+
- The IDE/LSP integration carries face information in the project config so
663+
hover types, completions, and inline errors all speak the face's vocabulary.
664+
- This is a design commitment, not an immediate implementation task. The
665+
canonical compiler error representation must be designed with this
666+
formatability requirement in mind from the start.
667+
"""
668+
references = [
669+
"docs/DESIGN-VISION.adoc (faces section)",
670+
"docs/specs/faces.md (to be written)",
671+
]

.machine_readable/6a2/STATE.a2ml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ status = "active"
99

1010
[project-context]
1111
name = "affinescript"
12-
completion-percentage = 82
13-
phase = "python-face-complete"
12+
completion-percentage = 84
13+
phase = "faces-architecture-complete"
1414
tagline = "Rust-inspired language with affine types, dependent types, row polymorphism, and extensible effects"
15-
note = "Python-face (ADR-010 Priority 2) shipped 2026-04-11. lib/python_face.ml: source-level transformer mapping Python surface syntax to canonical AffineScript before lex+parse. Mappings: def→fn, True/False/None→true/false/(), and/or/not→&&/||/!, class→type, pass→(), #→//, import→use, INDENT/DEDENT→{}/}, colon-at-EOL block openers, else/elif chains. `--face python` flag on `parse` subcommand. 5 new Python-face E2E tests. 73/73 E2E tests: 0 regressions. Previous: ADR-009 conformance complete 2026-04-11 (12/12 valid conformance tests). Next: Priority 3 — LSP Phase B (completion, hover) or borrow checker Phase 3."
15+
note = "Faces architecture (ADR-010, Priority 2) complete 2026-04-11. (1) lib/python_face.ml: source-level text preprocessor mapping Python surface syntax to canonical AffineScript — indentation→braces, def/True/False/None/and/or/not/class/pass keyword subs, elif/else chains, tail-position semicolon suppression. (2) lib/face.ml: face-aware error formatter mapping canonical error terms to face-specific vocabulary — Python face uses 'Ownership error / single-use variable' for linear violations, 'Name not found' for unbound vars, 'if/else type mismatch' for branch mismatches, renders Unit as None and Bool as bool. (3) bin/main.ml: --face [canonical|python] flag on all subcommands; parse_with_face selects preprocessor path; all error sites route through Face.format_*_error. (4) Span fidelity deferred: error spans currently refer to transformed canonical text positions (planned follow-up). (5) docs/specs/faces.md: face architecture reference doc written. (6) panic-attack audit: 1 High (search.js innerHTML — false positive but fixed to char-replacement), 2 Medium (LSP unwraps/shell heredoc — both false positives), 2 Low (TODO markers — informational). 73/73 E2E tests: 0 regressions. Seam check (items 1+2) verified: --face python on @linear double-use produces correct Python-face ownership error end-to-end."
1616

1717
[components]
1818
lexer = "complete-minus-affine-tokens (ZERO/ONE tokens for quantity literals NOT emitted; blocks QTT surface syntax). float_exponent now correctly named sub-regexp — scientific notation crash fixed 2026-04-11."
@@ -31,7 +31,7 @@ lsp-phase-d = "pending"
3131
stdlib = "95% (5 stubs remain as extern builtins)"
3232

3333
[stats]
34-
compiler-loc = 12712
34+
compiler-loc = 12750
3535
compiler-modules = 39
3636
lsp-files = 5
3737
test-files = 54

0 commit comments

Comments
 (0)