tala-frontend-0.1.0: src/Progmasoft/Tala/Template/Discovery.hs
-- SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com>
-- SPDX-License-Identifier: MPL-2.0 WITH AdditionRef-Progmasoft-Exception-1.1
{- |
Typed-AST discovery of concrete template layout demands.
Discovery is intentionally conservative. A concrete template type appearing
in an ordinary checked declaration requests layout, but never requests method
bodies. Open template declarations are definitions, not uses, so this pass
does not walk their parameter-dependent bodies. Member/call resolution will
add narrower body demands through the same planner API in a later stage.
-}
module Progmasoft.Tala.Template.Discovery
( TemplateTypeSite (..)
, TemplateDiscoveryOrigin (..)
, TemplateDiscoveryStatistics (..)
, TemplateDemandDiscovery (..)
, discoverTemplateDemands
, renderTemplateDiscoveryOrigin
) where
import Data.List (intercalate)
import Progmasoft.Tala.AST
import Progmasoft.Tala.Template.Application
import Progmasoft.Tala.Template.Specialization
-- | Syntactic/semantic position at which a concrete template application occurs.
data TemplateTypeSite
= -- | Type declaration annotation.
DeclarationTypeSite
| -- | Function's complete callable signature.
FunctionSignatureSite
| -- | Parameter index in a function signature.
ParameterTypeSite Int
| -- | Local binding annotation at statement index.
BindingTypeSite Int
| -- | Assignment target annotation at statement index.
AssignmentTypeSite Int
| -- | Return expression at statement index.
ReturnTypeSite Int
| -- | Conditional or loop condition at statement index.
ConditionTypeSite Int
| -- | General expression node at traversal index.
ExpressionTypeSite Int
| -- | Callable expression type at traversal index.
CallableTypeSite Int
| -- | Capture index nested in a callable expression.
CaptureTypeSite Int Int
| -- | Type argument nested at the indexed depth.
NestedTypeArgumentSite Int
| -- | Type inside a function-type parameter list.
FunctionParameterTypeSite Int
| -- | Result type nested inside a function type.
FunctionResultTypeSite
deriving (Eq, Ord, Read, Show)
-- | Source provenance explaining why one template application was discovered.
data TemplateDiscoveryOrigin = TemplateDiscoveryOrigin
{ discoveryDeclaration :: ResolvedName
-- ^ Enclosing declaration that contains the use.
, discoveryMember :: Maybe ResolvedName
-- ^ Enclosing member, when the use is within a type.
, discoverySites :: [TemplateTypeSite]
-- ^ Nested sites traversed to reach the application.
, discoverySpan :: SourceSpan
-- ^ Source location associated with the use.
}
deriving (Eq, Ord, Read, Show)
-- | Counters describing conservative demand discovery work.
data TemplateDiscoveryStatistics = TemplateDiscoveryStatistics
{ visitedTemplateTypeNodes :: Int
-- ^ Type nodes visited during traversal.
, discoveredTemplateApplications :: Int
-- ^ Concrete applications converted to demands.
, ignoredOrdinaryNamedTypes :: Int
-- ^ Non-template named types skipped.
, skippedOpenTemplateBodies :: Int
-- ^ Open declaration bodies deliberately not walked.
}
deriving (Eq, Ord, Read, Show)
-- | Explicit specialization demands, their provenance, and work counters.
data TemplateDemandDiscovery = TemplateDemandDiscovery
{ discoveredTemplateDemands :: [TemplateSpecializationDemand]
-- ^ Layout-only demands found in checked declarations.
, discoveredTemplateOrigins :: [TemplateDiscoveryOrigin]
-- ^ Source sites corresponding to discovered uses.
, templateDiscoveryStatistics :: TemplateDiscoveryStatistics
-- ^ Aggregate traversal counts.
}
deriving (Eq, Ord, Read, Show)
data DiscoveryState = DiscoveryState
{ stateDemands :: [TemplateSpecializationDemand]
, stateOrigins :: [TemplateDiscoveryOrigin]
, stateVisitedTypes :: Int
, stateIgnoredTypes :: Int
, stateSkippedTemplates :: Int
}
emptyState :: DiscoveryState
emptyState = DiscoveryState [] [] 0 0 0
-- | Find concrete template type uses without implicitly requesting method bodies.
discoverTemplateDemands :: TypedAST -> TemplateDemandDiscovery
discoverTemplateDemands typed@(TypedAST tree) =
let catalog = buildTemplateCatalog typed
final = foldl' (discoverTop catalog (syntaxNamespace tree)) emptyState (syntaxDeclarations tree)
demands = reverse (stateDemands final)
origins = reverse (stateOrigins final)
in TemplateDemandDiscovery
demands
origins
( TemplateDiscoveryStatistics
(stateVisitedTypes final)
(length demands)
(stateIgnoredTypes final)
(stateSkippedTemplates final)
)
discoverTop ::
TemplateCatalog ->
Maybe QualifiedName ->
DiscoveryState ->
Declaration ResolvedName Type ->
DiscoveryState
discoverTop catalog namespace state declaration = case declaration of
TemplateTypeDeclaration {} -> state {stateSkippedTemplates = stateSkippedTemplates state + 1}
TypeDeclaration spanValue name annotation members ->
let origin = TemplateDiscoveryOrigin name Nothing [DeclarationTypeSite] spanValue
afterType = discoverType catalog namespace origin annotation state
in foldl' (discoverMember catalog namespace name) afterType members
FunctionDeclaration {} ->
-- Top-level runtime functions are not part of the renewed source
-- grammar, but keeping this traversal total makes hand-built TypedAST
-- fixtures and future declaration categories deterministic.
discoverFunction catalog namespace (declarationName declaration) Nothing declaration state
-- An enum uses no template: its underlying type is a scalar.
EnumDeclaration {} -> state
discoverMember ::
TemplateCatalog ->
Maybe QualifiedName ->
ResolvedName ->
DiscoveryState ->
Declaration ResolvedName Type ->
DiscoveryState
discoverMember catalog namespace owner state member = case member of
FunctionDeclaration {} -> discoverFunction catalog namespace owner (Just (declarationName member)) member state
TypeDeclaration spanValue name annotation members ->
let origin = TemplateDiscoveryOrigin owner (Just name) [DeclarationTypeSite] spanValue
afterType = discoverType catalog namespace origin annotation state
in foldl' (discoverMember catalog namespace owner) afterType members
TemplateTypeDeclaration {} -> state {stateSkippedTemplates = stateSkippedTemplates state + 1}
EnumDeclaration {} -> state
discoverFunction ::
TemplateCatalog ->
Maybe QualifiedName ->
ResolvedName ->
Maybe ResolvedName ->
Declaration ResolvedName Type ->
DiscoveryState ->
DiscoveryState
discoverFunction catalog namespace owner member declaration state = case declaration of
FunctionDeclaration spanValue _ annotation _ parameters body _ _ ->
let root = TemplateDiscoveryOrigin owner member [FunctionSignatureSite] spanValue
-- Function annotations repeat parameter types in a callable shape.
-- Walk the result here and the declared parameters below so each
-- source type use produces exactly one demand and one origin.
resultType = case annotation of
FunctionType _ result -> result
other -> other
afterSignature = discoverType catalog namespace root resultType state
afterParameters =
foldl'
( \current (index, parameter) ->
discoverType
catalog
namespace
(root {discoverySites = [ParameterTypeSite index]})
(parameterAnnotation parameter)
current
)
afterSignature
(zip [0 ..] parameters)
in discoverBlock catalog namespace root body afterParameters
_ -> state
discoverBlock ::
TemplateCatalog ->
Maybe QualifiedName ->
TemplateDiscoveryOrigin ->
Block ResolvedName Type ->
DiscoveryState ->
DiscoveryState
discoverBlock catalog namespace root (Block statements) state =
foldl'
(\current (index, statement) -> discoverStatement catalog namespace root index statement current)
state
(zip [0 ..] statements)
discoverStatement ::
TemplateCatalog ->
Maybe QualifiedName ->
TemplateDiscoveryOrigin ->
Int ->
Statement ResolvedName Type ->
DiscoveryState ->
DiscoveryState
discoverStatement catalog namespace root index statement state = case statement of
BindingStatement spanValue _ _ _ annotation value ->
let origin = root {discoverySites = [BindingTypeSite index], discoverySpan = spanValue}
in discoverExpression catalog namespace origin value (discoverType catalog namespace origin annotation state)
AssignmentStatement spanValue _ annotation value ->
let origin = root {discoverySites = [AssignmentTypeSite index], discoverySpan = spanValue}
in discoverExpression catalog namespace origin value (discoverType catalog namespace origin annotation state)
ReturnStatement spanValue value ->
let origin = root {discoverySites = [ReturnTypeSite index], discoverySpan = spanValue}
in maybe state (\expression -> discoverExpression catalog namespace origin expression state) value
IfStatement spanValue condition trueBlock falseBlock ->
let origin = root {discoverySites = [ConditionTypeSite index], discoverySpan = spanValue}
afterCondition = discoverExpression catalog namespace origin condition state
afterTrue = discoverBlock catalog namespace origin trueBlock afterCondition
in maybe afterTrue (\block -> discoverBlock catalog namespace origin block afterTrue) falseBlock
WhileStatement spanValue condition body ->
let origin = root {discoverySites = [ConditionTypeSite index], discoverySpan = spanValue}
in discoverBlock catalog namespace origin body (discoverExpression catalog namespace origin condition state)
DoWhileStatement spanValue body condition ->
let origin = root {discoverySites = [ConditionTypeSite index], discoverySpan = spanValue}
afterBody = discoverBlock catalog namespace origin body state
in discoverExpression catalog namespace origin condition afterBody
ForStatement spanValue initializer condition updates body ->
let origin = root {discoverySites = [ConditionTypeSite index], discoverySpan = spanValue}
afterInitializer = maybe state (\value -> discoverStatement catalog namespace origin index value state) initializer
afterCondition = maybe afterInitializer (\value -> discoverExpression catalog namespace origin value afterInitializer) condition
afterUpdates = foldl' (\current value -> discoverStatement catalog namespace origin index value current) afterCondition updates
in discoverBlock catalog namespace origin body afterUpdates
ForEachStatement spanValue _ _ _ annotation source body ->
let origin = root {discoverySites = [ConditionTypeSite index], discoverySpan = spanValue}
afterAnnotation = discoverType catalog namespace origin annotation state
afterSource = discoverExpression catalog namespace origin source afterAnnotation
in discoverBlock catalog namespace origin body afterSource
IncrementStatement _ _ annotation -> discoverType catalog namespace root annotation state
CompoundAssignmentStatement spanValue _ _ annotation value ->
let origin = root {discoverySites = [AssignmentTypeSite index], discoverySpan = spanValue}
in discoverExpression catalog namespace origin value (discoverType catalog namespace origin annotation state)
DiscardStatement spanValue expression ->
discoverExpression
catalog
namespace
(root {discoverySites = [ExpressionTypeSite index], discoverySpan = spanValue})
expression
state
BreakStatement _ value -> maybe state (\expression -> discoverExpression catalog namespace root expression state) value
ContinueStatement {} -> state
GuardStatement spanValue condition block ->
let origin = root {discoverySites = [ConditionTypeSite index], discoverySpan = spanValue}
in discoverBlock catalog namespace origin block (discoverExpression catalog namespace origin condition state)
BlockStatement spanValue block ->
discoverBlock catalog namespace (root {discoverySites = [ConditionTypeSite index], discoverySpan = spanValue}) block state
ExpressionStatement spanValue expression _ ->
discoverExpression
catalog
namespace
(root {discoverySites = [ExpressionTypeSite index], discoverySpan = spanValue})
expression
state
discoverExpression ::
TemplateCatalog ->
Maybe QualifiedName ->
TemplateDiscoveryOrigin ->
Expression ResolvedName Type ->
DiscoveryState ->
DiscoveryState
discoverExpression catalog namespace origin expression state = case expression of
NameExpression _ _ annotation -> discoverType catalog namespace origin annotation state
LiteralExpression _ _ annotation -> discoverType catalog namespace origin annotation state
MemberAccessExpression _ receiver _ annotation ->
discoverExpression catalog namespace origin receiver (discoverType catalog namespace origin annotation state)
MethodReferenceExpression _ receiver _ annotation ->
discoverExpression catalog namespace origin receiver (discoverType catalog namespace origin annotation state)
CallExpression _ callee arguments annotation ->
let afterType = discoverType catalog namespace origin annotation state
afterCallee = discoverExpression catalog namespace origin callee afterType
in foldl' (\current value -> discoverExpression catalog namespace origin value current) afterCallee arguments
UnaryExpression _ _ value annotation ->
discoverExpression catalog namespace origin value (discoverType catalog namespace origin annotation state)
BinaryExpression _ _ left right annotation ->
let afterType = discoverType catalog namespace origin annotation state
afterLeft = discoverExpression catalog namespace origin left afterType
in discoverExpression catalog namespace origin right afterLeft
IsPatternExpression _ subject patternValue annotation ->
let afterType = discoverType catalog namespace origin annotation state
afterSubject = discoverExpression catalog namespace origin subject afterType
in discoverPattern catalog namespace origin patternValue afterSubject
ConditionalExpression _ condition first second annotation ->
let afterType = discoverType catalog namespace origin annotation state
in foldl'
(\current value -> discoverExpression catalog namespace origin value current)
afterType
[condition, first, second]
CoalesceExpression _ left fallback annotation ->
let afterType = discoverType catalog namespace origin annotation state
afterLeft = discoverExpression catalog namespace origin left afterType
in discoverExpression catalog namespace origin fallback afterLeft
AssignmentExpression _ _ _ value annotation ->
discoverExpression catalog namespace origin value (discoverType catalog namespace origin annotation state)
IncrementExpression _ _ _ annotation -> discoverType catalog namespace origin annotation state
LoopExpression _ loop annotation ->
discoverStatement catalog namespace origin 0 loop (discoverType catalog namespace origin annotation state)
BlockExpression _ block annotation ->
discoverBlock catalog namespace origin block (discoverType catalog namespace origin annotation state)
MatchExpression _ subjects arms annotation ->
let afterType = discoverType catalog namespace origin annotation state
afterPatterns =
foldl'
(\current patternValue -> discoverType catalog namespace origin (matchPatternAnnotation patternValue) current)
afterType
(concatMap matchArmPatterns arms)
in foldl'
(\current value -> discoverExpression catalog namespace origin value current)
afterPatterns
(subjects ++ concatMap matchArmExpressions arms)
CallableExpression spanValue _ captures parameters body annotation ->
let callableOrigin =
origin
{ discoverySites = discoverySites origin ++ [CallableTypeSite (sourceColumn (sourceStart spanValue))]
, discoverySpan = spanValue
}
afterType = discoverType catalog namespace callableOrigin annotation state
afterCaptures =
foldl'
(discoverCapture catalog namespace callableOrigin)
afterType
(zip [0 ..] captures)
afterParameters =
foldl'
( \current (index, parameter) ->
discoverType
catalog
namespace
(callableOrigin {discoverySites = discoverySites callableOrigin ++ [ParameterTypeSite index]})
(parameterAnnotation parameter)
current
)
afterCaptures
(zip [0 ..] parameters)
in discoverCallableBody catalog namespace callableOrigin body afterParameters
discoverPattern ::
TemplateCatalog ->
Maybe QualifiedName ->
TemplateDiscoveryOrigin ->
Pattern ResolvedName Type ->
DiscoveryState ->
DiscoveryState
discoverPattern catalog namespace origin patternValue state = case patternValue of
WildcardPattern _ annotation -> discoverAnnotation annotation state
NullPattern _ annotation -> discoverAnnotation annotation state
LiteralPattern _ _ annotation -> discoverAnnotation annotation state
TypePattern _ _ annotation -> discoverAnnotation annotation state
RelationalPattern _ _ _ annotation -> discoverAnnotation annotation state
NotPattern _ nested annotation ->
discoverPattern catalog namespace origin nested (discoverAnnotation annotation state)
AndPattern _ left right annotation -> discoverPair annotation left right
OrPattern _ left right annotation -> discoverPair annotation left right
where
discoverAnnotation = discoverType catalog namespace origin
discoverPair annotation left right =
let afterType = discoverAnnotation annotation state
afterLeft = discoverPattern catalog namespace origin left afterType
in discoverPattern catalog namespace origin right afterLeft
discoverCapture ::
TemplateCatalog ->
Maybe QualifiedName ->
TemplateDiscoveryOrigin ->
DiscoveryState ->
(Int, Capture ResolvedName Type) ->
DiscoveryState
discoverCapture catalog namespace root state (index, capture) =
let origin = root {discoverySites = discoverySites root ++ [CaptureTypeSite index 0], discoverySpan = captureSpan capture}
afterType = discoverType catalog namespace origin (captureAnnotation capture) state
in maybe afterType (\value -> discoverExpression catalog namespace origin value afterType) (captureInitializer capture)
discoverCallableBody ::
TemplateCatalog ->
Maybe QualifiedName ->
TemplateDiscoveryOrigin ->
CallableBody ResolvedName Type ->
DiscoveryState ->
DiscoveryState
discoverCallableBody catalog namespace origin body state = case body of
CallableExpressionBody expression -> discoverExpression catalog namespace origin expression state
CallableBlockBody block -> discoverBlock catalog namespace origin block state
discoverType ::
TemplateCatalog ->
Maybe QualifiedName ->
TemplateDiscoveryOrigin ->
Type ->
DiscoveryState ->
DiscoveryState
discoverType catalog namespace origin valueType state =
let visited = state {stateVisitedTypes = stateVisitedTypes state + 1}
in case valueType of
NamedType name arguments ->
let afterRoot = case resolveTemplateTarget namespace name catalog of
Just target
| isClosedType valueType ->
visited
{ stateDemands =
TemplateSpecializationDemand
(TemplateApplication target arguments)
TemplateLayoutDemand
(renderTemplateDiscoveryOrigin origin)
: stateDemands visited
, stateOrigins = origin : stateOrigins visited
}
_ -> visited {stateIgnoredTypes = stateIgnoredTypes visited + 1}
in foldl'
(\current (index, argument) -> discoverArgument catalog namespace origin index argument current)
afterRoot
(zip [0 ..] arguments)
FunctionType parameters result ->
let afterParameters =
foldl'
( \current (index, parameter) ->
discoverType
catalog
namespace
(origin {discoverySites = discoverySites origin ++ [FunctionParameterTypeSite index]})
parameter
current
)
visited
(zip [0 ..] parameters)
in discoverType
catalog
namespace
(origin {discoverySites = discoverySites origin ++ [FunctionResultTypeSite]})
result
afterParameters
TypeVariable _ -> visited
ErrorType -> visited
discoverArgument ::
TemplateCatalog ->
Maybe QualifiedName ->
TemplateDiscoveryOrigin ->
Int ->
TemplateArgument ->
DiscoveryState ->
DiscoveryState
discoverArgument catalog namespace origin index argument state = case argument of
TypeTemplateArgument nested ->
discoverType
catalog
namespace
(origin {discoverySites = discoverySites origin ++ [NestedTypeArgumentSite index]})
nested
state
ValueTemplateArgument _ -> state
resolveTemplateTarget :: Maybe QualifiedName -> QualifiedName -> TemplateCatalog -> Maybe QualifiedName
resolveTemplateTarget namespace name (TemplateCatalog declarations)
| any ((== name) . templateDeclarationName) declarations = Just name
| otherwise = case (namespace, qualifiedNameParts name) of
(Just (QualifiedName owner), [_]) ->
let qualified = QualifiedName (owner ++ qualifiedNameParts name)
in if any ((== qualified) . templateDeclarationName) declarations then Just qualified else Nothing
_ -> Nothing
isClosedType :: Type -> Bool
isClosedType valueType = case valueType of
NamedType _ arguments -> all closedArgument arguments
FunctionType parameters result -> all isClosedType parameters && isClosedType result
TypeVariable _ -> False
ErrorType -> False
where
closedArgument (TypeTemplateArgument nested) = isClosedType nested
closedArgument (ValueTemplateArgument (TemplateValueParameter _)) = False
closedArgument (ValueTemplateArgument _) = True
-- | Render a stable human-readable provenance path for one discovered demand.
renderTemplateDiscoveryOrigin :: TemplateDiscoveryOrigin -> String
renderTemplateDiscoveryOrigin origin =
sourceFile (discoverySpan origin)
++ ":"
++ show (sourceLine (sourceStart (discoverySpan origin)))
++ ":"
++ show (sourceColumn (sourceStart (discoverySpan origin)))
++ " in "
++ renderResolved (discoveryDeclaration origin)
++ maybe "" (("." ++) . renderResolved) (discoveryMember origin)
++ " ["
++ intercalate "/" (map renderSite (discoverySites origin))
++ "]"
renderResolved :: ResolvedName -> String
renderResolved = identifierText . resolvedSpelling
renderSite :: TemplateTypeSite -> String
renderSite site = case site of
DeclarationTypeSite -> "declaration"
FunctionSignatureSite -> "signature"
ParameterTypeSite index -> "parameter:" ++ show index
BindingTypeSite index -> "binding:" ++ show index
AssignmentTypeSite index -> "assignment:" ++ show index
ReturnTypeSite index -> "return:" ++ show index
ConditionTypeSite index -> "condition:" ++ show index
ExpressionTypeSite index -> "expression:" ++ show index
CallableTypeSite index -> "callable:" ++ show index
CaptureTypeSite index initializer -> "capture:" ++ show index ++ ":" ++ show initializer
NestedTypeArgumentSite index -> "argument:" ++ show index
FunctionParameterTypeSite index -> "function-parameter:" ++ show index
FunctionResultTypeSite -> "function-result"