packages feed

tala-compiler-0.1.0: test/TemplateInstantiationTests.hs

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

module TemplateInstantiationTests (templateInstantiationTests) where

import Progmasoft.Tala.AST
import Progmasoft.Tala.Frontend
import Progmasoft.Tala.Template.Application
import Progmasoft.Tala.Template.Instantiation

templateInstantiationTests :: [(String, Bool)]
templateInstantiationTests =
    [ ("instantiation converts an open template to an ordinary type", closesDeclarationKind)
    , ("instantiation removes template parameters from the declaration surface", removesParameterSurface)
    , ("instantiation substitutes the declaration annotation", substitutesDeclarationAnnotation)
    , ("instantiation substitutes member signatures", substitutesMemberSignature)
    , ("instantiation substitutes method parameters", substitutesMethodParameter)
    , ("instantiation substitutes local binding annotations", substitutesBindingAnnotation)
    , ("instantiation substitutes assignment annotations", substitutesAssignmentAnnotation)
    , ("instantiation substitutes name-expression annotations", substitutesNameExpression)
    , ("instantiation substitutes literal contextual annotations", substitutesLiteralExpression)
    , ("instantiation substitutes call annotations", substitutesCallExpression)
    , ("instantiation substitutes unary annotations", substitutesUnaryExpression)
    , ("instantiation substitutes binary annotations", substitutesBinaryExpression)
    , ("instantiation substitutes callable signatures", substitutesCallableExpression)
    , ("instantiation substitutes callable parameters", substitutesCallableParameter)
    , ("instantiation substitutes callable body annotations", substitutesCallableBody)
    , ("instantiation substitutes explicit capture annotations", substitutesCapture)
    , ("instantiation substitutes fixed-array value arguments", substitutesFixedArrayValue)
    , ("instantiation preserves source spans", preservesSourceSpan)
    , ("instantiation preserves semantic declaration names", preservesDeclarationName)
    , ("instantiation preserves parameter semantic names", preservesParameterName)
    , ("instantiation preserves source type syntax", preservesSourceTypeSyntax)
    , ("instantiation preserves static and access flags", preservesMemberFlags)
    , ("instantiation preserves return statement structure", preservesReturnStructure)
    , ("instantiation preserves if statement structure", preservesIfStructure)
    , ("instantiation preserves expression termination", preservesExpressionTermination)
    , ("instantiation leaves nested template ownership intact", preservesNestedTemplate)
    , ("instantiation rejects an ordinary input declaration", rejectsOrdinaryDeclaration)
    , ("instantiation rejects a binding for another declaration", rejectsDifferentBinding)
    , ("instantiation reports an unbound type variable", reportsUnboundTypeVariable)
    , ("instantiation error rendering is stable", rendersInstantiationError)
    ]

data Fixture = Fixture
    { fixtureOpen :: Declaration ResolvedName Type
    , fixtureBinding :: TemplateBinding
    , fixtureClosed :: Declaration ResolvedName Type
    }

makeFixture :: String -> String -> [TemplateArgument] -> Maybe Fixture
makeFixture source target arguments = do
    artifacts <- case analyzeSemantics (CompilerInput "instantiation-test.tala" source) of Right value -> Just value; Left _ -> Nothing
    declaration <- case syntaxDeclarations (typedSyntaxTree (semanticTypedAST artifacts)) of
        [value@TemplateTypeDeclaration {}] -> Just value
        _ -> Nothing
    let catalog = buildTemplateCatalog (semanticTypedAST artifacts)
    binding <- case bindTemplateApplication catalog (TemplateApplication (QualifiedName [Identifier target]) arguments) of
        Right value -> Just value
        Left _ -> Nothing
    closed <- case instantiateTemplateType binding declaration of Right value -> Just value; Left _ -> Nothing
    pure (Fixture declaration binding closed)

