packages feed

tala-compiler-0.1.0: test/ClosureTests.hs

-- SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com>
-- SPDX-License-Identifier: MPL-2.0 WITH AdditionRef-Progmasoft-Exception-1.1

{- | End-to-end tests for callable literals and closure conversion.

These tests intentionally inspect every compiler boundary.  A parser-only
assertion would miss capture identity bugs, while a CorePrep-only assertion
would make source regressions unnecessarily hard to diagnose.
-}
module ClosureTests (closureTests) where

import Data.List (find, isInfixOf)
import Progmasoft.Tala.AST
import Progmasoft.Tala.BuiltinTypes
import Progmasoft.Tala.Compiler
import Progmasoft.Tala.Core
import Progmasoft.Tala.Core.CorePrep
import Progmasoft.Tala.Core.CorePrep.Verifier
import Progmasoft.Tala.Core.CorePrep.Wire
import Progmasoft.Tala.Core.Verifier
import Progmasoft.Tala.Core.Wire
import Progmasoft.Tala.Diagnostic
import Progmasoft.Tala.Lexer
import Progmasoft.Tala.Parser

closureTests :: [(String, Bool)]
closureTests =
    [ ("lexer recognizes callable and capture delimiters", lexerRecognizesDelimiters)
    , ("typed callable parameters preserve their declared types", typedParameters)
    , ("parenthesized inferred callable parameters are accepted", parenthesizedInferredParameters)
    , ("bare inferred callable parameters are accepted", bareInferredParameters)
    , ("parenthesized zero-parameter callable is accepted", parenthesizedZeroParameters)
    , ("bare zero-parameter callable is accepted", bareZeroParameters)
    , ("callable expression body is retained", expressionBody)
    , ("callable block body is retained", blockBody)
    , ("empty explicit capture list is distinct from implicit capture", emptyExplicitCaptureList)
    , ("normal explicit capture is parsed", normalCapture)
    , ("weak explicit capture is parsed", weakCapture)
    , ("unowned explicit capture is parsed", unownedCapture)
    , ("capture aliases retain their initializer", captureAlias)
    , ("capture source order remains stable", captureOrder)
    , ("duplicate explicit capture is diagnosed", duplicateCapture)
    , ("an omitted explicit capture is rejected", omittedExplicitCapture)
    , ("callable parameters receive private symbols", privateParameterSymbols)
    , ("captured bindings receive private symbols", privateCaptureSymbols)
    , ("typed callable expression produces a function type", callableType)
    , ("callable invocation checks argument count", callableArity)
    , ("callable invocation checks argument types", callableArgumentType)
    , ("weak primitive capture is rejected", weakPrimitiveCapture)
    , ("weak capture rejects every canonical scalar family", weakScalarCaptures)
    , ("unowned primitive capture is rejected", unownedPrimitiveCapture)
    , ("weak String capture is accepted", weakStringCapture)
    , ("weak callable capture is accepted", weakCallableCapture)
    , ("implicit capture is discovered during Core lowering", implicitCaptureDiscovery)
    , ("explicit capture lowers its initializer", explicitCaptureLowering)
    , ("capture alias evaluates outside the callable body", aliasLowering)
    , ("closure conversion lifts a CorePrep function", liftedFunction)
    , ("closure creation records a CorePrep operation", makeClosureOperation)
    , ("lifted closure receives hidden capture parameters first", hiddenCaptureParameters)
    , ("closure result remains callable after CorePrep", closureResultType)
    , ("nested callable conversion reaches a fixed point", nestedClosureConversion)
    , ("Core verifier accepts a well-formed closure", coreVerifierAcceptsClosure)
    , ("Core verifier rejects mismatched closure type", coreVerifierRejectsTypeMismatch)
    , ("Core verifier rejects capture initializer mismatch", coreVerifierRejectsCaptureMismatch)
    , ("Core wire v10 round-trips closure values and ownership", coreWireClosureRoundTrip)
    , ("CorePrep wire v7 round-trips closure creation and ownership", corePrepWireClosureRoundTrip)
    , ("CorePrep verifier accepts converted closure", corePrepVerifierAcceptsClosure)
    , ("CorePrep verifier rejects primitive weak capture", corePrepVerifierRejectsWeakPrimitive)
    ]

