packages feed

tala-compiler-0.1.0: test/TemplateSpecializationPlannerTests.hs

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

module TemplateSpecializationPlannerTests (templateSpecializationPlannerTests) where

import Data.List (isInfixOf)
import Progmasoft.Tala.AST
import Progmasoft.Tala.Compiler
import Progmasoft.Tala.Core
import Progmasoft.Tala.Diagnostic
import Progmasoft.Tala.Frontend
import Progmasoft.Tala.Template.Application
import Progmasoft.Tala.Template.Freshen
import Progmasoft.Tala.Template.Specialization

templateSpecializationPlannerTests :: [(String, Bool)]
templateSpecializationPlannerTests =
    [ ("planner materializes a concrete ordinary declaration", materializesOrdinaryDeclaration)
    , ("planner preserves the concrete specialization type", preservesConcreteType)
    , ("planner assigns positive stable specialization ids", assignsStableIds)
    , ("planner sorts specialization identities deterministically", sortsIdentities)
    , ("layout demand does not instantiate methods", layoutSkipsMethods)
    , ("member demand instantiates the selected method", memberSelectsMethod)
    , ("member demand selects every overload spelling", memberSelectsOverloads)
    , ("member demand excludes unrelated methods", memberExcludesUnrelated)
    , ("complete demand retains every member", completeRetainsMembers)
    , ("duplicate demands share one specialization", duplicateDemandCoalesces)
    , ("duplicate demands retain unique origins", duplicateOriginsCoalesce)
    , ("duplicate member demands merge their selections", duplicateMembersMerge)
    , ("complete demand dominates member demand", completeDominatesMember)
    , ("member demand dominates layout demand", memberDominatesLayout)
    , ("default and explicit arguments share an identity", defaultAndExplicitCoalesce)
    , ("value arguments create distinct specializations", valueArgumentsStayDistinct)
    , ("freshening replaces declaration symbols", freshensDeclarationSymbol)
    , ("freshening replaces member symbols", freshensMemberSymbol)
    , ("freshening replaces parameter symbols", freshensParameterSymbol)
    , ("freshening keeps parameter references coherent", freshensParameterReference)
    , ("freshening replaces local binding symbols", freshensLocalSymbol)
    , ("freshening keeps local references coherent", freshensLocalReference)
    , ("freshening preserves source spelling", fresheningPreservesSpelling)
    , ("freshening starts above every source symbol", fresheningStartsAboveSource)
    , ("separate specializations receive disjoint symbols", specializationSymbolsAreDisjoint)
    , ("closed TypedAST contains only materialized declarations", typedViewIsClosed)
    , ("closed TypedAST follows emission order", typedViewFollowsOrder)
    , ("dependency edges connect planned member types", dependencyEdgesAreAttached)
    , ("emission order places dependencies first", dependenciesEmitFirst)
    , ("reference-recursive specialization still emits once", recursiveDemandTerminates)
    , ("unknown templates report application failure", unknownTemplateIsRejected)
    , ("ambiguous templates report application failure", ambiguousTemplateIsRejected)
    , ("missing members are rejected", missingMemberIsRejected)
    , ("category mismatches are retained", categoryMismatchIsRejected)
    , ("too few arguments are retained", tooFewArgumentsAreRejected)
    , ("specialization count limit is enforced", specializationLimitIsEnforced)
    , ("origin count limit is enforced", originLimitIsEnforced)
    , ("member count limit is enforced", memberLimitIsEnforced)
    , ("invalid limits fail before planning", invalidLimitsAreRejected)
    , ("statistics count requests and unique entries", statisticsCountDemands)
    , ("statistics count selected members", statisticsCountMembers)
    , ("statistics count dependency edges", statisticsCountDependencies)
    , ("lookup by id returns the planned entry", lookupByIdWorks)
    , ("lookup by identity returns the planned entry", lookupByIdentityWorks)
    , ("missing specialization lookups return Nothing", missingLookupReturnsNothing)
    , ("rendering preserves demand origin", renderingIncludesOrigin)
    , ("rendering names a missing member", renderingNamesMember)
    , ("rendering explains a count limit", renderingExplainsLimit)
    , ("compiler bridge lowers selected members to verified Core", compilerBridgeLowersCore)
    , ("compiler bridge maps planning failures to diagnostics", compilerBridgeMapsDiagnostics)
    ]

