packages feed

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

module HsBindgen.Frontend.Pass.ResolveBindingSpecs (
    resolveBindingSpecs
  ) where

import Control.Monad ((<=<))
import Control.Monad.Reader (MonadReader, ReaderT, runReaderT)
import Control.Monad.Reader qualified as Reader
import Control.Monad.State (MonadState, State, runState)
import Control.Monad.State qualified as State
import Control.Monad.Trans.Class (lift)
import Control.Monad.Trans.Maybe (MaybeT (..))
import Data.Map.Strict qualified as Map
import Data.Set qualified as Set

import Clang.HighLevel.Types

import HsBindgen.BindingSpec (MergedBindingSpecs, PrescriptiveBindingSpec)
import HsBindgen.BindingSpec qualified as BindingSpec
import HsBindgen.Errors
import HsBindgen.Frontend.Analysis.DeclIndex (DeclIndex)
import HsBindgen.Frontend.Analysis.DeclIndex qualified as DeclIndex
import HsBindgen.Frontend.Analysis.DeclUseGraph (DeclUseGraph)
import HsBindgen.Frontend.Analysis.DeclUseGraph qualified as DeclUseGraph
import HsBindgen.Frontend.Analysis.IncludeGraph (IncludeGraph)
import HsBindgen.Frontend.Analysis.IncludeGraph qualified as IncludeGraph
import HsBindgen.Frontend.Analysis.UseDeclGraph qualified as UseDeclGraph
import HsBindgen.Frontend.DeclMeta
import HsBindgen.Frontend.Pass.ReparseMacroExpansions.IsPass (ReparseMacroExpansions)
import HsBindgen.Frontend.Pass.ResolveBindingSpecs.IsPass
import HsBindgen.Frontend.Pass.TypecheckMacros.IsPass
import HsBindgen.Frontend.TranslationUnit qualified as C
import HsBindgen.Imports
import HsBindgen.IR.C qualified as C
import HsBindgen.IR.Pass
import HsBindgen.Language.Haskell qualified as Hs
import HsBindgen.Macro.Flip
import HsBindgen.Macro.Type qualified as Macro
import HsBindgen.Util.Monad (mapMaybeM)
import HsBindgen.Util.Tracer (withCallStack)

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

type PreviousPass = ReparseMacroExpansions

resolveBindingSpecs ::
     forall l. HasCallStack
  => Hs.ModuleName
  -> MergedBindingSpecs
  -> PrescriptiveBindingSpec
  -> C.TranslationUnit l PreviousPass
  -> (C.TranslationUnit l ResolveBindingSpecs, [AnnMsg ResolveBindingSpecs])
resolveBindingSpecs hsModuleName extSpecs pSpec unit =
    let pSpecModule = BindingSpec.moduleName pSpec
        (pSpecErrs, pSpec')
          | pSpecModule == hsModuleName = ([], pSpec)
          | otherwise =
              ( [withCallStack (ResolveBindingSpecsModuleMismatch hsModuleName pSpecModule)]
              , BindingSpec.empty hsModuleName
              )
        (decls, state) =
          runM
            extSpecs
            pSpec'
            unit.includeGraph
            unit.meta.declIndex
            (resolveDecls unit.decls)
        declUseGraph =
            DeclUseGraph.deleteRevDeps (Map.keysSet state.extTypes)
          . DeclUseGraph.deleteDeps state.opqTypes
          $ unit.meta.declUseGraph
        notUsedErrs = map (withCallStack . ResolveBindingSpecsTypeNotUsed) $ Map.keys state.noPTypes
    in  ( reconstruct decls declUseGraph state
        , pSpecErrs ++ reverse state.traces ++ notUsedErrs
        )
  where
    reconstruct ::
         [C.Decl l ResolveBindingSpecs]
      -> DeclUseGraph
      -> MState
      -> C.TranslationUnit l ResolveBindingSpecs
    reconstruct decls' declUseGraph state =
      let externals :: [(C.DeclId, C.DeclLocs)]
          externals = map (second (.locs)) $ Map.toList state.extTypes

          index' :: DeclIndex l
          index' =
                DeclIndex.registerExternalDeclarations externals
              . DeclIndex.registerOmittedDeclarations state.omitTypes
              $ unit.meta.declIndex

          unitMeta' :: DeclMeta l
          unitMeta' = DeclMeta {
                declIndex    = index'
              , useDeclGraph = UseDeclGraph.fromDeclUseGraph declUseGraph
              , declUseGraph = declUseGraph
              }

      in C.TranslationUnit{
             decls        = decls'
           , includeGraph = unit.includeGraph
           , meta         = unitMeta'
           }

