packages feed

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

module HsBindgen.Frontend.Pass.MangleNames.CreateNames (
    -- * Pass definition
    CreateNames
    -- * Traversal 1: create names
  , createNames
    -- * Shared types (needed by traversals 2 and 3)
  , MangleNamesFailure(..)
  , toFailure
    -- * Shared utility (needed by traversal 3)
  , withDeclNamespace
  ) where

import Control.Applicative ((<|>))
import Control.Monad.Except (ExceptT, MonadError (..), liftEither, runExcept,
                             runExceptT)
import Control.Monad.Reader (Reader, asks, runReader)
import Control.Monad.State (StateT (runStateT), modify)
import Data.Foldable qualified as Foldable
import Data.Map qualified as Map
import Data.Proxy

import Clang.HighLevel.Types

import HsBindgen.BindingSpec qualified as BindingSpec
import HsBindgen.Config.MangleCandidate (MangleCandidate (..))
import HsBindgen.Config.MangleCandidate qualified as MangleCandidate
import HsBindgen.Config.Prelims (FieldNamingStrategy (..))
import HsBindgen.Frontend.Analysis.Typedefs (TypedefAnalysis)
import HsBindgen.Frontend.Analysis.Typedefs qualified as TypedefAnalysis
import HsBindgen.Frontend.Pass.MangleNames.Error
import HsBindgen.Frontend.Pass.MangleNames.IsPass
import HsBindgen.Frontend.Pass.MangleNames.Names
import HsBindgen.Frontend.Pass.ResolveBindingSpecs.IsPass
import HsBindgen.Frontend.Pass.TypecheckMacros.IsPass
import HsBindgen.Imports
import HsBindgen.IR.C qualified as C
import HsBindgen.IR.Pass
import HsBindgen.IR.Translation
import HsBindgen.Language.Haskell qualified as Hs
import HsBindgen.Macro.Type qualified as Macro
import HsBindgen.Util.Tracer (WithCallStack, withCallStack)

{-------------------------------------------------------------------------------
  Intermediate pass: CreateNames

  This pass is local to name mangling: it is /not/ exposed as a frontend
  artefact and not selectable via the CLI. It is the result of the first of the
  three name-mangling traversals ("create names").

  'CreateNames' creates top-level declaration names (the 'Id' slots), scoped
  names ('ScopedName'), and all local auxiliary names such as auxiliary type or
  data constructor names.

  'CreateNames', however, does not resolve names within declarations; names that
  can be referred to from other declarations are stored in the 'NameMap'
  (top-level names and scoped names). Names that are used purely locally are
  stored in annotations (local auxiliary type and data constructor names).

  The final 'resolveNames' traversal rewrites 'C.DeclId's into 'DeclIdPair's and
  'C.ScopedName's into 'ScopedNamePair's, producing 'MangleNames'.

  Note that @'Ann' ix 'CreateNames'@ and @'Ann' ix 'MangleNames'@ reduce to the
  same bundle types. The annotations can therefore be carried across directly
  during 'resolveNames'.
-------------------------------------------------------------------------------}

-- | Create names pass (local to name mangling)
type CreateNames :: Pass
data CreateNames a

type family AnnCreateNames ix where
  AnnCreateNames "Decl"                 = PrescriptiveDeclSpec
  AnnCreateNames "Enum"                 = NewtypeNames
  AnnCreateNames "Flam"                 = FlamNames
  AnnCreateNames "Struct"               = StructNames
  AnnCreateNames "Typedef"              = TypedefNames
  AnnCreateNames "TypecheckedMacroType" = NewtypeNames
  AnnCreateNames "Union"                = NewtypeNames
  AnnCreateNames _                      = NoAnn

instance IsPass CreateNames

instance PassId CreateNames where
  type Id CreateNames = C.DeclId

instance PassScopedName CreateNames where
  type ScopedName CreateNames = C.ScopedName

instance PassTypes CreateNames

