packages feed

hs-bindgen-1.0.0.0: src-internal/HsBindgen/Frontend/Pass/FillUnnamedIds.hs

module HsBindgen.Frontend.Pass.FillUnnamedIds (
    fillUnnamedIds
  ) where

import Control.Monad.Except
import Control.Monad.Reader
import Data.Either (partitionEithers)
import Data.Map qualified as Map
import Data.Tuple

import HsBindgen.Frontend.Analysis.UnnamedIdUsage
import HsBindgen.Frontend.Pass.FillUnnamedIds.ChooseNames
import HsBindgen.Frontend.Pass.FillUnnamedIds.IsPass
import HsBindgen.Frontend.Pass.Parse.Msg
import HsBindgen.Frontend.Pass.Parse.Result
import HsBindgen.Frontend.Pass.SimplifyAST.IsPass (SimplifyAST)
import HsBindgen.Imports
import HsBindgen.IR.C qualified as C
import HsBindgen.IR.Pass
import HsBindgen.Util.Tracer (withCallStack)

{-------------------------------------------------------------------------------
  Top-level
-------------------------------------------------------------------------------}

-- | Assign name to all unnamed declarations
fillUnnamedIds ::
     HasCallStack
  => UnnamedIdUsageAnalysis
  -> [ParseResult l SimplifyAST]
  -> ([ParseResult l FillUnnamedIds], [AnnMsg FillUnnamedIds])
fillUnnamedIds usage parseResults =
    swap . partitionEithers $
      map (updateParseResult chosenNames) parseResults
  where
    chosenNames :: ChosenNames
    chosenNames = chooseNames usage

{-------------------------------------------------------------------------------
  Update 'ParseResults' with chosen names
-------------------------------------------------------------------------------}

updateParseResult ::
     forall l. HasCallStack
  => ChosenNames
  -> ParseResult l SimplifyAST
  -> Either (AnnMsg FillUnnamedIds) (ParseResult l FillUnnamedIds)
updateParseResult chosenNames result =
    case result.classification of
      ParseResultSuccess success -> do
        auxSuccess success <$>
          updateDefSite chosenNames success.decl.info.id
      ParseResultUnavailable ->
        auxUnavailable <$>
          updateDefSite chosenNames result.id
      ParseResultFailure failure ->
        auxFailure failure <$>
          updateDefSite chosenNames result.id
  where
    auxSuccess ::
         ParseSuccess l SimplifyAST
      -> C.DeclId
      -> ParseResult l FillUnnamedIds
    auxSuccess success declId' =
        case runM chosenNames updated of
          Left (UnusableUnnamedDecl unnamedId) -> ParseResult{
              id             = declId'
            , loc            = result.loc
            , classification = ParseResultFailure $ ParseUnusableUnnamedDecl unnamedId
            }
          Right (declInfo', declKind') -> ParseResult{
              id             = declInfo'.id
            , loc            = result.loc
            , classification = ParseResultSuccess ParseSuccess{
                  decl = C.Decl{
                      info = declInfo'
                    , kind = declKind'
                    , ann  = NoAnn
                    }
                , delayedParseMsgs = success.delayedParseMsgs
                }
            }

      where
        updated :: M (C.DeclInfo FillUnnamedIds, C.DeclKind l FillUnnamedIds)
        updated = (,)
            <$> updateDeclInfo declId' success.decl.info
            <*> updateUseSites         success.decl.kind

    auxUnavailable ::
         C.DeclId
      -> ParseResult l FillUnnamedIds
    auxUnavailable declId' = ParseResult{
          id             = declId'
        , loc            = result.loc
        , classification = ParseResultUnavailable
        }

    auxFailure ::
         DelayedParseMsg
      -> C.DeclId
      -> ParseResult l FillUnnamedIds
    auxFailure failure declId' = ParseResult{
          id             = declId'
        , loc            = result.loc
        , classification = ParseResultFailure failure
        }

{-------------------------------------------------------------------------------
  Update definition sites
-------------------------------------------------------------------------------}