{-------------------------------------------------------------------------------
  Internal: monad
-------------------------------------------------------------------------------}

newtype M l a = WrapM (ReaderT (MEnv l) (State MState) a)
  deriving newtype (
      Applicative
    , Functor
    , Monad
    , MonadReader (MEnv l)
    , MonadState MState
    )

runM ::
     MergedBindingSpecs
  -> PrescriptiveBindingSpec
  -> IncludeGraph
  -> DeclIndex l
  -> M l a
  -> (a, MState)
runM extSpecs pSpec includeGraph declIndex (WrapM m) =
    let env    = MEnv extSpecs pSpec includeGraph declIndex
        state0 = initMState pSpec
    in  runState (runReaderT m env) state0

{-------------------------------------------------------------------------------
  Internal: monad reader
-------------------------------------------------------------------------------}

data MEnv l = MEnv {
      extSpecs     :: MergedBindingSpecs
    , pSpec        :: PrescriptiveBindingSpec
    , includeGraph :: IncludeGraph
    , declIndex    :: DeclIndex l
    }

deriving stock instance Macro.HasTypes l => Show (MEnv l)

{-------------------------------------------------------------------------------
  Internal: monad state
-------------------------------------------------------------------------------}

data MState = MState {
      traces    :: [AnnMsg ResolveBindingSpecs] -- ^ reverse order
    , extTypes  :: Map C.DeclId (ExtBinding ResolveBindingSpecs)
    , noPTypes  :: Map C.DeclId [Set RealPath]
    , omitTypes :: Map C.DeclId (SingleLoc C.DeclPath)
    , opqTypes  :: Set C.DeclId -- ^ opaqued types
    }
  deriving (Show, Generic)

initMState :: PrescriptiveBindingSpec -> MState
initMState pSpec = MState {
      traces    = []
    , extTypes  = Map.empty
    , noPTypes  = BindingSpec.getCTypes pSpec
    , omitTypes = Map.empty
    , opqTypes  = Set.empty
    }

insertTrace :: AnnMsg ResolveBindingSpecs -> MState -> MState
insertTrace msg = #traces %~ (msg :)

insertExtType :: C.DeclId -> ExtBinding ResolveBindingSpecs -> MState -> MState
insertExtType cDeclId typ = #extTypes %~ Map.insert cDeclId typ

insertOpaquedType :: C.DeclId -> MState -> MState
insertOpaquedType cDeclId = #opqTypes %~ Set.insert cDeclId

deleteNoPType :: C.DeclId -> RealPath -> MState -> MState
deleteNoPType cDeclId path = #noPTypes %~ Map.update (aux []) cDeclId
  where
    aux :: [Set RealPath] -> [Set RealPath] -> Maybe [Set RealPath]
    aux acc = \case
      s : ss
        | Set.member path s ->
            case ss ++ acc of
              []  -> Nothing
              ss' -> Just ss'
        | otherwise -> aux (s : acc) ss
      [] -> Just acc

insertOmittedType :: C.DeclId -> SingleLoc C.DeclPath -> MState -> MState
insertOmittedType cDeclId sloc = #omitTypes %~ Map.insert cDeclId sloc

{-------------------------------------------------------------------------------
  Internal: implementation
-------------------------------------------------------------------------------}

-- Resolve declarations, in two passes
resolveDecls ::
     HasCallStack
  => [C.Decl l PreviousPass]
  -> M l [C.Decl l ResolveBindingSpecs]
resolveDecls = mapM (uncurry resolveDeep) <=< mapMaybeM resolveTop