lexerRecognizesDelimiters :: Bool
lexerRecognizesDelimiters = case lexText "[weak owner] \\(int value) -> value" of
    Right tokens -> all (`elem` map tokenText tokens) ["[", "]", "\\", "(", ")", "->"]
    Left _ -> False

typedParameters :: Bool
typedParameters = case parsedCallable "\\(int left, int right) -> left + right" of
    Just (CallableExpression _ False [] parameters _ _) ->
        map parameterTypeSyntax parameters
            == [ExplicitType (Identifier "int"), ExplicitType (Identifier "int")]
    _ -> False

parenthesizedInferredParameters :: Bool
parenthesizedInferredParameters = case parsedCallable "\\(left, right) -> left" of
    Just (CallableExpression _ False [] parameters _ _) ->
        map parameterTypeSyntax parameters == [AutoType, AutoType]
    _ -> False

bareInferredParameters :: Bool
bareInferredParameters = case parsedCallable "\\left, right -> right" of
    Just (CallableExpression _ False [] parameters _ _) ->
        map (identifierText . parameterName) parameters == ["left", "right"]
    _ -> False

parenthesizedZeroParameters :: Bool
parenthesizedZeroParameters = callableParameterCount "\\() -> 42" == Just 0

bareZeroParameters :: Bool
bareZeroParameters = callableParameterCount "\\ -> 42" == Just 0

expressionBody :: Bool
expressionBody = case parsedCallable "\\(int value) -> value + 1" of
    Just CallableExpression {} -> case callableBodyOf (parsedCallable "\\(int value) -> value + 1") of
        Just CallableExpressionBody {} -> True
        _ -> False
    _ -> False

blockBody :: Bool
blockBody = case callableBodyOf (parsedCallable "\\(int value) -> { int next = value + 1; next }") of
    Just (CallableBlockBody (Block statements)) -> length statements == 2
    _ -> False

emptyExplicitCaptureList :: Bool
emptyExplicitCaptureList = case parsedCallable "[] \\ -> 42" of
    Just (CallableExpression _ explicit captures _ _ _) -> explicit && null captures
    _ -> False

normalCapture :: Bool
normalCapture = captureModes "[value] \\ -> value" == Just [StrongCapture]

weakCapture :: Bool
weakCapture = captureModes "[weak owner] \\ -> owner" == Just [WeakCapture]

unownedCapture :: Bool
unownedCapture = captureModes "[unowned owner] \\ -> owner" == Just [UnownedCapture]

captureAlias :: Bool
captureAlias = case parsedCallable "[answer = seed + 1] \\ -> answer" of
    Just (CallableExpression _ True [Capture _ StrongCapture (Identifier "answer") _ (Just BinaryExpression {})] _ _ _) -> True
    _ -> False

captureOrder :: Bool
captureOrder = case parsedCallable "[first = one, second = two, third = three] \\ -> first" of
    Just (CallableExpression _ True captures _ _ _) ->
        map (identifierText . captureName) captures == ["first", "second", "third"]
    _ -> False

duplicateCapture :: Bool
duplicateCapture = hasDiagnostic "TAR0005" (compileSource "int seed = 1; auto f = [seed, seed] \\ -> seed;")

omittedExplicitCapture :: Bool
omittedExplicitCapture =
    hasDiagnostic "TAN0001" (compileSource "int first = 1; int second = 2; auto f = [first] \\ -> first + second;")

privateParameterSymbols :: Bool
privateParameterSymbols = case resolvedCallable "auto f = \\(int value) -> value;" of
    Just (CallableExpression _ _ _ [parameter] body _) ->
        resolvedSymbol (parameterName parameter) `elem` expressionSymbols body
    _ -> False

privateCaptureSymbols :: Bool
privateCaptureSymbols = case resolvedCallable "int seed = 1; auto f = [seed] \\ -> seed;" of
    Just (CallableExpression _ True [capture] _ body _) ->
        resolvedSymbol (captureName capture) `elem` expressionSymbols body
    _ -> False

callableType :: Bool
callableType = case typedCallable "auto f = \\(int value) -> value + 1;" of
    Just (CallableExpression _ _ _ _ _ (FunctionType [parameter] result)) ->
        parameter == intType && result == intType
    _ -> False

