packages feed

tala-compiler-0.1.0: test/ConditionalExpressionTests.hs

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

{- | Vertical tests for the conditional forms and the statement forms that
landed with them: @condition ? first : second@, @left ?: fallback@, compound
assignment, and the discard statement.

Shape assertions read the unoptimized Core wherever a call must stay visible,
because the optimizer is free to fold a pure callee. Laziness is asserted on
CorePrep blocks, which is the representation the native backend executes.
-}
module ConditionalExpressionTests (conditionalExpressionTests) where

import Data.List (isInfixOf, isPrefixOf)
import Progmasoft.Tala.AST
import Progmasoft.Tala.Compiler
import Progmasoft.Tala.Core
import Progmasoft.Tala.Core.CorePrep
import Progmasoft.Tala.Core.CorePrep.Verifier
import Progmasoft.Tala.Core.Verifier
import Progmasoft.Tala.Core.Wire
import Progmasoft.Tala.Diagnostic
import Progmasoft.Tala.Frontend
import Progmasoft.Tala.Lexer
import Progmasoft.Tala.Parser
import Progmasoft.Tala.Template.Application
import Progmasoft.Tala.Template.Specialization

conditionalExpressionTests :: [(String, Bool)]
conditionalExpressionTests =
    lexerTests
        ++ parserTests
        ++ typeTests
        ++ loweringTests
        ++ verifierTests
        ++ optimizerTests
        ++ corePrepTests

-- ---------------------------------------------------------------- lexer

lexerTests :: [(String, Bool)]
lexerTests =
    [ ("a spaced conditional lexes '?' and ':' separately", symbolsOf "a ? b : c" == ["?", ":"])
    , ("the omitted-middle operator is one token", symbolsOf "a ?: b" == ["?:"])
    , ("a conditional with a spaced empty middle is not the coalescing token", symbolsOf "a ? : b" == ["?", ":"])
    , ("null coalescing lexes as its own token", symbolsOf "a ?? b" == ["??"])
    , ("null-coalescing assignment lexes as its own token", symbolsOf "a ??= b" == ["??="])
    , ("every compound assignment operator is one token", all compoundIsOneToken compoundSpellings)
    , ("rounded-division assignment is not a division followed by '/='", symbolsOf "a //= b" == ["//="])
    , ("power assignment is not a multiplication followed by '*='", symbolsOf "a **= b" == ["**="])
    , ("shift assignments are single tokens", symbolsOf "a <<= b >>= c" == ["<<=", ">>="])
    , ("relational operators keep their spelling next to assignments", symbolsOf "a <= b >= c" == ["<=", ">="])
    , ("nested generic closers stay separate tokens", symbolsOf "A<B<C>> value" == ["<", "<", ">", ">"])
    , ("a line comment still starts at a spaced '--'", symbolsOf "a -= b -- c ? d : e" == ["-="])
    ]

compoundSpellings :: [String]
compoundSpellings = ["+=", "-=", "*=", "/=", "//=", "%=", "**=", "<<=", ">>=", "&=", "^=", "|="]

compoundIsOneToken :: String -> Bool
compoundIsOneToken operatorText = symbolsOf ("target " ++ operatorText ++ " 1") == [operatorText]

symbolsOf :: String -> [String]
symbolsOf text = case runLexer defaultLexer (LexerInput "conditional.tala" text) of
    Right tokens -> [tokenText token | token <- tokens, tokenKind token == SymbolToken]
    Left _ -> ["<lexer error>"]

-- ---------------------------------------------------------------- parser

parserTests :: [(String, Bool)]
parserTests =
    [ ("a conditional parses into its three operands", conditionalParses)
    , ("chained conditionals group to the right", conditionalIsRightAssociative)
    , ("a conditional may nest in its first result without parentheses", conditionalNestsInFirstResult)
    , ("logical OR binds more tightly than a conditional", logicalOrBindsTighter)
    , ("the conditional is the weakest expression level in a call argument", conditionalIsCallArgument)
    , ("truthy coalescing parses into its two operands", coalesceParses)
    , ("a spaced empty middle is the same omitted-middle form", spacedCoalesceParses)
    , ("chained truthy coalescing groups to the right", coalesceIsRightAssociative)
    , ("coalescing in a conditional result stays inside that result", coalesceNestsInConditional)
    , ("a conditional without ':' is a dedicated syntax error", parseRejectedWith "TAP0029" (returning "flag ? 1"))
    , ("null coalescing is rejected until nullable types exist", parseRejectedWith "TAP0030" (returning "left ?? right"))
    , ("null-coalescing assignment is rejected until nullable types exist", parseRejectedWith "TAP0031" (body "left ??= right; return 0;"))
    , ("every compound operator parses to its binary operator", all compoundParses compoundOperators)
    , ("a compound assignment needs a named target", parseRejectedWith "TAP0003" (body "Next() += 1; return 0;"))
    , ("a compound assignment needs its terminator", parseRejected (body "left += 1 return left;"))
    , ("a for update may be a compound assignment", forUpdateIsCompound)
    , ("a for update list may mix increments and compound assignments", forUpdateListParses)
    , ("the discard statement parses as its own statement", discardParses)
    , ("discard is not an expression", parseRejected (returning "(_ = Next())"))
    , ("discard needs its terminator", parseRejected (body "_ = Next() return 0;"))
    , ("a conditional is not a template value argument", parseRejectedWith "TAP0018" templateConditionalArgument)
    ]

compoundOperators :: [(String, BinaryOperator)]
compoundOperators =
    [ ("+=", Add)
    , ("-=", Subtract)
    , ("*=", Multiply)
    , ("/=", Divide)
    , ("//=", FloorDivide)
    , ("%=", Remainder)
    , ("**=", Power)
    , ("<<=", ShiftLeft)
    , (">>=", ShiftRight)
    , ("&=", BitwiseAnd)
    , ("^=", BitwiseXor)
    , ("|=", BitwiseOr)
    ]