-- Pass one: top-level
--
-- If a declaration has an external binding, then the declaration is dropped and
-- the external binding is recorded.
--
-- If a declaration is omitted, then the declaration is dropped and the omission
-- is recorded.
--
-- Otherwise, the declaration is kept and is associated with a type
-- specification when applicable.
resolveTop ::
     HasCallStack
  => C.Decl l PreviousPass
  -> M l
       ( Maybe
           ( C.Decl l PreviousPass
           , (Maybe BindingSpec.CTypeSpec, Maybe BindingSpec.HsTypeSpec)
           )
       )
resolveTop decl = case singleLocPath decl.info.loc of
    C.InHeader realPath -> resolveTopInHeader realPath decl
    -- Not in any header, so no binding specification refers to it
    C.InRootHeader      -> return $ Just (decl, (Nothing, Nothing))
    C.OnCommandLine     -> return $ Just (decl, (Nothing, Nothing))

resolveTopInHeader ::
     HasCallStack
  => RealPath
  -> C.Decl l PreviousPass
  -> M l
       ( Maybe
           ( C.Decl l PreviousPass
           , (Maybe BindingSpec.CTypeSpec, Maybe BindingSpec.HsTypeSpec)
           )
       )
resolveTopInHeader realPath decl = Reader.ask >>= \env -> do
    let declPaths  = IncludeGraph.reaches env.includeGraph realPath
        mMsg       = Just $ withCallStack $ ResolveBindingSpecsOmittedType decl.info.id
    isExt <- isJust <$>
      resolveExtBinding
        decl.info.id
        (C.DeclLoc decl.info.loc)
        declPaths mMsg
    if isExt
      then do
        State.modify' $ insertTrace (withCallStack $ ResolveBindingSpecsExtDecl decl.info.id)
        return Nothing
      else case BindingSpec.lookupCTypeSpec decl.info.id declPaths env.pSpec of
        Just (_hsModuleName, BindingSpec.Require cTypeSpec) -> do
          State.modify' $
              insertTrace (withCallStack $ ResolveBindingSpecsPreRequire decl.info.id)
            . deleteNoPType decl.info.id realPath
          let mHsTypeSpec = do
                hsIdentifier <- cTypeSpec.hsName
                BindingSpec.lookupHsTypeSpec hsIdentifier env.pSpec
          applyPrescriptive decl cTypeSpec mHsTypeSpec
        Just (_hsModuleName, BindingSpec.Omit) -> do
          State.modify' $
              insertTrace (withCallStack $ ResolveBindingSpecsPreOmit decl.info.id)
            . deleteNoPType decl.info.id realPath
            . insertOmittedType decl.info.id decl.info.loc
          return Nothing
        Nothing -> return $ Just (decl, (Nothing, Nothing))

-- | Apply prescriptive binding specifications
--
-- A prescriptive binding specification can change a declaration or even drop
-- it.
--
-- Type specifications that do not match declarations may themselves be mutated.
applyPrescriptive :: forall l.
     HasCallStack
  => C.Decl l PreviousPass
  -> BindingSpec.CTypeSpec
  -> Maybe BindingSpec.HsTypeSpec
  -> M l
       ( Maybe
           ( C.Decl l PreviousPass
           , (Maybe BindingSpec.CTypeSpec, Maybe BindingSpec.HsTypeSpec)
           )
       )
