packages feed

tala-compiler-0.1.0: test/TemplateDeclarationTests.hs

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

module TemplateDeclarationTests (templateDeclarationTests) where

import Progmasoft.Tala.AST
import Progmasoft.Tala.Diagnostic
import Progmasoft.Tala.Frontend
import Progmasoft.Tala.Lexer
import Progmasoft.Tala.Parser

templateDeclarationTests :: [(String, Bool)]
templateDeclarationTests =
    [ ("lexer reserves template declaration words", lexerReservesTemplateWords)
    , ("lexer emits ellipsis as one maximal token", lexerEmitsEllipsis)
    , ("parser preserves a type parameter", parserPreservesTypeParameter)
    , ("parser preserves a value parameter", parserPreservesValueParameter)
    , ("parser preserves a template-template parameter", parserPreservesTemplateParameter)
    , ("parser preserves a type parameter pack", parserPreservesTypePack)
    , ("parser preserves a value parameter pack", parserPreservesValuePack)
    , ("parser preserves a template-template parameter pack", parserPreservesTemplatePack)
    , ("parser preserves a type default", parserPreservesTypeDefault)
    , ("parser preserves a later type default reference", parserPreservesLaterDefault)
    , ("parser preserves a scalar value default", parserPreservesValueDefault)
    , ("parser preserves a named value default", parserPreservesNamedValueDefault)
    , ("parser preserves a boolean value default", parserPreservesBooleanDefault)
    , ("parser preserves a character value default", parserPreservesCharacterDefault)
    , ("parser preserves a qualified type default", parserPreservesQualifiedDefault)
    , ("parser preserves a generic type default", parserPreservesGenericDefault)
    , ("parser preserves nested template-template shapes", parserPreservesNestedTemplateShape)
    , ("parser preserves mixed template parameter order", parserPreservesMixedOrder)
    , ("parser rejects an empty template parameter list", parserRejectsEmptyParameters)
    , ("parser rejects an empty template-template signature", parserRejectsEmptyTemplateShape)
    , ("parser rejects a missing template close delimiter", parserRejectsMissingClose)
    , ("parser rejects a missing template parameter name", parserRejectsMissingName)
    , ("parser rejects a missing class after a template prefix", parserRejectsMissingClass)
    , ("parser rejects a dangling template default", parserRejectsDanglingDefault)
    , ("renamer assigns positive identities to template parameters", renamerAssignsPositiveIds)
    , ("renamer keeps source-order template identities", renamerKeepsParameterOrder)
    , ("renamer diagnoses duplicate template parameters", renamerRejectsDuplicateParameters)
    , ("renamer scopes template value parameters in method bodies", renamerScopesValueParameter)
    , ("renamer scopes template parameters independently per declaration", renamerIsolatesTemplates)
    , ("resolver preserves the declaration and parameter identities", resolverPreservesIdentities)
    , ("type checker represents T as a type variable", checkerBuildsTypeVariable)
    , ("type checker represents N as a template value parameter", checkerBuildsValueVariable)
    , ("type checker carries a type variable through callable types", checkerBuildsCallableType)
    , ("type checker carries type variables through arrays", checkerBuildsArrayType)
    , ("type checker carries value variables through fixed arrays", checkerBuildsFixedArrayType)
    , ("type checker carries variables through dictionaries", checkerBuildsDictionaryType)
    , ("type checker annotates value template parameters", checkerAnnotatesValueParameter)
    , ("type checker annotates type template parameters", checkerAnnotatesTypeParameter)
    , ("type checker annotates template-template parameters", checkerAnnotatesTemplateParameter)
    , ("type checker accepts a later type default reference", checkerAcceptsLaterDefault)
    , ("type checker accepts a named value default reference", checkerAcceptsNamedValueDefault)
    , ("type checker rejects a default on a parameter pack", checkerRejectsPackDefault)
    , ("type checker rejects unresolved value defaults", checkerRejectsUnknownValueDefault)
    , ("open templates do not emit unspecialized Core functions", openTemplateStopsBeforeCore)
    , ("ordinary declarations still pass semantic analysis", ordinaryDeclarationUnaffected)
    , ("template and ordinary declarations coexist", templateAndOrdinaryCoexist)
    , ("template member calls use semantic member signatures", templateMemberCallsResolve)
    , ("template identity methods retain exact type variables", identityMethodRetainsType)
    , ("template fixed-array identity retains both parameters", fixedArrayMethodRetainsParameters)
    ]

lexTokens :: String -> Either [Diagnostic] [Token]
lexTokens source = runLexer defaultLexer (LexerInput "template-test.tala" source)

