tala-compiler-0.1.0: test/TemplateMemberReachabilityTests.hs
-- SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com>
-- SPDX-License-Identifier: MPL-2.0 WITH AdditionRef-Progmasoft-Exception-1.1
module TemplateMemberReachabilityTests (templateMemberReachabilityTests) where
import Data.List (isInfixOf)
import Progmasoft.Tala.AST
import Progmasoft.Tala.Compiler
import Progmasoft.Tala.Core
import Progmasoft.Tala.Frontend
import Progmasoft.Tala.Template.Application
import Progmasoft.Tala.Template.Mangling
import Progmasoft.Tala.Template.MemberReachability
import Progmasoft.Tala.Template.Specialization
templateMemberReachabilityTests :: [(String, Bool)]
templateMemberReachabilityTests =
[ ("member graph retains direct semantic calls", graphRetainsDirectCalls)
, ("member graph classifies declaration-external calls", graphClassifiesExternalCalls)
, ("name roots select a transitive member closure", nameRootSelectsClosure)
, ("symbol roots select a transitive member closure", symbolRootSelectsClosure)
, ("selection output preserves declaration order", selectionPreservesSourceOrder)
, ("selection roots preserve declaration order", rootsPreserveSourceOrder)
, ("unreachable members remain lazy", unreachableMembersStayLazy)
, ("self-recursive calls terminate", selfRecursionTerminates)
, ("mutually recursive calls terminate", mutualRecursionTerminates)
, ("repeated calls retain evidence but share an edge", repeatedCallsShareEdge)
, ("calls nested in arguments remain reachable", nestedArgumentCallsAreFound)
, ("calls nested in conditions remain reachable", conditionCallsAreFound)
, ("calls nested in callable bodies remain reachable", callableBodyCallsAreFound)
, ("calls nested in capture initializers remain reachable", captureInitializerCallsAreFound)
, ("nested type bodies do not enter the enclosing graph", nestedTypeBodiesAreIsolated)
, ("overload name roots select every matching symbol", overloadNameSelectsEveryMatch)
, ("symbol roots distinguish same-spelling overloads", overloadSymbolSelectionIsExact)
, ("internal dependency selection remains overload-exact", dependencySelectionIsExact)
, ("missing member names are diagnosed", missingNameIsRejected)
, ("missing member symbols are diagnosed", missingSymbolIsRejected)
, ("zero member symbols are diagnosed", zeroSymbolIsRejected)
, ("duplicate member symbols are diagnosed", duplicateSymbolIsRejected)
, ("empty roots produce an empty closure", emptyRootsProduceEmptyClosure)
, ("reachability statistics count all graph nodes", statisticsCountNodes)
, ("reachability statistics distinguish call classes", statisticsCountCalls)
, ("reachability verifier accepts an authentic result", verifierAcceptsResult)
, ("reachability verifier rejects an omitted dependency", verifierRejectsOmittedDependency)
, ("reachability verifier rejects an invented edge", verifierRejectsInventedEdge)
, ("reachability verifier rejects stale member payload", verifierRejectsStaleMembers)
, ("reachability verifier rejects stale statistics", verifierRejectsStaleStatistics)
, ("reachability error rendering identifies names", errorRenderingIdentifiesName)
, ("reachability issue rendering identifies edges", issueRenderingIdentifiesEdge)
, ("planner expands a member demand through resolved calls", plannerExpandsMemberDemand)
, ("planner records the expanded effective scope", plannerRecordsExpandedScope)
, ("planner leaves unrelated members unmaterialized", plannerLeavesUnrelatedLazy)
, ("planner member limit includes reachable callees", plannerLimitIncludesCallees)
, ("planner freshens reachable call targets coherently", plannerFreshensCallTargets)
, ("planner mangles every reachable member", plannerManglesReachableMembers)
, ("compiler bridge lowers the reachable closure", compilerBridgeLowersClosure)
, ("planner surfaces an invalid member graph", plannerRejectsInvalidGraph)
, ("planner call-graph error rendering is actionable", plannerGraphErrorRenders)
]
chainSource :: String
chainSource =
unwords
[ "template<typename T> class Chain {"
, "T Entry(_ T value) { return Normalize(value); }"
, "T Normalize(_ T value) { return Finish(value); }"
, "T Finish(_ T value) { return value; }"
, "T Unused(_ T value) { return value; }"
, "}"
]
selfRecursiveSource :: String
selfRecursiveSource =
"template<typename T> class Loop { T Repeat(_ T value) { return Repeat(value); } T Idle(_ T value) { return value; } }"
mutualRecursiveSource :: String
mutualRecursiveSource =
"template<typename T> class Loop { T Left(_ T value) { return Right(value); } T Right(_ T value) { return Left(value); } }"
branchSource :: String
branchSource =
unwords
[ "template<typename T> class Branch {"
, "int Entry() { if (Gate()) { return Convert(Leaf()); } return 0; }"
, "bool Gate() { return true; }"
, "int Convert(_ int value) { return value; }"
, "int Leaf() { return 7; }"
, "int Unused() { return 9; }"
, "}"
]
analyzeMembers :: String -> Maybe [Declaration ResolvedName Type]
analyzeMembers source = do
typed <- case analyzeSemantics (CompilerInput "member-reachability-test.tala" source) of
Right artifacts -> Just (semanticTypedAST artifacts)
Left _ -> Nothing
templateMembers typed
templateMembers :: TypedAST -> Maybe [Declaration ResolvedName Type]
templateMembers (TypedAST tree) = case syntaxDeclarations tree of
[TemplateTypeDeclaration {typeMembers = members}] -> Just members
_ -> Nothing
reachByName :: String -> [String] -> Maybe TemplateMemberReachability
reachByName source names = do
members <- analyzeMembers source
rightValue (selectReachableMembersByName (map Identifier names) members)
reachBySymbol :: [Declaration ResolvedName Type] -> [SymbolId] -> Maybe TemplateMemberReachability
reachBySymbol members symbols = rightValue (selectReachableMembersBySymbol symbols members)
memberNames :: [Declaration ResolvedName annotation] -> [String]
memberNames = map (identifierText . resolvedSpelling . declarationName)
reachabilityNames :: TemplateMemberReachability -> [String]
reachabilityNames = memberNames . memberReachabilityMembers
memberNamed :: String -> [Declaration ResolvedName annotation] -> Maybe (Declaration ResolvedName annotation)
memberNamed name = first ((== name) . identifierText . resolvedSpelling . declarationName)
memberSymbolNamed :: String -> [Declaration ResolvedName annotation] -> Maybe SymbolId
memberSymbolNamed name members = resolvedSymbol . declarationName <$> memberNamed name members
graphRetainsDirectCalls :: Bool
graphRetainsDirectCalls = case analyzeMembers chainSource >>= rightValue . buildTemplateMemberGraph of
Just graph ->
length (templateGraphInternalCalls graph) == 2
&& edgeNames graph == [("Entry", "Normalize"), ("Normalize", "Finish")]
Nothing -> False
edgeNames :: TemplateMemberGraph -> [(String, String)]
edgeNames graph =
[ (nameOf (templateCallOwner call), nameOf (templateCallTarget call))
| call <- templateGraphInternalCalls graph
]
where
nameOf symbol = case first ((== symbol) . templateMemberSymbol) (templateGraphNodes graph) of
Just node -> identifierText (templateMemberName node)
Nothing -> "<external>"
graphClassifiesExternalCalls :: Bool
graphClassifiesExternalCalls = case analyzeMembers chainSource of
Just members -> case memberNamed "Entry" members of
Just entry ->
let changed = replaceMember "Entry" (appendExternalCall entry) members
in case buildTemplateMemberGraph changed of
Right graph ->
length (templateGraphInternalCalls graph) == 2
&& length (templateGraphExternalCalls graph) == 1
&& any ((== SymbolId 9000) . templateCallTarget) (templateGraphExternalCalls graph)
Left _ -> False
Nothing -> False
Nothing -> False
appendExternalCall :: Declaration ResolvedName Type -> Declaration ResolvedName Type
appendExternalCall declaration@FunctionDeclaration {declarationBody = Block statements} =
declaration
{ declarationBody =
Block
( ExpressionStatement
testSpan
( CallExpression
testSpan
(NameExpression testSpan (resolved 9000 "External") (FunctionType [] voidType))
[]
voidType
)
True
: statements
)
}
appendExternalCall declaration = declaration
nameRootSelectsClosure :: Bool
nameRootSelectsClosure = case reachByName chainSource ["Entry"] of
Just result -> reachabilityNames result == ["Entry", "Normalize", "Finish"]
Nothing -> False
symbolRootSelectsClosure :: Bool
symbolRootSelectsClosure = case analyzeMembers chainSource of
Just members -> case memberSymbolNamed "Entry" members >>= \symbol -> reachBySymbol members [symbol] of
Just result -> reachabilityNames result == ["Entry", "Normalize", "Finish"]
Nothing -> False
Nothing -> False
selectionPreservesSourceOrder :: Bool
selectionPreservesSourceOrder = case reachByName chainSource ["Finish", "Entry"] of
Just result -> reachabilityNames result == ["Entry", "Normalize", "Finish"]
Nothing -> False
rootsPreserveSourceOrder :: Bool
rootsPreserveSourceOrder = case analyzeMembers chainSource of
Just members -> case selectReachableMembersByName [Identifier "Finish", Identifier "Entry"] members of
Right result -> memberReachabilityRoots result == symbolsNamed ["Entry", "Finish"] members
Left _ -> False
Nothing -> False
unreachableMembersStayLazy :: Bool
unreachableMembersStayLazy = maybe False (notElem "Unused" . reachabilityNames) (reachByName chainSource ["Entry"])
selfRecursionTerminates :: Bool
selfRecursionTerminates = case reachByName selfRecursiveSource ["Repeat"] of
Just result ->
reachabilityNames result == ["Repeat"]
&& memberReachabilityEdges result == [(onlySymbol result, onlySymbol result)]
Nothing -> False
mutualRecursionTerminates :: Bool
mutualRecursionTerminates = case reachByName mutualRecursiveSource ["Left"] of
Just result ->
reachabilityNames result == ["Left", "Right"]
&& length (memberReachabilityEdges result) == 2
Nothing -> False
repeatedCallsShareEdge :: Bool
repeatedCallsShareEdge = case analyzeMembers chainSource of
Just members -> case (memberNamed "Entry" members, memberSymbolNamed "Normalize" members) of
(Just entry, Just target) ->
let changed = replaceMember "Entry" (appendCall target entry) members
in case selectReachableMembersByName [Identifier "Entry"] changed of
Right result ->
internalTemplateMemberCalls (memberReachabilityStatistics result) == 3
&& length (memberReachabilityEdges result) == 2
Left _ -> False
_ -> False
Nothing -> False
nestedArgumentCallsAreFound :: Bool
nestedArgumentCallsAreFound = case reachByName branchSource ["Entry"] of
Just result -> all (`elem` reachabilityNames result) ["Convert", "Leaf"]
Nothing -> False
conditionCallsAreFound :: Bool
conditionCallsAreFound = case reachByName branchSource ["Entry"] of
Just result -> "Gate" `elem` reachabilityNames result
Nothing -> False
callableBodyCallsAreFound :: Bool
callableBodyCallsAreFound = case analyzeMembers chainSource of
Just members -> case (memberNamed "Entry" members, memberSymbolNamed "Finish" members) of
(Just entry, Just target) ->
let changed = replaceMember "Entry" (prependCallable target entry) members
in maybe False (elem "Finish" . reachabilityNames) (rightValue (selectReachableMembersByName [Identifier "Entry"] changed))
_ -> False
Nothing -> False
captureInitializerCallsAreFound :: Bool
captureInitializerCallsAreFound = case analyzeMembers chainSource of
Just members -> case (memberNamed "Entry" members, memberSymbolNamed "Finish" members) of
(Just entry, Just target) ->
let changed = replaceMember "Entry" (prependCapture target entry) members
in maybe False (elem "Finish" . reachabilityNames) (rightValue (selectReachableMembersByName [Identifier "Entry"] changed))
_ -> False
Nothing -> False
nestedTypeBodiesAreIsolated :: Bool
nestedTypeBodiesAreIsolated = case analyzeMembers chainSource of
Just members -> case memberSymbolNamed "Finish" members of
Just target ->
let nestedMethod = simpleCallingMember 8101 "NestedCall" target
nested = TypeDeclaration testSpan (resolved 8100 "Nested") (namedType "Nested") [nestedMethod]
changed = nested : members
in case selectReachableMembersByName [Identifier "Nested"] changed of
Right result -> reachabilityNames result == ["Nested"]
Left _ -> False
Nothing -> False
Nothing -> False
overloadNameSelectsEveryMatch :: Bool
overloadNameSelectsEveryMatch = case overloadedMembers of
Just members -> case selectReachableMembersByName [Identifier "Finish"] members of
Right result -> reachabilityNames result == ["Finish", "Finish"]
Left _ -> False
Nothing -> False
overloadSymbolSelectionIsExact :: Bool
overloadSymbolSelectionIsExact = case overloadedMembers of
Just members -> case [resolvedSymbol (declarationName member) | member <- members, memberName member == "Finish"] of
_ : second : _ -> case selectReachableMembersBySymbol [second] members of
Right result -> map (resolvedSymbol . declarationName) (memberReachabilityMembers result) == [second]
Left _ -> False
_ -> False
Nothing -> False
dependencySelectionIsExact :: Bool
dependencySelectionIsExact = case overloadedMembers of
Just members -> case (memberSymbolNamed "Entry" members, memberSymbolNamed "Finish" members) of
(Just entry, Just originalFinish) -> case selectReachableMembersBySymbol [entry] members of
Right result ->
originalFinish `elem` memberReachabilitySymbols result
&& length [() | member <- memberReachabilityMembers result, memberName member == "Finish"] == 1
Left _ -> False
_ -> False
Nothing -> False
missingNameIsRejected :: Bool
missingNameIsRejected = case analyzeMembers chainSource of
Just members -> selectReachableMembersByName [Identifier "Absent"] members == Left [MissingTemplateMemberName (Identifier "Absent")]
Nothing -> False
missingSymbolIsRejected :: Bool
missingSymbolIsRejected = case analyzeMembers chainSource of
Just members -> selectReachableMembersBySymbol [SymbolId 9999] members == Left [MissingTemplateMemberSymbol (SymbolId 9999)]
Nothing -> False
zeroSymbolIsRejected :: Bool
zeroSymbolIsRejected = case analyzeMembers chainSource of
Just (firstMember : remaining) -> case buildTemplateMemberGraph (setMemberSymbol (SymbolId 0) firstMember : remaining) of
Left [InvalidTemplateMemberSymbol _ (SymbolId 0)] -> True
_ -> False
_ -> False
duplicateSymbolIsRejected :: Bool
duplicateSymbolIsRejected = case analyzeMembers chainSource of
Just (firstMember : secondMember : remaining) ->
let symbol = resolvedSymbol (declarationName firstMember)
in case buildTemplateMemberGraph (firstMember : setMemberSymbol symbol secondMember : remaining) of
Left problems -> any isDuplicate problems
Right _ -> False
_ -> False
where
isDuplicate (DuplicateTemplateMemberSymbol _ _) = True
isDuplicate _ = False
emptyRootsProduceEmptyClosure :: Bool
emptyRootsProduceEmptyClosure = case reachByName chainSource [] of
Just result -> null (memberReachabilityMembers result) && null (memberReachabilityEdges result)
Nothing -> False
statisticsCountNodes :: Bool
statisticsCountNodes = case reachByName chainSource ["Entry"] of
Just result ->
let statistics = memberReachabilityStatistics result
in totalTemplateMembers statistics == 4
&& callableTemplateMembers statistics == 4
&& requestedTemplateMemberRoots statistics == 1
&& reachableTemplateMembers statistics == 3
Nothing -> False
statisticsCountCalls :: Bool
statisticsCountCalls = case reachByName branchSource ["Entry"] of
Just result ->
let statistics = memberReachabilityStatistics result
in observedTemplateMemberCalls statistics == 3
&& internalTemplateMemberCalls statistics == 3
&& externalTemplateMemberCalls statistics == 0
&& uniqueTemplateMemberEdges statistics == 3
Nothing -> False
verifierAcceptsResult :: Bool
verifierAcceptsResult = case analyzeMembers chainSource of
Just members -> case selectReachableMembersByName [Identifier "Entry"] members of
Right result -> verifyTemplateMemberReachability members result == Right []
Left _ -> False
Nothing -> False
verifierRejectsOmittedDependency :: Bool
verifierRejectsOmittedDependency = case reachabilityFixture of
Just (members, result) -> case memberReachabilitySymbols result of
root : _ ->
let changed = result {memberReachabilitySymbols = [root], memberReachabilityMembers = take 1 (memberReachabilityMembers result)}
in hasIssue isDependency (verifyTemplateMemberReachability members changed)
[] -> False
Nothing -> False
where
isDependency ReachabilityDependencyNotSelected {} = True
isDependency _ = False
verifierRejectsInventedEdge :: Bool
verifierRejectsInventedEdge = case reachabilityFixture of
Just (members, result) ->
let changed = result {memberReachabilityEdges = memberReachabilityEdges result ++ [(SymbolId 8000, SymbolId 8001)]}
in hasIssue isUnexpected (verifyTemplateMemberReachability members changed)
Nothing -> False
where
isUnexpected (UnexpectedReachabilityEdge (SymbolId 8000) (SymbolId 8001)) = True
isUnexpected _ = False
verifierRejectsStaleMembers :: Bool
verifierRejectsStaleMembers = case reachabilityFixture of
Just (members, result) ->
let changed = result {memberReachabilityMembers = take 1 (memberReachabilityMembers result)}
in hasIssue isDifferent (verifyTemplateMemberReachability members changed)
Nothing -> False
where
isDifferent ReachabilityMembersDiffer {} = True
isDifferent _ = False
verifierRejectsStaleStatistics :: Bool
verifierRejectsStaleStatistics = case reachabilityFixture of
Just (members, result) ->
let statistics = memberReachabilityStatistics result
changed = result {memberReachabilityStatistics = statistics {reachableTemplateMembers = 99}}
in hasIssue isIncorrect (verifyTemplateMemberReachability members changed)
Nothing -> False
where
isIncorrect IncorrectReachabilityStatistics {} = True
isIncorrect _ = False
errorRenderingIdentifiesName :: Bool
errorRenderingIdentifiesName =
"Absent" `isInfixOf` renderTemplateMemberReachabilityError (MissingTemplateMemberName (Identifier "Absent"))
issueRenderingIdentifiesEdge :: Bool
issueRenderingIdentifiesEdge =
"SymbolId 10 -> SymbolId 11"
`isInfixOf` renderTemplateMemberReachabilityIssue (MissingReachabilityEdge (SymbolId 10) (SymbolId 11))
plannerExpandsMemberDemand :: Bool
plannerExpandsMemberDemand = case chainPlan defaultTemplateSpecializationLimits of
Right planValue -> maybe False ((== ["Entry", "Normalize", "Finish"]) . selectedNames) (onlySpecialization planValue)
Left _ -> False
plannerRecordsExpandedScope :: Bool
plannerRecordsExpandedScope = case chainPlan defaultTemplateSpecializationLimits >>= requireOnly of
Right specialization ->
templateSpecializationScope specialization
== TemplateMemberDemand (map Identifier ["Entry", "Normalize", "Finish"])
Left _ -> False
plannerLeavesUnrelatedLazy :: Bool
plannerLeavesUnrelatedLazy = case chainPlan defaultTemplateSpecializationLimits >>= requireOnly of
Right specialization -> "Unused" `notElem` selectedNames specialization
Left _ -> False
plannerLimitIncludesCallees :: Bool
plannerLimitIncludesCallees = case chainPlan limits of
Left [TemplateMemberLimitExceeded _ 2 3] -> True
_ -> False
where
limits = defaultTemplateSpecializationLimits {maximumMembersPerSpecialization = 2}
plannerFreshensCallTargets :: Bool
plannerFreshensCallTargets = case chainPlan defaultTemplateSpecializationLimits >>= requireOnly of
Right specialization -> case selectedMembers specialization of
members -> case (memberNamed "Entry" members, memberSymbolNamed "Normalize" members) of
(Just entry, Just normalize) -> directTargets entry == [normalize]
_ -> False
Left _ -> False
plannerManglesReachableMembers :: Bool
plannerManglesReachableMembers = case chainPlan defaultTemplateSpecializationLimits >>= requireOnly of
Right specialization ->
length (templateSpecializationMangledMembers specialization) == 3
&& all (not . null . mangledMemberText) (templateSpecializationMangledMembers specialization)
Left _ -> False
compilerBridgeLowersClosure :: Bool
compilerBridgeLowersClosure = case analyzeTyped chainSource of
Just typed -> case compileTemplateSpecializations defaultTemplateSpecializationLimits typed [entryDemand] of
Right (_, core) -> memberNamesFromCore core == ["Entry", "Normalize", "Finish"]
Left _ -> False
Nothing -> False
plannerRejectsInvalidGraph :: Bool
plannerRejectsInvalidGraph = case analyzeTyped chainSource >>= withZeroFirstMember of
Just typed -> case planTemplateSpecializations defaultTemplateSpecializationLimits typed [entryDemand] of
Left [TemplateMemberReachabilityFailed _ failures] -> any isInvalid failures
_ -> False
Nothing -> False
where
isInvalid InvalidTemplateMemberSymbol {} = True
isInvalid _ = False
plannerGraphErrorRenders :: Bool
plannerGraphErrorRenders =
"invalid member call graph"
`isInfixOf` renderTemplateSpecializationError
( TemplateMemberReachabilityFailed
(QualifiedName [Identifier "Chain"])
[InvalidTemplateMemberSymbol (Identifier "Entry") (SymbolId 0)]
)
chainPlan :: TemplateSpecializationLimits -> Either [TemplateSpecializationError] TemplateSpecializationPlan
chainPlan limits = case analyzeTyped chainSource of
Just typed -> planTemplateSpecializations limits typed [entryDemand]
Nothing -> Left []
entryDemand :: TemplateSpecializationDemand
entryDemand =
TemplateSpecializationDemand
(TemplateApplication (QualifiedName [Identifier "Chain"]) [TypeTemplateArgument stringType])
(TemplateMemberDemand [Identifier "Entry"])
"member-reachability-test"
analyzeTyped :: String -> Maybe TypedAST
analyzeTyped source = case analyzeSemantics (CompilerInput "member-reachability-test.tala" source) of
Right artifacts -> Just (semanticTypedAST artifacts)
Left _ -> Nothing
withZeroFirstMember :: TypedAST -> Maybe TypedAST
withZeroFirstMember (TypedAST tree) = case syntaxDeclarations tree of
[declaration@TemplateTypeDeclaration {typeMembers = firstMember : remaining}] ->
Just
(TypedAST tree {syntaxDeclarations = [declaration {typeMembers = setMemberSymbol (SymbolId 0) firstMember : remaining}]})
_ -> Nothing
overloadedMembers :: Maybe [Declaration ResolvedName Type]
overloadedMembers = do
members <- analyzeMembers chainSource
finish <- memberNamed "Finish" members
let SymbolId maximumValue = maximumSymbolInDeclarations members
duplicate = setMemberSymbol (SymbolId (maximumValue + 1)) finish
pure (insertAfter "Finish" duplicate members)
maximumSymbolInDeclarations :: [Declaration ResolvedName Type] -> SymbolId
maximumSymbolInDeclarations members =
SymbolId (maximum (0 : map (symbolIdValue . resolvedSymbol . declarationName) members))
insertAfter ::
String -> Declaration ResolvedName Type -> [Declaration ResolvedName Type] -> [Declaration ResolvedName Type]
insertAfter _ _ [] = []
insertAfter name addition (member : remaining)
| memberName member == name = member : addition : remaining
| otherwise = member : insertAfter name addition remaining
replaceMember ::
String -> Declaration ResolvedName Type -> [Declaration ResolvedName Type] -> [Declaration ResolvedName Type]
replaceMember name replacement = map (\member -> if memberName member == name then replacement else member)
setMemberSymbol :: SymbolId -> Declaration ResolvedName annotation -> Declaration ResolvedName annotation
setMemberSymbol symbol declaration =
declaration {declarationName = (declarationName declaration) {resolvedSymbol = symbol}}
memberName :: Declaration ResolvedName annotation -> String
memberName = identifierText . resolvedSpelling . declarationName
appendCall :: SymbolId -> Declaration ResolvedName Type -> Declaration ResolvedName Type
appendCall target declaration@FunctionDeclaration {declarationBody = Block statements} =
declaration
{ declarationBody =
Block
( statements
++ [ ExpressionStatement
testSpan
( CallExpression
testSpan
(NameExpression testSpan (ResolvedName target (Identifier "Normalize")) (FunctionType [] voidType))
[]
voidType
)
True
]
)
}
appendCall _ declaration = declaration
prependCallable :: SymbolId -> Declaration ResolvedName Type -> Declaration ResolvedName Type
prependCallable target declaration@FunctionDeclaration {declarationBody = Block statements} =
let call =
CallExpression
testSpan
(NameExpression testSpan (ResolvedName target (Identifier "Finish")) (FunctionType [] voidType))
[]
voidType
callable = CallableExpression testSpan False [] [] (CallableExpressionBody call) (FunctionType [] voidType)
in declaration {declarationBody = Block (ExpressionStatement testSpan callable False : statements)}
prependCallable _ declaration = declaration
prependCapture :: SymbolId -> Declaration ResolvedName Type -> Declaration ResolvedName Type
prependCapture target declaration@FunctionDeclaration {declarationBody = Block statements} =
let call =
CallExpression
testSpan
(NameExpression testSpan (ResolvedName target (Identifier "Finish")) (FunctionType [] voidType))
[]
voidType
capture = Capture testSpan StrongCapture (resolved 8200 "captured") voidType (Just call)
callable = CallableExpression testSpan True [capture] [] (CallableBlockBody (Block [])) (FunctionType [] voidType)
in declaration {declarationBody = Block (ExpressionStatement testSpan callable False : statements)}
prependCapture _ declaration = declaration
simpleCallingMember :: Int -> String -> SymbolId -> Declaration ResolvedName Type
simpleCallingMember symbol name target =
FunctionDeclaration
testSpan
(resolved symbol name)
(FunctionType [] voidType)
(ExplicitType (Identifier "void"))
[]
( Block
[ ExpressionStatement
testSpan
( CallExpression
testSpan
(NameExpression testSpan (ResolvedName target (Identifier "Finish")) (FunctionType [] voidType))
[]
voidType
)
True
, ReturnStatement testSpan Nothing
]
)
False
PublicAccess
directTargets :: Declaration ResolvedName Type -> [SymbolId]
directTargets FunctionDeclaration {declarationBody = Block statements} = concatMap statementTargets statements
directTargets _ = []
statementTargets :: Statement ResolvedName Type -> [SymbolId]
statementTargets statement = case statement of
BindingStatement _ _ _ _ _ value -> expressionTargets value
AssignmentStatement _ _ _ value -> expressionTargets value
ReturnStatement _ value -> maybe [] expressionTargets value
IfStatement _ condition trueBlock falseBlock ->
expressionTargets condition ++ blockTargets trueBlock ++ maybe [] blockTargets falseBlock
WhileStatement _ condition body -> expressionTargets condition ++ blockTargets body
DoWhileStatement _ body condition -> blockTargets body ++ expressionTargets condition
ForStatement _ initializer condition updates body ->
maybe [] statementTargets initializer
++ maybe [] expressionTargets condition
++ concatMap statementTargets updates
++ blockTargets body
ForEachStatement _ _ _ _ _ source body -> expressionTargets source ++ blockTargets body
IncrementStatement {} -> []
CompoundAssignmentStatement _ _ _ _ value -> expressionTargets value
DiscardStatement _ value -> expressionTargets value
BreakStatement _ value -> maybe [] expressionTargets value
ContinueStatement {} -> []
GuardStatement _ condition block -> expressionTargets condition ++ blockTargets block
BlockStatement _ block -> blockTargets block
ExpressionStatement _ value _ -> expressionTargets value
blockTargets :: Block ResolvedName Type -> [SymbolId]
blockTargets (Block statements) = concatMap statementTargets statements
expressionTargets :: Expression ResolvedName Type -> [SymbolId]
expressionTargets expression = case expression of
CallExpression _ (NameExpression _ name _) arguments _ -> resolvedSymbol name : concatMap expressionTargets arguments
CallExpression _ callee arguments _ -> expressionTargets callee ++ concatMap expressionTargets arguments
UnaryExpression _ _ value _ -> expressionTargets value
ConditionalExpression _ condition whenTrue whenFalse _ -> concatMap expressionTargets [condition, whenTrue, whenFalse]
CoalesceExpression _ left fallback _ -> expressionTargets left ++ expressionTargets fallback
BinaryExpression _ _ left right _ -> expressionTargets left ++ expressionTargets right
CallableExpression _ _ captures _ body _ ->
concatMap (maybe [] expressionTargets . captureInitializer) captures
++ case body of
CallableExpressionBody value -> expressionTargets value
CallableBlockBody block -> blockTargets block
BlockExpression _ block _ -> blockTargets block
MatchExpression _ subjects arms _ -> concatMap expressionTargets (subjects ++ concatMap matchArmExpressions arms)
_ -> []
selectedMembers :: TemplateSpecialization -> [Declaration ResolvedName Type]
selectedMembers specialization = case templateSpecializationDeclaration specialization of
TypeDeclaration {typeMembers = members} -> members
_ -> []
selectedNames :: TemplateSpecialization -> [String]
selectedNames = memberNames . selectedMembers
memberNamesFromCore :: CoreModule -> [String]
memberNamesFromCore = map (identifierText . resolvedSpelling . coreFunctionName) . coreModuleFunctions
onlySpecialization :: TemplateSpecializationPlan -> Maybe TemplateSpecialization
onlySpecialization planValue = case plannedTemplateSpecializations planValue of
[specialization] -> Just specialization
_ -> Nothing
requireOnly :: TemplateSpecializationPlan -> Either [TemplateSpecializationError] TemplateSpecialization
requireOnly planValue = maybe (Left []) Right (onlySpecialization planValue)
symbolsNamed :: [String] -> [Declaration ResolvedName Type] -> [SymbolId]
symbolsNamed names members =
[ resolvedSymbol (declarationName member)
| member <- members
, memberName member `elem` names
]
onlySymbol :: TemplateMemberReachability -> SymbolId
onlySymbol result = case memberReachabilitySymbols result of
[symbol] -> symbol
_ -> SymbolId 0
reachabilityFixture :: Maybe ([Declaration ResolvedName Type], TemplateMemberReachability)
reachabilityFixture = do
members <- analyzeMembers chainSource
result <- rightValue (selectReachableMembersByName [Identifier "Entry"] members)
pure (members, result)
hasIssue :: (TemplateMemberReachabilityIssue -> Bool) -> Either failure [TemplateMemberReachabilityIssue] -> Bool
hasIssue predicate result = case result of
Right issues -> any predicate issues
Left _ -> False
resolved :: Int -> String -> ResolvedName
resolved symbol name = ResolvedName (SymbolId symbol) (Identifier name)
testSpan :: SourceSpan
testSpan = SourceSpan "member-reachability-test.tala" (SourcePosition 1 1) (SourcePosition 1 2)
first :: (value -> Bool) -> [value] -> Maybe value
first _ [] = Nothing
first predicate (value : remaining)
| predicate value = Just value
| otherwise = first predicate remaining
rightValue :: Either failure value -> Maybe value
rightValue result = case result of
Right value -> Just value
Left _ -> Nothing