tala-compiler-0.1.0: test/TemplateApplicationTests.hs
-- SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com>
-- SPDX-License-Identifier: MPL-2.0 WITH AdditionRef-Progmasoft-Exception-1.1
module TemplateApplicationTests (templateApplicationTests) where
import Progmasoft.Tala.AST
import Progmasoft.Tala.Frontend
import Progmasoft.Tala.Template.Application
templateApplicationTests :: [(String, Bool)]
templateApplicationTests =
[ ("catalog records a qualified template name", catalogRecordsQualifiedName)
, ("catalog records the declaration symbol", catalogRecordsDeclarationSymbol)
, ("catalog records member semantic signatures", catalogRecordsMembers)
, ("catalog excludes ordinary declarations", catalogExcludesOrdinaryDeclarations)
, ("catalog preserves source declaration order", catalogPreservesOrder)
, ("catalog describes a type parameter", catalogDescribesTypeParameter)
, ("catalog describes a value parameter", catalogDescribesValueParameter)
, ("catalog describes a template parameter", catalogDescribesTemplateParameter)
, ("catalog preserves pack status", catalogPreservesPackStatus)
, ("catalog preserves default syntax", catalogPreservesDefault)
, ("lookup finds an exact qualified name", lookupFindsQualifiedName)
, ("lookup does not fall back to a short name", lookupRejectsShortName)
, ("lookup reports unknown declarations", lookupReportsUnknown)
, ("lookup reports ambiguous catalog entries", lookupReportsAmbiguous)
, ("minimum arity counts required parameters", minimumArityCountsRequired)
, ("minimum arity excludes defaults", minimumArityExcludesDefaults)
, ("minimum arity excludes packs", minimumArityExcludesPacks)
, ("maximum arity is exact without packs", maximumArityIsExact)
, ("maximum arity is unbounded with a pack", maximumArityIsUnbounded)
, ("binding accepts one type argument", bindingAcceptsType)
, ("binding accepts one value argument", bindingAcceptsValue)
, ("binding accepts a template declaration name", bindingAcceptsTemplateName)
, ("binding rejects a value for a type parameter", bindingRejectsValueForType)
, ("binding rejects a type for a value parameter", bindingRejectsTypeForValue)
, ("binding rejects a function type for a template parameter", bindingRejectsFunctionForTemplate)
, ("binding reports too few arguments", bindingReportsTooFew)
, ("binding reports too many arguments", bindingReportsTooMany)
, ("binding fills an omitted type default", bindingFillsTypeDefault)
, ("binding fills an omitted value default", bindingFillsValueDefault)
, ("binding resolves a later type default", bindingResolvesLaterTypeDefault)
, ("binding resolves a later value default", bindingResolvesLaterValueDefault)
, ("binding rejects a cyclic type default", bindingRejectsCyclicTypeDefault)
, ("binding places zero arguments in an empty pack", bindingCreatesEmptyPack)
, ("binding places every surplus argument in a final pack", bindingFillsFinalPack)
, ("binding reserves required suffix arguments after a pack", bindingReservesRequiredSuffix)
, ("binding keeps explicit and default origins distinct", bindingPreservesArgumentOrigin)
, ("substitution replaces a type variable", substitutionReplacesTypeVariable)
, ("substitution replaces nested named arguments", substitutionReplacesNestedType)
, ("substitution replaces callable parameter and result types", substitutionReplacesCallableType)
, ("substitution replaces a value parameter", substitutionReplacesValueParameter)
, ("substitution preserves concrete scalar values", substitutionPreservesConcreteValue)
, ("substitution preserves ErrorType", substitutionPreservesErrorType)
, ("substitution reports an unbound type variable", substitutionReportsMissingType)
, ("substitution reports an unbound value variable", substitutionReportsMissingValue)
, ("rendering names an unknown template", renderingNamesUnknownTemplate)
, ("rendering reports arity values", renderingReportsArity)
, ("rendering identifies a mismatched parameter", renderingIdentifiesParameter)
]
analyzeCatalog :: String -> Maybe TemplateCatalog
analyzeCatalog source = case analyzeSemantics (CompilerInput "application-test.tala" source) of
Right artifacts -> Just (buildTemplateCatalog (semanticTypedAST artifacts))
Left _ -> Nothing
singleDescriptor :: String -> Maybe TemplateDeclarationDescriptor
singleDescriptor source = do
TemplateCatalog declarations <- analyzeCatalog source
case declarations of { [declaration] -> Just declaration; _ -> Nothing }
qualified :: [String] -> QualifiedName
qualified = QualifiedName . map Identifier
typeArgument :: String -> TemplateArgument
typeArgument name = TypeTemplateArgument (namedType name)
valueArgument :: Integer -> TemplateArgument
valueArgument = ValueTemplateArgument . IntegerTemplateValue
application :: QualifiedName -> [TemplateArgument] -> TemplateApplication
application = TemplateApplication
bindSource :: String -> QualifiedName -> [TemplateArgument] -> Either [TemplateApplicationError] TemplateBinding
bindSource source name arguments = case analyzeCatalog source of
Just catalog -> bindTemplateApplication catalog (application name arguments)
Nothing -> Left []
catalogRecordsQualifiedName :: Bool
catalogRecordsQualifiedName = case singleDescriptor "namespace Library; template<typename T> class Box {}" of
Just descriptor -> templateDeclarationName descriptor == qualified ["Library", "Box"]
Nothing -> False
catalogRecordsDeclarationSymbol :: Bool
catalogRecordsDeclarationSymbol = case singleDescriptor "template<typename T> class Box {}" of
Just descriptor -> symbolIdValue (templateDeclarationSymbol descriptor) > 0
Nothing -> False
catalogRecordsMembers :: Bool
catalogRecordsMembers = case singleDescriptor source of
Just descriptor -> case templateDeclarationMembers descriptor of
[(name, FunctionType [TypeVariable input] (TypeVariable output))] ->
identifierText (resolvedSpelling name) == "Identity" && resolvedSymbol input == resolvedSymbol output
_ -> False
Nothing -> False
where
source = "template<typename T> class Box { T Identity(_ T value) { return value; } }"
catalogExcludesOrdinaryDeclarations :: Bool
catalogExcludesOrdinaryDeclarations = case analyzeCatalog "class App { void Run() { return; } }" of
Just (TemplateCatalog []) -> True
_ -> False
catalogPreservesOrder :: Bool
catalogPreservesOrder = case analyzeCatalog source of
Just (TemplateCatalog declarations) -> map templateDeclarationName declarations == map (qualified . (: [])) ["Left", "Right"]
Nothing -> False
where
source = "template<typename T> class Left {} template<typename U> class Right {}"
catalogDescribesTypeParameter :: Bool
catalogDescribesTypeParameter = case singleDescriptor "template<typename T> class Box {}" of
Just descriptor -> case templateDeclarationParameters descriptor of
[parameter] -> templateDescriptorCategory parameter == TypeParameterCategory
_ -> False
Nothing -> False
catalogDescribesValueParameter :: Bool
catalogDescribesValueParameter = case singleDescriptor "template<int N> class Buffer {}" of
Just descriptor -> case templateDeclarationParameters descriptor of
[parameter] -> templateDescriptorCategory parameter == ValueParameterCategory intType
_ -> False
Nothing -> False
catalogDescribesTemplateParameter :: Bool
catalogDescribesTemplateParameter = case singleDescriptor "template<template<typename> class C> class Box {}" of
Just descriptor -> case templateDeclarationParameters descriptor of
[parameter] -> case templateDescriptorCategory parameter of TemplateParameterCategory [_] -> True; _ -> False
_ -> False
Nothing -> False
catalogPreservesPackStatus :: Bool
catalogPreservesPackStatus = case singleDescriptor "template<typename... T> class Tuple {}" of
Just descriptor -> case templateDeclarationParameters descriptor of [parameter] -> templateDescriptorIsPack parameter; _ -> False
Nothing -> False
catalogPreservesDefault :: Bool
catalogPreservesDefault = case singleDescriptor "template<typename T = int> class Box {}" of
Just descriptor -> case templateDeclarationParameters descriptor of
[parameter] -> templateDescriptorDefault parameter == Just (TemplateTypeDefault (ExplicitType (Identifier "int")))
_ -> False
Nothing -> False
lookupFindsQualifiedName :: Bool
lookupFindsQualifiedName = case analyzeCatalog "namespace A; template<typename T> class Box {}" of
Just catalog -> case lookupTemplateDeclaration (qualified ["A", "Box"]) catalog of Right _ -> True; Left _ -> False
Nothing -> False
lookupRejectsShortName :: Bool
lookupRejectsShortName = case analyzeCatalog "namespace A; template<typename T> class Box {}" of
Just catalog -> lookupTemplateDeclaration (qualified ["Box"]) catalog == Left (UnknownTemplateDeclaration (qualified ["Box"]))
Nothing -> False
lookupReportsUnknown :: Bool
lookupReportsUnknown =
lookupTemplateDeclaration (qualified ["Missing"]) (TemplateCatalog [])
== Left (UnknownTemplateDeclaration (qualified ["Missing"]))
lookupReportsAmbiguous :: Bool
lookupReportsAmbiguous = case singleDescriptor "template<typename T> class Box {}" of
Just descriptor -> case lookupTemplateDeclaration (qualified ["Box"]) (TemplateCatalog [descriptor, descriptor]) of
Left (AmbiguousTemplateDeclaration _ symbols) -> length symbols == 2
_ -> False
Nothing -> False
minimumArityCountsRequired :: Bool
minimumArityCountsRequired = case singleDescriptor "template<typename T, int N> class Box {}" of
Just value -> minimumTemplateArity value == 2
Nothing -> False
minimumArityExcludesDefaults :: Bool
minimumArityExcludesDefaults = case singleDescriptor "template<typename T = int, int N = 4> class Box {}" of
Just value -> minimumTemplateArity value == 0
Nothing -> False
minimumArityExcludesPacks :: Bool
minimumArityExcludesPacks = case singleDescriptor "template<typename... T> class Box {}" of
Just value -> minimumTemplateArity value == 0
Nothing -> False
maximumArityIsExact :: Bool
maximumArityIsExact = case singleDescriptor "template<typename T, int N> class Box {}" of
Just value -> maximumTemplateArity value == Just 2
Nothing -> False
maximumArityIsUnbounded :: Bool
maximumArityIsUnbounded = case singleDescriptor "template<typename... T> class Box {}" of
Just value -> maximumTemplateArity value == Nothing
Nothing -> False
bindingAcceptsType :: Bool
bindingAcceptsType = case bindSource "template<typename T> class Box {}" (qualified ["Box"]) [typeArgument "String"] of
Right _ -> True
Left _ -> False
bindingAcceptsValue :: Bool
bindingAcceptsValue = case bindSource "template<int N> class Buffer {}" (qualified ["Buffer"]) [valueArgument 4] of
Right _ -> True
Left _ -> False
bindingAcceptsTemplateName :: Bool
bindingAcceptsTemplateName = case bindSource source (qualified ["Wrapper"]) [typeArgument "System.Array"] of Right _ -> True; Left _ -> False
where
source = "template<template<typename> class C> class Wrapper {}"
bindingRejectsValueForType :: Bool
bindingRejectsValueForType = categoryFailure (bindSource "template<typename T> class Box {}" (qualified ["Box"]) [valueArgument 1])
bindingRejectsTypeForValue :: Bool
bindingRejectsTypeForValue = categoryFailure (bindSource "template<int N> class Box {}" (qualified ["Box"]) [typeArgument "int"])
bindingRejectsFunctionForTemplate :: Bool
bindingRejectsFunctionForTemplate = categoryFailure (bindSource source (qualified ["Box"]) [TypeTemplateArgument (FunctionType [] voidType)])
where
source = "template<template<typename> class C> class Box {}"
categoryFailure :: Either [TemplateApplicationError] value -> Bool
categoryFailure result = case result of Left [TemplateArgumentCategoryMismatch {}] -> True; _ -> False
bindingReportsTooFew :: Bool
bindingReportsTooFew = case bindSource "template<typename T> class Box {}" (qualified ["Box"]) [] of
Left [TooFewTemplateArguments _ 1 0] -> True
_ -> False
bindingReportsTooMany :: Bool
bindingReportsTooMany = case bindSource "template<typename T> class Box {}" (qualified ["Box"]) [typeArgument "int", typeArgument "long"] of
Left [TooManyTemplateArguments _ 1 2] -> True
_ -> False
bindingFillsTypeDefault :: Bool
bindingFillsTypeDefault = defaultBindingIs (typeArgument "int") (bindSource "template<typename T = int> class Box {}" (qualified ["Box"]) [])
bindingFillsValueDefault :: Bool
bindingFillsValueDefault = defaultBindingIs (valueArgument 8) (bindSource "template<int N = 8> class Box {}" (qualified ["Box"]) [])
defaultBindingIs :: TemplateArgument -> Either [TemplateApplicationError] TemplateBinding -> Bool
defaultBindingIs expected result = case result of
Right binding -> case templateBindingArguments binding of [(_, [DefaultTemplateArgument actual])] -> actual == expected; _ -> False
Left _ -> False
bindingResolvesLaterTypeDefault :: Bool
bindingResolvesLaterTypeDefault = case bindSource source (qualified ["Pair"]) [] of
Right binding -> map (map unwrap . snd) (templateBindingArguments binding) == [[typeArgument "int"], [typeArgument "int"]]
Left _ -> False
where
source = "template<typename T = U, typename U = int> class Pair {}"
unwrap (ExplicitTemplateArgument value) = value
unwrap (DefaultTemplateArgument value) = value
bindingResolvesLaterValueDefault :: Bool
bindingResolvesLaterValueDefault = case bindSource source (qualified ["Buffer"]) [] of
Right binding -> map (map unwrap . snd) (templateBindingArguments binding) == [[valueArgument 4], [valueArgument 4]]
Left _ -> False
where
source = "template<int N = M, int M = 4> class Buffer {}"
unwrap (ExplicitTemplateArgument value) = value
unwrap (DefaultTemplateArgument value) = value
bindingRejectsCyclicTypeDefault :: Bool
bindingRejectsCyclicTypeDefault = case bindSource source (qualified ["Cycle"]) [] of Left [UnresolvedTemplateDefault {}] -> True; _ -> False
where
source = "template<typename T = U, typename U = T> class Cycle {}"
bindingCreatesEmptyPack :: Bool
bindingCreatesEmptyPack = case bindSource "template<typename... T> class Tuple {}" (qualified ["Tuple"]) [] of
Right binding -> case templateBindingArguments binding of [(_, [])] -> True; _ -> False
Left _ -> False
bindingFillsFinalPack :: Bool
bindingFillsFinalPack = case bindSource "template<typename... T> class Tuple {}" (qualified ["Tuple"]) arguments of
Right binding -> case templateBindingArguments binding of [(_, values)] -> length values == 3; _ -> False
Left _ -> False
where
arguments = map typeArgument ["int", "String", "bool"]
bindingReservesRequiredSuffix :: Bool
bindingReservesRequiredSuffix = case bindSource source (qualified ["Tuple"]) arguments of
Right binding -> map (length . snd) (templateBindingArguments binding) == [2, 1]
Left _ -> False
where
source = "template<typename... T, int N> class Tuple {}"
arguments = [typeArgument "int", typeArgument "String", valueArgument 2]
bindingPreservesArgumentOrigin :: Bool
bindingPreservesArgumentOrigin = case bindSource source (qualified ["Pair"]) [typeArgument "String"] of
Right binding -> case templateBindingArguments binding of
[(_, [ExplicitTemplateArgument _]), (_, [DefaultTemplateArgument _])] -> True
_ -> False
Left _ -> False
where
source = "template<typename T, typename U = int> class Pair {}"
typeBinding :: Maybe TemplateBinding
typeBinding = case bindSource "template<typename T> class Box {}" (qualified ["Box"]) [typeArgument "String"] of
Right value -> Just value
Left _ -> Nothing
valueBinding :: Maybe TemplateBinding
valueBinding = case bindSource "template<int N> class Buffer {}" (qualified ["Buffer"]) [valueArgument 16] of
Right value -> Just value
Left _ -> Nothing
firstParameterName :: TemplateBinding -> ResolvedName
firstParameterName binding = case templateBindingArguments binding of
(name, _) : _ -> name
[] -> ResolvedName (SymbolId (-1)) (Identifier "<missing-template-parameter>")
substitutionReplacesTypeVariable :: Bool
substitutionReplacesTypeVariable = case typeBinding of
Just binding -> substituteType binding (TypeVariable (firstParameterName binding)) == Right (namedType "String")
Nothing -> False
substitutionReplacesNestedType :: Bool
substitutionReplacesNestedType = case typeBinding of
Just binding ->
let variable = TypeVariable (firstParameterName binding)
input = NamedType (qualified ["System", "Array"]) [TypeTemplateArgument variable]
expected = NamedType (qualified ["System", "Array"]) [typeArgument "String"]
in substituteType binding input == Right expected
Nothing -> False
substitutionReplacesCallableType :: Bool
substitutionReplacesCallableType = case typeBinding of
Just binding ->
let variable = TypeVariable (firstParameterName binding)
in substituteType binding (FunctionType [variable] variable)
== Right (FunctionType [namedType "String"] (namedType "String"))
Nothing -> False
substitutionReplacesValueParameter :: Bool
substitutionReplacesValueParameter = case valueBinding of
Just binding ->
substituteTemplateValue binding (TemplateValueParameter (firstParameterName binding)) == Right (IntegerTemplateValue 16)
Nothing -> False
substitutionPreservesConcreteValue :: Bool
substitutionPreservesConcreteValue = case valueBinding of
Just binding -> substituteTemplateValue binding (IntegerTemplateValue 3) == Right (IntegerTemplateValue 3)
Nothing -> False
substitutionPreservesErrorType :: Bool
substitutionPreservesErrorType = case typeBinding of Just binding -> substituteType binding ErrorType == Right ErrorType; Nothing -> False
substitutionReportsMissingType :: Bool
substitutionReportsMissingType = case typeBinding of
Just binding -> case substituteType binding (TypeVariable (ResolvedName (SymbolId 999) (Identifier "Missing"))) of
Left UnresolvedTemplateDefault {} -> True
_ -> False
Nothing -> False
substitutionReportsMissingValue :: Bool
substitutionReportsMissingValue = case valueBinding of
Just binding -> case substituteTemplateValue binding (TemplateValueParameter (ResolvedName (SymbolId 999) (Identifier "Missing"))) of
Left UnresolvedTemplateDefault {} -> True
_ -> False
Nothing -> False
renderingNamesUnknownTemplate :: Bool
renderingNamesUnknownTemplate =
renderTemplateApplicationError (UnknownTemplateDeclaration (qualified ["A", "Box"]))
== "unknown template declaration A.Box"
renderingReportsArity :: Bool
renderingReportsArity =
renderTemplateApplicationError (TooFewTemplateArguments (qualified ["Box"]) 2 1)
== "Box requires at least 2 template arguments, but received 1"
renderingIdentifiesParameter :: Bool
renderingIdentifiesParameter = case singleDescriptor "template<typename T> class Box {}" of
Just descriptor -> case templateDeclarationParameters descriptor of
[parameter] ->
"parameter T"
`contains` renderTemplateApplicationError
( TemplateArgumentCategoryMismatch
(qualified ["Box"])
(templateDescriptorName parameter)
0
TypeParameterCategory
(valueArgument 1)
)
_ -> False
Nothing -> False
contains :: String -> String -> Bool
contains needle haystack = any (needle `prefixOf`) (tails haystack)
where
tails [] = [[]]
tails value@(_ : rest) = value : tails rest
prefixOf [] _ = True
prefixOf _ [] = False
prefixOf (left : leftRest) (right : rightRest) = left == right && prefixOf leftRest rightRest