parseSource :: String -> Either [Diagnostic] ParsedAST
parseSource source = do
    tokens <- lexTokens source
    runParser defaultParser (ParserInput "template-test.tala" tokens)

analyzeSource :: String -> Either [Diagnostic] SemanticArtifacts
analyzeSource = analyzeSemantics . CompilerInput "template-test.tala"

onlyParsedTemplate :: String -> Maybe (Declaration Identifier ())
onlyParsedTemplate source = case parseSource source of
    Right (ParsedAST (SyntaxTree _ [declaration@TemplateTypeDeclaration {}])) -> Just declaration
    _ -> Nothing

onlyTypedTemplate :: String -> Maybe (Declaration ResolvedName Type)
onlyTypedTemplate source = case analyzeSource source of
    Right artifacts -> case syntaxDeclarations (typedSyntaxTree (semanticTypedAST artifacts)) of
        [declaration@TemplateTypeDeclaration {}] -> Just declaration
        _ -> Nothing
    Left _ -> Nothing

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

identifier :: String -> Identifier
identifier = Identifier

parameterNames :: Declaration name annotation -> [name]
parameterNames TemplateTypeDeclaration {declarationTemplateParameters = parameters} = map templateParameterName parameters
parameterNames _ = []

lexerReservesTemplateWords :: Bool
lexerReservesTemplateWords = case lexTokens "template<typename T> class Box {}" of
    Right tokens ->
        [(tokenText token, tokenKind token) | token <- tokens, tokenText token `elem` ["template", "typename"]]
            == [("template", KeywordToken), ("typename", KeywordToken)]
    Left _ -> False

lexerEmitsEllipsis :: Bool
lexerEmitsEllipsis = case lexTokens "template<typename... Types> class Tuple {}" of
    Right tokens -> length [() | token <- tokens, tokenText token == "..." && tokenKind token == SymbolToken] == 1
    Left _ -> False

parserPreservesTypeParameter :: Bool
parserPreservesTypeParameter = case onlyParsedTemplate "template<typename T> class Box {}" of
    Just declaration -> case templateParameters declaration of
        [TemplateParameter _ name _ TemplateTypeParameter False Nothing] -> name == identifier "T"
        _ -> False
    Nothing -> False

parserPreservesValueParameter :: Bool
parserPreservesValueParameter = case onlyParsedTemplate "template<int Size> class Buffer {}" of
    Just declaration -> case templateParameters declaration of
        [TemplateParameter _ name _ (TemplateValueParameterKind valueType) False Nothing] ->
            name == identifier "Size" && valueType == ExplicitType (identifier "int")
        _ -> False
    Nothing -> False

parserPreservesTemplateParameter :: Bool
parserPreservesTemplateParameter = case onlyParsedTemplate "template<template<typename> class Container> class Wrapper {}" of
    Just declaration -> case templateParameters declaration of
        [TemplateParameter _ name _ (TemplateTemplateParameter [shape]) False Nothing] ->
            name == identifier "Container"
                && templateParameterShapeKind shape == TemplateTypeParameterShape
                && not (templateParameterShapeIsPack shape)
        _ -> False
    Nothing -> False

parserPreservesTypePack :: Bool
parserPreservesTypePack = case onlyParsedTemplate "template<typename... Types> class Tuple {}" of
    Just declaration -> case templateParameters declaration of
        [parameter] -> templateParameterKind parameter == TemplateTypeParameter && templateParameterIsPack parameter
        _ -> False
    Nothing -> False

parserPreservesValuePack :: Bool
parserPreservesValuePack = case onlyParsedTemplate "template<int... Values> class Integers {}" of
    Just declaration -> case templateParameters declaration of
        [parameter] ->
            templateParameterKind parameter == TemplateValueParameterKind (ExplicitType (identifier "int"))
                && templateParameterIsPack parameter
        _ -> False
    Nothing -> False

parserPreservesTemplatePack :: Bool
parserPreservesTemplatePack = case onlyParsedTemplate "template<template<typename> class... Containers> class Wrapper {}" of
    Just declaration -> case templateParameters declaration of
        [parameter] -> case templateParameterKind parameter of
            TemplateTemplateParameter [_] -> templateParameterIsPack parameter
            _ -> False
        _ -> False
    Nothing -> False

parserPreservesTypeDefault :: Bool
parserPreservesTypeDefault = case onlyParsedTemplate "template<typename T = int> class Box {}" of
    Just declaration -> case templateParameters declaration of
        [parameter] -> templateParameterDefault parameter == Just (TemplateTypeDefault (ExplicitType (identifier "int")))
        _ -> False
    Nothing -> False

