packages feed

hs-bindgen-1.0.0.0: src-internal/HsBindgen/Frontend/Analysis/UnnamedIdUsage.hs

-- | Analyse usage of unnamed declarations
--
-- Intended for qualified import.
--
-- > import HsBindgen.Frontend.Analysis.UnnamedIdUsage (UnnamedIdUsageAnalysis)
-- > import HsBindgen.Frontend.Analysis.UnnamedIdUsage qualified as UnnamedIdUsageAnalysis
module HsBindgen.Frontend.Analysis.UnnamedIdUsage (
    UnnamedIdUsageAnalysis(..)
  , Context(..)
  , fromDecls
  ) where

import Data.Map qualified as Map

import HsBindgen.Errors
import HsBindgen.Frontend.Pass.Parse.IsPass (Parse)
import HsBindgen.Imports
import HsBindgen.IR.C qualified as C

{-------------------------------------------------------------------------------
  Definition
-------------------------------------------------------------------------------}

-- | How are unnamed data types used?
data UnnamedIdUsageAnalysis = UnnamedIdUsageAnalysis{
      map :: Map C.UnnamedId Context
    }
  deriving stock (Show)

data Context =
    -- | Unnamed declaration inside a direct, named field
    --
    -- E.g.
    --
    -- > struct rect {
    -- >   struct { int x; int y; } topleft;
    -- >   struct { int x; int y; } bottomright;
    -- > }
    --
    -- @topleft@ and @bottomright@ are fields that reference untagged structs.
    -- Both fields are direct fields of struct @rect@.
    --
    -- NOTE: after the @Parse@ pass, unnamed fields can not exist. In case we
    -- parse unnamed fields (i.e., implicit fields) in the @Parse@ pass, then we
    -- generate a name for those fields. See the
    -- "HsBindgen.Frontend.Pass.Parse.Decl.ImplicitFields" module for more
    -- information.
    --
    -- Indirect fields are not analysed, because indirect fields only exist for
    -- regular fields and those will have been analysed already.
    --
    -- E.g.
    --
    -- > struct S {
    -- >   struct { // <-- anonymous struct
    -- >     struct { // <-- untagged struct
    -- >       int y;
    -- >     } x;
    -- >   };
    -- > };
    --
    -- @x@ is a field that references an untagged struct. @x@ is a direct field
    -- of the anonymous struct, and an indirect field of struct @rect@.
    Field (C.DeclInfo Parse) (C.FieldInfo Parse)

    -- | Direct use of unnamed declaration inside in a typedef
    --
    -- E.g.
    --
    -- > typedef struct { int; int y; } point;
  | TypedefDirect (C.DeclInfo Parse)

    -- | Indirect use of an unnamed declaration inside a typedef
    --
    -- The most typical example of this is
    --
    -- > typedef struct { int; int y; } * point;
    --
    -- but there are many others, such as
    --
    -- > typedef struct { int; int y; } points[10];
    --
    -- We distinguish this from 'TypedefDirect' because in the case of
    -- 'TypedefDirect' we use the name of the typedef as the tag of the struct
    -- (indeed, @clang >= 16@ already does this out of the box), but in the case
    -- of 'TypedefIndirect' we add a @_Aux@ suffix, because now the two types
    -- are meaningfully different (and @clang@ assigns no name at all).
  | TypedefIndirect (C.DeclInfo Parse)

    -- | Unnamed declaration used as the type of a global variable
    --
    -- E.g.
    --
    -- > struct { int x; int y; } a;
    --
    -- In this case, we use the name of the global variable as the name of
    -- the struct.
  | GlobalVar (C.DeclInfo Parse)
  deriving stock (Show)

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

fromDecls :: [C.Decl l Parse] -> UnnamedIdUsageAnalysis
fromDecls decls = UnnamedIdUsageAnalysis{
      map = Map.fromListWithKey resolveConflicts $
             concatMap analyseDecl decls
    }

-- | Resolve conflicts
--
-- Unnamed declarations can in rare circumstances have multiple use sites.
resolveConflicts :: C.UnnamedId -> Context -> Context -> Context
resolveConflicts unnamedId new old =
    case (old, new) of
      {-----------------------------------------------------------------------------
        Fields
      -----------------------------------------------------------------------------}

      (Field decl1 _, Field decl2 _) | decl1.id == decl2.id ->
        -- Example:
        --
        -- > struct rect {
        -- >   struct { int x; int y; } tl, br;
        -- > };
        --
        -- Multiple declarators for the same field.
        -- We choose the first field (in source order).
        old

      {-----------------------------------------------------------------------------
        Clang < 16
      -----------------------------------------------------------------------------}

      (TypedefDirect _, TypedefIndirect _) ->
        -- Example:
        --
        -- > typedef struct { int x; int y; } point2a, *point2b;
        --
        -- Mixed direct and indirect typedefs (direct seen first).
        old

      (TypedefIndirect _, TypedefDirect _) ->
        -- Mirror of the above case (when indirect appears first in source order).
        new

      (TypedefDirect _, TypedefDirect _) ->
        -- Example:
        --
        -- > typedef struct { int x; int y; } point1a, point1b;
        --
        -- Multiple direct typedefs for the same untagged struct.
        -- We choose the first typedef (in source order).
        old

      {-----------------------------------------------------------------------------
        Clang >= 16
      -----------------------------------------------------------------------------}

      (TypedefIndirect _, TypedefIndirect _) ->
        -- Example:
        --
        -- > typedef struct { int x; int y; } *point3a, *point3b;
        --
        -- Multiple indirect typedefs for the same untagged struct.
        -- We choose the first typedef (in source order).
        --
        -- NOTE: This is the ONLY conflict case that can occur in Clang >= 16.
        -- In Clang >= 16, when a typedef has both direct and indirect declarators
        -- (e.g., `typedef struct { ... } a, *b;`), Clang names the untagged struct
        -- with the first direct typedef name (@a@), and the indirect typedef ('b')
        -- becomes a pointer to that named type. Thus no conflict exists.
        old

      _otherwise ->
         panicPure $ concat [
             "Conflicting use sites for "
           , show unnamedId
           , ": "
           , show (old, new)
           ]