instance PassMacro CreateNames where
  type MacroId         CreateNames = C.DeclId
  type MacroBody       CreateNames = TypecheckedMacro CreateNames
  type MacroUnderlying CreateNames = C.Type CreateNames

  macroIdId _ = id

instance PassExtBinding CreateNames where
  type ExtBinding CreateNames = BindingSpec.ResolvedExtBinding

  extBindingId _ = (.cName)

instance PassCommentDecl CreateNames where
  type CommentDecl CreateNames = Maybe (C.Comment CreateNames)

instance PassAnn CreateNames where
  type Ann ix CreateNames = AnnCreateNames ix

instance PassMsg CreateNames where
  type Msg CreateNames = C.WithLocationInfo MangleNamesMsg

{-------------------------------------------------------------------------------
  CoercePass: ResolveBindingSpecs → CreateNames

  Used by the "create names" traversal to reindex the parts of a declaration
  that do not change representation (types, comments, scoped names, enclosing
  references). The 'Id' (and hence 'MacroId'), 'ScopedName', and 'ExtBinding'
  representations agree between the two passes, so these coercions are pure
  reindexing.
-------------------------------------------------------------------------------}

instance CoercePassId               ResolveBindingSpecs CreateNames
instance CoercePassMacroId          ResolveBindingSpecs CreateNames
instance CoercePassAnn "TypeFunArg" ResolveBindingSpecs CreateNames

instance CoercePassMacroUnderlying ResolveBindingSpecs CreateNames where
  coercePassMacroUnderlying _ = coercePass

instance CoercePassCommentDecl ResolveBindingSpecs CreateNames where
  coercePassCommentDecl _ = fmap coercePass

{-------------------------------------------------------------------------------
  Shared types
-------------------------------------------------------------------------------}

data MangleNamesFailure = MangleNamesFailure {
      id  :: C.DeclId
    , loc :: SingleLoc C.DeclPath
    , err :: MangleNamesError
    }
  deriving (Show, Eq, Ord, Generic)

toFailure :: (Id p ~ C.DeclId) => C.DeclInfo p -> MangleNamesError -> MangleNamesFailure
toFailure i e = MangleNamesFailure i.id i.loc e

{-------------------------------------------------------------------------------
  Shared utility
-------------------------------------------------------------------------------}

withDeclNamespace ::
     (MacroBody p ~ TypecheckedMacro p)
  => C.DeclKind l p
  -> (forall ns. Hs.SingNamespace ns => Proxy ns -> r)
  -> r
withDeclNamespace kind k =
    case kind of
      C.DeclStruct{}               -> k (Proxy @Hs.NsTypeConstr)
      C.DeclUnion{}                -> k (Proxy @Hs.NsTypeConstr)
      C.DeclTypedef{}              -> k (Proxy @Hs.NsTypeConstr)
      C.DeclEnum{}                 -> k (Proxy @Hs.NsTypeConstr)
      C.DeclUntaggedEnumConstant{} -> k (Proxy @Hs.NsConstr)
      C.DeclOpaque{}               -> k (Proxy @Hs.NsTypeConstr)
      C.DeclFunction{}             -> k (Proxy @Hs.NsVar)
      C.DeclGlobal{}               -> k (Proxy @Hs.NsVar)

      C.DeclMacro macro ->
        case macro of
          MacroType{}  -> k (Proxy @Hs.NsTypeConstr)
          MacroValue{} -> k (Proxy @Hs.NsVar)

{-------------------------------------------------------------------------------
  Internal: working with 'MangleCandidate'
-------------------------------------------------------------------------------}

type E m a = ExceptT MangleNamesCreationError m a

mangleCandidate :: forall ns.
     Hs.SingNamespace ns
  => MangleCandidate Maybe
  -> Proxy ns
  -> Text
  -> E Identity (Hs.Name ns)
mangleCandidate mc _ cName =
    case MangleCandidate.mangleCandidate mc cName of
      Just hsName -> pure hsName
      Nothing     -> throwError $ CreateNamesCouldNotMangle cName