updateDefSite ::
     HasCallStack
  => ChosenNames
  -> Id SimplifyAST
  -> Either (AnnMsg FillUnnamedIds) (Id FillUnnamedIds)
updateDefSite chosenNames =
    first (withCallStack . FillUnnamedIdsSkippedDecl) . fromPrelimDeclId chosenNames

updateDeclInfo ::
     C.DeclId
  -> C.DeclInfo SimplifyAST
  -> M (C.DeclInfo FillUnnamedIds)
updateDeclInfo declId' info = do
    enclosing' <- mapM updateEnclosing info.enclosing
    pure C.DeclInfo{
          loc              = info.loc
        , id               = declId'
        , sourceOrderIndex = info.sourceOrderIndex
        , origin           = info.origin
        , availability     = info.availability
        , comment          = ()
        , enclosing        = enclosing'
        }
  where
    updateEnclosing ::
      C.EnclosingRef SimplifyAST -> M (C.EnclosingRef FillUnnamedIds)
    updateEnclosing = \case
      C.EnclosingRef         x -> C.EnclosingRef <$> updateDeclId x
      C.UnusableEnclosingRef x -> pure $ C.UnusableEnclosingRef x

{-------------------------------------------------------------------------------
  Internal auxiliary: monad for updating use sites
-------------------------------------------------------------------------------}

newtype M a = WrapM (
      ReaderT ChosenNames (Except UnusableUnnamedDecl) a
    )
  deriving newtype (
      Functor
    , Applicative
    , Monad
    )

-- | We encountered an unusable unnamed declaration
--
-- Not all use sites of unnamed decls are given a name; for example, if we have a
-- function signature with an untagged struct, we will not even traverse that
-- function signature in "HsBindgen.Frontend.Analysis.UnnamedIdUsage". This means
-- that if we then try to /update/ those use sites, that will fail.
--
-- This is an internal type; the external equivalent is 'ParseUnusableUnnamedDecl'.
data UnusableUnnamedDecl = UnusableUnnamedDecl C.UnnamedId
  deriving stock (Show)

runM :: ChosenNames -> M a -> Either UnusableUnnamedDecl a
runM chosenNames (WrapM ma) = runExcept $ runReaderT ma chosenNames

{-------------------------------------------------------------------------------
  Update use sites
-------------------------------------------------------------------------------}

class UpdateUseSites a where
  updateUseSites :: a SimplifyAST -> M (a FillUnnamedIds)

instance UpdateUseSites (C.DeclKind l) where
  updateUseSites = \case
      C.DeclStruct               x -> C.DeclStruct               <$> updateUseSites x
      C.DeclUnion                x -> C.DeclUnion                <$> updateUseSites x
      C.DeclTypedef              x -> C.DeclTypedef              <$> updateUseSites x
      C.DeclEnum                 x -> C.DeclEnum                 <$> updateUseSites x
      C.DeclUntaggedEnumConstant x -> C.DeclUntaggedEnumConstant <$> updateUseSites x
      C.DeclFunction             x -> C.DeclFunction             <$> updateUseSites x
      C.DeclGlobal               x -> C.DeclGlobal               <$> updateUseSites x
      C.DeclMacro                x -> return $ C.DeclMacro x
      C.DeclOpaque mSize           -> return $ C.DeclOpaque mSize

instance UpdateUseSites C.Struct where
  updateUseSites struct =
      reconstruct
        <$> mapM updateUseSites struct.fields
        <*> C.traverseFlamField updateUseSites struct.flam
    where
      reconstruct ::
           [C.Field FillUnnamedIds]
        -> C.Flam FillUnnamedIds
        -> C.Struct FillUnnamedIds
      reconstruct fields' flam' = C.Struct {
            fields    = fields'
          , flam      = flam'
          , sizeof    = struct.sizeof
          , alignment = struct.alignment
          , ann       = NoAnn
          }

instance UpdateUseSites C.Union where
  updateUseSites union =
      reconstruct <$> mapM updateUseSites union.fields
    where
      reconstruct :: [C.Field FillUnnamedIds] -> C.Union FillUnnamedIds
      reconstruct fields' = C.Union {
            fields    = fields'
          , sizeof    = union.sizeof
          , alignment = union.alignment
          , ann       = NoAnn
          }

