packages feed

hs-bindgen-1.0.0.0: src-internal/HsBindgen/IR/C/Decl.hs

-- | C declarations
--
-- This module should only be used within the @HsBindgen.IR@ hierarchy.  From
-- outside the @HsBindgen.IR@ hierarchy, "HsBindgen.IR.C" should be used.
--
-- Within @HsBindgen.IR@, all modules aside from "HsBindgen.IR.C" should import
-- this module qualified for consistency.
--
-- > import HsBindgen.IR.C.Decl qualified as C
module HsBindgen.IR.C.Decl (
    -- * Declarations
    Decl(..)
  , Availability(..)
  , EnclosingRef(..)
  , DeclInfo(..)
  , DeclOrigin(..)
  , HeaderInfo(..)
  , FieldInfo(..)
  , DeclKind(..)
  , OpaqueSize(..)
  , Struct(..)
  , Flam(..)
  , flamStructField
  , traverseFlamField
  , mapFlamField
  , Union(..)

  , Typedef(..)
  , Enum(..)
  , EnumConstant(..)
  , UntaggedEnumConstant(..)
  , Function(..)
  , FunctionArg(..)
  , typeOfFunction
  , typeOfFunctionArg
  , FunctionAttributes(..)
  , FunctionPurity(..)
  , decideFunctionPurity
  , Global(..)
    -- ** Fields
  , Field(..)
  , mapField
  , mapMField
  , elimField
  , RegularField(..)
  , ImplicitField(..)
  , IndirectField(..)
    -- ** Comments
  , Comment(..)
  , CommentRef(..)
  ) where

import Prelude hiding (Enum)
import Prelude qualified as P

import GHC.Records (HasField (getField))

import Clang.HighLevel.Types

import HsBindgen.Imports
import HsBindgen.IR.C.DeclPath qualified as C
import HsBindgen.IR.C.HashIncludeArg qualified as C
import HsBindgen.IR.C.Naming qualified as C
import HsBindgen.IR.C.Type qualified as C
import HsBindgen.IR.Pass
import HsBindgen.IR.Pass.Types (CoercePassAnonRef (coercePassAnonRef))
import HsBindgen.Language.C (PrimType)
import HsBindgen.Macro.Type qualified as Macro

import Doxygen.Parser.Types qualified as Doxy

{-------------------------------------------------------------------------------
  Declarations

  NOTE: Struct and union fields, as well as enum constants, have their /own/
  'SingleLoc' (in addition to the 'SingleLoc' of the enclosing declaration).
-------------------------------------------------------------------------------}

data Decl l (p :: Pass) = Decl {
      info :: DeclInfo p
    , kind :: DeclKind l p
    , ann  :: Ann "Decl" p
    }
  deriving stock (Generic)

-- | Availability of declarations.
--
-- See 'Clang.LowLevel.Core.CXAvailabilityKind'.
data Availability =
    -- | Available and recommended for use
    Available
    -- | Available but deprecated; may result in compilation error
  | Deprecated
    -- | Unavailable or unaccessible; results in compilation error
  | Unavailable
  deriving stock (Bounded, Eq, Generic, Ord, P.Enum, Show)

-- | Reference to enclosing declaration
data EnclosingRef p =
    EnclosingRef (Id p)
  | UnusableEnclosingRef C.DeclId

deriving stock instance (Eq   (Id p)) => Eq   (EnclosingRef p)
deriving stock instance (Ord  (Id p)) => Ord  (EnclosingRef p)
deriving stock instance (Show (Id p)) => Show (EnclosingRef p)

