packages feed

hs-bindgen-1.0.0.0: src/HsBindgen/Internal/Macro/CExpr/Resolution.hs

module HsBindgen.Internal.Macro.CExpr.Resolution (
    resolveMacro
  ) where

import Data.Set qualified as Set

import C.Expr.Syntax qualified as CExpr

import HsBindgen.Frontend.Analysis
import HsBindgen.Imports
import HsBindgen.Internal.Macro.CExpr.Type
import HsBindgen.IR.C qualified as C
import HsBindgen.Macro.Error
import HsBindgen.Macro.Interface qualified as Macro
import HsBindgen.Macro.Type qualified as Macro

resolveMacro ::
     Set C.DeclId
  -> Macro.Unresolved CExpr
  -> Either MacroResolutionError (Macro.Resolved CExpr)
resolveMacro declIds (Macro.Unresolved macro) = do
    resolvedMacro <- CExpr.annotateMacro resolve macro
    pure Macro.Resolved{
        macro = resolvedMacro
      , deps  = getDependencies resolvedMacro
      }
  where
    resolve :: CExpr.Name -> () -> Either MacroResolutionError C.DeclId
    resolve name _ = case name of
      CExpr.NameOrdinary nm ->
        resolveBare nm.getIdentifier
      CExpr.NameTagged nm tag ->
        resolveTagged nm.getIdentifier (convertTagKind tag)

    -- | Resolves a bare name found in a parsed macro body
    --
    -- Result:
    --
    -- - @'Just' 'CNameKind'@: This bare name refers to another declaration that is in
    --   the 'DeclIndex'. For example, a macro, or a @typedef@.
    --
    -- - @'Nothing'@: This bare name is not in the set of known declarations. It
    --   may simply be non-existent, or refer to a built-in type or a system
    --   @typedef@ not present in the 'DeclIndex'. In this case, we /do not
    --   generate/ a dependency.
    resolveBare :: Text -> Either MacroResolutionError C.DeclId
    resolveBare nm = case (isMacro, isTypedef) of
        -- TODO <https://github.com/well-typed/hs-bindgen/issues/2097>
        --
        -- This is separate from user-selection or resolution of conflicts in
        -- the Select pass [issue-1553], but instead we'd need to know which
        -- definition C chooses to resolve to.
        --
        -- Crucially, this depends on the order of declarations (i.e., sequence
        -- order); and we currently do not have this order available. Once our
        -- lower limit on LLVM is 20.1, we can ensure sequence order and resolve
        -- to the name as C does.
        --
        -- [issue-1553] https://github.com/well-typed/hs-bindgen/issues/1553
        (True,  True)  -> Left $
          MacroResolutionError $
            "conflict detected: " <> show nm <> " is both a type and a macro"
        (True,  False) -> Right macroId
        (False, True)  -> Right typedefId
        (False, False) -> Left $
          MacroResolutionError $
            "bare identifier " <> show nm <> " not found"
      where
        macroId, typedefId :: C.DeclId
        macroId   = C.DeclId (C.DeclName nm C.NameKindMacro)    False
        typedefId = C.DeclId (C.DeclName nm C.NameKindOrdinary) False
        isMacro   = macroId   `Set.member` declIds
        isTypedef = typedefId `Set.member` declIds

    resolveTagged :: Text -> C.TagKind -> Either MacroResolutionError C.DeclId
    resolveTagged nm tag
      | taggedId `Set.member` declIds = Right taggedId
      | otherwise =
        Left $
          MacroResolutionError $
            "tagged identifier "
            <> show (C.tagKindPrefix tag)
            <> " "
            <> show nm
            <> " not found"
      where
        taggedId :: C.DeclId
        taggedId = C.DeclId (C.DeclName nm $ C.NameKindTagged tag) False

getDependencies :: Macro.Parsed CExpr C.DeclId -> [(C.DeclId, Dependency)]
getDependencies resolvedMacro =
    case resolvedMacro of
      CExpr.Macro _ _ _ expr -> goExpr NeedsShape expr
  where
    goExpr ::
         Dependency
      -> CExpr.Expr ctx (CExpr.Ps C.DeclId)
      -> [(C.DeclId, Dependency)]
    goExpr depTy = \case
      CExpr.Term term              -> goTerm depTy term
      -- Pointer: switch the dependency to 'NeedsNameOnly'.
      CExpr.TyApp CExpr.Pointer xs -> concatMap (goExpr NeedsNameOnly) xs
      CExpr.TyApp CExpr.Const   xs -> concatMap (goExpr depTy)   xs
      CExpr.VaApp _ _ xs           -> concatMap (goExpr depTy)   xs

    goTerm ::
         Dependency
      -> CExpr.Term ctx (CExpr.Ps C.DeclId)
      -> [(C.DeclId, Dependency)]
    goTerm depTy = \case
      CExpr.Literal{}    -> []
      CExpr.LocalParam{} -> []
      CExpr.Var (CExpr.XVarPs declId) _nm args ->
        (declId, depTy) : concatMap (goExpr depTy) args

{-------------------------------------------------------------------------------
  Internal helpers
-------------------------------------------------------------------------------}

convertTagKind :: CExpr.TagKind -> C.TagKind
convertTagKind = \case
  CExpr.TagStruct -> C.TagKindStruct
  CExpr.TagUnion  -> C.TagKindUnion
  CExpr.TagEnum   -> C.TagKindEnum