instance UpdateUseSites C.Field where
  updateUseSites = C.mapMField updateUseSites updateUseSites

instance UpdateUseSites C.FieldInfo where
  updateUseSites info = pure C.FieldInfo{
        comment = ()
      , name    = info.name
      , loc     = info.loc
      }

instance UpdateUseSites C.RegularField where
  updateUseSites field =
      reconstruct
        <$> updateUseSites field.info
        <*> updateUseSites field.typ
    where
      reconstruct ::
           C.FieldInfo FillUnnamedIds
        -> C.Type FillUnnamedIds
        -> C.RegularField FillUnnamedIds
      reconstruct info' typ' = C.RegularField {
            info   = info'
          , typ    = typ'
          , offset = field.offset
          , width  = field.width
          , ann    = field.ann
          }

instance UpdateUseSites C.ImplicitField where
  updateUseSites field =
      reconstruct
        <$> updateUseSites field.info
        <*> updateUseSites field.typRef
        <*> mapM updateUseSites field.indirect
    where
      reconstruct ::
           C.FieldInfo FillUnnamedIds
        -> C.AnonRef FillUnnamedIds
        -> [C.IndirectField FillUnnamedIds]
        -> C.ImplicitField FillUnnamedIds
      reconstruct info' typRef' indirect' = C.ImplicitField {
            info     = info'
          , typRef   = typRef'
          , offset   = field.offset
          , indirect = indirect'
          , ann      = NoAnn
          }

instance UpdateUseSites C.AnonRef where
  updateUseSites = \case
      C.AnonRef ref -> C.AnonRef <$> updateDeclId ref

instance UpdateUseSites C.IndirectField where
  updateUseSites field =
      reconstruct
        <$> updateUseSites field.info
        <*> updateUseSites field.typ
        <*> mapM updateUseSites field.path
    where
      reconstruct ::
           C.FieldInfo FillUnnamedIds
        -> C.Type FillUnnamedIds
        -> [C.AnonRef FillUnnamedIds]
        -> C.IndirectField FillUnnamedIds
      reconstruct info' typ' path' = C.IndirectField {
            info = info'
          , typ = typ'
          , offset = field.offset
          , width = Nothing
          , path = path'
          , ann = field.ann
          }

instance UpdateUseSites C.Typedef where
  updateUseSites typedef =
      reconstruct <$> updateUseSites typedef.typ
    where
      reconstruct :: C.Type FillUnnamedIds -> C.Typedef FillUnnamedIds
      reconstruct typedefType' = C.Typedef {
          typ = typedefType'
        , ann = typedef.ann
        }

instance UpdateUseSites C.Global where
  updateUseSites global =
      reconstruct <$> updateUseSites global.typ
    where
      reconstruct :: C.Type FillUnnamedIds -> C.Global FillUnnamedIds
      reconstruct globalType' = C.Global {
          typ = globalType'
        , ann = global.ann
        }

instance UpdateUseSites C.Enum where
  updateUseSites enum =
      reconstruct
        <$> updateUseSites enum.typ
        <*> mapM updateUseSites enum.constants
    where
      reconstruct ::
           C.Type FillUnnamedIds
        -> [C.EnumConstant FillUnnamedIds]
        -> C.Enum FillUnnamedIds
      reconstruct enumType' enumConstants' = C.Enum {
            typ       = enumType'
          , constants = enumConstants'
          , sizeof    = enum.sizeof
          , alignment = enum.alignment
          , ann       = enum.ann
          }

instance UpdateUseSites C.Function where
  updateUseSites function =
      reconstruct
        <$> mapM updateUseSites function.args
        <*> updateUseSites function.res
    where
      reconstruct ::
           [C.FunctionArg FillUnnamedIds]
        -> C.Type FillUnnamedIds
        -> C.Function FillUnnamedIds
      reconstruct functionArgs' functionRes' = C.Function {
            args  = functionArgs'
          , res   = functionRes'
          , attrs = function.attrs
          , ann   = function.ann
          }