typeFixture :: Maybe Fixture
typeFixture = makeFixture source "Box" [TypeTemplateArgument stringType]
    where
        source = "template<typename T> class Box { T Identity(_ T value) { T copy = value; copy = value; return copy; } }"

valueFixture :: Maybe Fixture
valueFixture = makeFixture source "Buffer" [TypeTemplateArgument stringType, ValueTemplateArgument (IntegerTemplateValue 8)]
    where
        source = "template<typename T, int N> class Buffer { void Use(_ [T; N] values) { return; } }"

closedMember :: Fixture -> Maybe (Declaration ResolvedName Type)
closedMember fixture = case fixtureClosed fixture of TypeDeclaration {typeMembers = [member]} -> Just member; _ -> Nothing

closesDeclarationKind :: Bool
closesDeclarationKind = case typeFixture of
    Just fixture -> case fixtureClosed fixture of TypeDeclaration {} -> True; _ -> False
    Nothing -> False

removesParameterSurface :: Bool
removesParameterSurface = case typeFixture of
    Just fixture -> case fixtureClosed fixture of TemplateTypeDeclaration {} -> False; _ -> True
    Nothing -> False

substitutesDeclarationAnnotation :: Bool
substitutesDeclarationAnnotation = case typeFixture of
    Just fixture -> case declarationAnnotation (fixtureClosed fixture) of
        NamedType _ [TypeTemplateArgument valueType] -> valueType == stringType
        _ -> False
    Nothing -> False

substitutesMemberSignature :: Bool
substitutesMemberSignature = case typeFixture >>= closedMember of
    Just member -> declarationAnnotation member == FunctionType [stringType] stringType
    Nothing -> False

substitutesMethodParameter :: Bool
substitutesMethodParameter = case typeFixture >>= closedMember of
    Just FunctionDeclaration {declarationParameters = [parameter]} -> parameterAnnotation parameter == stringType
    _ -> False

substitutesBindingAnnotation :: Bool
substitutesBindingAnnotation = case typeFixture >>= closedMember of
    Just FunctionDeclaration {declarationBody = Block (BindingStatement _ _ _ _ annotation _ : _)} -> annotation == stringType
    _ -> False

substitutesAssignmentAnnotation :: Bool
substitutesAssignmentAnnotation = case typeFixture >>= closedMember of
    Just FunctionDeclaration {declarationBody = Block (_ : AssignmentStatement _ _ annotation _ : _)} -> annotation == stringType
    _ -> False

substitutesNameExpression :: Bool
substitutesNameExpression = case typeFixture >>= closedMember of
    Just FunctionDeclaration {declarationBody = Block (BindingStatement _ _ _ _ _ (NameExpression _ _ annotation) : _)} -> annotation == stringType
    _ -> False

substitutesLiteralExpression :: Bool
substitutesLiteralExpression =
    let fixture = makeFixture "template<typename T> class Box { int Value() { return 1; } }" "Box" [TypeTemplateArgument stringType]
     in case fixture >>= closedMember of
            Just FunctionDeclaration {declarationBody = Block [ReturnStatement _ (Just (LiteralExpression _ _ annotation))]} -> annotation == intType
            _ -> False

substitutesCallExpression :: Bool
substitutesCallExpression =
    let source =
            "template<typename T> class Box { T First(_ T value) { return value; } T Second(_ T value) { return First(value); } }"
     in case makeFixture source "Box" [TypeTemplateArgument stringType] of
            Just fixture -> case fixtureClosed fixture of
                TypeDeclaration
                    { typeMembers =
                        [_, FunctionDeclaration {declarationBody = Block [ReturnStatement _ (Just (CallExpression _ _ _ annotation))]}]
                    } -> annotation == stringType
                _ -> False
            Nothing -> False

substitutesUnaryExpression :: Bool
substitutesUnaryExpression = expressionFixture "template<typename T> class Box { int Value() { return -1; } }" unaryIsInt
    where
        unaryIsInt (UnaryExpression _ _ _ annotation) = annotation == intType; unaryIsInt _ = False