conditionalParses :: Bool
conditionalParses = case returned "flag ? left : right" of
    Just (ConditionalExpression _ condition first second ()) ->
        isName "flag" condition && isName "left" first && isName "right" second
    _ -> False

conditionalIsRightAssociative :: Bool
conditionalIsRightAssociative = case returned "flag ? 1 : other ? 2 : 3" of
    Just (ConditionalExpression _ condition _ (ConditionalExpression _ inner _ _ ()) ()) ->
        isName "flag" condition && isName "other" inner
    _ -> False

conditionalNestsInFirstResult :: Bool
conditionalNestsInFirstResult = case returned "flag ? other ? 1 : 2 : 3" of
    Just (ConditionalExpression _ condition (ConditionalExpression _ inner _ _ ()) third ()) ->
        isName "flag" condition && isName "other" inner && isInteger 3 third
    _ -> False

logicalOrBindsTighter :: Bool
logicalOrBindsTighter = case returned "flag || other ? 1 : 2" of
    Just (ConditionalExpression _ (BinaryExpression _ LogicalOr _ _ ()) first second ()) ->
        isInteger 1 first && isInteger 2 second
    _ -> False

conditionalIsCallArgument :: Bool
conditionalIsCallArgument = case returned "Twice(flag ? 1 : 2)" of
    Just (CallExpression _ _ [ConditionalExpression {}] ()) -> True
    _ -> False

coalesceParses :: Bool
coalesceParses = case returned "left ?: right" of
    Just (CoalesceExpression _ left fallback ()) -> isName "left" left && isName "right" fallback
    _ -> False

spacedCoalesceParses :: Bool
spacedCoalesceParses = case returned "left ? : right" of
    Just (CoalesceExpression _ left fallback ()) -> isName "left" left && isName "right" fallback
    _ -> False

coalesceIsRightAssociative :: Bool
coalesceIsRightAssociative = case returned "left ?: right ?: 9" of
    Just (CoalesceExpression _ left (CoalesceExpression _ middle final ()) ()) ->
        isName "left" left && isName "right" middle && isInteger 9 final
    _ -> False

coalesceNestsInConditional :: Bool
coalesceNestsInConditional = case returned "flag ? left ?: 1 : right ?: 2" of
    Just (ConditionalExpression _ _ CoalesceExpression {} CoalesceExpression {} ()) -> True
    _ -> False

compoundParses :: (String, BinaryOperator) -> Bool
compoundParses (operatorText, operator) =
    case firstStatements (body ("left " ++ operatorText ++ " right; return left;")) of
        Just (CompoundAssignmentStatement _ parsed (Identifier "left") () value : _) ->
            parsed == operator && isName "right" value
        _ -> False

forUpdateIsCompound :: Bool
forUpdateIsCompound =
    case firstStatements (body "for (int index = 0; index < left; index += 2) {} return left;") of
        Just (ForStatement _ _ _ [CompoundAssignmentStatement _ Add (Identifier "index") () _] _ : _) -> True
        _ -> False

forUpdateListParses :: Bool
forUpdateListParses =
    case firstStatements (body "for (int index = 0; index < left; index++, right -= 1) {} return left;") of
        Just (ForStatement _ _ _ [IncrementStatement {}, CompoundAssignmentStatement _ Subtract _ () _] _ : _) -> True
        _ -> False

discardParses :: Bool
discardParses = case firstStatements (body "_ = Next(); return 0;") of
    Just (DiscardStatement _ CallExpression {} : _) -> True
    _ -> False

templateConditionalArgument :: String
templateConditionalArgument =
    unlines
        [ "template<int N> class Buffer {}"
        , "class Program {"
        , "    public static int Evaluate() {"
        , "        final Buffer<+(1 ? 2 : 3)> value = 0;"
        , "        return 0;"
        , "    }"
        , "}"
        ]

isName :: String -> Expression Identifier () -> Bool
isName expected expression = case expression of
    NameExpression _ (Identifier actual) () -> actual == expected
    _ -> False

isInteger :: Integer -> Expression Identifier () -> Bool
isInteger expected expression = case expression of
    LiteralExpression _ (IntegerLiteral actual) () -> actual == expected
    _ -> False

parseSource :: String -> Either [Diagnostic] ParsedAST
parseSource text = do
    tokens <- runLexer defaultLexer (LexerInput "conditional.tala" text)
    runParser defaultParser (ParserInput "conditional.tala" tokens)

parseRejected :: String -> Bool
parseRejected text = either (const True) (const False) (parseSource text)

parseRejectedWith :: String -> String -> Bool
parseRejectedWith code text = case parseSource text of
    Left problems -> any ((== code) . diagnosticCode) problems
    Right _ -> False

-- | Statements of the first method of the first class in a parsed unit.
firstStatements :: String -> Maybe [Statement Identifier ()]
firstStatements text = case parseSource text of
    Right (ParsedAST (SyntaxTree _ (TypeDeclaration {typeMembers = member : _} : _))) -> case member of
        FunctionDeclaration {declarationBody = Block statements} -> Just statements
        _ -> Nothing
    _ -> Nothing

returned :: String -> Maybe (Expression Identifier ())
returned expression = case firstStatements (returning expression) of
    Just [ReturnStatement _ value] -> value
    _ -> Nothing

-- ---------------------------------------------------------------- sources

-- | One method whose parameters give every test non-constant operands.
body :: String -> String
body statements =
    unlines
        [ "class Program {"
        , "    public static int Evaluate(_ bool flag, _ bool other, _ int left, _ int right) {"
        , "        " ++ statements
        , "    }"
        , "    public static int Next() { return 7; }"
        , "    public static int Twice(_ int value) { return value + value; }"
        , "    public static void Touch() { }"
        , "    public static int Countdown(_ int value) { return value > 0 ? Countdown(value - 1) : 0; }"
        , "}"
        ]