{-------------------------------------------------------------------------------
  Traversal 1: Create names

  For each declaration we first choose its top-level name (step 1a,
  'nameForDecl'). When that succeeds, we immediately create all its
  within-declaration names (step 1b, 'createDecl'). This includes creating
  scoped names and purely local names.

  The top-level name feeds the 'NameMap' regardless of what happens in 1b: a
  squashed or otherwise un-emitted declaration must still be resolvable by name.
  Scoped names are also fed into the 'NameMap'.
-------------------------------------------------------------------------------}

createNames ::
     forall l. (HasCallStack, Macro.HasTypes l)
  => TypedefAnalysis
  -> MangleCandidate Maybe
  -> FieldNamingStrategy
  -> [C.Decl l ResolveBindingSpecs]
  -> ( [C.Decl l CreateNames]
     , [(C.DeclId, Hs.Name Hs.NsTypeConstr, TypedefAnalysis.Squash)]
     , NameMap
     , Maybe (NonEmpty DupAssign)
     , [MangleNamesFailure]
     , [AnnMsg MangleNames]
     )
createNames td mc strategy decls = (
      map (snd . snd) successes
    , squashes
    , nameMap
    , nameMapDups
    , failures
    , messages
    )
  where
    specifiedNames :: Map C.DeclId (Hs.Name Hs.NsTypeConstr)
    specifiedNames = Map.fromList $ mapMaybe getSpecifiedName decls

    getSpecifiedName ::
      C.Decl l ResolveBindingSpecs -> Maybe (C.DeclId, Hs.Name Hs.NsTypeConstr)
    getSpecifiedName decl = (decl.info.id,) <$> ((.hsName) =<< decl.ann.cSpec)

    env :: CreateEnv
    env = CreateEnv{
        mangleCandidate     = mc
      , fieldNamingStrategy = strategy
      }

    results :: [CreateNamesResult ([ScopedNamePair], C.Decl l CreateNames)]
    messages :: [AnnMsg MangleNames]
    (results, messages) = second concat $ unzip $ map perDecl decls

    perDecl ::
         C.Decl l ResolveBindingSpecs
      -> (CreateNamesResult ([ScopedNamePair], C.Decl l CreateNames), [AnnMsg MangleNames])
    perDecl decl = (,msgs) $ case nameResult of
        CnFailure err      -> CnFailure err
        CnSquashed nC nH s -> CnSquashed nC nH s
        CnMangled name ()  -> createNamesWithin env name decl
      where
        nameResult :: CreateNamesResult ()
        msgs       :: [AnnMsg MangleNames]
        (nameResult, msgs) = nameForDecl td mc specifiedNames decl

    failures  :: [MangleNamesFailure]
    squashes  :: [(C.DeclId, Hs.Name Hs.NsTypeConstr, TypedefAnalysis.Squash)]
    successes :: [(DeclIdPair, ([ScopedNamePair], C.Decl l CreateNames))]
    (failures, squashes, successes) = partitionCreateNamesResults results

    nameMap :: NameMap
    nameMapDups :: Maybe (NonEmpty DupAssign)
    (nameMap, nameMapDups) = fromNames $
         map fromSuccess successes
      ++ map fromSquash squashes
      where
        fromSuccess (declIdPair, (scopedNamePairs, _)) = (declIdPair, scopedNamePairs)
        fromSquash (cN, hN, _)                         = (declIdPair, scopedNamePairs)
          where
            declIdPair = DeclIdPair cN (Hs.demoteNs hN)
            scopedNamePairs = []

nameForDecl ::
     HasCallStack
  => TypedefAnalysis
  -> MangleCandidate Maybe
  -> Map C.DeclId (Hs.Name Hs.NsTypeConstr)
  -> C.Decl l ResolveBindingSpecs
  -> (CreateNamesResult (), [AnnMsg MangleNames])
