packages feed

sbv-14.8: SBVTestSuite/TestSuite/CodeGeneration/ArrayCaptures.hs

-----------------------------------------------------------------------------
-- |
-- Module    : TestSuite.CodeGeneration.ArrayCaptures
-- Copyright : (c) Levent Erkok
-- License   : BSD3
-- Maintainer: erkokl@gmail.com
-- Stability : experimental
--
-- Reject unsupported array-lambda captures before emitting C artifacts.
-----------------------------------------------------------------------------

{-# OPTIONS_GHC -Wall -Werror #-}

module TestSuite.CodeGeneration.ArrayCaptures (tests) where

import Control.Exception (ErrorCall, displayException, try)
import Control.Monad (void)
import Data.List (isInfixOf)
import System.Directory (listDirectory)
import System.IO.Temp (withSystemTempDirectory)
import Test.Tasty.HUnit (assertBool, assertEqual)

import Data.SBV.Tools.CodeGen
import Utils.SBVTestFramework

-- | Captures in ordinary and operator-embedded operands must fail clearly,
-- including in nested callbacks and library components. Existing frontend
-- restrictions remain in place; closed callbacks remain supported.
tests :: TestTree
tests = testGroup "CodeGeneration.ArrayCaptures"
  [ rejects "direct result capture" captureDiagnostic $ do
      value <- cgInput "value" :: SBVCodeGen SWord32
      cgReturn (lambdaArray (const value) :: SArray Word8 Word32)
  , rejects "arithmetic capture" captureDiagnostic $ do
      value <- cgInput "value" :: SBVCodeGen SWord32
      cgReturn (lambdaArray (+ value) :: SArray Word32 Word32)
  , rejects "managed value capture" captureDiagnostic $ do
      value <- cgInput "value" :: SBVCodeGen SString
      cgReturn (lambdaArray (const value) :: SArray Word8 String)
  , rejects "array descriptor capture" captureDiagnostic $ do
      value <- cgInput "value" :: SBVCodeGen (SArray Word32 Word32)
      cgReturn (lambdaArray (readArray value) :: SArray Word32 Word32)
  , rejects "table entry capture" captureDiagnostic $ do
      value <- cgInput "value" :: SBVCodeGen SWord32
      cgReturn (lambdaArray (select [value, 17] 0) :: SArray Word8 Word32)
  , rejects "table default capture" captureDiagnostic $ do
      cgPerformRTCs True
      value <- cgInput "value" :: SBVCodeGen SWord32
      cgReturn (lambdaArray (select [3, 17] value) :: SArray Word8 Word32)
  , rejects "unchecked table default capture" captureDiagnostic $ do
      cgPerformRTCs False
      value <- cgInput "value" :: SBVCodeGen SWord32
      cgReturn (lambdaArray (select [3, 17] value) :: SArray Word8 Word32)
  , rejects "rounding-mode operand capture" captureDiagnostic $ do
      rounding <- cgInput "rounding" :: SBVCodeGen SRoundingMode
      cgReturn (lambdaArray (toSFloat rounding) :: SArray Word32 Float)
  , rejects "capture hidden in nested array" captureDiagnostic $ do
      value <- cgInput "value" :: SBVCodeGen SWord32
      cgReturn (lambdaArray (\_ -> lambdaArray (+ value)) :: SArray Word8 (ArrayModel Word32 Word32))
  , rejects "nested parameter hidden in a rounding-mode operand" captureDiagnostic $
      cgReturn (lambdaArray (lambdaArray . toSFloat) :: SArray RoundingMode (ArrayModel Word32 Float))
  , rejects "defined-function parameter hidden in a rounding-mode operand" captureDiagnostic $ do
      rounding <- cgInput "rounding" :: SBVCodeGen SRoundingMode
      cgReturn (smtFunction "rounding array" (\mode -> lambdaArray (toSFloat mode) :: SArray Word32 Float) rounding)
  , rejects "nested parameter capture remains frontend-rejected" "Detected free variables passed to a lambda" $
      cgReturn (lambdaArray (\outer -> lambdaArray (+ outer)) :: SArray Word32 (ArrayModel Word32 Word32))
  , rejects "defined-function parameter capture remains frontend-rejected" "Detected free variables passed to a lambda" $ do
      value <- cgInput "value" :: SBVCodeGen SWord32
      cgReturn (smtFunction "capturing array" (\offset -> lambdaArray (+ offset) :: SArray Word32 Word32) value)
  , testCase "library rejects captures without writing earlier components" libraryCapture
  , testCase "closed nested callbacks with literal constants still compile" closedArrays
  ]

-- | Stable user-facing diagnostic shared by all C-side capture failures.
captureDiagnostic :: String
captureDiagnostic = "Array lambdas that capture outer symbolic values are not supported"

-- | A failed standalone generation must create no files and must not expose
-- the scheduler's internal missing-assignment error as its capture diagnostic.
rejects :: String -> String -> SBVCodeGen () -> TestTree
rejects testName diagnostic program = testCase testName $
  withSystemTempDirectory "sbv-c-array-capture-rejection" $ \dir -> do
    result <- try (compileToC (Just dir) "capturingArray" (cgGenerateDriver False >> program)) :: IO (Either ErrorCall ())
    checkRejection diagnostic result
    assertEqual "Rejected generation must not create files" [] =<< listDirectory dir

-- | Keep whole-library preflight atomic even when a valid component precedes
-- the component with an unsupported capture.
libraryCapture :: Assertion
libraryCapture = withSystemTempDirectory "sbv-c-array-capture-library" $ \dir -> do
  let closed = cgGenerateDriver False >> cgReturn (lambdaArray (+ 3) :: SArray Word32 Word32)
      capturing = do
        cgGenerateDriver False
        value <- cgInput "value" :: SBVCodeGen SWord32
        cgReturn (lambdaArray (+ value) :: SArray Word32 Word32)
  result <- try (void $ compileToCLib (Just dir) "captureLibrary" [("closedArray", closed), ("capturingArray", capturing)])
  checkRejection captureDiagnostic result
  assertEqual "Rejected library must not create any component files" [] =<< listDirectory dir

-- | Check the explicit unsupported boundary instead of accepting any failure.
checkRejection :: String -> Either ErrorCall () -> Assertion
checkRejection diagnostic result = case result of
  Left err -> do
    let message = displayException err
    assertBool message (diagnostic `isInfixOf` message)
    assertBool "Capture diagnostics must not expose an internal scheduling failure"
               (not ("Missing assignment" `isInfixOf` message))
  Right () -> assertFailure "Expected generation to reject an array-lambda capture"

-- | Haskell lexical bindings that contain only literal constants do not
-- require a runtime environment, nor does nesting otherwise closed lambdas.
closedArrays :: Assertion
closedArrays = withSystemTempDirectory "sbv-c-closed-array-lambdas" $ \dir -> do
  compileToC (Just dir) "closedArrays" $ do
    cgOverwriteFiles True
    cgGenerateDriver False
    let offset = literal (3 :: Word32)
    cgReturn (lambdaArray (\_ -> lambdaArray (+ offset)) :: SArray Word8 (ArrayModel Word32 Word32))
  assertBool "Closed callbacks still produce C artifacts" . not . null =<< listDirectory dir