returning :: String -> String
returning expression = body ("return " ++ expression ++ ";")

compileSource :: String -> Either [Diagnostic] FrontendArtifacts
compileSource text = compileToCorePrep (CompilerInput "conditional.tala" text)

accepted :: String -> Bool
accepted text = either (const False) (const True) (compileSource text)

rejectedWith :: String -> String -> Bool
rejectedWith code text = case compileSource text of
    Left problems -> any ((== code) . diagnosticCode) problems
    Right _ -> False

-- ---------------------------------------------------------------- type checker

typeTests :: [(String, Bool)]
typeTests =
    [ ("a conditional over int operands is accepted", accepted (returning "flag ? left : right"))
    , ("a conditional test may be numeric", accepted (returning "left ? left : right"))
    , ("a conditional test must be bool or numeric", rejectedWith "TAT0036" (returning "\"text\" ? left : right"))
    , ("conditional results must have one type", rejectedWith "TAT0037" (returning "flag ? left : other"))
    , ("a conditional selects one of two strings", accepted (body "bool same = (flag ? \"a\" : \"b\") == \"a\"; return 0;"))
    , ("a string and a number are not results of one conditional", rejectedWith "TAT0037" (body "bool same = (flag ? \"a\" : 1) == \"a\"; return 0;"))
    , ("a void call is not a conditional result", rejected (body "flag ? Touch() : Touch(); return 0;"))
    , ("a literal first result takes the type of the second", accepted (longBody "long chosen = flag ? 1 : wide; return 0;"))
    , ("a literal second result takes the type of the first", accepted (longBody "long chosen = flag ? wide : 1; return 0;"))
    , ("an untyped literal pair takes the expected type", accepted (longBody "long chosen = flag ? 1 : 2; return 0;"))
    , ("an expected type does not convert a computed result", rejected (longBody "long chosen = flag ? narrow : 2; return 0;"))
    , ("a conditional result out of the target range is diagnosed", rejectedWith "TAT0016" (body "byte small = flag ? 300 : 1; return 0;"))
    , ("a floating conditional is accepted", accepted (body "double ratio = flag ? 1.5 : 2.5; return 0;"))
    , ("a Boolean conditional is accepted", accepted (body "bool chosen = flag ? other : false; return 0;"))
    , ("a conditional with a call is an effect-capable statement", accepted (body "flag ? Next() : 0; return 0;"))
    , ("a call-free conditional statement is a pure value statement", rejectedWith "TAT0013" (body "flag ? left : right; return 0;"))
    , ("truthy coalescing over int operands is accepted", accepted (returning "left ?: right"))
    , ("truthy coalescing over bool operands is accepted", accepted (body "bool chosen = flag ?: other; return 0;"))
    , ("truthy coalescing operands must have one type", rejectedWith "TAT0038" (returning "left ?: flag"))
    , ("truthy coalescing needs a condition-compatible left operand", rejectedWith "TAT0039" (body "bool same = (\"a\" ?: \"b\") == \"a\"; return 0;"))
    , ("a literal left operand takes the type of the fallback", accepted (longBody "long chosen = 0 ?: wide; return 0;"))
    , ("a literal fallback takes the type of the left operand", accepted (longBody "long chosen = wide ?: 5; return 0;"))
    , ("every arithmetic compound assignment type-checks on int", all compoundAccepted ["+=", "-=", "*=", "/=", "//=", "%=", "**=", "<<=", ">>=", "&=", "^=", "|="])
    , ("a compound literal operand takes the target type", accepted (longBody "wide += 1; return 0;"))
    , ("a compound assignment cannot modify an immutable binding", rejectedWith "TAT0003" (body "final int fixed = 1; fixed += 1; return fixed;"))
    , ("compound operands must match the target type", rejectedWith "TAT0012" (longBody "narrow += wide; return 0;"))
    , ("a bitwise compound assignment needs an integer target", rejectedWith "TAT0012" (body "double ratio = 1.5; ratio &= 2.5; return 0;"))
    , ("a compound assignment needs a numeric target", rejectedWith "TAT0012" (body "bool state = flag; state += other; return 0;"))
    , ("a compound result must have the target type", rejectedWith "TAT0035" (body "double ratio = 7.5; ratio //= 2.0; return 0;"))
    , ("a compound assignment to an unknown name is a resolution error", rejected (body "missing += 1; return 0;"))
    , ("discard accepts a call", accepted (body "_ = Next(); return 0;"))
    , ("discard accepts a void call", accepted (body "_ = Touch(); return 0;"))
    , ("discard accepts a pure value", accepted (body "_ = left + right; return 0;"))
    , ("discard still type-checks its operand", rejectedWith "TAT0012" (body "_ = left + flag; return 0;"))
    , ("the new forms survive template instantiation", templateInstantiates)
    , ("template reachability follows calls in both conditional results", templateReachesBothResults)
    , ("template reachability follows calls in a coalescing fallback and a discard", templateReachesFallbackAndDiscard)
    , ("a closure body may use the conditional forms", accepted closureSource)
    ]

rejected :: String -> Bool
rejected = not . accepted

compoundAccepted :: String -> Bool
compoundAccepted operatorText = accepted (body ("int value = left; value " ++ operatorText ++ " 2; return value;"))

longBody :: String -> String
longBody statements = body ("long wide = 5; int narrow = 3; " ++ statements)