data Fixture = Fixture
    { fixtureTyped :: TypedAST
    , fixturePlan :: TemplateSpecializationPlan
    }

qualified :: [String] -> QualifiedName
qualified = QualifiedName . map Identifier

typeArgument :: String -> TemplateArgument
typeArgument name = TypeTemplateArgument (namedType name)

valueArgument :: Integer -> TemplateArgument
valueArgument = ValueTemplateArgument . IntegerTemplateValue

demand :: String -> [TemplateArgument] -> TemplateDemandScope -> String -> TemplateSpecializationDemand
demand target arguments scope origin =
    TemplateSpecializationDemand
        (TemplateApplication (qualified [target]) arguments)
        scope
        origin

memberDemand :: String -> [TemplateArgument] -> [String] -> String -> TemplateSpecializationDemand
memberDemand target arguments names = demand target arguments (TemplateMemberDemand (map Identifier names))

analyze :: String -> Maybe TypedAST
analyze source = case analyzeSemantics (CompilerInput "specialization-planner-test.tala" source) of
    Right artifacts -> Just (semanticTypedAST artifacts)
    Left _ -> Nothing

plan :: String -> [TemplateSpecializationDemand] -> Either [TemplateSpecializationError] TemplateSpecializationPlan
plan source requests = case analyze source of
    Just typed -> planTemplateSpecializations defaultTemplateSpecializationLimits typed requests
    Nothing -> Left []

fixture :: Maybe Fixture
fixture = do
    typed <- analyze boxSource
    specializationPlan <-
        rightValue (planTemplateSpecializations defaultTemplateSpecializationLimits typed [boxIdentityDemand])
    pure (Fixture typed specializationPlan)

boxSource :: String
boxSource =
    "template<typename T> class Box { T Identity(_ T value) { T copy = value; return copy; } int Size() { return 1; } }"

boxIdentityDemand :: TemplateSpecializationDemand
boxIdentityDemand = memberDemand "Box" [typeArgument "String"] ["Identity"] "test:identity"

singleSpecialization :: TemplateSpecializationPlan -> Maybe TemplateSpecialization
singleSpecialization specializationPlan = case plannedTemplateSpecializations specializationPlan of
    [specialization] -> Just specialization
    _ -> Nothing

selectedMembers :: TemplateSpecialization -> [Declaration ResolvedName Type]
selectedMembers specialization = case templateSpecializationDeclaration specialization of
    TypeDeclaration {typeMembers = members} -> members
    _ -> []

selectedSpellings :: TemplateSpecialization -> [String]
selectedSpellings = map (identifierText . resolvedSpelling . declarationName) . selectedMembers

materializesOrdinaryDeclaration :: Bool
materializesOrdinaryDeclaration = case fixture >>= singleSpecialization . fixturePlan of
    Just TemplateSpecialization {templateSpecializationDeclaration = TypeDeclaration {}} -> True
    _ -> False

preservesConcreteType :: Bool
preservesConcreteType = case fixture >>= singleSpecialization . fixturePlan of
    Just specialization ->
        templateSpecializationType specialization
            == NamedType (qualified ["Box"]) [typeArgument "String"]
    Nothing -> False

assignsStableIds :: Bool
assignsStableIds = case plan boxSource requests of
    Right specializationPlan ->
        map (templateSpecializationIdValue . templateSpecializationId) (plannedTemplateSpecializations specializationPlan)
            == [1, 2]
    Left _ -> False
    where
        requests =
            [ memberDemand "Box" [typeArgument "String"] ["Identity"] "string"
            , memberDemand "Box" [typeArgument "int"] ["Identity"] "int"
            ]

sortsIdentities :: Bool
sortsIdentities = case plan boxSource requests of
    Right specializationPlan ->
        let identities = map templateSpecializationIdentity (plannedTemplateSpecializations specializationPlan)
         in identities == ordered identities
    Left _ -> False
    where
        requests =
            [ memberDemand "Box" [typeArgument "String"] ["Identity"] "later"
            , memberDemand "Box" [typeArgument "bool"] ["Identity"] "earlier"
            ]