data DeclInfo (p :: Pass) = DeclInfo{
      loc           :: SingleLoc C.DeclPath
    , id            :: Id p
    -- | Source order index
    --
    -- The position of this declaration in /source order/ (roughly, how
    -- declarations appear in the C source; see the ordering definitions in
    -- "HsBindgen.Frontend.Pass.Parse"), starting at 0. Declarations with a
    -- lower index come before those with a higher index. Populated only with
    -- Clang version 20.1 or newer; 'Nothing' otherwise.
    , sourceOrderIndex :: Maybe Natural
    , origin        :: DeclOrigin
    , availability  :: Availability
    , comment       :: CommentDecl p
      -- ^ Doxygen comment for this declaration
      --
      -- Pre-'HsBindgen.Frontend.Pass.EnrichComments.IsPass.EnrichComments'
      -- passes have @CommentDecl p = ()@: the type system guarantees comments
      -- cannot exist. Post-@EnrichComments@ passes have
      -- @CommentDecl p = Maybe (Comment p)@.
    , enclosing :: [EnclosingRef p]
      -- ^ List of enclosing declarations, if this declaration is nested.
      --
      -- Set during parsing for declarations nested inside another declaration
      -- (e.g., tagged, untagged, or anonymous structs\/unions inside an enclosing
      -- struct\/union). Empty for top-level declarations.
      --
      -- Used by 'EnrichComments' to build doxygen-qualified names and look up
      -- enclosing field comments in the doxygen state.
    }
  deriving stock (Generic)

-- | Where a declaration comes from
data DeclOrigin =
    -- | A header, possibly included transitively by a main header
    FromHeader HeaderInfo

    -- | A @#define@ root directive (see "HsBindgen.Frontend.RootHeader")
  | FromRootDirective

    -- | A @-D@ Clang option
  | FromCommandLine
  deriving stock (Show, Eq, Generic)

data HeaderInfo = HeaderInfo{
      -- | User-specified headers that provide the declaration
      --
      -- Note that the declaration may not be in this header directly, but in
      -- one of its (transitive) includes.
      mainHeaders :: NonEmpty C.HashIncludeArg

      -- | @#include@ argument used to include the file where the declaration is
      -- actually declared
    , includeArg :: C.HashIncludeArg

      -- | Raw macro used as a @#include@ argument, when applicable
      --
      -- For example, @#include FOO@ would record the macro text @FOO@ here.
    , includeMacroArg :: Maybe Text
    }
  deriving stock (Show, Eq, Generic)

data FieldInfo (p :: Pass) = FieldInfo {
      loc     :: SingleLoc C.DeclPath
    , name    :: ScopedName p
    , comment :: CommentDecl p
    }
  deriving stock (Generic)

data DeclKind l p =
    DeclStruct (Struct p)
  | DeclUnion (Union p)
  | DeclTypedef (Typedef p)
  | DeclEnum (Enum p)
    -- | Untagged Enum Constant
    --
    -- Represents individual constants from an untagged enum (e.g., @enum { FOO, BAR }@)
    -- as separate pattern synonym declarations.
  | DeclUntaggedEnumConstant (UntaggedEnumConstant p)
    -- | Opaque type
    --
    -- When parsing, a C @struct@, @union@, or @enum@ may be opaque.  Users may
    -- specify any kind of type to be opaque using a prescriptive binding
    -- specification, however, including @typedef@ types.
    --
    -- The size and alignment are retained when known (i.e. when a /complete/ C
    -- type is given the @emptydata@ representation), and 'Nothing' when the type
    -- is genuinely opaque in C (e.g. a forward declaration).
  | DeclOpaque (Maybe OpaqueSize)
  | DeclMacro (MacroBody p l)
  | DeclFunction (Function p)
    -- | A global variable, whether it be declared @extern@, @static@ or neither.
  | DeclGlobal (Global p)

-- | Size and alignment of an opaque type, when known
--
-- A complete C type given the @emptydata@ representation retains its size and
-- alignment here, which is what enables generating a @StaticSize@ instance for
-- the otherwise field-less Haskell type.
data OpaqueSize = OpaqueSize {
      sizeof    :: Int
    , alignment :: Int
    }
  deriving stock (Show, Eq, Generic)

data Struct (p :: Pass) = Struct {
      sizeof    :: Int
    , alignment :: Int
    , fields    :: [Field p]
    , flam      :: Flam p
    , ann       :: Ann "Struct" p
    }
  deriving stock (Generic)