templateSource :: String
templateSource =
    unwords
        [ "template<typename T> class Box {"
        , "int Entry(_ bool flag, _ int seed) {"
        , "int value = seed;"
        , "value += 2;"
        , "_ = Audit(value);"
        , "return flag ? First(value) : Second(seed) ?: Fallback();"
        , "}"
        , "int First(_ int value) { return value; }"
        , "int Second(_ int value) { return value; }"
        , "int Fallback() { return 7; }"
        , "int Audit(_ int value) { return value; }"
        , "int Unused() { return 9; }"
        , "}"
        ]

-- | Specialize @Box<string>@ for its @Entry@ member only.
templatePlan :: Maybe TemplateSpecializationPlan
templatePlan = case analyzeSemantics (CompilerInput "conditional-template.tala" templateSource) of
    Right artifacts ->
        either
            (const Nothing)
            Just
            ( planTemplateSpecializations
                defaultTemplateSpecializationLimits
                (semanticTypedAST artifacts)
                [ TemplateSpecializationDemand
                    (TemplateApplication (QualifiedName [Identifier "Box"]) [TypeTemplateArgument stringType])
                    (TemplateMemberDemand [Identifier "Entry"])
                    "conditional-template"
                ]
            )
    Left _ -> Nothing

specializedMembers :: [Declaration ResolvedName Type]
specializedMembers = case templatePlan of
    Just plan ->
        concat
            [ members
            | TypeDeclaration {typeMembers = members} <-
                map templateSpecializationDeclaration (plannedTemplateSpecializations plan)
            ]
    Nothing -> []

specializedNames :: [String]
specializedNames = map (identifierText . resolvedSpelling . declarationName) specializedMembers

-- The instantiated declaration is a fresh, closed copy of the template
-- member. Every new node must have been copied by the template passes.
templateInstantiates :: Bool
templateInstantiates =
    let closed = show specializedMembers
     in all
            (`isInfixOf` closed)
            ["ConditionalExpression", "CoalesceExpression", "CompoundAssignmentStatement", "DiscardStatement"]

templateReachesBothResults :: Bool
templateReachesBothResults = all (`elem` specializedNames) ["Entry", "First", "Second"]

templateReachesFallbackAndDiscard :: Bool
templateReachesFallbackAndDiscard =
    all (`elem` specializedNames) ["Fallback", "Audit"] && "Unused" `notElem` specializedNames

closureSource :: String
closureSource =
    unlines
        [ "class Program {"
        , "    public static int Evaluate(_ bool flag, _ int left, _ int right) {"
        , "        auto choose = \\ -> flag ? left : right ?: 1;"
        , "        return choose();"
        , "    }"
        , "}"
        ]

-- ---------------------------------------------------------------- lowering

loweringTests :: [(String, Bool)]
loweringTests =
    [ ("a conditional lowers to one Core conditional", conditionalLowers)
    , ("truthy coalescing binds its left operand once", coalesceBindsLeftOnce)
    , ("truthy coalescing reads the bound value as test and result", coalesceReadsBinding)
    , ("every compound operator lowers to its primitive", all compoundLowers compoundPrimitives)
    , ("a compound assignment reads its target as the left operand", compoundReadsTarget)
    , ("a for update compound assignment lowers into the update region", forUpdateLowers)
    , ("discard lowers to an evaluated expression", discardLowers)
    , ("the lowered conditional forms verify as Core", loweredCoreVerifies)
    , ("Core wire round-trips the conditional forms", wireRoundTrips)
    , ("a truncated conditional payload is rejected by the wire reader", wireRejectsTruncation)
    , ("a closure captures the operands of its conditional body", closureCapturesOperands)
    ]

compoundPrimitives :: [(String, CorePrimitive)]
compoundPrimitives =
    [ ("+=", CoreAdd)
    , ("-=", CoreSubtract)
    , ("*=", CoreMultiply)
    , ("/=", CoreDivide)
    , ("//=", CoreFloorDivide)
    , ("%=", CoreRemainder)
    , ("**=", CorePower)
    , ("<<=", CoreShiftLeft)
    , (">>=", CoreShiftRight)
    , ("&=", CoreBitwiseAnd)
    , ("^=", CoreBitwiseXor)
    , ("|=", CoreBitwiseOr)
    ]

-- | Unoptimized Core body of the method named @Evaluate@.
loweredBody :: String -> Maybe [CoreStatement]
loweredBody text = case compileSource text of
    Right artifacts -> evaluateBody (artifactCore artifacts)
    Left _ -> Nothing

evaluateBody :: CoreModule -> Maybe [CoreStatement]
evaluateBody moduleValue =
    case [coreFunctionBody function | function <- coreModuleFunctions moduleValue, named "Evaluate" function] of
        statements : _ -> Just statements
        [] -> Nothing

named :: String -> CoreFunction -> Bool
named expected function = identifierText (resolvedSpelling (coreFunctionName function)) == expected

conditionalLowers :: Bool
conditionalLowers = case loweredBody (returning "flag ? left : right") of
    Just [CoreReturn (CoreConditional (CoreVariable condition _) (CoreVariable first _) (CoreVariable second _) valueType)] ->
        map spelling [condition, first, second] == ["flag", "left", "right"] && valueType == intType
    _ -> False

coalesceBindsLeftOnce :: Bool
coalesceBindsLeftOnce = case loweredBody (returning "Next() ?: right") of
    Just [CoreReturn expression@(CoreLet _ _ CoreApply {} _ _)] -> countCalls expression == 1
    _ -> False

coalesceReadsBinding :: Bool
coalesceReadsBinding = case loweredBody (returning "Next() ?: right") of
    Just [CoreReturn (CoreLet bound _ _ (CoreConditional (CoreVariable test _) (CoreVariable first _) (CoreVariable second _) _) _)] ->
        resolvedSymbol test == resolvedSymbol bound
            && resolvedSymbol first == resolvedSymbol bound
            && spelling second == "right"
            && "$coalesce" `isPrefixOf` spelling bound
    _ -> False

