packages feed

tala-compiler-0.1.0: test/TemplateManglingTests.hs

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

module TemplateManglingTests (templateManglingTests) where

import Data.Char (isAscii)
import Data.List (isInfixOf, isPrefixOf)
import Progmasoft.Tala.AST
import Progmasoft.Tala.Template.Mangling

templateManglingTests :: [(String, Bool)]
templateManglingTests =
    [ ("type mangling uses the private versioned prefix", hasPrivatePrefix)
    , ("type mangling is deterministic", typeManglingIsDeterministic)
    , ("type mangling emits only portable ASCII", typeManglingIsAscii)
    , ("type mangling emits linker-safe characters", typeManglingIsLinkerSafe)
    , ("qualified names retain structural boundaries", qualifiedNamesHaveBoundaries)
    , ("qualified-name joins cannot collide", qualifiedNameJoinsCannotCollide)
    , ("type arguments retain structural boundaries", typeArgumentsHaveBoundaries)
    , ("type and value arguments cannot collide", typeAndValueArgumentsDiffer)
    , ("template argument order affects the symbol", argumentOrderMatters)
    , ("positive and negative integers cannot collide", integerSignMatters)
    , ("boolean template values are distinct", booleanValuesDiffer)
    , ("character and integer values cannot collide", characterAndIntegerDiffer)
    , ("Unicode identifiers become portable ASCII", unicodeNamesBecomeAscii)
    , ("Unicode scalar spellings remain distinct", unicodeNamesRemainDistinct)
    , ("function parameter order affects the symbol", functionParameterOrderMatters)
    , ("function result type affects the symbol", functionResultMatters)
    , ("function arity affects the symbol", functionArityMatters)
    , ("member names affect the symbol", memberNameMatters)
    , ("member signatures affect the symbol", memberSignatureMatters)
    , ("member staticness affects the symbol", memberStaticnessMatters)
    , ("member access affects the symbol", memberAccessMatters)
    , ("member mangling retains the source SymbolId", memberRetainsSymbol)
    , ("member mangling retains source spelling", memberRetainsSpelling)
    , ("nested types can be mangled as members", nestedTypeCanBeMangled)
    , ("member batches preserve declaration order", memberOrderIsStable)
    , ("member batches reject template declarations", memberBatchRejectsOpenTemplate)
    , ("member batches require an ordinary owner", memberBatchRejectsFunctionOwner)
    , ("open type variables are rejected", openTypesAreRejected)
    , ("open value parameters are rejected", openValuesAreRejected)
    , ("ErrorType is rejected", errorTypesAreRejected)
    , ("empty qualified names are rejected", emptyQualifiedNamesAreRejected)
    , ("empty identifiers are rejected", emptyIdentifiersAreRejected)
    , ("negative depth limits are rejected", negativeDepthLimitIsRejected)
    , ("zero length limits are rejected", zeroLengthLimitIsRejected)
    , ("zero name-part limits are rejected", zeroNamePartLimitIsRejected)
    , ("negative argument limits are rejected", negativeArgumentLimitIsRejected)
    , ("type-depth limits are enforced", typeDepthLimitIsEnforced)
    , ("qualified-name part limits are enforced", namePartLimitIsEnforced)
    , ("template argument limits are enforced", argumentLimitIsEnforced)
    , ("function parameter limits are enforced", functionLimitIsEnforced)
    , ("symbol length limits are enforced", symbolLengthLimitIsEnforced)
    , ("valid-symbol predicate rejects a wrong prefix", wrongPrefixIsRejected)
    , ("valid-symbol predicate rejects punctuation", punctuationIsRejected)
    , ("error rendering identifies open symbols", openErrorRenderingIsUseful)
    , ("error rendering identifies length limits", lengthErrorRenderingIsUseful)
    , ("default limits accept deeply nested practical types", defaultLimitsArePractical)
    ]

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

resolved :: Int -> String -> ResolvedName
resolved unique spelling = ResolvedName (SymbolId unique) (Identifier spelling)

typeArgument :: Type -> TemplateArgument
typeArgument = TypeTemplateArgument

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

templateType :: [String] -> [TemplateArgument] -> Type
templateType names = NamedType (qualified names)

boxOf :: Type -> Type
boxOf value = templateType ["Box"] [typeArgument value]

pairOf :: Type -> Type -> Type
pairOf left right = templateType ["Pair"] [typeArgument left, typeArgument right]

spanValue :: SourceSpan
spanValue = SourceSpan "mangling-test.tala" (SourcePosition 1 1) (SourcePosition 1 2)