parserPreservesLaterDefault :: Bool
parserPreservesLaterDefault = case onlyParsedTemplate "template<typename T = U, typename U = int> class Pair {}" of
    Just declaration -> case templateParameters declaration of
        first : second : [] ->
            templateParameterDefault first == Just (TemplateTypeDefault (ExplicitType (identifier "U")))
                && templateParameterDefault second == Just (TemplateTypeDefault (ExplicitType (identifier "int")))
        _ -> False
    Nothing -> False

parserPreservesValueDefault :: Bool
parserPreservesValueDefault = case onlyParsedTemplate "template<int Size = 64> class Buffer {}" of
    Just declaration -> case templateParameters declaration of
        [parameter] -> case templateParameterDefault parameter of
            Just (TemplateValueDefault (TemplateIntegerSyntax _ 64)) -> True
            _ -> False
        _ -> False
    Nothing -> False

parserPreservesNamedValueDefault :: Bool
parserPreservesNamedValueDefault = case onlyParsedTemplate "template<int N = M, int M = 4> class Buffer {}" of
    Just declaration -> case templateParameters declaration of
        first : _ -> case templateParameterDefault first of
            Just (TemplateValueDefault (TemplateNameSyntax _ (QualifiedName [name]))) -> name == identifier "M"
            _ -> False
        _ -> False
    Nothing -> False

parserPreservesBooleanDefault :: Bool
parserPreservesBooleanDefault = case onlyParsedTemplate "template<bool Enabled = true> class Feature {}" of
    Just declaration -> case templateParameters declaration of
        [parameter] -> case templateParameterDefault parameter of
            Just (TemplateValueDefault (TemplateBooleanSyntax _ True)) -> True
            _ -> False
        _ -> False
    Nothing -> False

parserPreservesCharacterDefault :: Bool
parserPreservesCharacterDefault = case onlyParsedTemplate "template<char Separator = 'x'> class Text {}" of
    Just declaration -> case templateParameters declaration of
        [parameter] -> case templateParameterDefault parameter of
            Just (TemplateValueDefault (TemplateCharacterSyntax _ value)) -> value == 120
            _ -> False
        _ -> False
    Nothing -> False

parserPreservesQualifiedDefault :: Bool
parserPreservesQualifiedDefault = case onlyParsedTemplate "template<typename T = System.String> class Box {}" of
    Just declaration -> case templateParameters declaration of
        [parameter] ->
            templateParameterDefault parameter
                == Just (TemplateTypeDefault (QualifiedTypeSyntax (QualifiedName [identifier "System", identifier "String"]) []))
        _ -> False
    Nothing -> False

parserPreservesGenericDefault :: Bool
parserPreservesGenericDefault = case onlyParsedTemplate "template<typename T = System.Array<int>> class Box {}" of
    Just declaration -> case templateParameters declaration of
        [parameter] -> case templateParameterDefault parameter of
            Just (TemplateTypeDefault (QualifiedTypeSyntax _ [TemplateTypeSyntax (ExplicitType name)])) -> name == identifier "int"
            _ -> False
        _ -> False
    Nothing -> False

parserPreservesNestedTemplateShape :: Bool
parserPreservesNestedTemplateShape = case onlyParsedTemplate source of
    Just declaration -> case templateParameters declaration of
        [parameter] -> case templateParameterKind parameter of
            TemplateTemplateParameter [TemplateParameterShape (TemplateTemplateParameterShape [inner]) False] ->
                templateParameterShapeKind inner == TemplateTypeParameterShape
            _ -> False
        _ -> False
    Nothing -> False
    where
        source = "template<template<template<typename> class> class Factory> class Owner {}"

parserPreservesMixedOrder :: Bool
parserPreservesMixedOrder = case onlyParsedTemplate source of
    Just declaration -> parameterNames declaration == map identifier ["T", "N", "Container", "Rest"]
    Nothing -> False
    where
        source = "template<typename T, int N, template<typename> class Container, typename... Rest> class Mixed {}"

parserRejectsEmptyParameters :: Bool
parserRejectsEmptyParameters = hasCode "TAP0019" (parseSource "template<> class Empty {}")

parserRejectsEmptyTemplateShape :: Bool
parserRejectsEmptyTemplateShape = hasCode "TAP0020" (parseSource "template<template<> class Empty> class Wrapper {}")

parserRejectsMissingClose :: Bool
parserRejectsMissingClose = case parseSource "template<typename T class Box {}" of Left _ -> True; Right _ -> False

parserRejectsMissingName :: Bool
parserRejectsMissingName = case parseSource "template<typename> class Box {}" of Left _ -> True; Right _ -> False