compoundLowers :: (String, CorePrimitive) -> Bool
compoundLowers (text, primitive) =
    case loweredBody (body ("int value = left; value " ++ text ++ " right; return value;")) of
        Just [_, CoreAssign target (CorePrimitive actual [_, _] valueType), _] ->
            spelling target == "value" && actual == primitive && valueType == intType
        _ -> False

compoundReadsTarget :: Bool
compoundReadsTarget = case loweredBody (body "int value = left; value -= right; return value;") of
    Just [_, CoreAssign target (CorePrimitive CoreSubtract [CoreVariable current _, CoreVariable operand _] _), _] ->
        resolvedSymbol current == resolvedSymbol target && spelling operand == "right"
    _ -> False

forUpdateLowers :: Bool
forUpdateLowers =
    case loweredBody (body "int total = 0; for (int index = 0; index < left; index += 2) { total += index; } return total;") of
        Just [_, _, CoreFor _ [CoreAssign _ (CorePrimitive CoreAdd _ _)] [CoreAssign index (CorePrimitive CoreAdd _ _)], _] ->
            spelling index == "index"
        _ -> False

discardLowers :: Bool
discardLowers = case loweredBody (body "_ = Next(); return 0;") of
    Just [CoreEvaluate CoreApply {}, CoreReturn _] -> True
    _ -> False

allFormsSource :: String
allFormsSource =
    body $
        unwords
            [ "int value = flag ? left : right;"
            , "value += other ? 1 : 2;"
            , "value = Twice(value) ?: Next();"
            , "_ = Countdown(value);"
            , "for (int index = 0; index < left; index += 1) { value -= index ?: 1; }"
            , "return flag ? other ? value : left : right ?: 3;"
            ]

loweredCoreVerifies :: Bool
loweredCoreVerifies = case compileSource allFormsSource of
    Right artifacts ->
        verifyCore (artifactCore artifacts) == Right (artifactCore artifacts)
            && verifyCore (artifactOptimizedCore artifacts) == Right (artifactOptimizedCore artifacts)
    Left _ -> False

wireRoundTrips :: Bool
wireRoundTrips = case compileSource allFormsSource of
    Right artifacts ->
        let core = artifactCore artifacts
         in moduleHasConditional core
                && (encodeCore defaultCoreWireLimits core >>= decodeCore defaultCoreWireLimits) == Right core
    Left _ -> False

wireRejectsTruncation :: Bool
wireRejectsTruncation = case encodeCore defaultCoreWireLimits conditionalModule of
    Right bytes ->
        -- Every proper prefix must fail: a conditional has no count that a
        -- shorter payload could satisfy.
        all (isLeft . decodeCore defaultCoreWireLimits) [take size bytes | size <- [0 .. length bytes - 1]]
            && decodeCore defaultCoreWireLimits bytes == Right conditionalModule
    Left _ -> False
    where
        isLeft = either (const True) (const False)

closureCapturesOperands :: Bool
closureCapturesOperands = case compileSource closureSource of
    Right artifacts ->
        let captured =
                [ spelling (coreCaptureName capture)
                | function <- coreModuleFunctions (artifactCore artifacts)
                , CoreClosure captures _ _ _ _ <- concatMap statementExpressions (coreFunctionBody function)
                , capture <- captures
                ]
         in captured == ["flag", "left", "right"]
    Left _ -> False

spelling :: ResolvedName -> String
spelling = identifierText . resolvedSpelling

-- | Top-level expressions of a statement list, without descending into them.
statementExpressions :: CoreStatement -> [CoreExpression]
statementExpressions statement = case statement of
    CoreBind binding -> [coreBindingValue binding]
    CoreAssign _ value -> [value]
    CoreReturn value -> [value]
    CoreEvaluate value -> [value]
    CoreIf condition yes no -> condition : concatMap statementExpressions (yes ++ no)
    CoreWhile condition loopBody -> condition : concatMap statementExpressions loopBody
    CoreDoWhile loopBody condition -> concatMap statementExpressions loopBody ++ [condition]
    CoreFor condition loopBody update -> condition : concatMap statementExpressions (loopBody ++ update)
    CoreBreak -> []
    CoreContinue -> []

-- | Every expression node reachable from an expression, itself included.
subexpressions :: CoreExpression -> [CoreExpression]
subexpressions expression =
    expression : case expression of
        CoreVariable {} -> []
        CoreLiteral {} -> []
        CoreApply callee arguments _ -> concatMap subexpressions (callee : arguments)
        CorePrimitive _ arguments _ -> concatMap subexpressions arguments
        CoreLet _ _ value letBody _ -> subexpressions value ++ subexpressions letBody
        CoreConditional condition whenTrue whenFalse _ -> concatMap subexpressions [condition, whenTrue, whenFalse]
        CoreClosure captures _ _ closureBody _ ->
            concatMap (subexpressions . coreCaptureValue) captures
                ++ concatMap subexpressions (concatMap statementExpressions closureBody)

moduleExpressions :: CoreModule -> [CoreExpression]
moduleExpressions moduleValue =
    concatMap
        subexpressions
        (concatMap statementExpressions (concatMap coreFunctionBody (coreModuleFunctions moduleValue)))

countCalls :: CoreExpression -> Int
countCalls expression = length [() | CoreApply {} <- subexpressions expression]

isConditional :: CoreExpression -> Bool
isConditional expression = case expression of
    CoreConditional {} -> True
    _ -> False

moduleHasConditional :: CoreModule -> Bool
moduleHasConditional = any isConditional . moduleExpressions

-- ---------------------------------------------------------------- Core verifier

