penelope-core-0.1.0.0: test/Inspection.hs
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# OPTIONS_GHC -fplugin=Polytropos.Plugin #-}
{-# OPTIONS_GHC -fplugin=Test.Inspection.Plugin #-}
{-# OPTIONS_GHC -dsuppress-all #-}
{- |
Module : Main
Description : Milestone 6.1 GHC Core inspection tests
Copyright : (c) 2026 The Penelope Authors
License : EUPL-1.2 OR Apache-2.0
Maintainer : nadia.yvette.chambers@ik.me
Stability : experimental
Portability : non-portable (GHC extensions)
Formally certifies GHC Core representation invariants via inspection-testing:
1. 'sendPolyF' and 'sendPolyH' compile to literal unboxed 'Word#' constants with
zero residual dictionaries ('ConstructorIndex', 'KnownRowMorph').
2. 'elaborateAll' and 'interpretAll' dispatch via branchless 'indexSmallArray#'
reads with zero 'eqWord#' comparisons and zero linear search cascades.
See Note [Core Architecture] [ref:note-core-architecture] and
Note [Unpacked Polycategorical Representation] [ref:note-unpacked-representation].
-}
module Main where
import Test.Hspec
import Test.Inspection
import Penelope.Core
import Polytropos (Field (..), Record, emptyRecord, insertField)
import Polytropos.Core (ConstructorIndex, Pair (..), Rep (..), RowRep (..))
import Polytropos.RowMorph (KnownRowMorph)
import Data.Functor.Identity (Identity (..))
import Data.Primitive.SmallArray (indexSmallArray)
import GHC.Exts (eqWord#, unsafeCoerce#)
-- Test effect signatures
data TestGet :: PolyEffect
data TestPut :: PolyEffect
type TestEF = '["get" ':= TestGet, "put" ':= TestPut]
data TestCatch :: PolyHEffect
type TestEH = '["catch" ':= TestCatch]
-- Target 1: sendPolyF compiled term
{-# NOINLINE sendTestF #-}
sendTestF :: Penelope '[] TestEF '["x" ':= Int] '["x" ':= Int]
sendTestF =
let retCont = BranchCont (\_ -> route @'["x" ':= Int] @'["x" ':= Int])
conts = insertField (Field @"ret") retCont emptyRecord
in sendPolyF' @"get" @'["x" ':= Int] (100 :: Int) conts
-- Target 2: sendPolyH compiled term
{-# NOINLINE sendTestH #-}
sendTestH :: Penelope TestEH '[] '["x" ':= Int] '["x" ':= Int]
sendTestH =
let retCont = BranchCont (\_ -> route @'["x" ':= Int] @'["x" ':= Int])
conts = insertField (Field @"ret") retCont emptyRecord
subProg :: Penelope TestEH '[] '["x" ':= Int] '["x" ':= Int]
subProg = route
reg = Region @TestEH @'[] @('PortSig '["x" ':= Int] '["x" ':= Int]) subProg
regions = insertField (Field @"body") reg emptyRecord
in sendPolyH' @"catch" @'["x" ':= Int] regions () conts
-- Target 3: elaborateAll wrapper and evaluation
{-# NOINLINE runElaborateAll #-}
runElaborateAll ::
Record (MapRow (Elaborator '[] TestEF) TestEH)
-> Penelope TestEH TestEF '["x" ':= Int] '["x" ':= Int]
-> Penelope '[] TestEF '["x" ':= Int] '["x" ':= Int]
runElaborateAll = elaborateAll
{-# NOINLINE elaborateTest #-}
elaborateTest :: Penelope '[] TestEF '["x" ':= Int] '["x" ':= Int]
elaborateTest =
let testElaborator :: Elaborator '[] TestEF TestCatch
testElaborator = Elaborator $ \inMap regs _ kConts ->
let !(Identity elabBody) = indexSmallArray (let RowRep (Rep _ arr) = regs in arr) 0
!(Region body) = unsafeCoerce# elabBody
!(Identity elabK) = indexSmallArray (let RowRep (Rep _ arr) = kConts in arr) 0
!(BranchCont retK) = unsafeCoerce# elabK
in preComposeRoute inMap (unsafeCoerce# (body `seqPenelope` retK emptyRecord))
elabs = insertField (Field @"catch") testElaborator emptyRecord
prog :: Penelope TestEH TestEF '["x" ':= Int] '["x" ':= Int]
prog = route
in elaborateAll elabs prog
-- Target 4: interpretAll wrapper and evaluation
{-# NOINLINE runInterpretAll #-}
runInterpretAll ::
Record (MapRow (Handler '[] (FrameKleisli Identity)) TestEF)
-> Penelope '[] TestEF '["x" ':= Int] '["x" ':= Int]
-> FrameKleisli Identity '["x" ':= Int] '["x" ':= Int]
runInterpretAll = interpretAll
{-# NOINLINE interpretTest #-}
interpretTest :: FrameKleisli Identity '["x" ':= Int] '["x" ':= Int]
interpretTest =
let getHandler :: Handler '[] (FrameKleisli Identity) TestGet
getHandler = Handler $ \_inMap _payload kConts ->
let !(Identity elabK) = indexSmallArray (let RowRep (Rep _ arr) = kConts in arr) 0
!(BranchCont retK) = unsafeCoerce# elabK
resFrame = insertField (Field @"x") (99 :: Int) emptyRecord
in FrameKleisli $ \_inF -> runFrameKleisli (retK resFrame) resFrame
putHandler :: Handler '[] (FrameKleisli Identity) TestPut
putHandler = Handler $ \_inMap _payload kConts ->
let !(Identity elabK) = indexSmallArray (let RowRep (Rep _ arr) = kConts in arr) 0
!(BranchCont retK) = unsafeCoerce# elabK
in FrameKleisli $ \inF -> runFrameKleisli (retK emptyRecord) inF
handlers = insertField (Field @"get") getHandler
$ insertField (Field @"put") putHandler emptyRecord
prog :: Penelope '[] TestEF '["x" ':= Int] '["x" ':= Int]
prog = route
in interpretAll handlers prog
-- Core Inspection Obligations
inspect $ 'sendTestF `hasNoType` ''ConstructorIndex
inspect $ 'sendTestF `hasNoType` ''KnownRowMorph
inspect $ hasNoTypeClasses 'sendTestF
inspect $ 'sendTestH `hasNoType` ''ConstructorIndex
inspect $ 'sendTestH `hasNoType` ''KnownRowMorph
inspect $ hasNoTypeClasses 'sendTestH
inspect $ 'runElaborateAll `doesNotUse` 'eqWord#
inspect $ 'elaborateTest `doesNotUse` 'eqWord#
inspect $ 'runInterpretAll `doesNotUse` 'eqWord#
inspect $ 'interpretTest `doesNotUse` 'eqWord#
main :: IO ()
main = hspec $ do
describe "Penelope.Core Inspection Obligations" $ do
it "sendPolyF eliminates ConstructorIndex dictionary" $ do
case $(inspectTest $ 'sendTestF `hasNoType` ''ConstructorIndex) of
Success msg -> putStrLn $ " [PASS] " ++ msg
Failure err -> expectationFailure err
it "sendPolyF eliminates KnownRowMorph dictionary" $ do
case $(inspectTest $ 'sendTestF `hasNoType` ''KnownRowMorph) of
Success msg -> putStrLn $ " [PASS] " ++ msg
Failure err -> expectationFailure err
it "sendPolyF passes zero dictionaries (hasNoTypeClasses)" $ do
case $(inspectTest $ hasNoTypeClasses 'sendTestF) of
Success msg -> putStrLn $ " [PASS] " ++ msg
Failure err -> expectationFailure err
it "sendPolyH eliminates ConstructorIndex dictionary" $ do
case $(inspectTest $ 'sendTestH `hasNoType` ''ConstructorIndex) of
Success msg -> putStrLn $ " [PASS] " ++ msg
Failure err -> expectationFailure err
it "sendPolyH eliminates KnownRowMorph dictionary" $ do
case $(inspectTest $ 'sendTestH `hasNoType` ''KnownRowMorph) of
Success msg -> putStrLn $ " [PASS] " ++ msg
Failure err -> expectationFailure err
it "sendPolyH passes zero dictionaries (hasNoTypeClasses)" $ do
case $(inspectTest $ hasNoTypeClasses 'sendTestH) of
Success msg -> putStrLn $ " [PASS] " ++ msg
Failure err -> expectationFailure err
it "elaborateAll does not use eqWord#" $ do
case $(inspectTest $ 'runElaborateAll `doesNotUse` 'eqWord#) of
Success msg -> putStrLn $ " [PASS] " ++ msg
Failure err -> expectationFailure err
it "elaborateTest does not use eqWord#" $ do
case $(inspectTest $ 'elaborateTest `doesNotUse` 'eqWord#) of
Success msg -> putStrLn $ " [PASS] " ++ msg
Failure err -> expectationFailure err
it "interpretAll does not use eqWord#" $ do
case $(inspectTest $ 'runInterpretAll `doesNotUse` 'eqWord#) of
Success msg -> putStrLn $ " [PASS] " ++ msg
Failure err -> expectationFailure err
it "interpretTest does not use eqWord#" $ do
case $(inspectTest $ 'interpretTest `doesNotUse` 'eqWord#) of
Success msg -> putStrLn $ " [PASS] " ++ msg
Failure err -> expectationFailure err