parserRejectsMissingClass :: Bool
parserRejectsMissingClass = case parseSource "template<typename T> Box {}" of Left _ -> True; Right _ -> False

parserRejectsDanglingDefault :: Bool
parserRejectsDanglingDefault = case parseSource "template<typename T => class Box {}" of Left _ -> True; Right _ -> False

renamerAssignsPositiveIds :: Bool
renamerAssignsPositiveIds = case analyzeSource "template<typename T> class Box {}" of
    Right artifacts -> case syntaxDeclarations (renamedSyntaxTree (semanticRenamedAST artifacts)) of
        [TemplateTypeDeclaration _ _ _ [parameter] _] -> renamedUnique (templateParameterName parameter) > 0
        _ -> False
    Left _ -> False

renamerKeepsParameterOrder :: Bool
renamerKeepsParameterOrder = case analyzeSource "template<typename A, typename B, int N> class Box {}" of
    Right artifacts -> case syntaxDeclarations (renamedSyntaxTree (semanticRenamedAST artifacts)) of
        [TemplateTypeDeclaration _ _ _ parameters _] ->
            let ids = map (renamedUnique . templateParameterName) parameters
             in ids == [minimum ids .. maximum ids]
        _ -> False
    Left _ -> False

renamerRejectsDuplicateParameters :: Bool
renamerRejectsDuplicateParameters = hasCode "TAR0006" (analyzeSource "template<typename T, int T> class Box {}")

renamerScopesValueParameter :: Bool
renamerScopesValueParameter = case analyzeSource source of
    Right _ -> True
    Left _ -> False
    where
        source = "template<int N> class Buffer { int Size() { return N; } }"

renamerIsolatesTemplates :: Bool
renamerIsolatesTemplates = case analyzeSource source of
    Right artifacts -> case syntaxDeclarations (resolvedSyntaxTree (semanticResolvedAST artifacts)) of
        [TemplateTypeDeclaration _ _ _ [left] _, TemplateTypeDeclaration _ _ _ [right] _] ->
            resolvedSymbol (templateParameterName left) /= resolvedSymbol (templateParameterName right)
        _ -> False
    Left _ -> False
    where
        source = "template<typename T> class Left {} template<typename T> class Right {}"

resolverPreservesIdentities :: Bool
resolverPreservesIdentities = case analyzeSource "template<typename T> class Box {}" of
    Right artifacts -> case syntaxDeclarations (resolvedSyntaxTree (semanticResolvedAST artifacts)) of
        [TemplateTypeDeclaration _ name _ [parameter] _] ->
            symbolIdValue (resolvedSymbol name) > 0
                && symbolIdValue (resolvedSymbol (templateParameterName parameter)) > symbolIdValue (resolvedSymbol name)
        _ -> False
    Left _ -> False

checkerBuildsTypeVariable :: Bool
checkerBuildsTypeVariable = case onlyTypedTemplate source of
    Just declaration -> case templateMembers declaration of
        [FunctionDeclaration {declarationAnnotation = FunctionType [TypeVariable input] (TypeVariable output)}] ->
            resolvedSymbol input == resolvedSymbol output
        _ -> False
    Nothing -> False
    where
        source = "template<typename T> class Box { T Identity(_ T value) { return value; } }"

checkerBuildsValueVariable :: Bool
checkerBuildsValueVariable = memberParameterMatches source expected
    where
        source = "template<typename T, int N> class Buffer { void Use(_ [T; N] value) { return; } }"
        expected (NamedType _ arguments) = any isValueParameter arguments
        expected _ = False
        isValueParameter (ValueTemplateArgument (TemplateValueParameter _)) = True
        isValueParameter _ = False

checkerBuildsCallableType :: Bool
checkerBuildsCallableType = case onlyTypedTemplate source of
    Just declaration -> case templateMembers declaration of
        [FunctionDeclaration {declarationAnnotation = FunctionType [FunctionType [TypeVariable _] (TypeVariable _)] _}] -> True
        _ -> False
    Nothing -> False
    where
        source = "template<typename T> class Higher { void Use(_ (T) -> T action) { return; } }"

checkerBuildsArrayType :: Bool
checkerBuildsArrayType = memberParameterMatches source expected
    where
        source = "template<typename T> class Arrays { void Use(_ [T] values) { return; } }"
        expected (NamedType (QualifiedName [Identifier "System", Identifier "Array"]) [TypeTemplateArgument (TypeVariable _)]) = True
        expected _ = False