functionMember :: Int -> String -> Type -> Bool -> Access -> Declaration ResolvedName Type
functionMember unique name signature isStatic access =
    FunctionDeclaration
        spanValue
        (resolved unique name)
        signature
        (ExplicitType (Identifier "void"))
        []
        (Block [])
        isStatic
        access

nestedMember :: Int -> String -> Type -> Declaration ResolvedName Type
nestedMember unique name annotation = TypeDeclaration spanValue (resolved unique name) annotation []

ownerDeclaration :: [Declaration ResolvedName Type] -> Declaration ResolvedName Type
ownerDeclaration members = TypeDeclaration spanValue (resolved 1 "Box") (boxOf intType) members

templateMember :: Declaration ResolvedName Type
templateMember =
    TemplateTypeDeclaration
        spanValue
        (resolved 90 "Nested")
        (templateType ["Nested"] [])
        []
        []

mangledText :: Type -> Maybe String
mangledText value = case mangleTemplateType defaultTemplateMangleLimits value of
    Right mangled -> Just (mangledTemplateTypeText mangled)
    Left _ -> Nothing

memberText :: Declaration ResolvedName Type -> Maybe String
memberText member = case mangleTemplateMember defaultTemplateMangleLimits (boxOf intType) member of
    Right mangled -> Just (mangledMemberText mangled)
    Left _ -> Nothing

distinctTypes :: Type -> Type -> Bool
distinctTypes left right = case (mangledText left, mangledText right) of
    (Just leftText, Just rightText) -> leftText /= rightText
    _ -> False

distinctMembers :: Declaration ResolvedName Type -> Declaration ResolvedName Type -> Bool
distinctMembers left right = case (memberText left, memberText right) of
    (Just leftText, Just rightText) -> leftText /= rightText
    _ -> False

hasPrivatePrefix :: Bool
hasPrivatePrefix = maybe False ("_TAT1_T" `isPrefixOf`) (mangledText (boxOf intType))

typeManglingIsDeterministic :: Bool
typeManglingIsDeterministic =
    let value = pairOf (boxOf stringType) (templateType ["Buffer"] [valueArgument 64])
     in mangleTemplateType defaultTemplateMangleLimits value
            == mangleTemplateType defaultTemplateMangleLimits value

typeManglingIsAscii :: Bool
typeManglingIsAscii = maybe False (all isAscii) (mangledText (templateType ["Dünya", "Kutu"] [typeArgument stringType]))

typeManglingIsLinkerSafe :: Bool
typeManglingIsLinkerSafe = maybe False validMangledTemplateSymbol (mangledText (pairOf intType stringType))

qualifiedNamesHaveBoundaries :: Bool
qualifiedNamesHaveBoundaries = distinctTypes (templateType ["AB", "C"] []) (templateType ["A", "BC"] [])

qualifiedNameJoinsCannotCollide :: Bool
qualifiedNameJoinsCannotCollide = distinctTypes (templateType ["A", "B", "C"] []) (templateType ["A", "BC"] [])

typeArgumentsHaveBoundaries :: Bool
typeArgumentsHaveBoundaries =
    distinctTypes
        (templateType ["Owner"] [typeArgument (templateType ["AB"] []), typeArgument (templateType ["C"] [])])
        (templateType ["Owner"] [typeArgument (templateType ["A"] []), typeArgument (templateType ["BC"] [])])

typeAndValueArgumentsDiffer :: Bool
typeAndValueArgumentsDiffer =
    distinctTypes
        (templateType ["Owner"] [typeArgument (templateType ["1"] [])])
        (templateType ["Owner"] [valueArgument 1])

argumentOrderMatters :: Bool
argumentOrderMatters = distinctTypes (pairOf intType stringType) (pairOf stringType intType)

integerSignMatters :: Bool
integerSignMatters =
    distinctTypes
        (templateType ["Buffer"] [valueArgument 8])
        (templateType ["Buffer"] [valueArgument (-8)])

booleanValuesDiffer :: Bool
booleanValuesDiffer =
    distinctTypes
        (templateType ["Feature"] [ValueTemplateArgument (BooleanTemplateValue True)])
        (templateType ["Feature"] [ValueTemplateArgument (BooleanTemplateValue False)])

characterAndIntegerDiffer :: Bool
characterAndIntegerDiffer =
    distinctTypes
        (templateType ["Token"] [ValueTemplateArgument (CharacterTemplateValue 65)])
        (templateType ["Token"] [valueArgument 65])