callableArity :: Bool
callableArity = hasDiagnostic "TAT0008" (compileSource "auto f = \\(int value) -> value; int result = f(1, 2);")

callableArgumentType :: Bool
callableArgumentType = hasDiagnostic "TAT0009" (compileSource "auto f = \\(int value) -> value; int result = f(true);")

weakPrimitiveCapture :: Bool
weakPrimitiveCapture = hasDiagnostic "TAT0014" (compileSource "int value = 1; auto f = [weak value] \\ -> value;")

weakScalarCaptures :: Bool
weakScalarCaptures = all rejectsWeak scalarTypes
    where
        rejectsWeak scalar = hasDiagnostic "TAT0014" (compileSource (scalarSource scalar))
        scalarSource scalar =
            scalarTypeName scalar
                ++ " value = "
                ++ scalarInitializer scalar
                ++ "; auto f = [weak value] \\ -> value;"
        scalarInitializer CharacterScalar = "'A'"
        scalarInitializer BooleanScalar = "true"
        scalarInitializer scalar
            | scalarTypeFamily scalar == FloatingFamily = "1.0"
            | otherwise = "1"

unownedPrimitiveCapture :: Bool
unownedPrimitiveCapture = hasDiagnostic "TAT0015" (compileSource "int value = 1; auto f = [unowned value] \\ -> value;")

weakStringCapture :: Bool
weakStringCapture =
    not (hasDiagnostic "TAT0014" (compileSource "String value = \"Wolf\"; auto f = [weak value] \\ -> value;"))

weakCallableCapture :: Bool
weakCallableCapture =
    not (hasDiagnostic "TAT0014" (compileSource "auto first = \\ -> 1; auto second = [weak first] \\ -> first;"))

implicitCaptureDiscovery :: Bool
implicitCaptureDiscovery = case coreClosure "int seed = 1; auto f = \\ -> seed;" of
    Just (CoreClosure captures [] _ _ _) -> map (identifierText . resolvedSpelling . coreCaptureName) captures == ["seed"]
    _ -> False

explicitCaptureLowering :: Bool
explicitCaptureLowering = case coreClosure "int seed = 1; auto f = [seed] \\ -> seed;" of
    Just (CoreClosure [capture] [] _ _ _) ->
        coreCaptureMode capture == StrongCapture && expressionType (coreCaptureValue capture) == intType
    _ -> False

aliasLowering :: Bool
aliasLowering = case coreClosure "int seed = 1; auto f = [answer = seed + 1] \\ -> answer;" of
    Just (CoreClosure [capture] [] _ _ _) -> case coreCaptureValue capture of
        CorePrimitive CoreAdd [CoreVariable _ _, CoreLiteral (CoreInteger 1) _] _ -> True
        _ -> False
    _ -> False

liftedFunction :: Bool
liftedFunction = case compiled "int seed = 1; auto f = \\ -> seed;" of
    Right artifacts -> length (corePrepModuleFunctions (artifactCorePrep artifacts)) == 2
    Left _ -> False

makeClosureOperation :: Bool
makeClosureOperation = case preparedClosure "int seed = 1; auto f = \\ -> seed;" of
    Just (CorePrepMakeClosure _ [_]) -> True
    _ -> False

hiddenCaptureParameters :: Bool
hiddenCaptureParameters = case compiled "int seed = 1; auto f = \\(int value) -> seed + value;" of
    Right artifacts -> case drop 1 (corePrepModuleFunctions (artifactCorePrep artifacts)) of
        [function] -> map (identifierText . resolvedSpelling . fst) (corePrepFunctionParameters function) == ["seed", "value"]
        _ -> False
    Left _ -> False

closureResultType :: Bool
closureResultType = case preparedBinding "auto f = \\(int value) -> value; f(1);" of
    Just (CorePrepBind _ valueType _ CorePrepMakeClosure {}) ->
        valueType == FunctionType [intType] intType
    _ -> False

nestedClosureConversion :: Bool
nestedClosureConversion = case compiled "int seed = 1; auto outer = \\ -> \\ -> seed;" of
    Right artifacts -> length (corePrepModuleFunctions (artifactCorePrep artifacts)) == 3
    Left _ -> False

