From aa620b767faf6adb90efd9947dcf67a66e43c33f Mon Sep 17 00:00:00 2001 From: giles Date: Fri, 8 May 2026 23:12:35 +0000 Subject: [PATCH] =?UTF-8?q?haskell:=20Phase=2017=20=E2=80=94=20expression?= =?UTF-8?q?=20type=20annotations=20(x=20::=20Int)=20(parse=20+=20desugar?= =?UTF-8?q?=20pass-through)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Parser hk-parse-parens gains a `::` arm after the first inner expression: consume `::`, parse a type via the existing hk-parse-type, expect `)`, emit (:type-ann EXPR TYPE). Sections, tuples, parenthesised expressions and unit `()` are unchanged. Desugar drops the annotation — :type-ann E _ → (hk-desugar E) — since the existing eval path has no type-directed dispatch. Phase 20 will extend infer.sx to consume the annotation and unify against the inferred type. tests/parse-extras.sx (12/12) covers literal, arithmetic, function arg, string, bool, tuple, nested annotation, function-typed annotation, and no-regression checks for plain parens / 3-tuples / left+right sections. eval (66/0), exceptions (14/0), typecheck (15/0), records (14/0), ioref (13/0) all still clean. Co-Authored-By: Claude Opus 4.7 (1M context) --- lib/haskell/desugar.sx | 1 + lib/haskell/parser.sx | 59 +++++++++++++++++------------ lib/haskell/tests/parse-extras.sx | 63 +++++++++++++++++++++++++++++++ plans/haskell-completeness.md | 11 +++++- 4 files changed, 108 insertions(+), 26 deletions(-) create mode 100644 lib/haskell/tests/parse-extras.sx diff --git a/lib/haskell/desugar.sx b/lib/haskell/desugar.sx index c2b5ebdc..17d30ca4 100644 --- a/lib/haskell/desugar.sx +++ b/lib/haskell/desugar.sx @@ -210,6 +210,7 @@ :op (nth node 1) (hk-desugar (nth node 2)) (hk-desugar (nth node 3)))) + ((= tag "type-ann") (hk-desugar (nth node 1))) ((= tag "neg") (list :neg (hk-desugar (nth node 1)))) ((= tag "if") (list diff --git a/lib/haskell/parser.sx b/lib/haskell/parser.sx index 3642d979..b7e0085f 100644 --- a/lib/haskell/parser.sx +++ b/lib/haskell/parser.sx @@ -275,38 +275,47 @@ (list :sect-right op-name expr-e)))))) (:else (let - ((first-e (hk-parse-expr-inner)) - (items (list)) - (is-tuple false)) - (append! items first-e) - (define - hk-tup-loop - (fn - () - (when - (hk-match? "comma" nil) - (do - (hk-advance!) - (set! is-tuple true) - (append! items (hk-parse-expr-inner)) - (hk-tup-loop))))) - (hk-tup-loop) + ((first-e (hk-parse-expr-inner))) (cond - ((hk-match? "rparen" nil) + ((hk-match? "reservedop" "::") (do (hk-advance!) - (if is-tuple (list :tuple items) first-e))) + (let + ((ann-type (hk-parse-type))) + (hk-expect! "rparen" nil) + (list :type-ann first-e ann-type)))) (:else (let - ((op-info2 (hk-section-op-info))) + ((items (list)) (is-tuple false)) + (append! items first-e) + (define + hk-tup-loop + (fn + () + (when + (hk-match? "comma" nil) + (do + (hk-advance!) + (set! is-tuple true) + (append! items (hk-parse-expr-inner)) + (hk-tup-loop))))) + (hk-tup-loop) (cond - ((and (not (nil? op-info2)) (not is-tuple) (let ((after2 (hk-peek-at (get op-info2 "len")))) (and (not (nil? after2)) (= (get after2 "type") "rparen")))) - (let - ((op-name (get op-info2 "name"))) - (hk-consume-op!) + ((hk-match? "rparen" nil) + (do (hk-advance!) - (list :sect-left op-name first-e))) - (:else (hk-err "expected ')' after expression")))))))))))))) + (if is-tuple (list :tuple items) first-e))) + (:else + (let + ((op-info2 (hk-section-op-info))) + (cond + ((and (not (nil? op-info2)) (not is-tuple) (let ((after2 (hk-peek-at (get op-info2 "len")))) (and (not (nil? after2)) (= (get after2 "type") "rparen")))) + (let + ((op-name (get op-info2 "name"))) + (hk-consume-op!) + (hk-advance!) + (list :sect-left op-name first-e))) + (:else (hk-err "expected ')' after expression"))))))))))))))))) (define hk-comp-qual-is-gen? (fn diff --git a/lib/haskell/tests/parse-extras.sx b/lib/haskell/tests/parse-extras.sx new file mode 100644 index 00000000..723dd22d --- /dev/null +++ b/lib/haskell/tests/parse-extras.sx @@ -0,0 +1,63 @@ +;; Phase 17 — parser polish unit tests. + +(hk-test + "type-ann: literal int annotated" + (hk-deep-force (hk-run "main = (42 :: Int)")) + 42) + +(hk-test + "type-ann: arithmetic annotated" + (hk-deep-force (hk-run "main = (1 + 2 :: Int)")) + 3) + +(hk-test + "type-ann: function arg annotated" + (hk-deep-force + (hk-run "f x = x + 1\nmain = f (1 :: Int)")) + 2) + +(hk-test + "type-ann: string annotated" + (hk-deep-force (hk-run "main = (\"hi\" :: String)")) + "hi") + +(hk-test + "type-ann: bool annotated" + (hk-deep-force (hk-run "main = (True :: Bool)")) + (list "True")) + +(hk-test + "type-ann: tuple annotated" + (hk-deep-force (hk-run "main = ((1, 2) :: (Int, Int))")) + (list "Tuple" 1 2)) + +(hk-test + "type-ann: nested annotation in arithmetic" + (hk-deep-force (hk-run "main = (1 :: Int) + (2 :: Int)")) + 3) + +(hk-test + "type-ann: function-typed annotation passes through eval" + (hk-deep-force + (hk-run "main = let f = ((\\x -> x + 1) :: Int -> Int) in f 5")) + 6) + +(hk-test + "no regression: plain parens still work" + (hk-deep-force (hk-run "main = (5)")) + 5) + +(hk-test + "no regression: 3-tuple still works" + (hk-deep-force (hk-run "main = (1, 2, 3)")) + (list "Tuple" 1 2 3)) + +(hk-test + "no regression: section-left still works" + (hk-deep-force (hk-run "main = (3 +) 4")) + 7) + +(hk-test + "no regression: section-right still works" + (hk-deep-force (hk-run "main = (+ 3) 4")) + 7) diff --git a/plans/haskell-completeness.md b/plans/haskell-completeness.md index d3df426f..cddb0516 100644 --- a/plans/haskell-completeness.md +++ b/plans/haskell-completeness.md @@ -316,7 +316,7 @@ No OCaml changes are needed. The view type is fully representable as an SX dict. Real Haskell programs use these on every page; closing the gaps unblocks larger conformance programs and removes one-line workarounds in test sources. -- [ ] Type annotations in expressions: `(x :: Int)`, `f (1 :: Int)`, +- [x] Type annotations in expressions: `(x :: Int)`, `f (1 :: Int)`, `return (42 :: Int)`. Parser currently rejects `::` in `aexp` position; desugar should drop the annotation (we have no inference at this layer yet, so it's a parse-only pass-through). @@ -420,6 +420,15 @@ constraints (qualified types `[ClassName var] => type`). _Newest first._ +**2026-05-08** — Phase 17 first box: expression type annotations `(x :: Int)`, +`f (1 :: Int)`, `(\x -> x+1) :: Int -> Int`. Parser's `hk-parse-parens` +gains a `::` arm after the first inner expression: consume `::`, parse a +type via the existing `hk-parse-type`, expect `)`, emit `(:type-ann EXPR +TYPE)`. Desugar drops the annotation — `:type-ann E _ → (hk-desugar E)` — +since the existing eval path has no type-directed dispatch; Phase 20 will +let inference consume the annotation. tests/parse-extras.sx 12/12; eval, +exceptions, typecheck, records, ioref still clean. + **2026-05-08** — Plan extends with Phases 20-22 (HM type system). Discovered during planning that `lib/haskell/infer.sx` already lands core Algorithm W (75 inference unit tests pass; let-polymorphism, sig checking, error