checkerBuildsFixedArrayType :: Bool
checkerBuildsFixedArrayType = memberParameterMatches source expected
    where
        source = "template<typename T, int N> class Arrays { void Use(_ [T; N] values) { return; } }"
        expected
            ( NamedType
                    (QualifiedName [Identifier "System", Identifier "Array"])
                    [TypeTemplateArgument (TypeVariable _), ValueTemplateArgument (TemplateValueParameter _)]
                ) = True
        expected _ = False

checkerBuildsDictionaryType :: Bool
checkerBuildsDictionaryType = memberParameterMatches source expected
    where
        source = "template<typename K, typename V> class Maps { void Use(_ [K to V] values) { return; } }"
        expected
            ( NamedType
                    (QualifiedName [Identifier "System", Identifier "Dictionary"])
                    [TypeTemplateArgument (TypeVariable _), TypeTemplateArgument (TypeVariable _)]
                ) = True
        expected _ = False

memberParameterMatches :: String -> (Type -> Bool) -> Bool
memberParameterMatches source predicate = case onlyTypedTemplate source of
    Just declaration -> case templateMembers declaration of
        [FunctionDeclaration {declarationParameters = [parameter]}] -> predicate (parameterAnnotation parameter)
        _ -> False
    Nothing -> False

checkerAnnotatesValueParameter :: Bool
checkerAnnotatesValueParameter = case onlyTypedTemplate "template<long N> class Buffer {}" of
    Just declaration -> case templateParameters declaration of
        [parameter] -> templateParameterAnnotation parameter == namedType "long"
        _ -> False
    Nothing -> False

checkerAnnotatesTypeParameter :: Bool
checkerAnnotatesTypeParameter = case onlyTypedTemplate "template<typename T> class Box {}" of
    Just declaration -> case templateParameters declaration of
        [parameter] -> case templateParameterAnnotation parameter of TypeVariable _ -> True; _ -> False
        _ -> False
    Nothing -> False

checkerAnnotatesTemplateParameter :: Bool
checkerAnnotatesTemplateParameter = case onlyTypedTemplate "template<template<typename> class C> class Box {}" of
    Just declaration -> case templateParameters declaration of
        [parameter] -> case templateParameterAnnotation parameter of TypeVariable _ -> True; _ -> False
        _ -> False
    Nothing -> False

checkerAcceptsLaterDefault :: Bool
checkerAcceptsLaterDefault = case analyzeSource "template<typename T = U, typename U = int> class Pair {}" of Right _ -> True; Left _ -> False

checkerAcceptsNamedValueDefault :: Bool
checkerAcceptsNamedValueDefault = case analyzeSource "template<int N = M, int M = 4> class Buffer {}" of Right _ -> True; Left _ -> False

checkerRejectsPackDefault :: Bool
checkerRejectsPackDefault = hasCode "TAT0019" (analyzeSource "template<typename... T = int> class Tuple {}")

checkerRejectsUnknownValueDefault :: Bool
checkerRejectsUnknownValueDefault = hasCode "TAT0020" (analyzeSource "template<int N = Missing> class Buffer {}")

openTemplateStopsBeforeCore :: Bool
openTemplateStopsBeforeCore = case analyzeSource "template<typename T> class Box { T Read(_ T value) { return value; } }" of
    Right _ -> True
    Left _ -> False

ordinaryDeclarationUnaffected :: Bool
ordinaryDeclarationUnaffected = case analyzeSource "class App { int Read() { return 1; } }" of Right _ -> True; Left _ -> False

templateAndOrdinaryCoexist :: Bool
templateAndOrdinaryCoexist = case analyzeSource source of
    Right artifacts -> length (syntaxDeclarations (typedSyntaxTree (semanticTypedAST artifacts))) == 2
    Left _ -> False
    where
        source = "template<typename T> class Box {} class App { void Run() { return; } }"

templateMemberCallsResolve :: Bool
templateMemberCallsResolve = case analyzeSource source of Right _ -> True; Left _ -> False
    where
        source =
            "template<typename T> class Box { T First(_ T value) { return value; } T Second(_ T value) { return First(value); } }"

identityMethodRetainsType :: Bool
identityMethodRetainsType = checkerBuildsTypeVariable

fixedArrayMethodRetainsParameters :: Bool
fixedArrayMethodRetainsParameters = checkerBuildsFixedArrayType

-- Total projections keep negative parser fixtures from crashing the test runner.
templateParameters :: Declaration name annotation -> [TemplateParameter name annotation]
templateParameters TemplateTypeDeclaration {declarationTemplateParameters = parameters} = parameters
templateParameters _ = []

templateMembers :: Declaration name annotation -> [Declaration name annotation]
templateMembers TemplateTypeDeclaration {typeMembers = members} = members
templateMembers _ = []