layoutSkipsMethods :: Bool
layoutSkipsMethods = case plan boxSource [demand "Box" [typeArgument "String"] TemplateLayoutDemand "layout"] of
    Right value -> maybe False (null . selectedMembers) (singleSpecialization value)
    Left _ -> False

memberSelectsMethod :: Bool
memberSelectsMethod = case fixture >>= singleSpecialization . fixturePlan of
    Just specialization -> selectedSpellings specialization == ["Identity"]
    Nothing -> False

memberSelectsOverloads :: Bool
memberSelectsOverloads = case analyze source >>= duplicateReadMember of
    Just typed -> case planTemplateSpecializations defaultTemplateSpecializationLimits typed requests of
        Right value -> maybe False ((== ["Read", "Read"]) . selectedSpellings) (singleSpecialization value)
        Left _ -> False
    Nothing -> False
    where
        source =
            "template<typename T> class Box { T Read(_ T value) { return value; } void Skip() { return; } }"
        requests = [memberDemand "Box" [typeArgument "int"] ["Read"] "overload"]

duplicateReadMember :: TypedAST -> Maybe TypedAST
duplicateReadMember typed@(TypedAST tree) = case syntaxDeclarations tree of
    [declaration@TemplateTypeDeclaration {typeMembers = readMember : remaining}] ->
        let SymbolId maximumSource = maximumSymbolInTypedAST typed
            duplicate =
                readMember
                    { declarationName =
                        ResolvedName (SymbolId (maximumSource + 1)) (resolvedSpelling (declarationName readMember))
                    }
         in Just (TypedAST tree {syntaxDeclarations = [declaration {typeMembers = readMember : duplicate : remaining}]})
    _ -> Nothing

memberExcludesUnrelated :: Bool
memberExcludesUnrelated = case fixture >>= singleSpecialization . fixturePlan of
    Just specialization -> "Size" `notElem` selectedSpellings specialization
    Nothing -> False

completeRetainsMembers :: Bool
completeRetainsMembers = case plan boxSource [demand "Box" [typeArgument "String"] TemplateCompleteDemand "complete"] of
    Right value -> maybe False ((== ["Identity", "Size"]) . selectedSpellings) (singleSpecialization value)
    Left _ -> False

duplicateDemandCoalesces :: Bool
duplicateDemandCoalesces = case plan boxSource [boxIdentityDemand, boxIdentityDemand] of
    Right value -> length (plannedTemplateSpecializations value) == 1
    Left _ -> False

duplicateOriginsCoalesce :: Bool
duplicateOriginsCoalesce = case plan boxSource requests of
    Right value -> case singleSpecialization value of
        Just specialization -> templateSpecializationOrigins specialization == ["first", "second"]
        Nothing -> False
    Left _ -> False
    where
        requests =
            [ memberDemand "Box" [typeArgument "String"] ["Identity"] "first"
            , memberDemand "Box" [typeArgument "String"] ["Identity"] "second"
            , memberDemand "Box" [typeArgument "String"] ["Identity"] "first"
            ]

duplicateMembersMerge :: Bool
duplicateMembersMerge = case plan boxSource requests of
    Right value -> maybe False ((== ["Identity", "Size"]) . selectedSpellings) (singleSpecialization value)
    Left _ -> False
    where
        requests =
            [ memberDemand "Box" [typeArgument "String"] ["Identity"] "identity"
            , memberDemand "Box" [typeArgument "String"] ["Size"] "size"
            ]

completeDominatesMember :: Bool
completeDominatesMember = case plan boxSource requests of
    Right value -> maybe False ((== TemplateCompleteDemand) . templateSpecializationScope) (singleSpecialization value)
    Left _ -> False
    where
        requests =
            [ boxIdentityDemand
            , demand "Box" [typeArgument "String"] TemplateCompleteDemand "complete"
            ]

memberDominatesLayout :: Bool
memberDominatesLayout = case plan boxSource requests of
    Right value -> maybe False ((== ["Identity"]) . selectedSpellings) (singleSpecialization value)
    Left _ -> False
    where
        requests =
            [ demand "Box" [typeArgument "String"] TemplateLayoutDemand "layout"
            , boxIdentityDemand
            ]

