You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Part of #75 (Phase 2). The single largest frontend blocker: 43 passing files stop here, and almost all of them on the same form.
Scope
(e :: T) in expression position.
The bare e :: T form, as in m0L = M0 0 [1, 2] :: MA Int.
Nested chains: (((\_ -> 0) :: b -> Int) :: forall b. b -> Int) :: forall a. a -> Int (passing/2941.purs).
A parenthesized ascription in a do bind: _ <- Nothing :: Maybe Number (passing/Do.purs, passing/Ado.purs).
Ascription on a constructor application and on a type application: (c :: Z Array) (passing/1110.purs), (Proxy :: _ Int) (passing/PolykindInstanceDispatch.purs).
Why it belongs on the spine
An ascription is a type-directed check at a known expression, not a coercion. It must elaborate through the shared representation — typecheck the expression against the written type, then keep the expression with its checked type — not become a wrapper node or a side table. FE-17 owns the elaboration; FE-05 owns the expression form.
Acceptance
Every file in the measured expression-lowering blocker list lowers, the count drops toward zero, and the ascription cases carry purs differential coverage. Type wildcards (_ as passing/PolykindInstanceDispatch.purs uses) land with or immediately after this, since they appear in the same position.
Landed in #111. The ascription is checked against its written type and leaves no node behind.
The design
An ascription is a type-directed check at a known expression, not a coercion, so it elaborates on the spine instead of becoming a wrapper. AST and HIR carry Typed { expression, ty }; the checker elaborates the written type the way it elaborates a signature, checks the expression against it, and returns the expression carrying that type. Typed Core has no Typed node, there is no runtime representation, and there is no side table. The ascription is erased by type checking, which is the only correct answer because e :: T has no runtime effect.
A written forall is checked with skolems and becomes the result type, so f = (\_ -> 0) :: forall b. b -> Int needs no signature.
Measured: 28 passing files recovered
P2 surface lowering drops from 134 to 106 in both the L2 and L6 boards.
The missing-module count rises from 216 to 244 for the same reason — those files now reach resolution and stop on Effect.Console. Both numbers are recorded together in D-04, because either alone would be misleading: it looks like the library blocker got worse when the frontend blocker got better by more.
Two more failing cases now reach the type checker, so the denominators moved with the numerators:
Gate
Before
After
M4 types
12/38
12/40
M5 classes
37/74
41/81
Scope not covered, and why
A free type variable in an ascription does not work yet — passing/2941.purs alone, in the chain ((\_ -> 0) :: b -> Int) :: forall b. b -> Int. purs resolves the inner b against the outer forall b, which needs the enclosing signature's variables in scope while the body is checked. I tried sharing them and produced a second set of variables that could not unify, so the work stays open with #87 (type wildcards) rather than landing a wrong rule.
The bare form (e :: T), the unparenthesized e :: T, and a written forall all work.
Type wildcards did not land
The issue says wildcards _ land with or immediately after this, since they appear in the same position. They did not: a wildcard is a fresh type variable at a known position, and the resolution above — which purs also applies to wildcards — is the same missing piece. Leaving it to #87 keeps one rule in one place instead of two partial ones.
Evidence: cargo fmt --all --check; cargo clippy --workspace --all-targets -- -D warnings; PSRS_REQUIRE_WASMTIME=1 cargo test --workspace (992 passed, 0 failed, 0 skipped); CI green on #111.
Activity
biuld commentedon Oct 2, 2026
Landed in #111. The ascription is checked against its written type and leaves no node behind.
The design
An ascription is a type-directed check at a known expression, not a coercion, so it elaborates on the spine instead of becoming a wrapper. AST and HIR carry
Typed { expression, ty }; the checker elaborates the written type the way it elaborates a signature, checks the expression against it, and returns the expression carrying that type. Typed Core has noTypednode, there is no runtime representation, and there is no side table. The ascription is erased by type checking, which is the only correct answer becausee :: Thas no runtime effect.A written
forallis checked with skolems and becomes the result type, sof = (\_ -> 0) :: forall b. b -> Intneeds no signature.Measured: 28
passingfiles recoveredP2 surface loweringdrops from 134 to 106 in both the L2 and L6 boards.The missing-module count rises from 216 to 244 for the same reason — those files now reach resolution and stop on
Effect.Console. Both numbers are recorded together in D-04, because either alone would be misleading: it looks like the library blocker got worse when the frontend blocker got better by more.Two more
failingcases now reach the type checker, so the denominators moved with the numerators:Scope not covered, and why
A free type variable in an ascription does not work yet —
passing/2941.pursalone, in the chain((\_ -> 0) :: b -> Int) :: forall b. b -> Int.pursresolves the innerbagainst the outerforall b, which needs the enclosing signature's variables in scope while the body is checked. I tried sharing them and produced a second set of variables that could not unify, so the work stays open with #87 (type wildcards) rather than landing a wrong rule.The bare form
(e :: T), the unparenthesizede :: T, and a writtenforallall work.Type wildcards did not land
The issue says wildcards
_land with or immediately after this, since they appear in the same position. They did not: a wildcard is a fresh type variable at a known position, and the resolution above — whichpursalso applies to wildcards — is the same missing piece. Leaving it to #87 keeps one rule in one place instead of two partial ones.Evidence:
cargo fmt --all --check;cargo clippy --workspace --all-targets -- -D warnings;PSRS_REQUIRE_WASMTIME=1 cargo test --workspace(992 passed, 0 failed, 0 skipped); CI green on #111.feat(frontend): check the expression ascription against its written type