verifierTests :: [(String, Bool)]
verifierTests =
    [ ("the Core verifier accepts a well-typed conditional", verifyCore conditionalModule == Right conditionalModule)
    , ("the Core verifier rejects a non-condition test", coreRejectedWith "TAC1067" (conditionalOf text one two intType))
    , ("the Core verifier rejects a mistyped first result", coreRejectedWith "TAC1068" (conditionalOf flagRead truth two intType))
    , ("the Core verifier rejects a mistyped second result", coreRejectedWith "TAC1069" (conditionalOf flagRead one truth intType))
    , ("the Core verifier rejects a non-scalar result", coreRejectedWith "TAC1070" (conditionalOf flagRead text text stringType))
    , ("the Core verifier rejects a void result", coreRejectedWith "TAC1070" (conditionalOf flagRead unit unit unitType))
    , ("the Core verifier checks names inside both results", coreRejectedWith "TAC1020" (conditionalOf flagRead one missing intType))
    , ("the Core verifier checks names inside the test", coreRejectedWith "TAC1020" (conditionalOf missingFlag one two intType))
    , ("a let inside a result does not leak into the other result", coreRejectedWith "TAC1020" leakingLet)
    ]
    where
        text = CoreLiteral (CoreString "text") stringType
        truth = CoreLiteral (CoreBoolean True) boolType
        unit = CoreLiteral CoreUnit unitType
        missing = CoreVariable (ResolvedName (SymbolId 90) (Identifier "missing")) intType
        missingFlag = CoreVariable (ResolvedName (SymbolId 91) (Identifier "missing")) boolType
        scoped = ResolvedName (SymbolId 50) (Identifier "scoped")
        leakingLet =
            conditionalOf
                flagRead
                (CoreLet scoped intType one (CoreVariable scoped intType) intType)
                (CoreVariable scoped intType)
                intType

flagName :: ResolvedName
flagName = ResolvedName (SymbolId 2) (Identifier "flag")

flagRead :: CoreExpression
flagRead = CoreVariable flagName boolType

one, two :: CoreExpression
one = CoreLiteral (CoreInteger 1) intType
two = CoreLiteral (CoreInteger 2) intType

-- | A one-function module returning the given conditional.
conditionalOf :: CoreExpression -> CoreExpression -> CoreExpression -> Type -> CoreModule
conditionalOf condition whenTrue whenFalse valueType =
    CoreModuleWithSources
        (QualifiedName [Identifier "Conditional"])
        [ CoreFunction
            (ResolvedName (SymbolId 1) (Identifier "Choose"))
            [(flagName, boolType)]
            valueType
            [CoreReturn (CoreConditional condition whenTrue whenFalse valueType)]
        ]
        []
        []

conditionalModule :: CoreModule
conditionalModule = conditionalOf flagRead one two intType

coreRejectedWith :: String -> CoreModule -> Bool
coreRejectedWith code moduleValue = case verifyCore moduleValue of
    Left problems -> any ((== code) . diagnosticCode) problems
    Right _ -> False

-- ---------------------------------------------------------------- optimizer

optimizerTests :: [(String, Bool)]
optimizerTests =
    [ ("a constant true test leaves only the first result", foldsTo "true ? left : right" "left")
    , ("a constant false test leaves only the second result", foldsTo "false ? left : right" "right")
    , ("a numeric constant test selects by truth", foldsTo "0 ? left : right" "right")
    , ("a non-constant test keeps the conditional", optimizedHasConditional (returning "flag ? left : right"))
    , ("a truthy constant left operand removes the fallback", coalesceKeepsConstantLeft)
    , ("a zero left operand selects the fallback", coalesceSelectsFallback)
    , ("constants fold inside a kept conditional", constantsFoldInResults)
    , ("a pure discarded value disappears", optimizedBodyLength (body "_ = left + right; return 0;") == Just 1)
    , ("a recursive call stays behind its conditional", optimizedKeepsRecursiveCall)
    , ("the optimized conditional forms still verify", optimizedVerifies)
    ]

optimizedBody :: String -> Maybe [CoreStatement]
optimizedBody text = case compileSource text of
    Right artifacts -> evaluateBody (artifactOptimizedCore artifacts)
    Left _ -> Nothing

optimizedBodyLength :: String -> Maybe Int
optimizedBodyLength = fmap length . optimizedBody

foldsTo :: String -> String -> Bool
foldsTo expression expected = case optimizedBody (returning expression) of
    Just [CoreReturn (CoreVariable name _)] -> spelling name == expected
    _ -> False

-- The constant folder propagates the bound literal into the body but keeps
-- the now unread binding; only the conditional itself must be gone.
coalesceKeepsConstantLeft :: Bool
coalesceKeepsConstantLeft = case optimizedBody (returning "5 ?: right") of
    Just [CoreReturn (CoreLet _ _ (CoreLiteral (CoreInteger 5) _) (CoreLiteral (CoreInteger 5) _) _)] -> True
    _ -> False

coalesceSelectsFallback :: Bool
coalesceSelectsFallback = case optimizedBody (returning "0 ?: right") of
    Just [CoreReturn (CoreLet _ _ (CoreLiteral (CoreInteger 0) _) (CoreVariable name _) _)] -> spelling name == "right"
    _ -> False

optimizedHasConditional :: String -> Bool
optimizedHasConditional text = case optimizedBody text of
    Just statements -> any isConditional (concatMap subexpressions (concatMap statementExpressions statements))
    Nothing -> False

constantsFoldInResults :: Bool
constantsFoldInResults = case optimizedBody (returning "flag ? 2 + 3 : 4 * 5") of
    Just [CoreReturn (CoreConditional _ (CoreLiteral (CoreInteger 5) _) (CoreLiteral (CoreInteger 20) _) _)] -> True
    _ -> False