defaultAndExplicitCoalesce :: Bool
defaultAndExplicitCoalesce = case plan source requests of
    Right value -> length (plannedTemplateSpecializations value) == 1
    Left _ -> False
    where
        source = "template<typename T = int> class Box { T Read(_ T value) { return value; } }"
        requests =
            [ memberDemand "Box" [] ["Read"] "default"
            , memberDemand "Box" [typeArgument "int"] ["Read"] "explicit"
            ]

valueArgumentsStayDistinct :: Bool
valueArgumentsStayDistinct = case plan source requests of
    Right value -> length (plannedTemplateSpecializations value) == 2
    Left _ -> False
    where
        source = "template<int N> class Buffer { int Size() { return 1; } }"
        requests =
            [ memberDemand "Buffer" [valueArgument 4] ["Size"] "four"
            , memberDemand "Buffer" [valueArgument 8] ["Size"] "eight"
            ]

freshensDeclarationSymbol :: Bool
freshensDeclarationSymbol = case (fixture, fixture >>= singleSpecialization . fixturePlan) of
    (Just value, Just specialization) ->
        sourceDeclarationSymbol (fixtureTyped value) /= declarationSymbol (templateSpecializationDeclaration specialization)
    _ -> False

freshensMemberSymbol :: Bool
freshensMemberSymbol = case (fixture, fixture >>= singleSpecialization . fixturePlan) of
    (Just value, Just specialization) -> case (sourceMemberSymbol (fixtureTyped value), selectedMembers specialization) of
        (Just old, [member]) -> old /= resolvedSymbol (declarationName member)
        _ -> False
    _ -> False

freshensParameterSymbol :: Bool
freshensParameterSymbol = case fixture >>= singleSpecialization . fixturePlan of
    Just specialization -> case selectedMembers specialization of
        [FunctionDeclaration {declarationParameters = [parameter]}] ->
            resolvedSymbol (parameterName parameter) `elem` map snd (templateSpecializationSymbolMap specialization)
        _ -> False
    Nothing -> False

freshensParameterReference :: Bool
freshensParameterReference = case fixture >>= singleSpecialization . fixturePlan of
    Just specialization -> case selectedMembers specialization of
        [ FunctionDeclaration
                { declarationParameters = [parameter]
                , declarationBody = Block (BindingStatement _ _ _ _ _ (NameExpression _ reference _) : _)
                }
            ] ->
                resolvedSymbol (parameterName parameter) == resolvedSymbol reference
        _ -> False
    Nothing -> False

freshensLocalSymbol :: Bool
freshensLocalSymbol = case fixture >>= singleSpecialization . fixturePlan of
    Just specialization -> case selectedMembers specialization of
        [FunctionDeclaration {declarationBody = Block (BindingStatement _ _ _ local _ _ : _)}] ->
            resolvedSymbol local `elem` map snd (templateSpecializationSymbolMap specialization)
        _ -> False
    Nothing -> False

freshensLocalReference :: Bool
freshensLocalReference = case fixture >>= singleSpecialization . fixturePlan of
    Just specialization -> case selectedMembers specialization of
        [ FunctionDeclaration
                { declarationBody = Block [BindingStatement _ _ _ local _ _, ReturnStatement _ (Just (NameExpression _ reference _))]
                }
            ] ->
                resolvedSymbol local == resolvedSymbol reference
        _ -> False
    Nothing -> False

fresheningPreservesSpelling :: Bool
fresheningPreservesSpelling = case fixture >>= singleSpecialization . fixturePlan of
    Just specialization ->
        identifierText (resolvedSpelling (declarationName (templateSpecializationDeclaration specialization))) == "Box"
            && selectedSpellings specialization == ["Identity"]
    Nothing -> False

fresheningStartsAboveSource :: Bool
fresheningStartsAboveSource = case (fixture, fixture >>= singleSpecialization . fixturePlan) of
    (Just value, Just specialization) ->
        let SymbolId maximumSource = maximumSymbolInTypedAST (fixtureTyped value)
            generated = map (symbolIdValue . snd) (templateSpecializationSymbolMap specialization)
         in not (null generated) && all (> maximumSource) generated
    _ -> False

