tala-compiler-0.1.0: test/SpecializationTests.hs
-- SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com>
-- SPDX-License-Identifier: MPL-2.0 WITH AdditionRef-Progmasoft-Exception-1.1
module SpecializationTests (specializationTests) where
import Progmasoft.Tala.AST
import Progmasoft.Tala.Core.Specialization
import Progmasoft.Tala.Core.Template
specializationTests :: [(String, Bool)]
specializationTests =
[ ("specialization preparation substitutes type and value parameters", preparesBothKinds)
, ("specialization preparation supports partial substitution diagnostics", rejectsPartiallyOpen)
, ("specialization preparation rejects an untouched generic type", rejectsOpenType)
, ("specialization preparation rejects duplicate type bindings", rejectsDuplicateTypes)
, ("specialization preparation rejects duplicate value bindings", rejectsDuplicateValues)
, ("specialization preparation rejects cross-kind binding conflicts", rejectsBindingKindConflict)
, ("specialization preparation validates the substituted result", rejectsInvalidSubstitution)
, ("specialization catalog starts empty", emptyCatalogContract)
, ("specialization catalog assigns positive insertion-order ids", insertionOrderIds)
, ("specialization catalog coalesces identical concrete types", coalescesIdentity)
, ("specialization catalog distinguishes fixed array sizes", distinguishesArraySizes)
, ("specialization catalog distinguishes argument order", distinguishesArgumentOrder)
, ("specialization lookup works by id and structural type", lookupContract)
, ("specialization snapshot follows id order", snapshotOrder)
, ("specialization batches preserve duplicate request results", batchDuplicates)
, ("specialization batches roll back on invalid input", batchFailureIsAtomic)
, ("specialization catalog rejects malformed array families", rejectsMalformedArray)
, ("specialization identity preserves value argument kinds", distinguishesValueKinds)
]
resolved :: Int -> String -> ResolvedName
resolved symbol spelling = ResolvedName (SymbolId symbol) (Identifier spelling)
named :: String -> [TemplateArgument] -> Type
named name = NamedType (QualifiedName [Identifier name])
systemArray :: [TemplateArgument] -> Type
systemArray = NamedType (QualifiedName [Identifier "System", Identifier "Array"])
typeArgument :: Type -> TemplateArgument
typeArgument = TypeTemplateArgument
integerArgument :: Integer -> TemplateArgument
integerArgument = ValueTemplateArgument . IntegerTemplateValue
booleanArgument :: Bool -> TemplateArgument
booleanArgument = ValueTemplateArgument . BooleanTemplateValue
characterArgument :: Integer -> TemplateArgument
characterArgument = ValueTemplateArgument . CharacterTemplateValue
fixedArray :: Type -> Integer -> Type
fixedArray element size = systemArray [typeArgument element, integerArgument size]
typeParameterName, valueParameterName :: ResolvedName
typeParameterName = resolved 10 "T"
valueParameterName = resolved 20 "N"
genericArray :: Type
genericArray =
systemArray
[ typeArgument (TypeVariable typeParameterName)
, ValueTemplateArgument (TemplateValueParameter valueParameterName)
]
preparesBothKinds :: Bool
preparesBothKinds =
prepareSpecialization
[(SymbolId 10, stringType)]
[(SymbolId 20, IntegerTemplateValue 32)]
genericArray
== Right (fixedArray stringType 32)
rejectsPartiallyOpen :: Bool
rejectsPartiallyOpen =
prepareSpecialization [(SymbolId 10, stringType)] [] genericArray
== Left (OpenSpecialization [SymbolId 20])
rejectsOpenType :: Bool
rejectsOpenType =
prepareSpecialization [] [] genericArray
== Left (OpenSpecialization [SymbolId 10, SymbolId 20])
rejectsDuplicateTypes :: Bool
rejectsDuplicateTypes =
prepareSpecialization
[(SymbolId 10, intType), (SymbolId 10, stringType)]
[]
(TypeVariable typeParameterName)
== Left (DuplicateTypeBinding [SymbolId 10])
rejectsDuplicateValues :: Bool
rejectsDuplicateValues =
prepareSpecialization
[]
[(SymbolId 20, IntegerTemplateValue 4), (SymbolId 20, IntegerTemplateValue 8)]
genericArray
== Left (DuplicateValueBinding [SymbolId 20])
rejectsBindingKindConflict :: Bool
rejectsBindingKindConflict =
prepareSpecialization
[(SymbolId 10, intType)]
[(SymbolId 10, IntegerTemplateValue 4)]
intType
== Left (ConflictingBindingKinds [SymbolId 10])
rejectsInvalidSubstitution :: Bool
rejectsInvalidSubstitution =
case prepareSpecialization
[(SymbolId 10, intType)]
[(SymbolId 20, IntegerTemplateValue (-1))]
genericArray of
Left (InvalidSpecialization issues) ->
any ((== TemplateNegativeArraySize) . templateIssueKind) issues
_ -> False
emptyCatalogContract :: Bool
emptyCatalogContract =
catalogSize emptyCatalog == 0
&& null (specializationSnapshot emptyCatalog)
&& findSpecialization (SpecializationId 1) emptyCatalog == Nothing
insertionOrderIds :: Bool
insertionOrderIds = case internSpecializations [fixedArray intType 4, fixedArray intType 5] emptyCatalog of
Right ([first, second], catalog) ->
specializationId first == SpecializationId 1
&& specializationId second == SpecializationId 2
&& catalogSize catalog == 2
_ -> False
coalescesIdentity :: Bool
coalescesIdentity = case internSpecialization (fixedArray intType 4) emptyCatalog of
Right (first, True, once) -> case internSpecialization (fixedArray intType 4) once of
Right (second, False, twice) ->
first == second && once == twice && catalogSize twice == 1
_ -> False
_ -> False
distinguishesArraySizes :: Bool
distinguishesArraySizes = case internSpecializations [fixedArray intType 4, fixedArray intType 5] emptyCatalog of
Right ([first, second], _) -> specializationIdentity first /= specializationIdentity second
_ -> False
distinguishesArgumentOrder :: Bool
distinguishesArgumentOrder =
let first = named "Mix" [typeArgument intType, integerArgument 4]
second = named "Mix" [integerArgument 4, typeArgument intType]
in case internSpecializations [first, second] emptyCatalog of
Right ([left, right], catalog) ->
specializationIdentity left /= specializationIdentity right
&& catalogSize catalog == 2
_ -> False
lookupContract :: Bool
lookupContract = case internSpecialization (fixedArray stringType 16) emptyCatalog of
Right (entry, _, catalog) ->
findSpecialization (specializationId entry) catalog == Just entry
&& findSpecializationByType (specializationType entry) catalog == Just entry
&& findSpecialization (SpecializationId 99) catalog == Nothing
_ -> False
snapshotOrder :: Bool
snapshotOrder = case internSpecializations inputs emptyCatalog of
Right (entries, catalog) -> specializationSnapshot catalog == entries
_ -> False
where
inputs = [fixedArray intType 8, named "Flag" [booleanArgument True], named "Code" [characterArgument 65]]
batchDuplicates :: Bool
batchDuplicates = case internSpecializations [value, value, value] emptyCatalog of
Right (entries, catalog) ->
length entries == 3
&& all ((== SpecializationId 1) . specializationId) entries
&& catalogSize catalog == 1
_ -> False
where
value = fixedArray intType 64
batchFailureIsAtomic :: Bool
batchFailureIsAtomic =
let malformed = systemArray []
in case internSpecializations [fixedArray intType 4, malformed] emptyCatalog of
Left (InvalidSpecialization _) -> catalogSize emptyCatalog == 0
_ -> False
rejectsMalformedArray :: Bool
rejectsMalformedArray = case internSpecialization (systemArray [integerArgument 8]) emptyCatalog of
Left (InvalidSpecialization issues) ->
any ((== TemplateMalformedArrayFamily) . templateIssueKind) issues
_ -> False
distinguishesValueKinds :: Bool
distinguishesValueKinds = case internSpecializations values emptyCatalog of
Right (entries, catalog) ->
catalogSize catalog == 3
&& length (map specializationIdentity entries) == 3
&& allDifferent (map specializationIdentity entries)
_ -> False
where
values =
[ named "Value" [integerArgument 1]
, named "Value" [booleanArgument True]
, named "Value" [characterArgument 1]
]
allDifferent [first, second, third] = first /= second && first /= third && second /= third
allDifferent _ = False