tala-frontend-0.1.0: src/Progmasoft/Tala/Template/Freshen.hs
-- SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com>
-- SPDX-License-Identifier: MPL-2.0 WITH AdditionRef-Progmasoft-Exception-1.1
{- |
Deterministic semantic-name freshening for cloned declarations.
Template specialization copies a typed declaration, but a copy must not keep
the source declaration's SymbolIds. Core identifies definitions by SymbolId;
sharing those ids would make two otherwise distinct specializations collide.
This module performs the alpha-renaming after substitution and before Core
lowering. Spelling and source locations remain unchanged for diagnostics.
-}
module Progmasoft.Tala.Template.Freshen
( FreshenResult (..)
, maximumSymbolInTypedAST
, freshenDeclaration
) where
import Data.Foldable (traverse_)
import Data.Map.Strict (Map)
import Data.Map.Strict qualified as Map
import Progmasoft.Tala.AST
-- | Freshened declaration and the old-to-new identities allocated for it.
data FreshenResult = FreshenResult
{ freshenedDeclaration :: Declaration ResolvedName Type
-- ^ Cloned tree with definitions and references updated.
, freshenedSymbols :: [(SymbolId, SymbolId)]
-- ^ Identity mapping in ascending old-symbol order.
, nextFreshSymbol :: SymbolId
-- ^ First identity available to the next clone.
}
deriving (Eq, Ord, Read, Show)
data FreshState = FreshState
{ freshNext :: Int
, freshMap :: Map SymbolId ResolvedName
}
-- | Find the greatest symbol identity present anywhere in a typed AST.
maximumSymbolInTypedAST :: TypedAST -> SymbolId
maximumSymbolInTypedAST (TypedAST tree) =
SymbolId (maximum (0 : concatMap declarationSymbols (syntaxDeclarations tree)))
-- | Alpha-rename one cloned declaration, preserving spelling and source spans.
freshenDeclaration :: SymbolId -> Declaration ResolvedName Type -> FreshenResult
freshenDeclaration (SymbolId first) declaration =
let (closed, finalState) = runFresh (freshDeclaration declaration) (FreshState (max 1 first) Map.empty)
pairs = [(old, resolvedSymbol replacement) | (old, replacement) <- Map.toAscList (freshMap finalState)]
in FreshenResult closed pairs (SymbolId (freshNext finalState))
newtype Fresh a = Fresh {runFresh :: FreshState -> (a, FreshState)}
instance Functor Fresh where
fmap transform action = Fresh $ \state ->
let (value, next) = runFresh action state
in (transform value, next)
instance Applicative Fresh where
pure value = Fresh (value,)
functionAction <*> valueAction = Fresh $ \state ->
let (function, next) = runFresh functionAction state
(value, final) = runFresh valueAction next
in (function value, final)
instance Monad Fresh where
action >>= continuation = Fresh $ \state ->
let (value, next) = runFresh action state
in runFresh (continuation value) next
freshDefinition :: ResolvedName -> Fresh ResolvedName
freshDefinition original = Fresh $ \state ->
case Map.lookup (resolvedSymbol original) (freshMap state) of
Just existing -> (existing, state)
Nothing ->
let replacement = ResolvedName (SymbolId (freshNext state)) (resolvedSpelling original)
nextState =
state
{ freshNext = freshNext state + 1
, freshMap = Map.insert (resolvedSymbol original) replacement (freshMap state)
}
in (replacement, nextState)
freshReference :: ResolvedName -> Fresh ResolvedName
freshReference original = Fresh $ \state ->
(Map.findWithDefault original (resolvedSymbol original) (freshMap state), state)
freshDeclaration :: Declaration ResolvedName Type -> Fresh (Declaration ResolvedName Type)
freshDeclaration declaration = case declaration of
TypeDeclaration spanValue name annotation members -> do
closedName <- freshDefinition name
closedAnnotation <- freshType annotation
-- A type scope is recursive: a member body may call a member declared
-- later in source order. Reserve every immediate member identity before
-- rewriting any body so forward calls and mutually recursive methods
-- cannot retain the template declaration's old SymbolIds.
traverse_ reserveDeclarationName members
closedMembers <- traverse freshDeclaration members
pure (TypeDeclaration spanValue closedName closedAnnotation closedMembers)
FunctionDeclaration spanValue name annotation returnSyntax parameters body isStatic access -> do
closedName <- freshDefinition name
-- Parameters are definitions and must be allocated before the body is
-- walked, otherwise references in the body retain the template copy's
-- old semantic identity.
closedParameters <- traverse freshParameterDefinition parameters
closedAnnotation <- freshType annotation
closedBody <- freshBlock body
pure
( FunctionDeclaration
spanValue
closedName
closedAnnotation
returnSyntax
closedParameters
closedBody
isStatic
access
)
TemplateTypeDeclaration {} ->
-- A nested template introduces a separate substitution/freshening
-- environment. It will be selected independently when demanded.
pure declaration
-- An enum has no body and no template parameter in it.
EnumDeclaration {} -> pure declaration
reserveDeclarationName :: Declaration ResolvedName Type -> Fresh ()
reserveDeclarationName declaration = case declaration of
TypeDeclaration _ name _ _ -> freshDefinition name >> pure ()
FunctionDeclaration _ name _ _ _ _ _ _ -> freshDefinition name >> pure ()
TemplateTypeDeclaration {} ->
-- Nested templates own a separate specialization environment and must
-- not leak definitions into the enclosing concrete type's map.
pure ()
EnumDeclaration {} -> pure ()
freshParameterDefinition :: Parameter ResolvedName Type -> Fresh (Parameter ResolvedName Type)
freshParameterDefinition parameter = do
name <- freshDefinition (parameterName parameter)
annotation <- freshType (parameterAnnotation parameter)
pure
( Parameter
(parameterSpan parameter)
name
annotation
(parameterTypeSyntax parameter)
)
freshBlock :: Block ResolvedName Type -> Fresh (Block ResolvedName Type)
freshBlock (Block statements) = Block <$> traverse freshStatement statements
freshStatement :: Statement ResolvedName Type -> Fresh (Statement ResolvedName Type)
freshStatement statement = case statement of
BindingStatement spanValue kind syntax name annotation value -> do
-- Initializers are evaluated in the surrounding scope. Allocate the
-- local only after rewriting its initializer so self-shadowing does
-- not accidentally rewrite an outer reference.
closedValue <- freshExpression value
closedName <- freshDefinition name
closedAnnotation <- freshType annotation
pure (BindingStatement spanValue kind syntax closedName closedAnnotation closedValue)
AssignmentStatement spanValue name annotation value ->
AssignmentStatement spanValue
<$> freshReference name
<*> freshType annotation
<*> freshExpression value
ReturnStatement spanValue value -> ReturnStatement spanValue <$> traverse freshExpression value
IfStatement spanValue condition trueBlock falseBlock ->
IfStatement spanValue
<$> freshExpression condition
<*> freshBlock trueBlock
<*> traverse freshBlock falseBlock
WhileStatement spanValue condition body ->
WhileStatement spanValue <$> freshExpression condition <*> freshBlock body
DoWhileStatement spanValue body condition ->
DoWhileStatement spanValue <$> freshBlock body <*> freshExpression condition
ForStatement spanValue initializer condition updates body -> do
closedInitializer <- traverse freshStatement initializer
closedCondition <- traverse freshExpression condition
closedUpdates <- traverse freshStatement updates
closedBody <- freshBlock body
pure (ForStatement spanValue closedInitializer closedCondition closedUpdates closedBody)
ForEachStatement spanValue kind syntax name annotation source body -> do
closedSource <- freshExpression source
closedName <- freshDefinition name
closedAnnotation <- freshType annotation
closedBody <- freshBlock body
pure (ForEachStatement spanValue kind syntax closedName closedAnnotation closedSource closedBody)
IncrementStatement spanValue name annotation ->
IncrementStatement spanValue <$> freshReference name <*> freshType annotation
CompoundAssignmentStatement spanValue operator name annotation value ->
CompoundAssignmentStatement spanValue operator
<$> freshReference name
<*> freshType annotation
<*> freshExpression value
DiscardStatement spanValue value -> DiscardStatement spanValue <$> freshExpression value
BreakStatement spanValue value -> BreakStatement spanValue <$> traverse freshExpression value
ContinueStatement spanValue -> pure (ContinueStatement spanValue)
GuardStatement spanValue condition block ->
GuardStatement spanValue <$> freshExpression condition <*> freshBlock block
BlockStatement spanValue block -> BlockStatement spanValue <$> freshBlock block
ExpressionStatement spanValue value terminated ->
ExpressionStatement spanValue <$> freshExpression value <*> pure terminated
freshExpression :: Expression ResolvedName Type -> Fresh (Expression ResolvedName Type)
freshExpression expression = case expression of
NameExpression spanValue name annotation ->
NameExpression spanValue <$> freshReference name <*> freshType annotation
LiteralExpression spanValue literal annotation ->
LiteralExpression spanValue literal <$> freshType annotation
MemberAccessExpression spanValue receiver member annotation ->
MemberAccessExpression spanValue <$> freshExpression receiver <*> pure member <*> freshType annotation
MethodReferenceExpression spanValue receiver member annotation ->
MethodReferenceExpression spanValue <$> freshExpression receiver <*> pure member <*> freshType annotation
CallExpression spanValue callee arguments annotation ->
CallExpression spanValue
<$> freshExpression callee
<*> traverse freshExpression arguments
<*> freshType annotation
UnaryExpression spanValue operator value annotation ->
UnaryExpression spanValue operator <$> freshExpression value <*> freshType annotation
BinaryExpression spanValue operator left right annotation ->
BinaryExpression spanValue operator
<$> freshExpression left
<*> freshExpression right
<*> freshType annotation
IsPatternExpression spanValue subject patternValue annotation ->
IsPatternExpression spanValue
<$> freshExpression subject
<*> freshPattern patternValue
<*> freshType annotation
ConditionalExpression spanValue condition first second annotation ->
ConditionalExpression spanValue
<$> freshExpression condition
<*> freshExpression first
<*> freshExpression second
<*> freshType annotation
CoalesceExpression spanValue left fallback annotation ->
CoalesceExpression spanValue
<$> freshExpression left
<*> freshExpression fallback
<*> freshType annotation
AssignmentExpression spanValue operator name value annotation ->
AssignmentExpression spanValue operator
<$> freshReference name
<*> freshExpression value
<*> freshType annotation
IncrementExpression spanValue isPrefix name annotation ->
IncrementExpression spanValue isPrefix <$> freshReference name <*> freshType annotation
LoopExpression spanValue loop annotation ->
LoopExpression spanValue <$> freshStatement loop <*> freshType annotation
BlockExpression spanValue block annotation ->
BlockExpression spanValue <$> freshBlock block <*> freshType annotation
-- The subjects are evaluated in the surrounding scope. Each arm then
-- allocates the locals its patterns bind before its guard and its body
-- refer to them.
MatchExpression spanValue subjects arms annotation ->
MatchExpression spanValue
<$> traverse freshExpression subjects
<*> traverse (traverseMatchArm (traverseMatchPattern freshDefinition freshType) freshExpression) arms
<*> freshType annotation
CallableExpression spanValue explicit captures parameters body annotation -> do
closedCaptures <- traverse freshCaptureDefinition captures
closedParameters <- traverse freshParameterDefinition parameters
closedBody <- freshCallableBody body
closedAnnotation <- freshType annotation
pure (CallableExpression spanValue explicit closedCaptures closedParameters closedBody closedAnnotation)
freshPattern :: Pattern ResolvedName Type -> Fresh (Pattern ResolvedName Type)
freshPattern patternValue = case patternValue of
WildcardPattern spanValue annotation -> WildcardPattern spanValue <$> freshType annotation
NullPattern spanValue annotation -> NullPattern spanValue <$> freshType annotation
LiteralPattern spanValue literal annotation ->
LiteralPattern spanValue literal <$> freshType annotation
TypePattern spanValue syntax annotation -> TypePattern spanValue syntax <$> freshType annotation
RelationalPattern spanValue operator literal annotation ->
RelationalPattern spanValue operator literal <$> freshType annotation
NotPattern spanValue nested annotation ->
NotPattern spanValue <$> freshPattern nested <*> freshType annotation
AndPattern spanValue left right annotation ->
AndPattern spanValue <$> freshPattern left <*> freshPattern right <*> freshType annotation
OrPattern spanValue left right annotation ->
OrPattern spanValue <$> freshPattern left <*> freshPattern right <*> freshType annotation
freshCaptureDefinition :: Capture ResolvedName Type -> Fresh (Capture ResolvedName Type)
freshCaptureDefinition capture = do
-- The initializer belongs to the outer scope, just like a local binding
-- initializer. The capture name becomes visible only in the callable.
initializer <- traverse freshExpression (captureInitializer capture)
name <- freshDefinition (captureName capture)
annotation <- freshType (captureAnnotation capture)
pure (Capture (captureSpan capture) (captureMode capture) name annotation initializer)
freshCallableBody :: CallableBody ResolvedName Type -> Fresh (CallableBody ResolvedName Type)
freshCallableBody body = case body of
CallableExpressionBody expression -> CallableExpressionBody <$> freshExpression expression
CallableBlockBody block -> CallableBlockBody <$> freshBlock block
freshType :: Type -> Fresh Type
freshType valueType = case valueType of
NamedType name arguments -> NamedType name <$> traverse freshArgument arguments
FunctionType parameters result -> FunctionType <$> traverse freshType parameters <*> freshType result
TypeVariable name -> TypeVariable <$> freshReference name
ErrorType -> pure ErrorType
freshArgument :: TemplateArgument -> Fresh TemplateArgument
freshArgument argument = case argument of
TypeTemplateArgument valueType -> TypeTemplateArgument <$> freshType valueType
ValueTemplateArgument value -> ValueTemplateArgument <$> freshValue value
freshValue :: TemplateValue -> Fresh TemplateValue
freshValue value = case value of
TemplateValueParameter name -> TemplateValueParameter <$> freshReference name
_ -> pure value
declarationSymbols :: Declaration ResolvedName Type -> [Int]
declarationSymbols declaration = case declaration of
TypeDeclaration _ name annotation members ->
nameSymbol name : typeSymbols annotation ++ concatMap declarationSymbols members
FunctionDeclaration _ name annotation _ parameters body _ _ ->
nameSymbol name
: typeSymbols annotation
++ concatMap parameterSymbols parameters
++ blockSymbols body
TemplateTypeDeclaration _ name annotation parameters members ->
nameSymbol name
: typeSymbols annotation
++ concatMap templateParameterSymbols parameters
++ concatMap declarationSymbols members
EnumDeclaration _ name annotation _ _ -> nameSymbol name : typeSymbols annotation
templateParameterSymbols :: TemplateParameter ResolvedName Type -> [Int]
templateParameterSymbols parameter =
nameSymbol (templateParameterName parameter) : typeSymbols (templateParameterAnnotation parameter)
parameterSymbols :: Parameter ResolvedName Type -> [Int]
parameterSymbols parameter = nameSymbol (parameterName parameter) : typeSymbols (parameterAnnotation parameter)
blockSymbols :: Block ResolvedName Type -> [Int]
blockSymbols (Block statements) = concatMap statementSymbols statements
statementSymbols :: Statement ResolvedName Type -> [Int]
statementSymbols statement = case statement of
BindingStatement _ _ _ name annotation value -> nameSymbol name : typeSymbols annotation ++ expressionSymbols value
AssignmentStatement _ name annotation value -> nameSymbol name : typeSymbols annotation ++ expressionSymbols value
ReturnStatement _ value -> maybe [] expressionSymbols value
IfStatement _ condition trueBlock falseBlock ->
expressionSymbols condition ++ blockSymbols trueBlock ++ maybe [] blockSymbols falseBlock
WhileStatement _ condition body -> expressionSymbols condition ++ blockSymbols body
DoWhileStatement _ body condition -> blockSymbols body ++ expressionSymbols condition
ForStatement _ initializer condition updates body ->
maybe [] statementSymbols initializer
++ maybe [] expressionSymbols condition
++ concatMap statementSymbols updates
++ blockSymbols body
ForEachStatement _ _ _ name annotation source body ->
nameSymbol name : typeSymbols annotation ++ expressionSymbols source ++ blockSymbols body
IncrementStatement _ name annotation -> nameSymbol name : typeSymbols annotation
CompoundAssignmentStatement _ _ name annotation value ->
nameSymbol name : typeSymbols annotation ++ expressionSymbols value
DiscardStatement _ value -> expressionSymbols value
BreakStatement _ value -> maybe [] expressionSymbols value
ContinueStatement {} -> []
GuardStatement _ condition block -> expressionSymbols condition ++ blockSymbols block
BlockStatement _ block -> blockSymbols block
ExpressionStatement _ value _ -> expressionSymbols value
expressionSymbols :: Expression ResolvedName Type -> [Int]
expressionSymbols expression = case expression of
NameExpression _ name annotation -> nameSymbol name : typeSymbols annotation
LiteralExpression _ _ annotation -> typeSymbols annotation
MemberAccessExpression _ receiver _ annotation -> expressionSymbols receiver ++ typeSymbols annotation
MethodReferenceExpression _ receiver _ annotation -> expressionSymbols receiver ++ typeSymbols annotation
CallExpression _ callee arguments annotation ->
expressionSymbols callee ++ concatMap expressionSymbols arguments ++ typeSymbols annotation
UnaryExpression _ _ value annotation -> expressionSymbols value ++ typeSymbols annotation
BinaryExpression _ _ left right annotation ->
expressionSymbols left ++ expressionSymbols right ++ typeSymbols annotation
IsPatternExpression _ subject patternValue annotation ->
expressionSymbols subject ++ patternSymbols patternValue ++ typeSymbols annotation
ConditionalExpression _ condition first second annotation ->
concatMap expressionSymbols [condition, first, second] ++ typeSymbols annotation
CoalesceExpression _ left fallback annotation ->
expressionSymbols left ++ expressionSymbols fallback ++ typeSymbols annotation
AssignmentExpression _ _ name value annotation ->
nameSymbol name : expressionSymbols value ++ typeSymbols annotation
IncrementExpression _ _ name annotation -> nameSymbol name : typeSymbols annotation
LoopExpression _ loop annotation -> statementSymbols loop ++ typeSymbols annotation
BlockExpression _ block annotation -> blockSymbols block ++ typeSymbols annotation
MatchExpression _ subjects arms annotation ->
concatMap expressionSymbols (subjects ++ concatMap matchArmExpressions arms)
++ concat
[ maybe [] ((: []) . nameSymbol) (matchPatternBinding patternValue)
++ typeSymbols (matchPatternAnnotation patternValue)
| arm <- arms
, patternValue <- matchArmPatterns arm
]
++ typeSymbols annotation
CallableExpression _ _ captures parameters body annotation ->
concatMap captureSymbols captures
++ concatMap parameterSymbols parameters
++ callableBodySymbols body
++ typeSymbols annotation
patternSymbols :: Pattern ResolvedName Type -> [Int]
patternSymbols patternValue = case patternValue of
WildcardPattern _ annotation -> typeSymbols annotation
NullPattern _ annotation -> typeSymbols annotation
LiteralPattern _ _ annotation -> typeSymbols annotation
TypePattern _ _ annotation -> typeSymbols annotation
RelationalPattern _ _ _ annotation -> typeSymbols annotation
NotPattern _ nested annotation -> patternSymbols nested ++ typeSymbols annotation
AndPattern _ left right annotation ->
patternSymbols left ++ patternSymbols right ++ typeSymbols annotation
OrPattern _ left right annotation ->
patternSymbols left ++ patternSymbols right ++ typeSymbols annotation
captureSymbols :: Capture ResolvedName Type -> [Int]
captureSymbols capture =
nameSymbol (captureName capture)
: typeSymbols (captureAnnotation capture)
++ maybe [] expressionSymbols (captureInitializer capture)
callableBodySymbols :: CallableBody ResolvedName Type -> [Int]
callableBodySymbols body = case body of
CallableExpressionBody expression -> expressionSymbols expression
CallableBlockBody block -> blockSymbols block
typeSymbols :: Type -> [Int]
typeSymbols valueType = case valueType of
NamedType _ arguments -> concatMap argumentSymbols arguments
FunctionType parameters result -> concatMap typeSymbols (result : parameters)
TypeVariable name -> [nameSymbol name]
ErrorType -> []
argumentSymbols :: TemplateArgument -> [Int]
argumentSymbols argument = case argument of
TypeTemplateArgument valueType -> typeSymbols valueType
ValueTemplateArgument (TemplateValueParameter name) -> [nameSymbol name]
ValueTemplateArgument _ -> []
nameSymbol :: ResolvedName -> Int
nameSymbol = symbolIdValue . resolvedSymbol