specializationSymbolsAreDisjoint :: Bool
specializationSymbolsAreDisjoint = case plan boxSource requests of
    Right value -> case plannedTemplateSpecializations value of
        [left, right] ->
            let leftSymbols = map snd (templateSpecializationSymbolMap left)
                rightSymbols = map snd (templateSpecializationSymbolMap right)
             in null [symbol | symbol <- leftSymbols, symbol `elem` rightSymbols]
        _ -> False
    Left _ -> False
    where
        requests =
            [ boxIdentityDemand
            , memberDemand "Box" [typeArgument "int"] ["Identity"] "int"
            ]

typedViewIsClosed :: Bool
typedViewIsClosed = case fixture of
    Just value -> case syntaxDeclarations (typedSyntaxTree (specializationTypedAST (fixturePlan value))) of
        [TypeDeclaration {}] -> True
        _ -> False
    Nothing -> False

typedViewFollowsOrder :: Bool
typedViewFollowsOrder = case plan boxSource requests of
    Right value ->
        length (syntaxDeclarations (typedSyntaxTree (specializationTypedAST value)))
            == length (specializationEmissionOrder value)
    Left _ -> False
    where
        requests =
            [ boxIdentityDemand
            , memberDemand "Box" [typeArgument "int"] ["Identity"] "int"
            ]

dependencySource :: String
dependencySource =
    "template<typename T> class Leaf { T Read(_ T value) { return value; } } "
        ++ "template<typename T> class Holder { Leaf<T> Make(_ Leaf<T> value) { return value; } }"

dependencyRequests :: [TemplateSpecializationDemand]
dependencyRequests =
    [ memberDemand "Holder" [typeArgument "String"] ["Make"] "holder"
    , memberDemand "Leaf" [typeArgument "String"] ["Read"] "leaf"
    ]

dependencyEdgesAreAttached :: Bool
dependencyEdgesAreAttached = case plan dependencySource dependencyRequests of
    Right value -> case findByTarget "Holder" value of
        Just specialization -> length (templateSpecializationDependencies specialization) == 1
        Nothing -> False
    Left _ -> False

dependenciesEmitFirst :: Bool
dependenciesEmitFirst = case plan dependencySource dependencyRequests of
    Right value -> case (findByTarget "Holder" value, findByTarget "Leaf" value) of
        (Just holder, Just leaf) ->
            before
                (templateSpecializationId leaf)
                (templateSpecializationId holder)
                (specializationEmissionOrder value)
        _ -> False
    Left _ -> False

recursiveDemandTerminates :: Bool
recursiveDemandTerminates = case plan source requests of
    Right value -> length (specializationEmissionOrder value) == 1
    Left _ -> False
    where
        source = "template<typename T> class Node { Node<T> Next(_ Node<T> value) { return value; } }"
        requests = [memberDemand "Node" [typeArgument "String"] ["Next"] "recursive"]

unknownTemplateIsRejected :: Bool
unknownTemplateIsRejected = case plan boxSource [demand "Missing" [] TemplateLayoutDemand "unknown-site"] of
    Left [TemplateApplicationFailed "unknown-site" [UnknownTemplateDeclaration {}]] -> True
    _ -> False

ambiguousTemplateIsRejected :: Bool
ambiguousTemplateIsRejected = case analyze source >>= duplicateTopTemplate of
    Just typed -> case planTemplateSpecializations defaultTemplateSpecializationLimits typed requests of
        Left [TemplateApplicationFailed _ [AmbiguousTemplateDeclaration {}]] -> True
        _ -> False
    Nothing -> False
    where
        source = "template<typename T> class Box {}"
        requests = [demand "Box" [typeArgument "int"] TemplateLayoutDemand "ambiguous"]

duplicateTopTemplate :: TypedAST -> Maybe TypedAST
duplicateTopTemplate (TypedAST tree) = case syntaxDeclarations tree of
    [declaration@TemplateTypeDeclaration {}] ->
        Just (TypedAST tree {syntaxDeclarations = [declaration, declaration]})
    _ -> Nothing