applyPrescriptive decl cTypeSpec mHsTypeSpec = runMaybeT $ do
    mCTypeSpec'           <- applyCTypeSpec
    (decl', mHsTypeSpec') <- applyHsTypeSpec
    return (decl', (mCTypeSpec', mHsTypeSpec'))
  where
    applyCTypeSpec :: MaybeT (M l) (Maybe BindingSpec.CTypeSpec)
    applyCTypeSpec = do
      case (decl.kind, cTypeSpec.enum) of
        (C.DeclEnum{}, _)       -> return ()
        (_,            Nothing) -> return ()
        (_,            Just{})  -> lift . State.modify' $
          insertTrace
            (withCallStack $ ResolveBindingSpecsEnumTypeMismatch decl.info.id)
      return (Just cTypeSpec)

    applyHsTypeSpec ::
      MaybeT (M l) (C.Decl l PreviousPass, Maybe BindingSpec.HsTypeSpec)
    applyHsTypeSpec = case mHsTypeSpec of
      Nothing         -> return (decl, Nothing)
      Just hsTypeSpec -> do
        -- TODO <https://github.com/well-typed/hs-bindgen/issues/1447>
        -- We should validate instances only set for supported kinds
        -- (instances themselves are to be resolved in a separate pass)
        (decl', hsRep') <- case hsTypeSpec.hsRep of
          Nothing    -> return (decl, Nothing)
          Just hsRep -> case hsRep of
            BindingSpec.HsTypeRepRecord    recordRep  -> auxRecord recordRep
            BindingSpec.HsTypeRepNewtype   newtypeRep -> auxNewtype newtypeRep
            BindingSpec.HsTypeRepEmptyData            -> auxEmptyData
            BindingSpec.HsTypeRepTypeAlias            -> auxTypeAlias
        return (decl', Just hsTypeSpec{ BindingSpec.hsRep = hsRep' })

    auxRecord ::
         BindingSpec.HsRecordRep
      -> MaybeT (M l) (C.Decl l PreviousPass, Maybe BindingSpec.HsTypeRep)
    auxRecord recordRep =
      -- TODO <https://github.com/well-typed/hs-bindgen/issues/1447>
      -- We should validate the record type and number of fields.
      return (decl, Just (BindingSpec.HsTypeRepRecord recordRep))

    auxNewtype ::
         BindingSpec.HsNewtypeRep
      -> MaybeT (M l) (C.Decl l PreviousPass, Maybe BindingSpec.HsTypeRep)
    auxNewtype newtypeRep =
      -- TODO <https://github.com/well-typed/hs-bindgen/issues/1447>
      -- We should validate enum, typedef, or macro type
      return (decl, Just (BindingSpec.HsTypeRepNewtype newtypeRep))

    auxEmptyData ::
      MaybeT (M l) (C.Decl l PreviousPass, Maybe BindingSpec.HsTypeRep)
    auxEmptyData = do
      declIndex <- lift $ Reader.asks (.declIndex)
      -- A complete C type keeps its size and alignment, so that a 'StaticSize'
      -- instance can be generated for the empty data type.  Structs, unions, and
      -- enums carry the layout directly; a typedef is followed to its underlying
      -- type.  Types that are genuinely opaque in C, primitives and pointers
      -- (whose size is not available at this pass), and macro types keep
      -- 'Nothing'.
      let (isValid, mSize) = case decl.kind of
            C.DeclStruct s    -> (True, Just (C.OpaqueSize s.sizeof s.alignment))
            C.DeclUnion  u    -> (True, Just (C.OpaqueSize u.sizeof u.alignment))
            C.DeclEnum   e    -> (True, Just (C.OpaqueSize e.sizeof e.alignment))
            C.DeclTypedef td  -> (True, underlyingOpaqueSize declIndex td.typ)
            C.DeclOpaque m    -> (True, m)
            C.DeclMacro macro -> case macro of
              MacroType{}  -> (True,  Nothing)
              MacroValue{} -> (False, Nothing)
            _otherwise        -> (False, Nothing)
      if isValid
        then do
          lift . State.modify' $
              insertTrace (withCallStack $ ResolveBindingSpecsPreEmptyData decl.info.id)
            . insertOpaquedType decl.info.id
          -- Cannot use record update because 'C.kind' is ambiguous
          let decl' = C.Decl{
                  C.info = decl.info
                , C.kind = C.DeclOpaque mSize
                , C.ann  = decl.ann
                }
          return (decl', Just BindingSpec.HsTypeRepEmptyData)
        else do
          lift . State.modify' $
            insertTrace (withCallStack $ ResolveBindingSpecsPreEmptyDataInvalid decl.info.id)
          return (decl, Nothing)

    auxTypeAlias ::
      MaybeT (M l) (C.Decl l PreviousPass, Maybe BindingSpec.HsTypeRep)
    auxTypeAlias =
      -- TODO <https://github.com/well-typed/hs-bindgen/issues/1447>
      -- We should validate types.
      -- Return different decl?
      return (decl, Just BindingSpec.HsTypeRepTypeAlias)

-- | Size and alignment of an @emptydata@ type's underlying C type, when it can
-- be determined at this pass.
--
-- 'C.getCanonicalType' resolves typedef and macro-type references and strips
-- qualifiers, so a typedef to a struct or union surfaces as a 'C.TypeRef' to its
-- declaration, whose layout we recover via the declaration index.  Primitives,
-- pointers, and enums canonicalise to a type with no such reference and yield
-- 'Nothing', since their sizes are not available in this frontend pass.
underlyingOpaqueSize ::
     DeclIndex l
  -> C.Type PreviousPass
  -> Maybe C.OpaqueSize
underlyingOpaqueSize declIndex ty =
    case C.getCanonicalType ty of
      C.TypeRef declId -> do
        decl <- DeclIndex.lookup declId declIndex
        case decl.kind of
          C.DeclStruct s -> pure (C.OpaqueSize s.sizeof s.alignment)
          C.DeclUnion  u -> pure (C.OpaqueSize u.sizeof u.alignment)
          _otherwise     -> Nothing
      _otherwise       -> Nothing

-- Pass two: deep
--
-- Types within the declaration are resolved, and it is reconstructed for the
-- current pass.
resolveDeep ::
     C.Decl l PreviousPass
  -> (Maybe BindingSpec.CTypeSpec, Maybe BindingSpec.HsTypeSpec)
  -> M l (C.Decl l ResolveBindingSpecs)
resolveDeep decl (cSpec, hsSpec) = do
    declKind' <- resolve decl.info.id decl.kind
    return C.Decl {
        info = coercePass decl.info
      , kind = declKind'
      , ann  = PrescriptiveDeclSpec{cSpec = cSpec, hsSpec = hsSpec}
      }

{-------------------------------------------------------------------------------
  Instances
-------------------------------------------------------------------------------}

-- | Resolve references to external declarations
--
-- This is part of the second pass ('resolveDeep').
--
-- We do not need to handle references to omitted declarations here since
-- declarations depending on unavailable declarations will be removed in the
-- @Select@ pass.
class Resolve a l where
  resolve ::
       HasCallStack
    => C.DeclId -- context declaration
    -> a PreviousPass
    -> M l (a ResolveBindingSpecs)

resolveFlip ::
     Resolve (Flip f n) l
  => C.DeclId
  -> f PreviousPass n
  -> M l (f ResolveBindingSpecs n)
resolveFlip declId = flipM (resolve declId)

instance Resolve (C.DeclKind l) l where
  resolve ctx = \case
      C.DeclStruct struct                  -> C.DeclStruct   <$> resolve ctx struct
      C.DeclUnion union                    -> C.DeclUnion    <$> resolve ctx union
      C.DeclTypedef typedef                -> C.DeclTypedef  <$> resolve ctx typedef
      C.DeclEnum enum                      -> C.DeclEnum     <$> resolve ctx enum
      C.DeclUntaggedEnumConstant enumConst -> pure $ C.DeclUntaggedEnumConstant (coercePass enumConst)
      C.DeclOpaque mSize                   -> return (C.DeclOpaque mSize)
      C.DeclMacro macro                    -> C.DeclMacro    <$> resolveFlip ctx macro
      C.DeclFunction fun                   -> C.DeclFunction <$> resolve ctx fun
      C.DeclGlobal ty                      -> C.DeclGlobal   <$> resolve ctx ty

instance Resolve C.Struct l where
  resolve ctx struct =
      reconstruct
        <$> mapM (resolve ctx) struct.fields
        <*> C.traverseFlamField (resolve ctx) struct.flam
    where
      reconstruct ::
           [C.Field ResolveBindingSpecs]
        -> C.Flam ResolveBindingSpecs
        -> C.Struct ResolveBindingSpecs
      reconstruct structFields' structFlam' = C.Struct {
            fields    = structFields'
          , flam      = structFlam'
          , sizeof    = struct.sizeof
          , alignment = struct.alignment
          , ann       = struct.ann
          }

instance Resolve C.Union l where
  resolve ctx union =
      reconstruct <$> mapM (resolve ctx) union.fields
    where
      reconstruct ::
           [C.Field ResolveBindingSpecs]
        -> C.Union ResolveBindingSpecs
      reconstruct unionFields' = C.Union {
          fields    = unionFields'
        , sizeof    = union.sizeof
        , alignment = union.alignment
        , ann       = union.ann
        }