-- | The flexible array member (FLAM) of a struct, if any
--
-- A C struct may end in a flexible array member, e.g.
--
-- > struct foo { size_t len; char data[]; };
--
-- When a FLAM is present we generate an auxiliary type for the struct, and the
-- 'Flam' constructor bundles the element-type field together with the auxiliary
-- type-constructor name that code generation requires. That name is only
-- available once the name mangler has run (it is 'NoAnn' at earlier passes), so
-- carrying it /inside/ the constructor ties name creation to the FLAM itself:
-- the backend can never disagree with the name mangler over whether a name was
-- minted (see <https://github.com/well-typed/hs-bindgen/issues/1925>).
data Flam (p :: Pass) =
    NoFlam
  | Flam (RegularField p) (Ann "Flam" p)
  deriving stock (Generic)

-- | The element-type field of a FLAM, if present
flamStructField :: Flam p -> Maybe (RegularField p)
flamStructField = \case
    NoFlam   -> Nothing
    Flam f _ -> Just f

-- | Traverse the element-type field of a FLAM, preserving its annotation
traverseFlamField ::
     (Applicative f, Ann "Flam" p ~ Ann "Flam" p')
  => (RegularField p -> f (RegularField p'))
  -> Flam p
  -> f (Flam p')
traverseFlamField f = \case
    NoFlam       -> pure NoFlam
    Flam fld ann -> (\fld' -> Flam fld' ann) <$> f fld

-- | Map over the element-type field of a FLAM, preserving its annotation
mapFlamField ::
     (Ann "Flam" p ~ Ann "Flam" p')
  => (RegularField p -> RegularField p')
  -> Flam p
  -> Flam p'
mapFlamField f = \case
    NoFlam       -> NoFlam
    Flam fld ann -> Flam (f fld) ann

data Union (p :: Pass) = Union {
      sizeof    :: Int
    , alignment :: Int
    , fields    :: [Field p]
    , ann       :: Ann "Union" p
    }
  deriving stock (Generic)

data Typedef (p :: Pass) = Typedef {
      typ :: Types p
    , ann :: Ann "Typedef" p
    }
  deriving stock (Generic)

data Enum (p :: Pass) = Enum {
      typ       :: Types p
    , sizeof    :: Int
    , alignment :: Int
    , constants :: [EnumConstant p]
    , ann       :: Ann "Enum" p
    }
  deriving stock (Generic)

data EnumConstant (p :: Pass) = EnumConstant {
      info  :: FieldInfo p
    , value :: Integer
    }
  deriving stock (Generic)

-- | Untagged Enum Constant
--
-- This represents an untagged enum constant (e.g., from @enum { FOO, BAR }@)
-- that will be rendered as a pattern synonym in Haskell (e.g., @pattern fOO :: CUInt@)
data UntaggedEnumConstant (p :: Pass) = UntaggedEnumConstant {
      typ       :: PrimType
    , constant  :: EnumConstant p
    }
  deriving stock (Generic)

data Function (p :: Pass) = Function {
      args  :: [FunctionArg p]
    , res   :: Types p
    , attrs :: FunctionAttributes
    , ann   :: Ann "Function" p
    }
  deriving stock (Generic)

-- | Function argument
--
-- Separate types are used to represent function arguments in declarations and
-- function arguments in types ('HsBindgen.IR.C.Type.TypeFunArg').
--
-- * An argument in a declaration may have a name, while type arguments do not
--   have names.
-- * We translate declaration arguments to Haskell, while recursively
--   translating type arguments is not necessary.
--
-- Both of these types use the @TypeFunArg@ annotation, however.
data FunctionArg (p :: Pass) = FunctionArg {
      name :: Maybe (ScopedName p)
    , typ  :: Types p
    , ann  :: Ann "TypeFunArg" p
    }
    deriving stock (Generic)

-- | Get the type of a function declaration
typeOfFunction :: forall p. PassTypes p => Function p -> C.Type p
typeOfFunction fun =
    C.TypeFun (map typeOfFunctionArg fun.args) (cType (Proxy @p) fun.res)

-- | Get the type of a function argument
typeOfFunctionArg :: forall p. PassTypes p => FunctionArg p -> C.TypeFunArg p
typeOfFunctionArg functionArg =
    C.TypeFunArgF{
        typ = cType (Proxy @p) functionArg.typ
      , ann = functionArg.ann
      }

-- | Function attributes specify properties for C functions
--
-- Function attributes may help the C compiler. In addition, @hs-bindgen@ can in
-- some cases modify the bindings it generates based on these function
-- attributes.
--
-- This type is an interpretation of the syntactic function attributes that are
-- put on C functions. For example, a C function can have multiple @pure@
-- and\/or @const@ attributes, but we interpret these attributes together as a
-- 'FunctionPurity', see 'decideFunctionPurity'.
data FunctionAttributes = FunctionAttributes {
      purity :: FunctionPurity
    }
  deriving stock (Eq, Generic, Ord, Show)

-- | The diagnosed purity of a C function determines whether to include 'IO' in
-- its foreign import.
data FunctionPurity =
    -- | C functions that are impure in the Haskell sense of the word.
    --
    -- C functions without a @const@ or @pure@ function attribute are
    -- Haskell-impure. They do not guarantee to return the same output for the
    -- same inputs. Foreign imports of such Haskell-impure functions can /not/
    -- omit the 'IO' in their return type.
    ImpureFunction
    -- | C functions that are pure in the Haskell sense of the word.
    --
    -- C functions with a @const@ function attribute are Haskell-pure. They
    -- always return the same output for the same inputs. Foreign imports of
    -- such Haskell-pure C functions can omit the 'IO' in their return type.
    --
    -- > int square (int) __attribute__ ((const));
    --
    -- As far as the @hs-bindgen@ authors are aware, @clang@\/@gcc@ do /not
    -- always/ diagnose whether C functions with a @const@ attribute satisfy all
    -- the requirements imposed by the attribute. If a C function has a @const@
    -- attribute when it should not, then it is arguably a bug in the C library
    -- and not in @hs-bindgen@.
    --
    -- If C functions have both @const@ and @pure@ attributes, then we always
    -- pick @const@ over @pure@, because @const@ is the stronger attribute of
    -- the two.
    --
    -- <https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#index-const-function-attribute>
  | HaskellPureFunction
    -- | C functions that are pure in the C sense of the word.
    --
    -- C functions with a @pure@ function attribute are C-pure. C-pure is
    -- different from Haskell-pure, in that C-pure functions only return the
    -- same output for the same input as long as the /the state of the program
    -- observable by the C function did not change/. In the @hash@ example
    -- below, the observable state includes the contents of the input array
    -- itself. Such C-pure functions may read from pointers, and since the
    -- contents of pointers can change between invocations of the function,
    -- foreign imports of such C-pure C functions can /not/ omit the 'IO' in
    -- their return type.
    --
    -- > int hash (char *) __attribute__ ((pure));
    --
    -- Note that uses of a C-pure function can sometimes be safely encapsulated
    -- with @unsafePerformIO@ to obtain a Haskell-pure function. For example:
    --
    -- > unsafePerformIO $ withCString "abc" hash
    --
    -- As far as the @hs-bindgen@ authors are aware, @clang@\/@gcc@ do /not
    -- always/ diagnose whether C functions with a @pure@ attribute satisfy all
    -- the requirements imposed by the attribute. If a C function has a @pure@
    -- attribute when it should not, then it is arguably a bug in the C library
    -- and not in @hs-bindgen@.
    --
    -- If C functions have both @const@ and @pure@ attributes, then we always
    -- pick @const@ over @pure@, because @const@ is the stronger attribute of
    -- the two.
    --
    -- <https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#index-pure-function-attribute>
  | CPureFunction
  deriving stock (Eq, Generic, Ord, Show)

decideFunctionPurity :: [FunctionPurity] -> FunctionPurity
decideFunctionPurity = foldr prefer ImpureFunction
  where
    prefer HaskellPureFunction _                   = HaskellPureFunction
    prefer _                   HaskellPureFunction = HaskellPureFunction
    prefer CPureFunction       _                   = CPureFunction
    prefer _                   CPureFunction       = CPureFunction
    prefer _                   _                   = ImpureFunction

    -- In case we add new constructors, this case expression throsw a compiler
    -- error, which should hopefully indicate to the reader that
    -- 'decideFunctionPurity' has to be updated.
    _coveredAllCases' = \case
      ImpureFunction -> ()
      HaskellPureFunction -> ()
      CPureFunction -> ()

data Global (p :: Pass) = Global {
      typ :: Types p
    , ann :: Ann "Global" p
    }
  deriving stock (Generic)

{-------------------------------------------------------------------------------
  Fields
-------------------------------------------------------------------------------}

data Field p =
    FieldRegular  (RegularField p)
  | FieldImplicit (ImplicitField p)
  deriving stock (Generic)

instance HasField "info" (Field p) (FieldInfo p) where
  getField = elimField (.info) (.info)

instance (ty ~ Types p, PassTypes p) => HasField "typ" (Field p) ty where
  getField = elimField (.typ) (.typ)

instance HasField "offset" (Field p) Int where
  getField = elimField (.offset) (.offset)

instance HasField "width" (Field p) (Maybe Int) where
  getField = elimField (.width) (.width)

mapField ::
     (RegularField p -> RegularField p')
  -> (ImplicitField p -> ImplicitField p')
  -> Field p
  -> Field p'
mapField f g = \case
    FieldRegular  field -> FieldRegular  $ f field
    FieldImplicit field -> FieldImplicit $ g field

mapMField ::
     Monad m
  => (RegularField p -> m (RegularField p'))
  -> (ImplicitField p -> m (ImplicitField p'))
  -> Field p
  -> m (Field p')
mapMField f g = \case
    FieldRegular  field -> FieldRegular  <$> f field
    FieldImplicit field -> FieldImplicit <$> g field

elimField :: (RegularField p -> a) -> (ImplicitField p -> a) -> Field p -> a
elimField f g = \case
    FieldRegular  field -> f field
    FieldImplicit field -> g field

data RegularField p = RegularField {
      info   :: FieldInfo p
    , typ    :: Types p
      -- | Offset in bits
    , offset :: Int
    , width  :: Maybe Int
    , ann    :: Ann "RegularField" p
    }
    deriving stock (Generic)

data ImplicitField p = ImplicitField {
      info     :: FieldInfo p
      -- | Implicit fields can only refer to anonymous structs or unions
    , typRef   :: AnonRef p
      -- | Offset in bits
    , offset   :: Int
      -- | Indirect fields that go via this implicit field
      --
      -- Indirect fields only exist via implicit fields. This is enforced
      -- statically by making indirect fields a sub-tree of an implicit field.
    , indirect :: [IndirectField p]
    , ann      :: Ann "ImplicitField" p
    }
    deriving stock (Generic)

-- | Implicit fields can only refer to anonymous structs or unions. Use the
-- @typRef@ field to access the reference directly.
instance (ty ~ Types p, PassTypes p) => HasField "typ" (ImplicitField p) ty where
  getField x = anonRefTypes (Proxy @p) x.typRef

-- | Implicit fields can only refer to anonymous structs or unions, so they have
-- no bit width.
instance HasField "width" (ImplicitField p) (Maybe Int) where
  getField _x = Nothing

-- | An indirect field is a member of a nested anonymous struct\/union that can
-- be accessed /as if/ it were a member of the enclosing struct\/union
data IndirectField p = IndirectField {
      info   :: FieldInfo p
    , typ    :: Types p
      -- | Offset in bits
    , offset :: Int
    , width  :: Maybe Int
      -- | The path of recursively nested anonymous structs\/unions that
      -- eventually leads to the origin of the indirect field
      --
      -- We use this information to query whether an indirect field crosses an
      -- external binding spec abstraction boundary. See the
      -- @ResolveBindingSpecs@ pass for more information.
      --
      -- NOTE: technically we could make this @[Id p]@ if we disallow external
      -- references
    , path   :: [AnonRef p]
    , ann    :: Ann "IndirectField" p
    }
    deriving stock (Generic)

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

newtype Comment p = Comment{
      doxygen :: Doxy.Comment (CommentRef p)
    }
  deriving stock (Generic)

-- | Cross-reference in a Doxygen comment
--
-- The 'Doxy.RefKind' from the Doxygen XML @kindref@ attribute narrows the
-- search in 'HsBindgen.Frontend.Pass.MangleNames.IsPass.MangleNames': compounds
-- (struct\/union) are looked up in the type constructor namespace, members
-- (function\/typedef\/macro) in the variable and type constructor namespaces.
data CommentRef p = CommentRef Text (Maybe (Id p)) (Maybe Doxy.RefKind)

{-------------------------------------------------------------------------------
  Eq and Show instances
-------------------------------------------------------------------------------}

deriving stock instance IsPass p => Eq (Comment              p)
deriving stock instance IsPass p => Eq (CommentRef           p)
deriving stock instance IsPass p => Eq (DeclInfo             p)
deriving stock instance IsPass p => Eq (Enum                 p)
deriving stock instance IsPass p => Eq (EnumConstant         p)
deriving stock instance IsPass p => Eq (Field                p)
deriving stock instance IsPass p => Eq (FieldInfo            p)
deriving stock instance IsPass p => Eq (Flam                 p)
deriving stock instance IsPass p => Eq (Function             p)
deriving stock instance IsPass p => Eq (FunctionArg          p)
deriving stock instance IsPass p => Eq (Global               p)
deriving stock instance IsPass p => Eq (ImplicitField        p)
deriving stock instance IsPass p => Eq (IndirectField        p)
deriving stock instance IsPass p => Eq (RegularField         p)
deriving stock instance IsPass p => Eq (Struct               p)
deriving stock instance IsPass p => Eq (Typedef              p)
deriving stock instance IsPass p => Eq (Union                p)
deriving stock instance IsPass p => Eq (UntaggedEnumConstant p)

deriving stock instance IsPass p => Show (Comment              p)
deriving stock instance IsPass p => Show (CommentRef           p)
deriving stock instance IsPass p => Show (DeclInfo             p)
deriving stock instance IsPass p => Show (Enum                 p)
deriving stock instance IsPass p => Show (EnumConstant         p)
deriving stock instance IsPass p => Show (Field                p)
deriving stock instance IsPass p => Show (FieldInfo            p)
deriving stock instance IsPass p => Show (Flam                 p)
deriving stock instance IsPass p => Show (Function             p)
deriving stock instance IsPass p => Show (FunctionArg          p)
deriving stock instance IsPass p => Show (Global               p)
deriving stock instance IsPass p => Show (ImplicitField        p)
deriving stock instance IsPass p => Show (IndirectField        p)
deriving stock instance IsPass p => Show (RegularField         p)
deriving stock instance IsPass p => Show (Struct               p)
deriving stock instance IsPass p => Show (Typedef              p)
deriving stock instance IsPass p => Show (Union                p)
deriving stock instance IsPass p => Show (UntaggedEnumConstant p)

deriving stock instance (Macro.HasTypes l, IsPass p) => Eq (DeclKind l p)

deriving stock instance (Macro.HasTypes l, IsPass p) => Show (Decl     l p)
deriving stock instance (Macro.HasTypes l, IsPass p) => Show (DeclKind l p)

{-------------------------------------------------------------------------------
  CoercePass instances
-------------------------------------------------------------------------------}

instance (
      CoercePass DeclInfo p p'
    , CoercePass (DeclKind l) p p'
    , Ann "Decl" p ~ Ann "Decl" p'
    ) => CoercePass (Decl l) p p' where
  coercePass decl = Decl{
        info = coercePass decl.info
      , kind = coercePass decl.kind
      , ann  = decl.ann
      }

instance (CoercePassId p p') => CoercePass EnclosingRef p p' where
    coercePass = \case
      EnclosingRef x ->
        EnclosingRef (coercePassId (Proxy @'(p, p')) x)
      UnusableEnclosingRef x ->
        UnusableEnclosingRef x

instance (
      CoercePassId p p'
    , CoercePassCommentDecl p p'
    ) => CoercePass DeclInfo p p' where
  coercePass info = DeclInfo{
        loc              = info.loc
      , id               = coercePassId (Proxy @'(p, p')) info.id
      , sourceOrderIndex = info.sourceOrderIndex
      , origin           = info.origin
      , availability     = info.availability
      , comment          = coercePassCommentDecl (Proxy @'(p, p')) info.comment
      , enclosing        = map coercePass info.enclosing
      }

instance (
      CoercePassCommentDecl p p'
    , ScopedName p ~ ScopedName p'
    ) => CoercePass FieldInfo p p' where
  coercePass info = FieldInfo{
        comment = coercePassCommentDecl (Proxy @'(p, p')) info.comment
      , name    = info.name
      , loc     = info.loc
      }

instance (
       CoercePass Struct   p p'
     , CoercePass Enum     p p'
     , CoercePass Union    p p'
     , CoercePass Typedef  p p'
     , CoercePass Function p p'
     , CoercePass Global   p p'
     , CoercePass UntaggedEnumConstant p p'
     , CoercePassMacroBody p p'
     ) => CoercePass (DeclKind l) p p' where
  coercePass = \case
      DeclStruct               x -> DeclStruct               $ coercePass x
      DeclUnion                x -> DeclUnion                $ coercePass x
      DeclTypedef              x -> DeclTypedef              $ coercePass x
      DeclEnum                 x -> DeclEnum                 $ coercePass x
      DeclUntaggedEnumConstant x -> DeclUntaggedEnumConstant $ coercePass x
      DeclFunction             x -> DeclFunction             $ coercePass x
      DeclGlobal               x -> DeclGlobal               $ coercePass x
      DeclMacro                x -> DeclMacro                $ coercePassMacroBody (Proxy @'(p, p')) x
      DeclOpaque           mSize -> DeclOpaque mSize

instance (
      CoercePass Flam p p'
    , CoercePass Field p p'
    , Ann "Struct" p ~ Ann "Struct" p'
    ) => CoercePass Struct p p' where
  coercePass struct = Struct{
        fields    = coercePass <$> struct.fields
      , flam      = coercePass struct.flam
      , sizeof    = struct.sizeof
      , alignment = struct.alignment
      , ann       = struct.ann
      }

instance (
      CoercePass RegularField p p'
    , Ann "Flam" p ~ Ann "Flam" p'
    ) => CoercePass Flam p p' where
  coercePass = \case
    NoFlam       -> NoFlam
    Flam fld ann -> Flam (coercePass fld) ann

instance (
      CoercePass Field p p'
    , Ann "Union" p ~ Ann "Union" p'
    ) => CoercePass Union p p' where
  coercePass union = Union{
        fields    = coercePass <$> union.fields
      , sizeof    = union.sizeof
      , alignment = union.alignment
      , ann       = union.ann
      }

instance (
      CoercePass RegularField p p'
    , CoercePass ImplicitField p p'
    ) => CoercePass Field p p' where
  coercePass = \case
      FieldRegular field -> FieldRegular (coercePass field)
      FieldImplicit field -> FieldImplicit (coercePass field)

instance (
      CoercePass FieldInfo p p'
    , CoercePassTypes p p'
    , Ann "RegularField" p ~ Ann "RegularField" p'
    ) => CoercePass RegularField p p' where
  coercePass field = RegularField {
        info = coercePass field.info
      , typ = coercePassTypes (Proxy @'(p, p')) field.typ
      , offset = field.offset
      , width = field.width
      , ann = field.ann
      }

instance (
      CoercePass FieldInfo p p'
    , CoercePassAnonRef p p'
    , CoercePass IndirectField p p'
    , Ann "ImplicitField" p ~ Ann "ImplicitField" p'
    ) => CoercePass ImplicitField p p' where
  coercePass field = ImplicitField {
        info = coercePass field.info
      , typRef = coercePassAnonRef (Proxy @'(p, p')) field.typRef
      , offset = field.offset
      , indirect = fmap coercePass field.indirect
      , ann = field.ann
      }

instance (
      CoercePass FieldInfo p p'
    , CoercePassTypes p p'
    , CoercePassAnonRef p p'
    , CoercePassAnn "IndirectField" p p'
    ) => CoercePass IndirectField p p' where
  coercePass field = IndirectField {
        info = coercePass field.info
      , typ = coercePassTypes (Proxy @'(p, p')) field.typ
      , offset = field.offset
      , width = field.width
      , path = fmap (coercePassAnonRef (Proxy @'(p, p'))) field.path
      , ann = coercePassAnn (Proxy @'("IndirectField", p, p')) field.ann
      }

instance (
      CoercePassTypes p p'
    , Ann "Typedef" p ~ Ann "Typedef" p'
    ) => CoercePass Typedef p p' where
  coercePass typedef = Typedef{
        typ = coercePassTypes (Proxy @'(p, p')) typedef.typ
      , ann = typedef.ann
      }

instance (
       CoercePassTypes p p'
     , CoercePass EnumConstant p p'
     , Ann "Enum" p ~ Ann "Enum" p'
     ) => CoercePass Enum p p' where
  coercePass enum = Enum{
        typ       = coercePassTypes (Proxy @'(p, p')) enum.typ
      , constants = coercePass <$> enum.constants
      , sizeof    = enum.sizeof
      , alignment = enum.alignment
      , ann       = enum.ann
      }

instance (
      CoercePassCommentDecl p p'
    , ScopedName p ~ ScopedName p'
    ) => CoercePass EnumConstant p p' where
  coercePass constant = EnumConstant{
        info  = coercePass constant.info
      , value = constant.value
      }

instance (
       CoercePass EnumConstant p p'
     , Ann "PatternSynonym" p ~ Ann "PatternSynonym" p'
     ) => CoercePass UntaggedEnumConstant p p' where
  coercePass (UntaggedEnumConstant typ' constant') = UntaggedEnumConstant{
        typ      = typ'
      , constant = coercePass constant'
      }

instance (
      CoercePassTypes p p'
    , CoercePassAnn "TypeFunArg" p p'
    , ScopedName p ~ ScopedName p'
    , Ann "Function" p ~ Ann "Function" p'
    ) => CoercePass Function p p' where
  coercePass function = Function{
        args  = map coercePass function.args
      , res   = coercePassTypes (Proxy @'(p, p')) function.res
      , attrs = function.attrs
      , ann   = function.ann
      }

instance (
      CoercePassTypes p p'
    , CoercePassAnn "TypeFunArg" p p'
    , ScopedName p ~ ScopedName p'
    ) => CoercePass FunctionArg p p' where
  coercePass functionArg = FunctionArg{
        name = functionArg.name
      , typ  = coercePassTypes (Proxy @'(p, p')) functionArg.typ
      , ann  = coercePassAnn (Proxy @'("TypeFunArg", p, p')) functionArg.ann
      }

instance (
      CoercePassTypes p p'
    , Ann "Global" p ~ Ann "Global" p'
    ) => CoercePass Global p p' where
  coercePass global = Global{
        typ = coercePassTypes (Proxy @'(p, p')) global.typ
      , ann = global.ann
      }

instance (
      CoercePass Doxy.Comment (CommentRef p) (CommentRef p')
    ) => CoercePass Comment p p' where
  coercePass (Comment c) = Comment (coercePass c)

instance (
      CoercePassId p p'
    ) => CoercePass Doxy.Comment (CommentRef p) (CommentRef p') where
  coercePass comment = fmap coercePass comment

instance (
      CoercePassId p p'
    ) => CoercePass CommentRef p p' where
  coercePass (CommentRef c hs k) =
      CommentRef c (coercePassId (Proxy @'(p, p')) <$> hs) k