missingMemberIsRejected :: Bool
missingMemberIsRejected = case plan boxSource [memberDemand "Box" [typeArgument "String"] ["Missing"] "member"] of
    Left [TemplateMemberNotFound _ (Identifier "Missing")] -> True
    _ -> False

categoryMismatchIsRejected :: Bool
categoryMismatchIsRejected = case plan boxSource [demand "Box" [valueArgument 1] TemplateLayoutDemand "category"] of
    Left [TemplateApplicationFailed _ [TemplateArgumentCategoryMismatch {}]] -> True
    _ -> False

tooFewArgumentsAreRejected :: Bool
tooFewArgumentsAreRejected = case plan boxSource [demand "Box" [] TemplateLayoutDemand "arity"] of
    Left [TemplateApplicationFailed _ [TooFewTemplateArguments {}]] -> True
    _ -> False

specializationLimitIsEnforced :: Bool
specializationLimitIsEnforced = case analyze boxSource of
    Just typed -> case planTemplateSpecializations limits typed requests of
        Left [TemplateSpecializationLimitExceeded 1 _] -> True
        _ -> False
    Nothing -> False
    where
        limits = defaultTemplateSpecializationLimits {maximumTemplateSpecializations = 1}
        requests =
            [ memberDemand "Box" [typeArgument "int"] ["Identity"] "one"
            , memberDemand "Box" [typeArgument "String"] ["Identity"] "two"
            ]

originLimitIsEnforced :: Bool
originLimitIsEnforced = case analyze boxSource of
    Just typed -> case planTemplateSpecializations limits typed requests of
        Left [TemplateOriginLimitExceeded 1 "second"] -> True
        _ -> False
    Nothing -> False
    where
        limits = defaultTemplateSpecializationLimits {maximumTemplateOrigins = 1}
        requests =
            [ memberDemand "Box" [typeArgument "String"] ["Identity"] "first"
            , memberDemand "Box" [typeArgument "String"] ["Identity"] "second"
            ]

memberLimitIsEnforced :: Bool
memberLimitIsEnforced = case analyze boxSource of
    Just typed -> case planTemplateSpecializations limits typed requests of
        Left [TemplateMemberLimitExceeded _ 1 2] -> True
        _ -> False
    Nothing -> False
    where
        limits = defaultTemplateSpecializationLimits {maximumMembersPerSpecialization = 1}
        requests = [demand "Box" [typeArgument "String"] TemplateCompleteDemand "complete"]

invalidLimitsAreRejected :: Bool
invalidLimitsAreRejected = case analyze boxSource of
    Just typed -> case planTemplateSpecializations invalid typed [boxIdentityDemand] of
        Left [InvalidTemplateSpecializationLimits _] -> True
        _ -> False
    Nothing -> False
    where
        invalid = defaultTemplateSpecializationLimits {maximumTemplateSpecializations = 0}

statisticsCountDemands :: Bool
statisticsCountDemands = case plan boxSource [boxIdentityDemand, boxIdentityDemand] of
    Right value ->
        let statistics = plannedTemplateStatistics value
         in requestedTemplateSpecializations statistics == 2
                && uniqueTemplateSpecializations statistics == 1
                && coalescedTemplateSpecializations statistics == 1
    Left _ -> False

statisticsCountMembers :: Bool
statisticsCountMembers = case plan boxSource [demand "Box" [typeArgument "String"] TemplateCompleteDemand "complete"] of
    Right value -> instantiatedTemplateMembers (plannedTemplateStatistics value) == 2
    Left _ -> False

statisticsCountDependencies :: Bool
statisticsCountDependencies = case plan dependencySource dependencyRequests of
    Right value -> templateDependencyEdges (plannedTemplateStatistics value) == 1
    Left _ -> False

lookupByIdWorks :: Bool
lookupByIdWorks = case fixture of
    Just value -> case specializationEmissionOrder (fixturePlan value) of
        identifier : _ -> findTemplateSpecialization identifier (fixturePlan value) /= Nothing
        [] -> False
    Nothing -> False

lookupByIdentityWorks :: Bool
lookupByIdentityWorks = case fixture >>= singleSpecialization . fixturePlan of
    Just specialization -> case fixture of
        Just value ->
            findTemplateSpecializationByIdentity (templateSpecializationIdentity specialization) (fixturePlan value)
                == Just specialization
        Nothing -> False
    Nothing -> False