instance Resolve C.Field l where
  resolve ctx = \case
      C.FieldRegular  field -> C.FieldRegular  <$> resolve ctx field
      C.FieldImplicit field -> C.FieldImplicit <$> resolve ctx field

instance Resolve C.RegularField l where
  resolve ctx field =
      reconstruct <$> resolve ctx field.typ
    where
      reconstruct ::
           C.Type ResolveBindingSpecs
        -> C.RegularField ResolveBindingSpecs
      reconstruct typ' = C.RegularField {
          typ    = typ'
        , info   = coercePass field.info
        , offset = field.offset
        , width  = field.width
        , ann    = field.ann
        }

instance Resolve C.ImplicitField l where
  resolve ctx field = do
      typRef' <- resolve ctx field.typRef
      indirect' <- mapM (resolveIndirectField ctx) field.indirect
      pure $ reconstruct typRef' $ catMaybes indirect'
    where
      reconstruct ::
           C.AnonRef ResolveBindingSpecs
        -> [C.IndirectField ResolveBindingSpecs]
        -> C.ImplicitField ResolveBindingSpecs
      reconstruct typRef' indirect' = C.ImplicitField {
          typRef   = typRef'
        , info     = coercePass field.info
        , offset   = field.offset
        , indirect = indirect'
        , ann      = field.ann
        }