instance UpdateUseSites C.FunctionArg where
  updateUseSites functionArg =
      reconstruct
        <$> updateUseSites functionArg.typ
    where
      reconstruct ::
           C.Type FillUnnamedIds
        -> C.FunctionArg FillUnnamedIds
      reconstruct typ' = C.FunctionArg {
            name = functionArg.name
          , typ  = typ'
          , ann  = functionArg.ann
          }

instance UpdateUseSites C.Type where
  updateUseSites = go
    where
      go :: C.Type SimplifyAST -> M (C.Type FillUnnamedIds)
      go = \case
          -- Actual modifications
          C.TypeRef     ref     -> C.TypeRef     <$> updateDeclId ref
          C.TypeEnum    ref     ->
            fmap C.TypeEnum $ C.Ref
                <$> updateDeclId ref.name
                <*> updateUseSites ref.underlying
          C.TypeTypedef ref ->
            fmap C.TypeTypedef $ C.Ref
                <$> updateDeclId ref.name
                <*> updateUseSites ref.underlying

          -- Recursive cases
          C.TypePointers n      ty -> C.TypePointers n <$> go ty
          C.TypeConstArray n    ty -> C.TypeConstArray n <$> go ty
          C.TypeIncompleteArray ty -> C.TypeIncompleteArray <$> go ty
          C.TypeBlock           ty -> C.TypeBlock <$> go ty
          C.TypeQual qual       ty -> C.TypeQual qual <$> go ty
          C.TypeFun args res       -> C.TypeFun <$> mapM updateUseSites args <*> go res

          -- SimpleCases
          C.TypeVoid           -> return $ C.TypeVoid
          C.TypePrim    pt     -> return $ C.TypePrim    pt
          C.TypeComplex pt     -> return $ C.TypeComplex pt

instance UpdateUseSites C.TypeFunArg where
  updateUseSites arg = do
      typ' <- updateUseSites arg.typ
      pure C.TypeFunArgF {
          typ = typ'
        , ann = arg.ann
        }

updateDeclId :: C.PrelimDeclId -> M C.DeclId
updateDeclId prelimDeclId = WrapM $ do
    chosenNames <- ask
    case fromPrelimDeclId chosenNames prelimDeclId of
      Left  unnamedId -> throwError $ UnusableUnnamedDecl unnamedId
      Right declId -> return declId

{-------------------------------------------------------------------------------
  Comments
-------------------------------------------------------------------------------}

instance UpdateUseSites C.EnumConstant where
  updateUseSites constant =
      reconstruct <$> updateUseSites constant.info
    where
      reconstruct :: C.FieldInfo FillUnnamedIds -> C.EnumConstant FillUnnamedIds
      reconstruct enumConstantInfo' = C.EnumConstant{
            info  = enumConstantInfo'
          , value = constant.value
          }

instance UpdateUseSites C.UntaggedEnumConstant where
  updateUseSites enumConst =
      reconstruct <$> updateUseSites enumConst.constant
    where
      reconstruct :: C.EnumConstant FillUnnamedIds -> C.UntaggedEnumConstant FillUnnamedIds
      reconstruct constant' = C.UntaggedEnumConstant{
            typ      = enumConst.typ  -- PrimType has no use sites to update
          , constant = constant'
          }

{-------------------------------------------------------------------------------
  Internal auxiliary
-------------------------------------------------------------------------------}

-- | Construct 'C.DeclId' from 'C.PrelimDeclId'
--
-- Returns 'Left' an 'C.UnnamedId' if the 'C.PrelimDeclId' is unnamed and we
-- have assigned no name.
fromPrelimDeclId :: ChosenNames -> C.PrelimDeclId -> Either C.UnnamedId C.DeclId
fromPrelimDeclId chosenNames = \case
    C.PrelimDeclIdNamed name ->
      Right C.DeclId{name = name, isUnnamed = False}
    C.PrelimDeclIdUnnamed unnamedId ->
      maybe (Left unnamedId) Right $ Map.lookup unnamedId chosenNames