missingLookupReturnsNothing :: Bool
missingLookupReturnsNothing = case fixture of
    Just value ->
        findTemplateSpecialization (TemplateSpecializationId 999) (fixturePlan value) == Nothing
            && findTemplateSpecializationByIdentity "missing" (fixturePlan value) == Nothing
    Nothing -> False

renderingIncludesOrigin :: Bool
renderingIncludesOrigin =
    "source:42"
        `isInfixOf` renderTemplateSpecializationError
            (TemplateApplicationFailed "source:42" [UnknownTemplateDeclaration (qualified ["Missing"])])

renderingNamesMember :: Bool
renderingNamesMember =
    "member named Read"
        `isInfixOf` renderTemplateSpecializationError
            (TemplateMemberNotFound (qualified ["Box"]) (Identifier "Read"))

renderingExplainsLimit :: Bool
renderingExplainsLimit =
    "limit 4"
        `isInfixOf` renderTemplateSpecializationError
            (TemplateSpecializationLimitExceeded 4 "Box<String>")

compilerBridgeLowersCore :: Bool
compilerBridgeLowersCore = case analyze boxSource of
    Just typed -> case compileTemplateSpecializations defaultTemplateSpecializationLimits typed [boxIdentityDemand] of
        Right (_, core) -> case coreModuleFunctions core of
            [function] -> identifierText (resolvedSpelling (coreFunctionName function)) == "Identity"
            _ -> False
        Left _ -> False
    Nothing -> False

compilerBridgeMapsDiagnostics :: Bool
compilerBridgeMapsDiagnostics = case analyze boxSource of
    Just typed -> case compileTemplateSpecializations defaultTemplateSpecializationLimits typed requests of
        Left [problem] ->
            diagnosticStage problem == TypeCheckerStage
                && "unknown template declaration Missing" `isInfixOf` diagnosticMessage problem
        Left _ -> False
        Right _ -> False
    Nothing -> False
    where
        requests = [demand "Missing" [] TemplateLayoutDemand "bridge"]

sourceDeclarationSymbol :: TypedAST -> SymbolId
sourceDeclarationSymbol (TypedAST tree) = case syntaxDeclarations tree of
    declaration : _ -> resolvedSymbol (declarationName declaration)
    [] -> SymbolId 0

sourceMemberSymbol :: TypedAST -> Maybe SymbolId
sourceMemberSymbol (TypedAST tree) = case syntaxDeclarations tree of
    TemplateTypeDeclaration {typeMembers = member : _} : _ -> Just (resolvedSymbol (declarationName member))
    _ -> Nothing

declarationSymbol :: Declaration ResolvedName Type -> SymbolId
declarationSymbol = resolvedSymbol . declarationName

findByTarget :: String -> TemplateSpecializationPlan -> Maybe TemplateSpecialization
findByTarget target specializationPlan =
    first matching (plannedTemplateSpecializations specializationPlan)
    where
        matching specialization = case templateSpecializationType specialization of
            NamedType (QualifiedName parts) _ -> case reverse parts of
                Identifier name : _ -> name == target
                [] -> False
            _ -> False

before :: (Eq value) => value -> value -> [value] -> Bool
before left right values = case (position left values, position right values) of
    (Just leftIndex, Just rightIndex) -> leftIndex < rightIndex
    _ -> False

position :: (Eq value) => value -> [value] -> Maybe Int
position needle = go 0
    where
        go _ [] = Nothing
        go index (value : remaining)
            | value == needle = Just index
            | otherwise = go (index + 1) remaining

ordered :: (Ord value) => [value] -> [value]
ordered [] = []
ordered (value : remaining) =
    ordered [candidate | candidate <- remaining, candidate <= value]
        ++ [value]
        ++ ordered [candidate | candidate <- remaining, candidate > value]

first :: (value -> Bool) -> [value] -> Maybe value
first _ [] = Nothing
first predicate (value : remaining)
    | predicate value = Just value
    | otherwise = first predicate remaining

rightValue :: Either failure value -> Maybe value
rightValue result = case result of
    Right value -> Just value
    Left _ -> Nothing