instance Resolve C.AnonRef l where
  resolve ctx (C.AnonRef ref) = do
      mResolved <- resolveUseSite ctx ref
      let underlying' = C.TypeRef ref
      case mResolved of
        Just r  -> return $ C.AnonExtBinding $ C.Ref r underlying'
        Nothing -> return $ C.AnonRef ref

resolveIndirectField ::
     HasCallStack
  => C.DeclId
  -> C.IndirectField PreviousPass
  -> M l (Maybe (C.IndirectField ResolveBindingSpecs))
resolveIndirectField ctx field = do
    path' <- mapM (resolve ctx) field.path
    -- We can only generate bindings for indirect fields if we know how to get
    -- there via nested uses of 'HasField.getField'. We can't do this if the
    -- indirect field goes via an anonymous struct/union that is specified by an
    -- external binding spec. In such cases, the binding spec does not include
    -- enough information to generate bindings, so we drop the indirect field.
    --
    -- TODO <https://github.com/well-typed/hs-bindgen/issues/2152>:
    -- theoretically we could generate bindings for indirect fields that cross
    -- binding spec boundaries
    if any crossesBoundary path' then do
      State.modify' $
        insertTrace (withCallStack $ ResolveBindingSpecsIndirectFieldDropped ctx field.info.name)
      pure Nothing
    else
      Just <$> (reconstruct <$> resolve ctx field.typ <*> pure path')
  where
    crossesBoundary = \case
        C.AnonRef{} -> False
        C.AnonExtBinding{} -> True

    reconstruct ::
          C.Type ResolveBindingSpecs
      -> [C.AnonRef ResolveBindingSpecs]
      -> C.IndirectField ResolveBindingSpecs
    reconstruct typ' path' = C.IndirectField {
        typ    = typ'
      , info   = coercePass field.info
      , offset = field.offset
      , width  = field.width
      , path   = path'
      , ann    = field.ann
      }

instance Resolve C.Enum l where
  resolve ctx enum =
      reconstruct <$> resolve ctx enum.typ
    where
      reconstruct :: C.Type ResolveBindingSpecs -> C.Enum ResolveBindingSpecs
      reconstruct enumType' = C.Enum {
          typ       = enumType'
        , constants = map coercePass enum.constants
        , sizeof    = enum.sizeof
        , alignment = enum.alignment
        , ann       = enum.ann
        }

instance Resolve C.Typedef l where
  resolve ctx typedef =
      reconstruct <$> resolve ctx typedef.typ
    where
      reconstruct :: C.Type ResolveBindingSpecs -> C.Typedef ResolveBindingSpecs
      reconstruct typedefType' = C.Typedef {
            typ = typedefType'
          , ann = typedef.ann
          }

instance Resolve C.Function l where
  resolve ctx function =
    reconstruct
      <$> mapM (resolve ctx) function.args
      <*> resolve ctx function.res
    where
      reconstruct ::
           [C.FunctionArg ResolveBindingSpecs]
        -> C.Type ResolveBindingSpecs
        -> C.Function ResolveBindingSpecs
      reconstruct functionArgs' functionRes' = C.Function {
          args  = functionArgs'
        , res   = functionRes'
        , attrs = function.attrs
        , ann   = function.ann
        }