optimizedKeepsRecursiveCall :: Bool
optimizedKeepsRecursiveCall = case compileSource (returning "Countdown(left)") of
    Right artifacts ->
        case [coreFunctionBody function | function <- coreModuleFunctions (artifactOptimizedCore artifacts), named "Countdown" function] of
            [[CoreReturn (CoreConditional _ whenTrue (CoreLiteral (CoreInteger 0) _) _)]] -> countCalls whenTrue == 1
            _ -> False
    Left _ -> False

optimizedVerifies :: Bool
optimizedVerifies = case compileSource allFormsSource of
    Right artifacts -> verifyCore (artifactOptimizedCore artifacts) == Right (artifactOptimizedCore artifacts)
    Left _ -> False

-- ---------------------------------------------------------------- CorePrep

corePrepTests :: [(String, Bool)]
corePrepTests =
    [ ("a conditional lowers to a branch, two arms and a join", conditionalHasFourBlocks)
    , ("both arms assign the result slot and jump to the join", armsAssignAndJoin)
    , ("the result slot has exactly one mutable seed", slotHasOneSeed)
    , ("the join returns the result slot", joinReturnsSlot)
    , ("a numeric test is compared with zero before the branch", numericTestIsBooleanized)
    , ("a Boolean test is branched on directly", booleanTestHasNoComparison)
    , ("a floating result slot starts from a floating zero", floatingSlotSeed)
    , ("a Boolean result slot starts from false", booleanSlotSeed)
    , ("a call in a result is emitted only in that arm", callStaysInItsArm)
    , ("nested conditionals create one region each", nestedConditionalRegions)
    , ("truthy coalescing evaluates its fallback behind a branch", coalesceFallbackIsConditional)
    , ("a conditional in a loop condition keeps the back-edge", conditionalInLoopCondition)
    , ("every generated block id is unique", all blockIdsAreUnique (preparedFunctions allFormsSource))
    , ("every branch and jump target exists", all targetsExist (preparedFunctions allFormsSource))
    , ("CorePrep of the conditional forms survives its verifier", preparedVerifies)
    , ("a discarded call stays one result-dropping instruction", discardedCallIsEvaluated)
    , ("a discarded non-call value is computed into a temporary", discardedOperatorIsBound)
    , ("a discarded division still executes", discardedDivisionIsBound)
    , ("a conditional statement leaves no result-dropping copy", conditionalStatementHasNoEvaluate)
    , ("a discarded closure creation is bound, not dropped", discardedClosureIsBound)
    ]

preparedFunctions :: String -> [CorePrepFunction]
preparedFunctions text = case compileSource text of
    Right artifacts -> corePrepModuleFunctions (artifactCorePrep artifacts)
    Left _ -> []

preparedNamed :: String -> String -> Maybe CorePrepFunction
preparedNamed name text =
    case [function | function <- preparedFunctions text, spelling (corePrepFunctionName function) == name] of
        function : _ -> Just function
        [] -> Nothing

prepared :: String -> Maybe CorePrepFunction
prepared = preparedNamed "Evaluate"

blocksOf :: String -> [CorePrepBlock]
blocksOf = maybe [] corePrepFunctionBlocks . prepared

instructionsOf :: String -> [CorePrepInstruction]
instructionsOf = concatMap corePrepBlockInstructions . blocksOf

simple :: String
simple = returning "flag ? left : right"

conditionalHasFourBlocks :: Bool
conditionalHasFourBlocks = case blocksOf simple of
    [entry, whenTrue, whenFalse, joinBlock] ->
        corePrepBlockTerminator entry
            `isBranchTo` (corePrepBlockId whenTrue, corePrepBlockId whenFalse)
            && corePrepBlockTerminator whenTrue == CorePrepJump (corePrepBlockId joinBlock)
            && corePrepBlockTerminator whenFalse == CorePrepJump (corePrepBlockId joinBlock)
    _ -> False

isBranchTo :: CorePrepTerminator -> (Int, Int) -> Bool
isBranchTo terminator (expectedTrue, expectedFalse) = case terminator of
    CorePrepBranch _ actualTrue actualFalse -> actualTrue == expectedTrue && actualFalse == expectedFalse
    _ -> False

armsAssignAndJoin :: Bool
armsAssignAndJoin = case blocksOf simple of
    [_, whenTrue, whenFalse, _] ->
        map assignedFrom (corePrepBlockInstructions whenTrue) == [Just "left"]
            && map assignedFrom (corePrepBlockInstructions whenFalse) == [Just "right"]
    _ -> False
    where
        assignedFrom instruction = case instruction of
            CorePrepAssign target (CorePrepVariable value _) | isSlot target -> Just (spelling value)
            _ -> Nothing

isSlot :: ResolvedName -> Bool
isSlot name = "$conditional" `isPrefixOf` spelling name

slotSeeds :: [CorePrepInstruction] -> [(Type, CorePrepOperation)]
slotSeeds instructions =
    [(valueType, operation) | CorePrepBind name valueType True operation <- instructions, isSlot name]

slotHasOneSeed :: Bool
slotHasOneSeed = case blocksOf simple of
    entry : rest ->
        slotSeeds (corePrepBlockInstructions entry) == [(intType, CorePrepCopy (CorePrepLiteral (CoreInteger 0) intType))]
            && null (slotSeeds (concatMap corePrepBlockInstructions rest))
    [] -> False

joinReturnsSlot :: Bool
joinReturnsSlot = case reverse (blocksOf simple) of
    joinBlock : _ -> case corePrepBlockTerminator joinBlock of
        CorePrepReturn (CorePrepVariable name _) -> isSlot name
        _ -> False
    [] -> False

comparisons :: [CorePrepInstruction] -> Int
comparisons instructions =
    length [() | CorePrepBind _ _ _ (CorePrepPrimitive CoreNotEqual _) <- instructions]