coreVerifierAcceptsClosure :: Bool
coreVerifierAcceptsClosure = case compiled "int seed = 1; auto f = \\ -> seed;" of
    Right artifacts -> verifyCore (artifactOptimizedCore artifacts) == Right (artifactOptimizedCore artifacts)
    Left _ -> False

coreVerifierRejectsTypeMismatch :: Bool
coreVerifierRejectsTypeMismatch = isLeftDiagnostics (verifyCore invalidClosureType)

coreVerifierRejectsCaptureMismatch :: Bool
coreVerifierRejectsCaptureMismatch = isLeftDiagnostics (verifyCore invalidCaptureType)

coreWireClosureRoundTrip :: Bool
coreWireClosureRoundTrip = case compiled "int seed = 1; auto f = \\ -> seed;" of
    Right artifacts ->
        let value = artifactOptimizedCore artifacts
         in (encodeCore defaultCoreWireLimits value >>= decodeCore defaultCoreWireLimits) == Right value
    Left _ -> False

corePrepWireClosureRoundTrip :: Bool
corePrepWireClosureRoundTrip = case compiled "int seed = 1; auto f = \\ -> seed;" of
    Right artifacts ->
        let value = artifactCorePrep artifacts
         in (encodeCorePrep value >>= decodeCorePrep) == Right value
    Left _ -> False

corePrepVerifierAcceptsClosure :: Bool
corePrepVerifierAcceptsClosure = case compiled "int seed = 1; auto f = \\ -> seed;" of
    Right artifacts -> verifyCorePrep (artifactCorePrep artifacts) == Right (artifactCorePrep artifacts)
    Left _ -> False

corePrepVerifierRejectsWeakPrimitive :: Bool
corePrepVerifierRejectsWeakPrimitive = isLeftDiagnostics (verifyCorePrep invalidPreparedWeakCapture)

-- Test source is wrapped in the smallest valid declaration context.  Statements
-- are injected before a final unit-producing return so callable values may be
-- exercised without conflating closure behavior with entry-point validation.
compileSource :: String -> Either [Diagnostic] FrontendArtifacts
compileSource = compiled

compiled :: String -> Either [Diagnostic] FrontendArtifacts
compiled statements =
    compileToCorePrep
        ( CompilerInput
            "closure-test.tala"
            ( unlines
                [ "namespace ClosureTests;"
                , "class Program {"
                , "  public static void Main() {"
                , statements
                , "    return;"
                , "  }"
                , "}"
                ]
            )
        )

parseOnly :: String -> Either [Diagnostic] ParsedAST
parseOnly statements = do
    tokens <- lexText (source statements)
    runParser defaultParser (ParserInput "closure-test.tala" tokens)

lexText :: String -> Either [Diagnostic] [Token]
lexText = runLexer defaultLexer . LexerInput "closure-test.tala"

source :: String -> String
source statements =
    unlines
        [ "namespace ClosureTests;"
        , "class Program {"
        , "  public static void Main() {"
        , "    auto callable = " ++ statements ++ ";"
        , "    return;"
        , "  }"
        , "}"
        ]

parsedCallable :: String -> Maybe (Expression Identifier ())
parsedCallable value = either (const Nothing) findParsedCallable (parseOnly value)

findParsedCallable :: ParsedAST -> Maybe (Expression Identifier ())
findParsedCallable (ParsedAST tree) = firstCallableInTree tree

firstCallableInTree :: SyntaxTree name annotation -> Maybe (Expression name annotation)
firstCallableInTree tree = firstJust (map declarationCallable (syntaxDeclarations tree))

declarationCallable :: Declaration name annotation -> Maybe (Expression name annotation)
declarationCallable declaration = case declaration of
    TypeDeclaration {typeMembers = members} -> firstJust (map declarationCallable members)
    TemplateTypeDeclaration {typeMembers = members} -> firstJust (map declarationCallable members)
    FunctionDeclaration {declarationBody = body} -> blockCallable body
    EnumDeclaration {} -> Nothing

blockCallable :: Block name annotation -> Maybe (Expression name annotation)
blockCallable (Block statements) = firstJust (map statementCallable statements)