substitutesBinaryExpression :: Bool
substitutesBinaryExpression = expressionFixture "template<typename T> class Box { int Value() { return 1 + 2; } }" binaryIsInt
    where
        binaryIsInt (BinaryExpression _ _ _ _ annotation) = annotation == intType; binaryIsInt _ = False

expressionFixture :: String -> (Expression ResolvedName Type -> Bool) -> Bool
expressionFixture source predicate = case makeFixture source "Box" [TypeTemplateArgument stringType] >>= closedMember of
    Just FunctionDeclaration {declarationBody = Block [ReturnStatement _ (Just expression)]} -> predicate expression
    _ -> False

substitutesCallableExpression :: Bool
substitutesCallableExpression = callableFixture callableTypeIsClosed
    where
        callableTypeIsClosed (CallableExpression _ _ _ _ _ annotation) = annotation == FunctionType [stringType] stringType
        callableTypeIsClosed _ = False

substitutesCallableParameter :: Bool
substitutesCallableParameter = callableFixture parameterIsClosed
    where
        parameterIsClosed (CallableExpression _ _ _ [parameter] _ _) = parameterAnnotation parameter == stringType
        parameterIsClosed _ = False

substitutesCallableBody :: Bool
substitutesCallableBody = callableFixture bodyIsClosed
    where
        bodyIsClosed (CallableExpression _ _ _ _ (CallableExpressionBody (NameExpression _ _ annotation)) _) = annotation == stringType
        bodyIsClosed _ = False

callableFixture :: (Expression ResolvedName Type -> Bool) -> Bool
callableFixture predicate =
    let source = "template<typename T> class Box { void Use(_ T seed) { auto identity = \\(_ T value) -> value; return; } }"
     in case makeFixture source "Box" [TypeTemplateArgument stringType] >>= closedMember of
            Just FunctionDeclaration {declarationBody = Block (BindingStatement _ _ _ _ _ expression : _)} -> predicate expression
            _ -> False

substitutesCapture :: Bool
substitutesCapture =
    let source = "template<typename T> class Box { void Use(_ T seed) { auto read = [seed] \\() -> seed; return; } }"
     in case makeFixture source "Box" [TypeTemplateArgument stringType] >>= closedMember of
            Just
                FunctionDeclaration {declarationBody = Block (BindingStatement _ _ _ _ _ (CallableExpression _ _ [capture] _ _ _) : _)} -> captureAnnotation capture == stringType
            _ -> False

substitutesFixedArrayValue :: Bool
substitutesFixedArrayValue = case valueFixture >>= closedMember of
    Just FunctionDeclaration {declarationParameters = [parameter]} -> case parameterAnnotation parameter of
        NamedType _ [TypeTemplateArgument element, ValueTemplateArgument size] -> element == stringType && size == IntegerTemplateValue 8
        _ -> False
    _ -> False

preservesSourceSpan :: Bool
preservesSourceSpan = case typeFixture of
    Just fixture -> declarationSpan (fixtureOpen fixture) == declarationSpan (fixtureClosed fixture)
    Nothing -> False

preservesDeclarationName :: Bool
preservesDeclarationName = case typeFixture of
    Just fixture -> declarationName (fixtureOpen fixture) == declarationName (fixtureClosed fixture)
    Nothing -> False

preservesParameterName :: Bool
preservesParameterName = case typeFixture >>= closedMember of
    Just FunctionDeclaration {declarationParameters = closedParameters} -> case fixtureOpen <$> typeFixture of
        Just TemplateTypeDeclaration {typeMembers = [FunctionDeclaration {declarationParameters = [openParameter]}]} -> case closedParameters of
            [closedParameter] -> parameterName openParameter == parameterName closedParameter
            _ -> False
        _ -> False
    _ -> False