nameForDecl td mc specifiedNames decl =
    second toMs $
    withDeclNamespace decl.kind $ \(nsProxy :: Proxy ns) ->
      let mangleNs :: Text -> Either MangleNamesCreationError (Hs.Name ns)
          mangleNs d = runExcept $ mangleCandidate mc nsProxy d
      in case Map.lookup declId td.map of
        -- Squashing always affects two declarations: the surrounding typedef
        -- (squashed) and the inner declaration, which "uses the name of" the
        -- typedef (see 'UseNameOf' below). We generate no binding for the
        -- typedef, but we still need _two_ name-map entries pointing at the same
        -- Haskell name: one for the inner declaration and one for the typedef.
        --
        -- Crucially, this squash decision is independent of whether a name was
        -- specified: a squashed typedef must never be emitted as an ordinary
        -- binding, or it would collide with the inner declaration it shares a
        -- name with. A specified name (on the typedef itself, or failing that on
        -- its squash target) is honoured for the name-map entry; otherwise we
        -- mangle the typedef's own name.
        --
        -- TODO <https://github.com/well-typed/hs-bindgen/issues/1436>
        -- When squashing becomes configurable, revisit how a specified name
        -- interacts with squashing here.
        Just (TypedefAnalysis.Squash s) ->
          case Hs.singNamespace @ns of
            Hs.SNsTypeConstr ->
              case Map.lookup declId specifiedNames
                     <|> Map.lookup s.targetId specifiedNames of
                Just hsNm -> (CnSquashed declId hsNm s, [])
                Nothing   ->
                  mangleType declId.name.text & \case
                    Left err   -> failWith err
                    Right hsNm -> (CnSquashed declId hsNm s, [])
            _otherwise ->
              failWith $ CreateNamesSquashMustBeTypeConstr
                (Hs.namespaceOf $ Hs.singNamespace @ns)

        mConclusion ->
          case Map.lookup declId specifiedNames of
            Just hsNm ->
              -- Binding spec specified a name for this declaration. This
              -- overrides any naming decision we might make here.
              (succeedWith hsNm, [])
            Nothing ->
              case mConclusion of
                Nothing ->
                  mangleNs declId.name.text & \case
                    Left err   -> failWith err
                    Right hsNm -> (succeedWith hsNm, [])
                Just (TypedefAnalysis.AddSuffix suffix) ->
                  mangleNs (declId.name.text <> suffix) & \case
                    Left err ->
                      failWith err
                    Right hsNm ->
                      ( succeedWith hsNm
                      , [MangleNamesAssignedName (Hs.demoteNs hsNm)] )
                Just (TypedefAnalysis.UseNameOf declId') ->
                  case Map.lookup declId' specifiedNames of
                    Just hsNm ->
                      ( succeedWith hsNm
                      , [ MangleNamesReusedAssignedName (Hs.demoteNs hsNm) ] )
                    Nothing ->
                      mangleNs declId'.name.text & \case
                        Left err ->
                          failWith err
                        Right hsNm ->
                          ( succeedWith hsNm
                          , [ MangleNamesAssignedName (Hs.demoteNs hsNm)
                            | declId.name.text /= declId'.name.text ] )
  where
    declId :: C.DeclId
    declId = decl.info.id

    info :: C.DeclInfo ResolveBindingSpecs
    info = decl.info

    failWith :: MangleNamesCreationError -> (CreateNamesResult a, [MangleNamesMsg])
    failWith err = (CnFailure $ toFailure info (MangleNamesCreationError err), [])

    getPair :: Hs.SingNamespace ns => Hs.Name ns -> DeclIdPair
    getPair = DeclIdPair declId . Hs.demoteNs

    succeedWith :: Hs.SingNamespace ns => Hs.Name ns -> CreateNamesResult ()
    succeedWith n = CnMangled (getPair n) ()

    toMs :: HasCallStack => [a] -> [WithCallStack (C.WithLocationInfo a)]
    toMs = map (withCallStack . withDeclLoc decl.info)

    mangleType :: Text -> Either MangleNamesCreationError (Hs.Name Hs.NsTypeConstr)
    mangleType d =
      runExcept $
        mangleCandidate mc (Proxy :: Proxy Hs.NsTypeConstr) d

{-------------------------------------------------------------------------------
  Traversal 1b: within-declaration names
-------------------------------------------------------------------------------}

data CreateEnv = CreateEnv{
      mangleCandidate     :: MangleCandidate Maybe
    , fieldNamingStrategy :: FieldNamingStrategy
    }
  deriving stock (Generic)

type CreateM   = Reader CreateEnv

data CreateSt = CreateSt{
      scopedNames :: [ScopedNamePair]
    }
  deriving stock (Generic)

emptyCreateSt :: CreateSt
emptyCreateSt = CreateSt []

type CreateS   = StateT CreateSt CreateM

type CreateE a = ExceptT MangleNamesCreationError CreateS a

runCreate :: CreateEnv -> CreateSt -> CreateE a -> (Either MangleNamesCreationError a, CreateSt)
runCreate env st action = runReader (runStateT (runExceptT action) st) env

-- | Apply Haskell naming rules to a candidate name
mkName ::
     Hs.SingNamespace ns
  => Proxy ns -> Text -> CreateE (Hs.Name ns)
mkName ns candidate = do
    mc <- asks (.mangleCandidate)
    liftEither . runExcept $ mangleCandidate mc ns candidate

data CreateNamesResult a =
      CnFailure   MangleNamesFailure
    | CnSquashed  C.DeclId (Hs.Name Hs.NsTypeConstr) TypedefAnalysis.Squash
    | CnMangled   DeclIdPair a

partitionCreateNamesResults ::
     [CreateNamesResult a]
  -> ( [MangleNamesFailure]
     , [(C.DeclId, Hs.Name Hs.NsTypeConstr, TypedefAnalysis.Squash)]
     , [(DeclIdPair, a)] )
partitionCreateNamesResults = rev . Foldable.foldl' aux ([], [], [])
  where
    aux (fs, ss, ds) = \case
      CnFailure  f       -> (f:fs, ss,           ds      )
      CnSquashed cN hN s -> (fs,   (cN,hN,s):ss, ds      )
      CnMangled  n d     -> (fs,   ss,           (n,d):ds)

    rev (fs, ss, ds) = (reverse fs, reverse ss, reverse ds)

-- | Create the within-declaration names of a declaration whose top-level name
-- ('hsName') has already been chosen in step 1a.
createNamesWithin ::
     Macro.HasTypes l
  => CreateEnv
  -> DeclIdPair
  -> C.Decl l ResolveBindingSpecs
  -> CreateNamesResult ([ScopedNamePair], C.Decl l CreateNames)
createNamesWithin env declIdPair decl =
    case runCreate env emptyCreateSt (createDeclKind declIdPair.hsName.text decl.kind) of
      (Left err, _)   -> CnFailure $ toFailure decl.info (MangleNamesCreationError err)
      (Right kind, st) -> CnMangled declIdPair (
          st.scopedNames
        , C.Decl{
              info = coercePass decl.info
            , kind = kind
            , ann  = decl.ann
            }
        )

createDeclKind ::
     Macro.HasTypes l
  => Text
  -> C.DeclKind l ResolveBindingSpecs
  -> CreateE (C.DeclKind l CreateNames)
createDeclKind hsName = \case
    C.DeclStruct               x -> C.DeclStruct               <$> createStruct hsName x
    C.DeclUnion                x -> C.DeclUnion                <$> createUnion hsName x
    C.DeclEnum                 x -> C.DeclEnum                 <$> createEnum hsName x
    C.DeclUntaggedEnumConstant x -> C.DeclUntaggedEnumConstant <$> createUntaggedEnumConstant x
    C.DeclTypedef              x -> C.DeclTypedef              <$> createTypedef hsName x
    C.DeclFunction             x -> C.DeclFunction             <$> createFunction x
    C.DeclMacro                x -> C.DeclMacro                <$> createMacro hsName x
    C.DeclGlobal               x -> C.DeclGlobal               <$> createGlobal x
    C.DeclOpaque mSize           -> pure (C.DeclOpaque mSize)

{-------------------------------------------------------------------------------
  Traversal 1b: name-bundle constructors
-------------------------------------------------------------------------------}

createStructNames :: Text -> CreateE StructNames
createStructNames name = do
    constr <- mkName (Proxy @Hs.NsConstr) name
    pure StructNames{
        constr = constr
      }

-- | Create names for the flexible array member (FLAM), if present
--
-- The auxiliary type-constructor name is created exactly when there is a FLAM
-- and carried inside the 'C.Flam' constructor alongside the field, so that name
-- creation cannot diverge from code generation
-- (<https://github.com/well-typed/hs-bindgen/issues/1925>).
createFlam :: Text -> C.Flam ResolveBindingSpecs -> CreateE (C.Flam CreateNames)
createFlam _      C.NoFlam        = pure C.NoFlam
createFlam hsName (C.Flam field _) = do
    field'  <- createRegularField hsName field
    auxName <- mkName (Proxy @Hs.NsTypeConstr) (hsName <> "_Aux")
    pure $ C.Flam field' FlamNames{ aux = auxName }

-- | Generic construction of newtype names, given only the type name
createNewtypeNames :: FieldNamingStrategy -> Text -> CreateE NewtypeNames
createNewtypeNames strategy name = do
    dataConstr <- mkName (Proxy @Hs.NsConstr) name
    field      <- mkName (Proxy @Hs.NsVar) $ case strategy of
                    AddFieldPrefixes  -> "unwrap" <> name
                    OmitFieldPrefixes -> "unwrap"
    pure NewtypeNames{
        dataConstr = dataConstr
      , field      = field
      }

-- TODO <https://github.com/well-typed/hs-bindgen/issues/1925>
--
-- Whether we need the auxiliary name or not should be reflected in the AST, so
-- we can use an annotation just like we do for FLAMs.
createTypedefNames ::
  Bool -> FieldNamingStrategy -> Text -> CreateE TypedefNames
createTypedefNames isFunPtr strategy name = do
    orig <- createNewtypeNames strategy name
    aux  <-
      if isFunPtr then do
        auxName  <- mkName (Proxy @Hs.NsTypeConstr) $ name <> "_Aux"
        auxNames <- createNewtypeNames strategy auxName.text
        pure $ Just (auxName, auxNames)
      else
        pure Nothing
    pure TypedefNames{
        orig = orig
      , aux  = aux
      }

{-------------------------------------------------------------------------------
  Traversal 1b: scoped names
-------------------------------------------------------------------------------}

createFieldName :: Text -> C.ScopedName -> CreateE ScopedNamePair
createFieldName hsName fieldCName = do
    strategy <- asks (.fieldNamingStrategy)
    let candidate :: Text
        candidate = case strategy of
          AddFieldPrefixes  -> hsName <> "_" <> fieldCName.text
          OmitFieldPrefixes -> fieldCName.text
    name <- mkName (Proxy @Hs.NsVar) candidate
    let scopedNamePair = ScopedNamePair{
        cName  = fieldCName
      , hsName = Hs.demoteNs name
      }
    modify $ #scopedNames %~ (scopedNamePair:)
    pure scopedNamePair

-- | Create an enum constant name
--
-- Since these live in the global namespace, we do not prepend the name of the
-- enclosing enum.
createEnumConstantName :: C.ScopedName -> CreateE ScopedNamePair
createEnumConstantName cName = do
    name <- mkName (Proxy @Hs.NsConstr) cName.text
    let scopedNamePair = ScopedNamePair{
        cName  = cName
      , hsName = Hs.demoteNs name
      }
    modify $ #scopedNames %~ (scopedNamePair:)
    pure scopedNamePair

-- | Create function argument name
--
-- Function argument names are not really used when generating Haskell code.
-- They are more relevant for documentation purposes so we don't do any
-- mangling.
createArgumentName :: C.ScopedName -> CreateE ScopedNamePair
createArgumentName argName = do
    name <- mkName (Proxy @Hs.NsVar) argName.text
    let scopedNamePair = ScopedNamePair{
        cName  = argName
      , hsName = Hs.demoteNs name
      }
    modify $ #scopedNames %~ (scopedNamePair:)
    pure scopedNamePair

{-------------------------------------------------------------------------------
  Traversal 1b: per-construct creators
-------------------------------------------------------------------------------}

createStruct ::
     Text -> C.Struct ResolveBindingSpecs -> CreateE (C.Struct CreateNames)
createStruct hsName struct = do
    fields <- mapM (createField hsName) struct.fields
    flam   <- createFlam hsName struct.flam
    names  <- createStructNames hsName
    pure C.Struct{
        fields    = fields
      , flam      = flam
      , ann       = names
      , sizeof    = struct.sizeof
      , alignment = struct.alignment
      }

createUnion ::
     Text -> C.Union ResolveBindingSpecs -> CreateE (C.Union CreateNames)
createUnion hsName union = do
    strategy <- asks (.fieldNamingStrategy)
    names    <- createNewtypeNames strategy hsName
    fields   <- mapM (createField hsName) union.fields
    pure C.Union{
        fields    = fields
      , ann       = names
      , sizeof    = union.sizeof
      , alignment = union.alignment
      }

createField ::
     Text
  -> C.Field ResolveBindingSpecs
  -> CreateE (C.Field CreateNames)
createField hsName = C.mapMField (createRegularField hsName) (createImplicitField hsName)

createRegularField ::
     Text
  -> C.RegularField ResolveBindingSpecs
  -> CreateE (C.RegularField CreateNames)
createRegularField hsName field = do
    name' <- createFieldName hsName field.info.name
    pure C.RegularField{
        info   = C.FieldInfo{
                     loc     = field.info.loc
                   , name    = name'.cName
                   , comment = fmap coercePass field.info.comment
                   }
      , typ    = coercePass field.typ
      , offset = field.offset
      , width  = field.width
      , ann    = field.ann
      }

createImplicitField ::
     Text
  -> C.ImplicitField ResolveBindingSpecs
  -> CreateE (C.ImplicitField CreateNames)
createImplicitField hsName field = do
    name'     <- createFieldName hsName field.info.name
    typRef'   <- createAnonRef field.typRef
    indirect' <- mapM (createIndirectField hsName) field.indirect
    pure C.ImplicitField{
        info = C.FieldInfo{
                   loc     = field.info.loc
                 , name    = name'.cName
                 , comment = fmap coercePass field.info.comment
                 }
      , typRef = typRef'
      , offset = field.offset
      , indirect = indirect'
      , ann  = NoAnn
      }

createAnonRef ::
     C.AnonRef ResolveBindingSpecs
  -> CreateE (C.AnonRef CreateNames)
createAnonRef = \case
    C.AnonRef ref -> pure $ C.AnonRef ref
    C.AnonExtBinding ext -> pure $ C.AnonExtBinding $ C.coercePassExtBindingRef ext

createIndirectField ::
     Text
  -> C.IndirectField ResolveBindingSpecs
  -> CreateE (C.IndirectField CreateNames)
createIndirectField hsName field = do
    name' <- createFieldName hsName field.info.name
    path' <- mapM createAnonRef field.path
    pure C.IndirectField{
        info = C.FieldInfo{
                   loc     = field.info.loc
                 , name    = name'.cName
                 , comment = fmap coercePass field.info.comment
                 }
      , typ  = coercePass field.typ
      , offset = field.offset
      , width = field.width
      , path = path'
      , ann  = NoAnn
      }

createEnum :: Text -> C.Enum ResolveBindingSpecs -> CreateE (C.Enum CreateNames)
createEnum hsName enum = do
    strategy  <- asks (.fieldNamingStrategy)
    names     <- createNewtypeNames strategy hsName
    constants <- mapM createEnumConstant enum.constants
    pure C.Enum{
        typ       = coercePass enum.typ
      , constants = constants
      , ann       = names
      , sizeof    = enum.sizeof
      , alignment = enum.alignment
      }

createEnumConstant ::
     C.EnumConstant ResolveBindingSpecs
  -> CreateE (C.EnumConstant CreateNames)
createEnumConstant constant = do
    name' <- createEnumConstantName constant.info.name
    pure C.EnumConstant{
        info  = C.FieldInfo{
                    loc     = constant.info.loc
                  , name    = name'.cName
                  , comment = fmap coercePass constant.info.comment
                  }
      , value = constant.value
      }

createUntaggedEnumConstant ::
     C.UntaggedEnumConstant ResolveBindingSpecs
  -> CreateE (C.UntaggedEnumConstant CreateNames)
createUntaggedEnumConstant (C.UntaggedEnumConstant primTyp constant) = do
    constant' <- createEnumConstant constant
    pure C.UntaggedEnumConstant{
        typ      = primTyp
      , constant = constant'
      }

createTypedef ::
     Text -> C.Typedef ResolveBindingSpecs -> CreateE (C.Typedef CreateNames)
createTypedef hsName typedef = do
    strategy <- asks (.fieldNamingStrategy)
    names    <- createTypedefNames isFunPtr strategy hsName
    pure C.Typedef{
        typ = coercePass typedef.typ
      , ann = names
      }
  where
    -- TODO https://github.com/well-typed/hs-bindgen/issues/1925
    --
    -- Tie generation of names to the generation of the associated code. This
    -- is especially ugly.
    isFunPtr :: Bool
    isFunPtr = isJust $ typedef <$ C.getFirstFunTypeIndirection typedef.typ

createFunction ::
     C.Function ResolveBindingSpecs -> CreateE (C.Function CreateNames)
createFunction function = do
    args <- mapM createFunctionArg function.args
    pure C.Function{
        args  = args
      , res   = coercePass function.res
      , attrs = function.attrs
      , ann   = function.ann
      }
  where
    createFunctionArg ::
         C.FunctionArg ResolveBindingSpecs
      -> CreateE (C.FunctionArg CreateNames)
    createFunctionArg arg = do
      name' <- traverse createArgumentName arg.name
      pure C.FunctionArg{
          name = fmap (.cName) name'
        , typ  = coercePass arg.typ
        , ann  = arg.ann
        }

createGlobal ::
     C.Global ResolveBindingSpecs
  -> CreateE (C.Global CreateNames)
createGlobal global = pure C.Global{
      typ = coercePass global.typ
    , ann = global.ann
    }

createMacro ::
     Macro.HasTypes l
  => Text
  -> TypecheckedMacro ResolveBindingSpecs l
  -> CreateE (TypecheckedMacro CreateNames l)
createMacro hsName = \case
    MacroType  typ -> MacroType <$> createMacroType hsName typ
    MacroValue val -> pure $ MacroValue (coercePass val)

createMacroType ::
     Macro.HasTypes l
  => Text
  -> TypecheckedMacroType l ResolveBindingSpecs
  -> CreateE (TypecheckedMacroType l CreateNames)
createMacroType hsName macroType = do
    strategy <- asks (.fieldNamingStrategy)
    names    <- createNewtypeNames strategy hsName
    pure TypecheckedMacroType{
        body = fmap coercePass macroType.body
      , deps = macroType.deps
      , ann  = names
      }

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

withDeclLoc :: forall p a.
     IsPass p
  => C.DeclInfo p -> a -> C.WithLocationInfo a
withDeclLoc info msg = C.WithLocationInfo{
      loc = idLocationInfo (Proxy @p) info.id [info.loc]
    , msg = msg
    }