statementCallable :: Statement name annotation -> Maybe (Expression name annotation)
statementCallable statement = case statement of
    BindingStatement _ _ _ _ _ value -> expressionCallable value
    AssignmentStatement _ _ _ value -> expressionCallable value
    ReturnStatement _ value -> value >>= expressionCallable
    IfStatement _ condition yes no ->
        expressionCallable condition `orElse` blockCallable yes `orElse` (no >>= blockCallable)
    WhileStatement _ condition body -> expressionCallable condition `orElse` blockCallable body
    DoWhileStatement _ body condition -> blockCallable body `orElse` expressionCallable condition
    ForStatement _ initializer condition updates body ->
        (initializer >>= statementCallable)
            `orElse` (condition >>= expressionCallable)
            `orElse` blockCallable body
            `orElse` firstJust (map statementCallable updates)
    ForEachStatement _ _ _ _ _ collection body -> expressionCallable collection `orElse` blockCallable body
    IncrementStatement {} -> Nothing
    CompoundAssignmentStatement _ _ _ _ value -> expressionCallable value
    DiscardStatement _ value -> expressionCallable value
    BreakStatement _ value -> value >>= expressionCallable
    ContinueStatement {} -> Nothing
    GuardStatement _ condition block -> expressionCallable condition `orElse` blockCallable block
    BlockStatement _ block -> blockCallable block
    ExpressionStatement _ value _ -> expressionCallable value

expressionCallable :: Expression name annotation -> Maybe (Expression name annotation)
expressionCallable expression@CallableExpression {} = Just expression
expressionCallable expression = case expression of
    CallExpression _ callee arguments _ -> expressionCallable callee `orElse` firstJust (map expressionCallable arguments)
    UnaryExpression _ _ value _ -> expressionCallable value
    BinaryExpression _ _ left right _ -> expressionCallable left `orElse` expressionCallable right
    _ -> Nothing

callableParameterCount :: String -> Maybe Int
callableParameterCount value = case parsedCallable value of
    Just (CallableExpression _ _ _ parameters _ _) -> Just (length parameters)
    _ -> Nothing

callableBodyOf :: Maybe (Expression name annotation) -> Maybe (CallableBody name annotation)
callableBodyOf (Just (CallableExpression _ _ _ _ body _)) = Just body
callableBodyOf _ = Nothing

captureModes :: String -> Maybe [CaptureMode]
captureModes value = case parsedCallable value of
    Just (CallableExpression _ _ captures _ _ _) -> Just (map captureMode captures)
    _ -> Nothing

resolvedCallable :: String -> Maybe (Expression ResolvedName ())
resolvedCallable statements = case compiledFrontend statements of
    Left _ -> Nothing
    Right artifacts -> firstCallableInTree (resolvedSyntaxTree (artifactResolvedAST artifacts))

typedCallable :: String -> Maybe (Expression ResolvedName Type)
typedCallable statements = case compiledFrontend statements of
    Left _ -> Nothing
    Right artifacts -> firstCallableInTree (typedSyntaxTree (artifactTypedAST artifacts))

compiledFrontend :: String -> Either [Diagnostic] FrontendArtifacts
compiledFrontend = compiled

expressionSymbols :: CallableBody ResolvedName annotation -> [SymbolId]
expressionSymbols body = case body of
    CallableExpressionBody expression -> symbols expression
    CallableBlockBody block -> blockSymbols block

symbols :: Expression ResolvedName annotation -> [SymbolId]
symbols expression = case expression of
    NameExpression _ name _ -> [resolvedSymbol name]
    LiteralExpression {} -> []
    MemberAccessExpression _ receiver _ _ -> symbols receiver
    MethodReferenceExpression _ receiver _ _ -> symbols receiver
    CallExpression _ callee arguments _ -> symbols callee ++ concatMap symbols arguments
    UnaryExpression _ _ value _ -> symbols value
    BinaryExpression _ _ left right _ -> symbols left ++ symbols right
    IsPatternExpression _ value _ _ -> symbols value
    ConditionalExpression _ condition whenTrue whenFalse _ -> concatMap symbols [condition, whenTrue, whenFalse]
    CoalesceExpression _ left fallback _ -> symbols left ++ symbols fallback
    AssignmentExpression _ _ name value _ -> resolvedSymbol name : symbols value
    IncrementExpression _ _ name _ -> [resolvedSymbol name]
    LoopExpression {} -> []
    BlockExpression _ block _ -> blockSymbols block
    MatchExpression _ subjects arms _ -> concatMap symbols (subjects ++ concatMap matchArmExpressions arms)
    CallableExpression _ _ captures parameters body _ ->
        map (resolvedSymbol . captureName) captures
            ++ map (resolvedSymbol . parameterName) parameters
            ++ expressionSymbols body