instance Resolve C.Global l where
  resolve ctx global =
      reconstruct <$> resolve ctx global.typ
    where
      reconstruct :: C.Type ResolveBindingSpecs -> C.Global ResolveBindingSpecs
      reconstruct globalType' = C.Global{
            typ = globalType'
          , ann = global.ann
          }

instance Resolve C.FunctionArg l where
  resolve ctx functionArg =
    reconstruct
      <$> resolve ctx functionArg.typ
    where
      reconstruct ::
           C.Type ResolveBindingSpecs
        -> C.FunctionArg ResolveBindingSpecs
      reconstruct typ' = C.FunctionArg{
            name = functionArg.name
          , typ  = typ'
          , ann  = functionArg.ann
          }

instance Resolve (Flip TypecheckedMacro l) l where
  resolve ctx (Flip m) = Flip <$> case m of
    MacroType  typ -> MacroType  <$> resolve ctx typ
    MacroValue val -> MacroValue <$> pure (coercePass val)

instance Resolve (TypecheckedMacroType l) l where
  resolve ctx = \case
      (TypecheckedMacroType body deps ann) -> do
        body' <- traverse resolveVar body
        pure TypecheckedMacroType{
            body = body'
          , deps = deps
          , ann  = ann
          }
    where
      -- TODO <https://github.com/well-typed/hs-bindgen/issues/1969>
      --
      -- Usage of 'MacroTypeBodyVar' hints at possible drawbacks of our design
      -- of 'C.DeclId'. Maybe we can directly refer to external bindings from
      -- 'C.DeclId'?
      resolveVar ::
           MacroTypeBodyVar PreviousPass
        -> M l (MacroTypeBodyVar ResolveBindingSpecs)
      resolveVar (MacroTypeExtBinding   x) = absurd x
      resolveVar (MacroTypeBodyVar declId) = do
          mExt <- resolveUseSite ctx declId
          pure $ case mExt of
            Just ext -> MacroTypeExtBinding ext
            Nothing  -> MacroTypeBodyVar declId

instance Resolve C.Type l where
  resolve ctx = \case
      C.TypeRef uid -> do
        mResolved <- resolveUseSite ctx uid
        let ref' = C.TypeRef uid
        case mResolved of
          Just r  -> return $ C.TypeExtBinding $ C.Ref r ref'
          Nothing -> return ref'
      C.TypeEnum ref -> do
        mResolved <- resolveUseSite ctx ref.name
        underlying' <- resolve ctx ref.underlying
        let ref' = C.TypeEnum (C.Ref ref.name underlying')
        case mResolved of
          Just r  -> return $ C.TypeExtBinding $ C.Ref r ref'
          Nothing -> return ref'
      C.TypeMacro ref -> do
        mResolved <- resolveUseSite ctx ref.name
        underlying' <- resolve ctx ref.underlying
        let ref' = C.TypeMacro (C.MacroRef ref.name underlying')
        case mResolved of
          Just r  -> return $ C.TypeExtBinding $ C.Ref r ref'
          Nothing -> return ref'
      C.TypeTypedef ref -> do
        mResolved <- resolveUseSite ctx ref.name
        underlying' <- resolve ctx ref.underlying
        let ref' = C.TypeTypedef (C.Ref ref.name underlying')
        case mResolved of
          Just r  -> return $ C.TypeExtBinding $ C.Ref r ref'
          Nothing -> return ref'

      -- Recursive cases
      C.TypePointers n t      -> C.TypePointers n <$> resolve ctx t
      C.TypeFun args res      ->
        C.TypeFun <$> mapM (resolve ctx) args <*> resolve ctx res
      C.TypeConstArray n t    -> C.TypeConstArray n <$> resolve ctx t
      C.TypeIncompleteArray t -> C.TypeIncompleteArray <$> resolve ctx t
      C.TypeBlock t           -> C.TypeBlock <$> resolve ctx t
      C.TypeQual qual t       -> C.TypeQual qual <$> resolve ctx t

      -- Simple cases
      C.TypePrim t         -> return (C.TypePrim t)
      C.TypeVoid           -> return (C.TypeVoid)
      C.TypeComplex t      -> return (C.TypeComplex t)