unicodeNamesBecomeAscii :: Bool
unicodeNamesBecomeAscii = case mangledText (templateType ["İşlem"] []) of
    Just value -> all isAscii value && validMangledTemplateSymbol value
    Nothing -> False

unicodeNamesRemainDistinct :: Bool
unicodeNamesRemainDistinct = distinctTypes (templateType ["I"] []) (templateType ["İ"] [])

functionParameterOrderMatters :: Bool
functionParameterOrderMatters =
    distinctTypes (FunctionType [intType, stringType] voidType) (FunctionType [stringType, intType] voidType)

functionResultMatters :: Bool
functionResultMatters = distinctTypes (FunctionType [intType] stringType) (FunctionType [intType] boolType)

functionArityMatters :: Bool
functionArityMatters = distinctTypes (FunctionType [intType] voidType) (FunctionType [intType, intType] voidType)

memberNameMatters :: Bool
memberNameMatters =
    distinctMembers
        (functionMember 10 "Read" (FunctionType [] intType) False PublicAccess)
        (functionMember 10 "Write" (FunctionType [] intType) False PublicAccess)

memberSignatureMatters :: Bool
memberSignatureMatters =
    distinctMembers
        (functionMember 10 "Read" (FunctionType [intType] intType) False PublicAccess)
        (functionMember 10 "Read" (FunctionType [stringType] intType) False PublicAccess)

memberStaticnessMatters :: Bool
memberStaticnessMatters =
    distinctMembers
        (functionMember 10 "Read" (FunctionType [] intType) False PublicAccess)
        (functionMember 10 "Read" (FunctionType [] intType) True PublicAccess)

memberAccessMatters :: Bool
memberAccessMatters =
    and
        [ distinctMembers publicMember privateMember
        , distinctMembers publicMember internalMember
        , distinctMembers publicMember protectedMember
        , distinctMembers publicMember defaultMember
        ]
    where
        make access = functionMember 10 "Read" (FunctionType [] intType) False access
        publicMember = make PublicAccess
        privateMember = make PrivateAccess
        internalMember = make InternalAccess
        protectedMember = make ProtectedAccess
        defaultMember = make DefaultAccess

memberRetainsSymbol :: Bool
memberRetainsSymbol = case mangleTemplateMember defaultTemplateMangleLimits (boxOf intType) member of
    Right mangled -> mangledMemberSourceSymbol mangled == SymbolId 42
    Left _ -> False
    where
        member = functionMember 42 "Read" (FunctionType [] intType) False PublicAccess

memberRetainsSpelling :: Bool
memberRetainsSpelling = case mangleTemplateMember defaultTemplateMangleLimits (boxOf intType) member of
    Right mangled -> mangledMemberSourceName mangled == Identifier "Read"
    Left _ -> False
    where
        member = functionMember 42 "Read" (FunctionType [] intType) False PublicAccess

nestedTypeCanBeMangled :: Bool
nestedTypeCanBeMangled = case memberText (nestedMember 12 "Iterator" (templateType ["Box", "Iterator"] [])) of
    Just value -> "_N" `isInfixOf` value && validMangledTemplateSymbol value
    Nothing -> False

memberOrderIsStable :: Bool
memberOrderIsStable = case mangleTemplateMembers defaultTemplateMangleLimits (boxOf intType) (ownerDeclaration members) of
    Right mangled -> map mangledMemberSourceName mangled == map (Identifier . fst) names
    Left _ -> False
    where
        names = [("First", 10), ("Second", 11), ("Third", 12)]
        members = [functionMember unique name (FunctionType [] voidType) False PublicAccess | (name, unique) <- names]

memberBatchRejectsOpenTemplate :: Bool
memberBatchRejectsOpenTemplate = case mangleTemplateMembers defaultTemplateMangleLimits (boxOf intType) (ownerDeclaration [templateMember]) of
    Left [ExpectedMangleableMemberDeclaration] -> True
    _ -> False

memberBatchRejectsFunctionOwner :: Bool
memberBatchRejectsFunctionOwner = case mangleTemplateMembers defaultTemplateMangleLimits (boxOf intType) member of
    Left [ExpectedMangleableMemberDeclaration] -> True
    _ -> False
    where
        member = functionMember 10 "Read" (FunctionType [] intType) False PublicAccess

openTypesAreRejected :: Bool
openTypesAreRejected = case mangleTemplateType defaultTemplateMangleLimits (TypeVariable (resolved 7 "T")) of
    Left (OpenTypeCannotBeMangled name) -> resolvedSymbol name == SymbolId 7
    _ -> False