{-------------------------------------------------------------------------------
  Declarations
-------------------------------------------------------------------------------}

-- | Analyse declaration
--
-- NOTE: Unnamed declarations that appear in function signatures and
-- global variables are unusable, and so we do not assign a name to them
-- (this will cause them to be removed from the list of declarations).
analyseDecl :: C.Decl l Parse -> [(C.UnnamedId, Context)]
analyseDecl decl =
    case decl.kind of
      C.DeclStruct               x -> analyseStruct  decl.info x
      C.DeclUnion                x -> analyseUnion   decl.info x
      C.DeclTypedef              x -> analyseTypedef decl.info x
      C.DeclEnum                 _ -> []
      C.DeclUntaggedEnumConstant _ -> []
      C.DeclOpaque{}               -> []
      C.DeclMacro                _ -> []
      C.DeclFunction             _ -> []
      C.DeclGlobal               x -> analyseGlobal  decl.info x.typ

analyseStruct :: C.DeclInfo Parse -> C.Struct Parse -> [(C.UnnamedId, Context)]
analyseStruct info struct = concat [
      concatMap (analyseField info)        struct.fields
    , foldMap   (analyseRegularField info) (C.flamStructField struct.flam)
    ]

analyseUnion :: C.DeclInfo Parse -> C.Union Parse -> [(C.UnnamedId, Context)]
analyseUnion info union =
    concatMap (analyseField info) union.fields

analyseField :: C.DeclInfo Parse -> C.Field Parse -> [(C.UnnamedId, Context)]
analyseField info = C.elimField (analyseRegularField info) (analyseImplicitField info)

analyseRegularField :: C.DeclInfo Parse -> C.RegularField Parse -> [(C.UnnamedId, Context)]
analyseRegularField info field = analyseType (Field info field.info) field.typ

analyseImplicitField :: C.DeclInfo Parse -> C.ImplicitField Parse -> [(C.UnnamedId, Context)]
analyseImplicitField info field = concat [
      analyseType (Field info field.info) field.typ
    , concatMap (analyseIndirectField info) field.indirect
    ]

analyseIndirectField :: C.DeclInfo Parse -> C.IndirectField Parse -> [(C.UnnamedId, Context)]
analyseIndirectField _info _field =
    -- Indirect fields can only exist if there is a direct field, and we always
    -- prefer the direct field. See also the haddocks on 'Field'.
    []

analyseTypedef :: C.DeclInfo Parse -> C.Typedef Parse -> [(C.UnnamedId, Context)]
analyseTypedef info typedef = analyseType (TypedefDirect info) typedef.typ

analyseGlobal :: C.DeclInfo Parse -> C.Type Parse -> [(C.UnnamedId, Context)]
analyseGlobal info = analyseType (GlobalVar info)

{-------------------------------------------------------------------------------
  Types

  This is where the real work happens; the rest is just setting up context.
-------------------------------------------------------------------------------}

analyseType :: Context -> C.Type Parse -> [(C.UnnamedId, Context)]
analyseType = go
  where
    go :: Context -> C.Type Parse -> [(C.UnnamedId, Context)]
    go ctxt = \case
        -- Base case
        C.TypeRef ref ->
            case ref of
              C.PrelimDeclIdNamed{}     -> []
              C.PrelimDeclIdUnnamed unnamedId -> [(unnamedId, ctxt)]
        C.TypeEnum ref ->
            case ref.name of
              C.PrelimDeclIdNamed{}     -> []
              C.PrelimDeclIdUnnamed unnamedId -> [(unnamedId, ctxt)]

        -- Recursion
        --
        -- For the @const@ case, something like
        --
        -- > typedef const struct { .. } foo;
        --
        -- perhaps, we follow @libclang@ and consider this an indirect usage
        -- (\"follow\" in the sense that @libclang@ does /not/ assign the name
        -- of the typedef to the struct in this case; we will add the suffix).
        C.TypePointers _n     ty -> indirect ty
        C.TypeQual _qual ty      -> indirect ty
        C.TypeConstArray _sz  ty -> indirect ty
        C.TypeIncompleteArray ty -> indirect ty
        C.TypeBlock           ty -> indirect ty
        C.TypeFun args res       -> concatMap analyseTypeFunArg args ++ indirect res

        -- Trivial cases
        C.TypeComplex{}    -> []
        C.TypePrim{}       -> []
        C.TypeTypedef{}    -> []
        C.TypeVoid{}       -> []
      where
        indirect :: C.Type Parse -> [(C.UnnamedId, Context)]
        indirect =
           case ctxt of
             TypedefDirect declInfo -> go (TypedefIndirect declInfo)
             _otherwise             -> go ctxt

        analyseTypeFunArg :: C.TypeFunArg Parse -> [(C.UnnamedId, Context)]
        analyseTypeFunArg arg = indirect arg.typ