instance Resolve C.TypeFunArg l where
  resolve ctx arg = do
      typ' <- resolve ctx arg.typ
      pure C.TypeFunArgF {
          typ = typ'
        , ann = arg.ann
        }

resolveUseSite ::
     HasCallStack
  => C.DeclId
     -- ^ The declaration in which we resolve the use site; used for trace
     -- messages only
  -> C.DeclId
     -- ^ Use site itself
  -> M l (Maybe BindingSpec.ResolvedExtBinding)
resolveUseSite ctx cDeclId = Reader.ask >>= \env -> State.get >>= \state ->
    case Map.lookup cDeclId state.extTypes of
      -- 1. Search cache: We expect all usable declarations with external
      -- bindings to be in the cache.
      Just ty -> do
        State.modify' $ insertTrace (withCallStack $ ResolveBindingSpecsExtType ctx cDeclId)
        pure (Just ty)
      -- 2. No cache hit: The declaration is either usable and has no external
      -- binding, or it is unusable.
      Nothing ->
        case DeclIndex.lookupEntry cDeclId env.declIndex of
          Nothing ->
            panicPure $
              "resolveUseSite: declaration ID "
              <> show cDeclId
              <> " not in declaration index"
          -- Interesting case, an unusable declaration may have an external
          -- binding specification.
          Just (DeclIndex.UnusableEntry x) -> do
              let locs :: C.DeclLocs
                  locs = DeclIndex.unusableToLoc x
                  declPaths =
                    foldMap
                      (IncludeGraph.reaches env.includeGraph)
                      (mapMaybe (C.declPathRealPath . singleLocPath) $
                        C.declLocsToList locs)
              mTy <- resolveExtBinding cDeclId locs declPaths Nothing
              case mTy of
                Just ty -> do
                  State.modify' $
                      insertTrace (withCallStack $ ResolveBindingSpecsExtType ctx cDeclId)
                    . insertExtType cDeclId ty
                  pure (Just ty)
                Nothing -> pure Nothing
          -- Cannot have an external binding specification.
          Just DeclIndex.UsableEntry{} ->
            pure Nothing

{-------------------------------------------------------------------------------
  Internal: auxiliary functions
-------------------------------------------------------------------------------}

-- | Lookup qualified name in the 'HsBindgen.BindingSpec.Private.V1.ResolvedBindingSpec'
resolveExtBinding ::
     HasCallStack
  => C.DeclId
  -> C.DeclLocs
  -> Set RealPath
     -- | Message to emit for omitted types.
  -> Maybe (AnnMsg ResolveBindingSpecs)
  -> M l (Maybe BindingSpec.ResolvedExtBinding)
resolveExtBinding cDeclId locs declPaths mMsg = do
    env <- Reader.ask
    case BindingSpec.lookupMergedBindingSpecs cDeclId declPaths env.extSpecs of
      Just (hsModuleName, BindingSpec.Require cTypeSpec, mHsTypeSpec) ->
        case (cTypeSpec.hsName, mHsTypeSpec) of
          (Just hsName, Just hsTypeSpec) -> do
            let resolved = BindingSpec.ResolvedExtBinding {
                    cName  = cDeclId
                  , locs   = locs
                  , hsName = Hs.ExtRef hsModuleName hsName
                  , cSpec  = cTypeSpec
                  , hsSpec = hsTypeSpec
                  }
            State.modify' $ insertExtType cDeclId resolved
            return (Just resolved)
          (Nothing, _) -> do
            State.modify' $
              insertTrace (withCallStack $ ResolveBindingSpecsExtHsRefNoIdentifier cDeclId)
            return Nothing
          (_, Nothing) -> do
            State.modify' $
              insertTrace (withCallStack $ ResolveBindingSpecsNoHsTypeSpec cDeclId)
            return Nothing
      Just (_hsModuleName, BindingSpec.Omit, _mHsTypeSpec) -> do
        forM_ mMsg $ \msg -> State.modify' $ insertTrace msg
        return Nothing
      Nothing ->
        return Nothing