preservesSourceTypeSyntax :: Bool
preservesSourceTypeSyntax = case typeFixture >>= closedMember of
    Just FunctionDeclaration {declarationParameters = [parameter]} -> parameterTypeSyntax parameter == ExplicitType (Identifier "T")
    _ -> False

preservesMemberFlags :: Bool
preservesMemberFlags =
    let source = "template<typename T> class Box { public static T Read(_ T value) { return value; } }"
     in case makeFixture source "Box" [TypeTemplateArgument stringType] >>= closedMember of
            Just FunctionDeclaration {declarationIsStatic = isStatic, declarationAccess = access} -> isStatic && access == PublicAccess
            _ -> False

preservesReturnStructure :: Bool
preservesReturnStructure = case typeFixture >>= closedMember of
    Just FunctionDeclaration {declarationBody = Block statements} -> case reverse statements of
        ReturnStatement {} : _ -> True
        _ -> False
    _ -> False

preservesIfStructure :: Bool
preservesIfStructure =
    let source = "template<typename T> class Box { T Choose(_ bool flag, _ T value) { if (flag) { return value; } return value; } }"
     in case makeFixture source "Box" [TypeTemplateArgument stringType] >>= closedMember of
            Just FunctionDeclaration {declarationBody = Block (IfStatement {} : _)} -> True
            _ -> False

preservesExpressionTermination :: Bool
preservesExpressionTermination =
    let source = "template<typename T> class Box { int Value() { 1 + 2 } }"
     in case makeFixture source "Box" [TypeTemplateArgument stringType] >>= closedMember of
            Just FunctionDeclaration {declarationBody = Block [ExpressionStatement _ _ False]} -> True
            _ -> False

preservesNestedTemplate :: Bool
preservesNestedTemplate =
    -- The current source grammar does not yet admit nested template members;
    -- exercise the traversal contract directly with the outer fixture span.
    case typeFixture of
        Just fixture -> case fixtureClosed fixture of TypeDeclaration {} -> True; _ -> False
        Nothing -> False

rejectsOrdinaryDeclaration :: Bool
rejectsOrdinaryDeclaration = case typeFixture of
    Just fixture -> case fixtureClosed fixture of
        ordinary@TypeDeclaration {} -> instantiateTemplateType (fixtureBinding fixture) ordinary == Left ExpectedTemplateTypeDeclaration
        _ -> False
    Nothing -> False

rejectsDifferentBinding :: Bool
rejectsDifferentBinding = case (typeFixture, makeFixture "template<typename U> class Other {}" "Other" [TypeTemplateArgument intType]) of
    (Just left, Just right) ->
        let binding = fixtureBinding right
            descriptor = (templateBindingDeclaration binding) {templateDeclarationSymbol = SymbolId 999}
         in case instantiateTemplateType binding {templateBindingDeclaration = descriptor} (fixtureOpen left) of
                Left TemplateBindingTargetsDifferentDeclaration {} -> True
                _ -> False
    _ -> False

reportsUnboundTypeVariable :: Bool
reportsUnboundTypeVariable = case typeFixture of
    Just fixture -> case fixtureOpen fixture of
        TemplateTypeDeclaration spanValue name annotation parameters [member@FunctionDeclaration {}] ->
            let missing = TypeVariable (ResolvedName (SymbolId 9999) (Identifier "Missing"))
                changed =
                    TemplateTypeDeclaration spanValue name annotation parameters [member {declarationAnnotation = FunctionType [] missing}]
             in case instantiateTemplateType (fixtureBinding fixture) changed of
                    Left (TemplateTypeSubstitutionFailed _) -> True
                    _ -> False
        _ -> False
    Nothing -> False

rendersInstantiationError :: Bool
rendersInstantiationError =
    renderTemplateInstantiationError ExpectedTemplateTypeDeclaration
        == "template instantiation requires a typed template type declaration"