blockSymbols :: Block ResolvedName annotation -> [SymbolId]
blockSymbols (Block statements) = concatMap statementSymbols statements

statementSymbols :: Statement ResolvedName annotation -> [SymbolId]
statementSymbols statement = case statement of
    BindingStatement _ _ _ name _ value -> resolvedSymbol name : symbols value
    AssignmentStatement _ name _ value -> resolvedSymbol name : symbols value
    ReturnStatement _ value -> maybe [] symbols value
    IfStatement _ condition yes no -> symbols condition ++ blockSymbols yes ++ maybe [] blockSymbols no
    WhileStatement _ condition body -> symbols condition ++ blockSymbols body
    DoWhileStatement _ body condition -> blockSymbols body ++ symbols condition
    ForStatement _ initializer condition updates body ->
        maybe [] statementSymbols initializer
            ++ maybe [] symbols condition
            ++ blockSymbols body
            ++ concatMap statementSymbols updates
    ForEachStatement _ _ _ name _ collection body ->
        resolvedSymbol name : symbols collection ++ blockSymbols body
    IncrementStatement _ name _ -> [resolvedSymbol name]
    CompoundAssignmentStatement _ _ name _ value -> resolvedSymbol name : symbols value
    DiscardStatement _ value -> symbols value
    BreakStatement _ value -> maybe [] symbols value
    ContinueStatement {} -> []
    GuardStatement _ condition block -> symbols condition ++ blockSymbols block
    BlockStatement _ block -> blockSymbols block
    ExpressionStatement _ value _ -> symbols value

coreClosure :: String -> Maybe CoreExpression
coreClosure statements = case compiled statements of
    Left _ -> Nothing
    Right artifacts -> findCoreClosure (artifactOptimizedCore artifacts)

findCoreClosure :: CoreModule -> Maybe CoreExpression
findCoreClosure moduleValue =
    firstJust
        [ firstJust (map statementCoreClosure (coreFunctionBody function))
        | function <- coreModuleFunctions moduleValue
        ]

statementCoreClosure :: CoreStatement -> Maybe CoreExpression
statementCoreClosure statement = case statement of
    CoreBind binding -> expressionCoreClosure (coreBindingValue binding)
    CoreAssign _ value -> expressionCoreClosure value
    CoreReturn value -> expressionCoreClosure value
    CoreIf condition yes no ->
        expressionCoreClosure condition
            `orElse` firstJust (map statementCoreClosure yes)
            `orElse` firstJust (map statementCoreClosure no)
    CoreEvaluate value -> expressionCoreClosure value
    CoreWhile condition body -> expressionCoreClosure condition `orElse` firstJust (map statementCoreClosure body)
    CoreDoWhile body condition ->
        firstJust (map statementCoreClosure body) `orElse` expressionCoreClosure condition
    CoreFor condition body update ->
        expressionCoreClosure condition
            `orElse` firstJust (map statementCoreClosure body)
            `orElse` firstJust (map statementCoreClosure update)
    CoreBreak -> Nothing
    CoreContinue -> Nothing

expressionCoreClosure :: CoreExpression -> Maybe CoreExpression
expressionCoreClosure expression@CoreClosure {} = Just expression
expressionCoreClosure expression = case expression of
    CoreApply callee arguments _ -> expressionCoreClosure callee `orElse` firstJust (map expressionCoreClosure arguments)
    CorePrimitive _ arguments _ -> firstJust (map expressionCoreClosure arguments)
    _ -> Nothing

preparedClosure :: String -> Maybe CorePrepOperation
preparedClosure statements = preparedBinding statements >>= operationOf

