packages feed

tala-frontend-0.1.0: src/Progmasoft/Tala/TypeClassification.hs

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

{- |
Recursive value/reference classification for resolved Tala types.

The declaration catalog is explicit on purpose. A spelling alone cannot tell
the frontend whether a user-defined type is a value, reference, or CoW family,
and silently treating every unknown name as a reference would make ownership
lowering unsound. The resolver will populate this catalog when all renewed
declaration families are represented in the typed AST.
-}
module Progmasoft.Tala.TypeClassification
    ( StorageClass (..)
    , NominalKind (..)
    , NominalCatalog
    , CatalogError (..)
    , emptyNominalCatalog
    , registerNominal
    , lookupNominal
    , nominalCatalogSize
    , classifyNominal
    , classifyType
    , usesAarc
    , usesCopyOnWrite
    ) where

import Data.Map.Strict (Map)
import Data.Map.Strict qualified as Map
import Progmasoft.Tala.AST
import Progmasoft.Tala.BuiltinTypes (typeToScalarType)

-- | Storage behavior needed by Core ownership lowering.
data StorageClass
    = -- | No managed reference representation is required.
      TrivialValueStorage
    | -- | Value semantics use shared copy-on-write storage.
      CopyOnWriteValueStorage
    | -- | Reference semantics use AARC-managed ownership.
      AarcReferenceStorage
    | -- | Nominal metadata is absent or contains an open type.
      UnresolvedStorage
    deriving (Eq, Ord, Read, Show)

-- | Semantic declaration families, deliberately independent of parser tokens.
data NominalKind
    = -- | Value-like data declaration.
      DataNominal
    | -- | Named value type declaration.
      LiteralTypeNominal
    | -- | Value-like classic enumeration.
      ClassicEnumNominal
    | -- | Reference type declaration.
      ClassNominal
    | -- | Value-like class declaration.
      DataClassNominal
    | -- | Reference-like enumeration declaration.
      EnumClassNominal
    | -- | Root reference object declaration.
      ObjectNominal
    | -- | Reference contract declaration.
      InterfaceNominal
    deriving (Bounded, Enum, Eq, Ord, Read, Show)

-- | Explicit case-sensitive mapping from qualified names to declaration kinds.
newtype NominalCatalog = NominalCatalog (Map QualifiedName NominalKind)
    deriving (Eq, Read, Show)

-- | Rejected nominal type registration.
data CatalogError
    = -- | Qualified name has no components.
      EmptyNominalName
    | -- | One name component is empty.
      EmptyNominalNameComponent QualifiedName
    | -- | Name already exists in the catalog.
      DuplicateNominalName QualifiedName
    deriving (Eq, Ord, Read, Show)

-- | Create an empty catalog with no inferred or implicit declarations.
emptyNominalCatalog :: NominalCatalog
emptyNominalCatalog = NominalCatalog Map.empty

-- | Insert exactly one case-sensitive, fully qualified declaration name.
registerNominal :: QualifiedName -> NominalKind -> NominalCatalog -> Either CatalogError NominalCatalog
registerNominal name kind (NominalCatalog declarations)
    | null parts = Left EmptyNominalName
    | any (null . identifierText) parts = Left (EmptyNominalNameComponent name)
    | Map.member name declarations = Left (DuplicateNominalName name)
    | otherwise = Right (NominalCatalog (Map.insert name kind declarations))
    where
        parts = qualifiedNameParts name

-- | Find an exact, case-sensitive qualified declaration name.
lookupNominal :: QualifiedName -> NominalCatalog -> Maybe NominalKind
lookupNominal name (NominalCatalog declarations) = Map.lookup name declarations

-- | Return the number of registered nominal declarations.
nominalCatalogSize :: NominalCatalog -> Int
nominalCatalogSize (NominalCatalog declarations) = Map.size declarations

-- | Map a declaration family to its baseline ownership/storage category.
classifyNominal :: NominalKind -> StorageClass
classifyNominal kind = case kind of
    DataNominal -> CopyOnWriteValueStorage
    LiteralTypeNominal -> CopyOnWriteValueStorage
    ClassicEnumNominal -> CopyOnWriteValueStorage
    ClassNominal -> AarcReferenceStorage
    DataClassNominal -> AarcReferenceStorage
    EnumClassNominal -> AarcReferenceStorage
    ObjectNominal -> AarcReferenceStorage
    InterfaceNominal -> AarcReferenceStorage

-- | Classify the complete constructed type, including nested type arguments.
classifyType :: NominalCatalog -> Type -> StorageClass
classifyType catalog valueType = case intrinsicStorage valueType of
    Just storage -> storage
    Nothing -> case valueType of
        NamedType name arguments -> case lookupNominal name catalog of
            Nothing -> UnresolvedStorage
            Just kind -> refineConstructedValue catalog (classifyNominal kind) arguments
        TypeVariable _ -> UnresolvedStorage
        ErrorType -> UnresolvedStorage
        FunctionType _ _ -> AarcReferenceStorage

-- | Test whether a resolved constructed type requires AARC management.
usesAarc :: NominalCatalog -> Type -> Bool
usesAarc catalog valueType = classifyType catalog valueType == AarcReferenceStorage

-- | Test whether a resolved constructed type uses copy-on-write storage.
usesCopyOnWrite :: NominalCatalog -> Type -> Bool
usesCopyOnWrite catalog valueType = classifyType catalog valueType == CopyOnWriteValueStorage

intrinsicStorage :: Type -> Maybe StorageClass
intrinsicStorage valueType
    | typeToScalarType valueType /= Nothing = Just TrivialValueStorage
    | valueType == voidType = Just TrivialValueStorage
    | valueType == unitType = Just TrivialValueStorage
    | valueType == stringType = Just AarcReferenceStorage
intrinsicStorage (FunctionType _ _) = Just AarcReferenceStorage
intrinsicStorage _ = Nothing

refineConstructedValue :: NominalCatalog -> StorageClass -> [TemplateArgument] -> StorageClass
refineConstructedValue _ AarcReferenceStorage _ = AarcReferenceStorage
refineConstructedValue _ UnresolvedStorage _ = UnresolvedStorage
refineConstructedValue _ TrivialValueStorage _ = TrivialValueStorage
refineConstructedValue catalog CopyOnWriteValueStorage arguments =
    finish (foldl' inspectArgument (False, False) arguments)
    where
        -- Value arguments affect specialization identity, but never ownership.
        inspectArgument state (ValueTemplateArgument _) = state
        inspectArgument (hasReference, hasUnresolved) (TypeTemplateArgument argumentType) =
            case classifyType catalog argumentType of
                AarcReferenceStorage -> (True, hasUnresolved)
                UnresolvedStorage -> (hasReference, True)
                _ -> (hasReference, hasUnresolved)

        -- A known reference dominates unknown siblings. This keeps A<T, String>
        -- and A<String, T> equivalent even before T has been substituted.
        finish (True, _) = AarcReferenceStorage
        finish (False, True) = UnresolvedStorage
        finish (False, False) = CopyOnWriteValueStorage