openValuesAreRejected :: Bool
openValuesAreRejected = case mangleTemplateType defaultTemplateMangleLimits value of
    Left (OpenTypeCannotBeMangled name) -> resolvedSpelling name == Identifier "N"
    _ -> False
    where
        value = templateType ["Buffer"] [ValueTemplateArgument (TemplateValueParameter (resolved 8 "N"))]

errorTypesAreRejected :: Bool
errorTypesAreRejected = mangleTemplateType defaultTemplateMangleLimits ErrorType == Left ErrorTypeCannotBeMangled

emptyQualifiedNamesAreRejected :: Bool
emptyQualifiedNamesAreRejected =
    mangleTemplateType defaultTemplateMangleLimits (NamedType (QualifiedName []) [])
        == Left EmptyQualifiedNameCannotBeMangled

emptyIdentifiersAreRejected :: Bool
emptyIdentifiersAreRejected =
    mangleTemplateType defaultTemplateMangleLimits (templateType [""] [])
        == Left EmptyIdentifierCannotBeMangled

negativeDepthLimitIsRejected :: Bool
negativeDepthLimitIsRejected = invalidLimits (defaultTemplateMangleLimits {maximumMangledTypeDepth = -1})

zeroLengthLimitIsRejected :: Bool
zeroLengthLimitIsRejected = invalidLimits (defaultTemplateMangleLimits {maximumMangledSymbolLength = 0})

zeroNamePartLimitIsRejected :: Bool
zeroNamePartLimitIsRejected = invalidLimits (defaultTemplateMangleLimits {maximumMangledNameParts = 0})

negativeArgumentLimitIsRejected :: Bool
negativeArgumentLimitIsRejected = invalidLimits (defaultTemplateMangleLimits {maximumMangledArguments = -1})

invalidLimits :: TemplateMangleLimits -> Bool
invalidLimits limits = case mangleTemplateType limits intType of
    Left (InvalidTemplateMangleLimits _) -> True
    _ -> False

typeDepthLimitIsEnforced :: Bool
typeDepthLimitIsEnforced = case mangleTemplateType limits (boxOf (boxOf intType)) of
    Left (MangledTypeDepthExceeded 0) -> True
    _ -> False
    where
        limits = defaultTemplateMangleLimits {maximumMangledTypeDepth = 0}

namePartLimitIsEnforced :: Bool
namePartLimitIsEnforced = case mangleTemplateType limits (templateType ["A", "B"] []) of
    Left (MangledNamePartLimitExceeded 1 2) -> True
    _ -> False
    where
        limits = defaultTemplateMangleLimits {maximumMangledNameParts = 1}

argumentLimitIsEnforced :: Bool
argumentLimitIsEnforced = case mangleTemplateType limits (pairOf intType stringType) of
    Left (MangledArgumentLimitExceeded 1 2) -> True
    _ -> False
    where
        limits = defaultTemplateMangleLimits {maximumMangledArguments = 1}

functionLimitIsEnforced :: Bool
functionLimitIsEnforced = case mangleTemplateType limits (FunctionType [intType, stringType] voidType) of
    Left (MangledArgumentLimitExceeded 1 2) -> True
    _ -> False
    where
        limits = defaultTemplateMangleLimits {maximumMangledArguments = 1}

symbolLengthLimitIsEnforced :: Bool
symbolLengthLimitIsEnforced = case mangleTemplateType limits (boxOf intType) of
    Left (MangledSymbolLengthExceeded 8 actual) -> actual > 8
    _ -> False
    where
        limits = defaultTemplateMangleLimits {maximumMangledSymbolLength = 8}

wrongPrefixIsRejected :: Bool
wrongPrefixIsRejected = not (validMangledTemplateSymbol "Box_1")

punctuationIsRejected :: Bool
punctuationIsRejected = not (validMangledTemplateSymbol "_TAT1_Box<int>")

openErrorRenderingIsUseful :: Bool
openErrorRenderingIsUseful = "open template symbol" `isInfixOf` renderTemplateMangleError (OpenTypeCannotBeMangled (resolved 9 "T"))

lengthErrorRenderingIsUseful :: Bool
lengthErrorRenderingIsUseful =
    "limit 10" `isInfixOf` renderTemplateMangleError (MangledSymbolLengthExceeded 10 11)

defaultLimitsArePractical :: Bool
defaultLimitsArePractical = case mangleTemplateType defaultTemplateMangleLimits deeplyNested of
    Right mangled -> validMangledTemplateSymbol (mangledTemplateTypeText mangled)
    Left _ -> False
    where
        deeplyNested = foldr (\_ nested -> boxOf nested) intType [1 .. 64 :: Int]