numericTestIsBooleanized :: Bool
numericTestIsBooleanized = comparisons (instructionsOf (returning "left ? left : right")) == 1

booleanTestHasNoComparison :: Bool
booleanTestHasNoComparison = comparisons (instructionsOf simple) == 0

floatingSlotSeed :: Bool
floatingSlotSeed =
    map snd (slotSeeds (instructionsOf (body "double ratio = flag ? 1.5 : 2.5; return ratio > 2.0 ? 1 : 0;")))
        == [ CorePrepCopy (CorePrepLiteral (CoreFloating "0") doubleType)
           , CorePrepCopy (CorePrepLiteral (CoreInteger 0) intType)
           ]
    where
        doubleType = namedType "double"

booleanSlotSeed :: Bool
booleanSlotSeed =
    map snd (slotSeeds (instructionsOf (body "bool chosen = flag ? other : left > right; return chosen ? 1 : 0;")))
        == [ CorePrepCopy (CorePrepLiteral (CoreBoolean False) boolType)
           , CorePrepCopy (CorePrepLiteral (CoreInteger 0) intType)
           ]

isCall :: CorePrepInstruction -> Bool
isCall instruction = case instruction of
    CorePrepBind _ _ _ CorePrepCall {} -> True
    _ -> False

callStaysInItsArm :: Bool
callStaysInItsArm = case maybe [] corePrepFunctionBlocks (preparedNamed "Countdown" simple) of
    [entry, whenTrue, whenFalse, joinBlock] ->
        not (any isCall (corePrepBlockInstructions entry))
            && length (filter isCall (corePrepBlockInstructions whenTrue)) == 1
            && not (any isCall (corePrepBlockInstructions whenFalse))
            && not (any isCall (corePrepBlockInstructions joinBlock))
    _ -> False

branches :: [CorePrepBlock] -> Int
branches blocks = length [() | CorePrepBranch {} <- map corePrepBlockTerminator blocks]

nestedConditionalRegions :: Bool
nestedConditionalRegions =
    let blocks = blocksOf (returning "flag ? other ? left : right : 0")
     in branches blocks == 2
            && length (slotSeeds (concatMap corePrepBlockInstructions blocks)) == 2
            && length blocks == 7

coalesceFallbackIsConditional :: Bool
coalesceFallbackIsConditional =
    case blocksOf (returning "left ?: Countdown(right)") of
        [entry, whenTrue, whenFalse, _] ->
            not (any isCall (corePrepBlockInstructions entry))
                && not (any isCall (corePrepBlockInstructions whenTrue))
                && length (filter isCall (corePrepBlockInstructions whenFalse)) == 1
        _ -> False

conditionalInLoopCondition :: Bool
conditionalInLoopCondition =
    let blocks = blocksOf (body "int value = left; while (flag ? value > 0 : value > right) { value -= 1; } return value;")
        backward block = case corePrepBlockTerminator block of
            CorePrepJump target -> target < corePrepBlockId block
            _ -> False
     in branches blocks == 2 && length (filter backward blocks) == 1

evaluations :: [CorePrepInstruction] -> [CorePrepOperation]
evaluations instructions = [operation | CorePrepEvaluate operation <- instructions]

discardedCallIsEvaluated :: Bool
discardedCallIsEvaluated =
    case evaluations (instructionsOf (body "_ = Countdown(left); return 0;")) of
        [CorePrepCall {}] -> True
        _ -> False

-- `_ = !Countdown(left);` once reached later stages as a discarded bitwise
-- instruction, which only calls and closure creations may be.
discardedOperatorIsBound :: Bool
discardedOperatorIsBound =
    let instructions = instructionsOf (body "_ = !Countdown(left); return 0;")
     in null (evaluations instructions)
            && length [() | CorePrepBind _ _ _ (CorePrepPrimitive CoreBitwiseNot _) <- instructions] == 1
            && length (filter isCall instructions) == 1

discardedDivisionIsBound :: Bool
discardedDivisionIsBound =
    let instructions = instructionsOf (body "_ = 12 / left; return 0;")
     in null (evaluations instructions)
            && length [() | CorePrepBind _ _ _ (CorePrepPrimitive CoreDivide _) <- instructions] == 1

conditionalStatementHasNoEvaluate :: Bool
conditionalStatementHasNoEvaluate =
    let blocks = blocksOf (body "flag ? Countdown(left) : 0; return 0;")
        instructions = concatMap corePrepBlockInstructions blocks
     in null (evaluations instructions) && branches blocks == 1 && length (filter isCall instructions) == 1

discardedClosureIsBound :: Bool
discardedClosureIsBound =
    let instructions = instructionsOf (body "_ = \\ -> left; return 0;")
     in null (evaluations instructions)
            && length [() | CorePrepBind _ _ _ CorePrepMakeClosure {} <- instructions] == 1

blockIdsAreUnique :: CorePrepFunction -> Bool
blockIdsAreUnique function =
    let identifiers = map corePrepBlockId (corePrepFunctionBlocks function)
     in all (\identifier -> length (filter (== identifier) identifiers) == 1) identifiers

targetsExist :: CorePrepFunction -> Bool
targetsExist function =
    let blocks = corePrepFunctionBlocks function
        identifiers = map corePrepBlockId blocks
        targets block = case corePrepBlockTerminator block of
            CorePrepJump target -> [target]
            CorePrepBranch _ whenTrue whenFalse -> [whenTrue, whenFalse]
            _ -> []
     in all (`elem` identifiers) (concatMap targets blocks)

preparedVerifies :: Bool
preparedVerifies = case compileSource allFormsSource of
    Right artifacts -> verifyCorePrep (artifactCorePrep artifacts) == Right (artifactCorePrep artifacts)
    Left _ -> False