preparedBinding :: String -> Maybe CorePrepInstruction
preparedBinding statements = case compiled statements of
    Left _ -> Nothing
    Right artifacts ->
        firstJust
            [ find isClosureBinding (corePrepBlockInstructions block)
            | function <- corePrepModuleFunctions (artifactCorePrep artifacts)
            , block <- corePrepFunctionBlocks function
            ]

isClosureBinding :: CorePrepInstruction -> Bool
isClosureBinding (CorePrepBind _ _ _ CorePrepMakeClosure {}) = True
isClosureBinding (CorePrepEvaluate CorePrepMakeClosure {}) = True
isClosureBinding _ = False

operationOf :: CorePrepInstruction -> Maybe CorePrepOperation
operationOf (CorePrepBind _ _ _ operation) = Just operation
operationOf (CorePrepEvaluate operation) = Just operation
operationOf _ = Nothing

hasDiagnostic :: String -> Either [Diagnostic] value -> Bool
hasDiagnostic code result = case result of
    Left problems -> any ((== code) . diagnosticCode) problems
    Right _ -> False

isLeftDiagnostics :: Either [Diagnostic] value -> Bool
isLeftDiagnostics (Left (_ : _)) = True
isLeftDiagnostics _ = False

orElse :: Maybe value -> Maybe value -> Maybe value
orElse (Just value) _ = Just value
orElse Nothing right = right

firstJust :: [Maybe value] -> Maybe value
firstJust = foldr orElse Nothing

testName :: Int -> String -> ResolvedName
testName value spelling = ResolvedName (SymbolId value) (Identifier spelling)

invalidClosureType :: CoreModule
invalidClosureType =
    CoreModule
        (QualifiedName [Identifier "ClosureTests"])
        [ CoreFunction
            (testName 1 "Main")
            []
            unitType
            [ CoreBind
                ( CoreBinding
                    (testName 2 "callable")
                    intType
                    False
                    (CoreClosure [] [] intType [CoreReturn (CoreLiteral (CoreInteger 1) intType)] intType)
                )
            , CoreReturn (CoreLiteral CoreUnit unitType)
            ]
        ]

invalidCaptureType :: CoreModule
invalidCaptureType =
    CoreModule
        (QualifiedName [Identifier "ClosureTests"])
        [ CoreFunction
            (testName 1 "Main")
            []
            unitType
            [ CoreBind (CoreBinding (testName 2 "seed") intType True (CoreLiteral (CoreInteger 1) intType))
            , CoreBind
                ( CoreBinding
                    (testName 3 "callable")
                    (FunctionType [] intType)
                    False
                    ( CoreClosure
                        [CoreCapture StrongCapture (testName 4 "seed") intType (CoreLiteral (CoreBoolean True) boolType)]
                        []
                        intType
                        [CoreReturn (CoreVariable (testName 4 "seed") intType)]
                        (FunctionType [] intType)
                    )
                )
            , CoreReturn (CoreLiteral CoreUnit unitType)
            ]
        ]

invalidPreparedWeakCapture :: CorePrepModule
invalidPreparedWeakCapture =
    CorePrepModule
        (QualifiedName [Identifier "ClosureTests"])
        [ CorePrepFunction
            (testName 1 "Main")
            ""
            []
            unitType
            0
            [ CorePrepBlock
                0
                [ CorePrepBind
                    (testName 2 "callable")
                    (FunctionType [] intType)
                    False
                    ( CorePrepMakeClosure
                        (testName 3 "lifted")
                        [CorePrepCapture WeakCapture (testName 4 "value") intType (CorePrepLiteral (CoreInteger 1) intType)]
                    )
                ]
                (CorePrepReturn (CorePrepLiteral CoreUnit unitType))
            ]
        , CorePrepFunction
            (testName 3 "lifted")
            ""
            [(testName 4 "value", intType)]
            intType
            0
            [CorePrepBlock 0 [] (CorePrepReturn (CorePrepVariable (testName 4 "value") intType))]
        ]
        []

-- Keep a textual assertion near the wire tests so failures caused by an
-- accidental version rollback explain themselves in the test output.
_wireVersionContext :: String
_wireVersionContext = "closures require Core wire version 10 and CorePrep wire version 8"

_diagnosticContext :: Diagnostic -> Bool
_diagnosticContext diagnostic = "closure" `isInfixOf` diagnosticMessage diagnostic