packages feed

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

-- | Parse messages
module HsBindgen.Frontend.Pass.Parse.Msg (
    -- * Immediate parse messages
    ImmediateParseMsg(..)

    -- * Delayed parse messages
  , DelayedParseMsg(..)
  , UnsupportedFloatType(..)
  , ParseImplicitFieldsMsg(..)
  ) where

import Control.Exception (Exception (..))
import Foreign.C (CInt)
import Text.SimplePrettyPrint ((><))
import Text.SimplePrettyPrint qualified as PP

import Clang.Enum.Simple
import Clang.HighLevel.Types
import Clang.LowLevel.Core
import Clang.Paths

import HsBindgen.Errors
import HsBindgen.Imports
import HsBindgen.IR.C qualified as C
import HsBindgen.Macro.Error
import HsBindgen.Util.Tracer

{-------------------------------------------------------------------------------
  Immediate parse messages
-------------------------------------------------------------------------------}

-- | Parse messages that we emit immediately
--
-- Reasons for immediate emission:
--
-- - The info/debug message is useful to developers
--
-- - The declaration we fail to parse may affect other declarations
data ImmediateParseMsg =
    -- | We do not support getting macro expansions for declarations spanning
    --   multiple files.
    ParseGetMacroExpansionsMultipleFiles (Range (SingleLoc RealPath))

    -- | At a macro expansion site, we failed to get the name of the expanded
    --   macro.
  | ParseMacroExpansionNoMacroName

    -- TODO <https://github.com/well-typed/hs-bindgen/issues/1820>
    -- | We failed to parse a declaration that is required for scoping.
  | ParseOfDeclarationRequiredForScopingFailed

    -- | Source-order indices were populated, using
    -- 'clang_isBeforeInTranslationUnit'
    --
    -- Source-order indices record the order in which declarations appear in the
    -- C source.  Populating them requires Clang >= 20.1.
  | ParseSourceOrderPopulated

    -- | Source-order indices could not be populated
    --
    -- 'clang_isBeforeInTranslationUnit' is not available; this requires
    -- Clang >= 20.1.
  | ParseSourceOrderUnavailable

    -- | We failed to analyse the macro definitions for ambiguity
    --
    -- Either the definition could not be split, or the name of the split
    -- definition disagrees with the name @libclang@ reported for the cursor.
    -- The macros are treated as ambiguous.
    --
    -- The payload contains the names of the failed macros.
  | ParseMacroDefinitionAnalysisFailed (NonEmpty Text)

  deriving stock (Show, Eq, Ord, Generic)

instance PrettyForTrace ImmediateParseMsg where
  prettyForTrace = \case
      ParseGetMacroExpansionsMultipleFiles range -> PP.hsep [
          "Could not get macro expansions"
        , "(declaration spans multiple files):"
        , PP.show range
        ]
      ParseMacroExpansionNoMacroName ->
        "Could not obtain macro name at macro expansion location"
      ParseOfDeclarationRequiredForScopingFailed -> PP.hsep [
          "Parse of declaration required for scoping failed;"
        , "the failed declaration may be required when parsing"
        , "other declarations containing macro replacements"
        ]
      ParseSourceOrderPopulated -> PP.hsep [
          "Source order of declarations populated"
        , "(requires Clang >= 20.1)"
        ]
      ParseSourceOrderUnavailable -> PP.hsep [
          "Source order of declarations unavailable:"
        , "clang_isBeforeInTranslationUnit requires Clang >= 20.1"
        ]
      ParseMacroDefinitionAnalysisFailed names -> PP.hsep [
          "Failed to analyse these macro definitions for ambiguity:"
        , PP.string (show names)
        ]

instance IsTrace Level ImmediateParseMsg where
  getDefaultLogLevel = \case
      ParseGetMacroExpansionsMultipleFiles{}       -> Warning
      ParseMacroExpansionNoMacroName{}             -> Bug
      ParseOfDeclarationRequiredForScopingFailed{} -> Info
      ParseSourceOrderPopulated{}                  -> Info
      ParseSourceOrderUnavailable{}                -> Info
      ParseMacroDefinitionAnalysisFailed{}         -> Bug
  getSource  = const HsBindgen
  getTraceId = \case
    ParseMacroExpansionNoMacroName       -> "parse-immediate-macro"
    ParseMacroDefinitionAnalysisFailed{} -> "parse-immediate-macro"
    _otherwise                           -> "parse-immediate"

{-------------------------------------------------------------------------------
  Delayed parse messages
-------------------------------------------------------------------------------}

-- Note to developers: We order delayed parse message constructors by
-- 1. Recursive/nested constructors come first
-- 2. Severity; debug messages come first
-- 3. Constructor name

-- | Delayed parse messages
--
-- We emit these parse messages only when we attempt to select the attached
-- declaration.
--
-- We distinguish between \"unsupported\", which refers to C features that one
-- could reasonably expect to be supported eventually, and \"unexpected\", for C
-- input we are not prepared for.
data DelayedParseMsg =
    -- | Recursive case; we failed to parse the target of a @typedef@ with a
    --   delayed parse message.
    ParseUnderlyingTypeFailed C.PrelimDeclId DelayedParseMsg

    -- | We tried to parse an implicit field for a struct or union object, but
    -- it was unsuccessful
  | ParseImplicitFieldFailed ParseImplicitFieldsMsg

    -- | We did not attempt to parse a macro with an empty replacement list
    -- (@#define FOO@)
    --
    -- Reported separately from 'ParseMacroErrorParse' because every include
    -- guard has this shape, and not attempting to parse one is not a failure.
    -- See 'HsBindgen.Frontend.Pass.Parse.IsPass.EmptyMacros'.
  | ParseMacroEmpty C.PrelimDeclId [Token SourcePath TokenSpelling]

    -- | We could not parse the macro (macro def sites)
  | ParseMacroErrorParse MacroParseError


    -- | Fully defined global variables and functions with external linkage.
    --
    -- Such definitions can lead to duplicate symbols (linker errors) if they
    -- are included more than once in the same program. See the manual section
    -- on globals for details.
    --
    -- Duplicate symbols can also exist across multiple shared libraries as long
    -- as these symbols have public visibility. However, in such cases the
    -- linker will pick one according to the rules of linker symbol
    -- interposition, rather than throw a linker error. It can be surprising for
    -- users if the linker picks an unexpected definition for the symbol they
    -- are referencing. So, if a symbol has non-public visibility, the risk of
    -- such surprises is mitigated somewhat. See the "Visibility" section in the
    -- manual for more details.
  | ParsePotentialDuplicateSymbol
      -- | The symbol has public visibility
      Bool

    -- | Struct/union tag first declared inside a function prototype, e.g.
    -- @void f(struct foo* arg);@
    --
    -- Might indicate a missing @#include@ in the header.
  | ParseDeclarationNotVisible C.TagKind Text

    -- | A function declaration was encountered where the type of the function
    -- is typedef reference. This is not yet supported by hs-bindgen.
    --
    -- For example:
    --
    -- > typedef int int2int(int);
    -- > extern int2int foo;
    --
    -- <https://github.com/well-typed/hs-bindgen/issues/1034>
  | ParseFunctionOfTypeTypedef

  | ParseInvalidLinkage

  | ParseInvalidVisibility

    -- | Failed to parse a declaration because some of its nested declarations
    -- failed to parse
    --
    -- For example, to successfully parse a struct (or union), all of its
    -- nested declarations have to be parsed successfully as well. If any
    -- nested declaration fails to parse, then we could miss seeing some
    -- (implicit) fields, and we can't generate Haskell bindings for structs
    -- (or unions) with an incomplete list of fields.
  | ParseNestedDeclsFailed

    -- | A function declaration or global variable declaration has a problematic
    -- case of non-public visibility that can lead to linker errors if the
    -- symbol is defined in a shared library.
    --
    -- In such cases, we emit this message. Arguably declarations like these are
    -- a bug in the C library, given the way that header files are @#include@d
    -- in other header and body files.
    --
    -- Concretely, a linker error can occur for a declared symbol if it:
    --
    -- 1. has non-public visibility,
    -- 2. has external linkage, and
    -- 3. is not a definition (and there is no definition elsewhere in the
    --    header).
    --
    -- For example:
    --
    -- > extern void __attribute__ ((visibility ("hidden"))) f (void);
    -- > extern int __attribute__ ((visibility ("hidden"))) i;
  | ParseNonPublicVisibility

    -- | Declaration availability can not be determined.
    --
    -- That is 'Clang.LowLevel.Core.clang_getCursorAvailability' does not
    -- provide a valid 'Clang.LowLevel.Core.CXAvailabilityKind'.
  | ParseUnknownCursorAvailability (SimpleEnum CXAvailabilityKind)

    -- | Variable declaration
  | ParseUnknownStorageClass (SimpleEnum CX_StorageClass)

    -- | Unexposed type
    --
    -- In some cases, @libclang@ does not expose information about a type. For
    -- example, LLVM/Clang 22 gives the @size_t@ arguments and results of
    -- library builtins such as @malloc@ and @strlen@ the predefined sugar type
    -- @__size_t@, which its @libclang@ does not classify (see
    -- <https://github.com/llvm/llvm-project/issues/192268>). LLVM/Clang 23
    -- fixes this, by reporting these as 'CXType_PredefinedSugar' instead, which
    -- we handle.
  | ParseUnexposedType

    -- | Unsupported unnamed declaration inside @extern@
    --
    -- Something like
    --
    -- > extern struct { .. } config;
    --
    -- does not make sense: this declares the existence of some externally
    -- defined global variable, but it is impossible to actually define said
    -- global variable; an attempt such as
    --
    -- > #include "config.h"
    -- > struct { .. } config = ..
    --
    -- will result in an error: "conflicting types for @config@".
    --
    -- The /header/ however by itself will not result in a @clang@ warning, so
    -- we detect the situation and warn the user in @hs-bindgen@.
    --
    -- (As of C23, the situation is different for /tagged/ structs:
    -- multiple uses of a struct with the same tag are considered
    -- compatible as of WG14-N3037.)
  | ParseUnsupportedUnnamedInExtern

    -- | Unsupported unnamed declaration inside function signature
    --
    -- Consider:
    --
    -- > void f(struct named { int x; int y; } arg);
    -- > void g(struct { int x; int y; } arg);
    --
    -- Somewhat surprisingly, @clang@ warns about the first
    --
    -- > warning: declaration of 'struct named' will not be visible outside of
    -- > this function
    --
    -- but accepts the second (untagged struct); @gcc@ warns in both cases:
    --
    -- > {‘struct named’, untagged struct} declared inside parameter list will
    -- > not be visible outside of this definition or declaration
    --
    -- as does the C compiler used by vscode, which in both cases says
    --
    -- > type definition not allowed
    --
    -- It's not entirely clear if C23/WG14-N3037 affects this or not; @gcc@
    -- warns about both declarations even with @-std=c2x@.
    --
    -- For our purposes, only the unnamed case is really problematic (we have
    -- no way of assigning a tag to the struct). Since it is relatively clear
    -- that the unnamed version is anyway unusable (callers would have no way
    -- of constructing any values), we rule them out.
  | ParseUnsupportedUnnamedInSignature

    -- | Clang built-in declaration
  | ParseUnsupportedBuiltin Text

    -- | We do not support this floating-point type
  | ParseUnsupportedFloatType UnsupportedFloatType

  | ParseUnsupportedLinkage String CXLinkageKind

    -- | Thread local variables
    --
    -- <https://github.com/well-typed/hs-bindgen/issues/828>
  | ParseUnsupportedTLS

    -- | We do not support variadic (varargs) functions
  | ParseUnsupportedVariadicFunction

    -- | We do not support SIMD vector types (yet)
  | ParseUnsupportedVector

    -- | Unusable unnamed declaration
    --
    -- When an unusable declaration appears in some outer declaration (say a
    -- function signature), we will fail to \"parse\" that outer declaration
    -- (actually this happens as a separate post-processing step in
    -- @FillUnnamedIds@). We record the identifier of the unnamed declaration
    -- here (that is, it's source location); the identifier of the outer
    -- declaration is recorded in the encloding 'HsBindgen.Frontend.Pass.Parse.Result.ParseResult'.
  | ParseUnusableUnnamedDecl C.UnnamedId

  | ParseExpectedFunctionType String

    -- | A macro definition has no name, even though macros can not be unnamed
  | ParseMacroDefinitionNoMacroName

    -- | Complex types can only be defined using primitive types, e.g.
    -- @double complex@. @struct Point complex@ is not allowed.
  | ParseUnexpectedComplexType CXType

    -- | We encountered an unexpected cursor kind
    --
    -- Similar comments apply as for 'ParseUnexpectedTypeKind'.
  | ParseUnexpectedCursorKind (Either CInt CXCursorKind)

  | ParseUnexpectedLinkage (Either CInt CXLinkageKind)

    -- | We encountered an unexpected type kind
    --
    -- This is always a bug in hs-bindgen: if this kind of type is unsupported,
    -- we should explicitly check for it and throw an appropriate exception.
    --
    -- If this is a @Left@ value, it means that our @libclang@ bindings are
    -- incomplete.
  | ParseUnexpectedTypeKind (Either CInt CXTypeKind)

  | ParseUnexpectedVisibility (Either CInt CXLinkageKind)

  | ParseNoMainHeadersException String RealPath
  deriving stock (Show, Generic)

-- | Floating-point types we do not support
--
-- @float@ and @double@ are the only floating-point types we support; all others
-- either have a platform dependent representation or lack a Haskell FFI
-- counterpart.
--
-- <https://github.com/well-typed/hs-bindgen/issues/349>
-- <https://github.com/well-typed/hs-bindgen/issues/2232>
data UnsupportedFloatType =
    -- | @long double@
    UnsupportedLongDouble

    -- | @__float128@, @_Float128@
  | UnsupportedFloat128

    -- | @_Float16@
  | UnsupportedFloat16

    -- | @__fp16@ (half precision, storage only)
  | UnsupportedHalf

    -- | @__bf16@ (brain floating point)
  | UnsupportedBFloat16

    -- | @__ibm128@ (PowerPC double-double)
  | UnsupportedIbm128
  deriving stock (Show, Eq, Ord, Enum, Bounded, Generic)

instance PrettyForTrace UnsupportedFloatType where
  prettyForTrace = \case
      UnsupportedLongDouble -> "long double"
      UnsupportedFloat128   -> "__float128"
      UnsupportedFloat16    -> "_Float16"
      UnsupportedHalf       -> "__fp16"
      UnsupportedBFloat16   -> "__bf16"
      UnsupportedIbm128     -> "__ibm128"

instance Exception DelayedParseMsg where
  displayException = PP.renderCtxDoc (PP.mkContext 100) . prettyForTrace

instance PrettyForTrace DelayedParseMsg where
  prettyForTrace = \case
      ParseUnderlyingTypeFailed name err -> PP.hcat [
          "Parse failure of underlying type of typedef "
        , prettyForTrace name
        , ": "
        , prettyForTrace err
        ]
      ParseImplicitFieldFailed reason -> PP.hsep [
          "Failed to parse an implicit struct or union field"
        , "referencing an anonymous object. Reason: "
        , prettyForTrace reason
        ]
      ParseMacroEmpty name tokens -> PP.hsep [
          "Not parsing macro with empty replacement list"
        , prettyForTrace name >< ";"
        , "use --parse-empty-macros to parse it:"
        , PP.show tokens
        ]
      ParseMacroErrorParse err -> PP.vcat [
          "Could not parse macro:"
        , PP.nest 2 $
            PP.renderedLines $ \_maxWidth -> lines err.macroParseError
        ]
      ParsePotentialDuplicateSymbol isPublic -> PP.hcat $ [
            "Bindings may result in duplicate symbols; "
          , "consider using 'static' or 'extern'"
          ] ++
          if isPublic
          then [
              "; "
            , "or if that is not an option, consider attributing hidden "
            , "visibility to the symbol"
            ]
          else []
      ParseFunctionOfTypeTypedef ->
        "Unsupported function declared with a typedef type"
      ParseInvalidLinkage ->
        "Invalid linkage (CXLinkage_Invalid)"
      ParseInvalidVisibility ->
        "Invalid visibility (CXVisibility_Invalid)"
      ParseNestedDeclsFailed -> PP.hsep [
          "Failed to parse a declaration because some of its nested declarations"
        , "failed to parse"
        ]
      ParseNonPublicVisibility -> PP.hsep [
          "Bindings may result in linker errors"
        , "because the symbol has non-public visibility"
        ]
      ParseUnknownCursorAvailability simpleKind -> PP.hsep [
          "Unknown declaration cursor availability:"
        , PP.show simpleKind
        ]
      ParseUnknownStorageClass storage -> PP.hsep [
          "Unsupported storage class"
        , PP.show storage
        ]
      ParseUnexposedType ->
        "Unexposed type"
      ParseUnsupportedUnnamedInExtern ->
        "Unexpected unnamed declaration in global variable"
      ParseUnsupportedUnnamedInSignature ->
        "Unexpected unnamed declaration in function signature"
      ParseUnsupportedBuiltin name ->
        "Unsupported built-in " >< PP.show name
      ParseUnsupportedFloatType ty ->
        "Unsupported floating-point type " >< prettyForTrace ty
      ParseUnsupportedLinkage comment linkage -> PP.hcat [
          "Unsupported linkage: "
        , PP.show linkage
        , " ("
        , PP.string comment
        , ")"
        ]
      ParseUnsupportedTLS ->
        "Unsupported thread-local variable"
      ParseUnsupportedVariadicFunction ->
        "Unsupported variadic (varargs) function"
      ParseUnsupportedVector ->
        "Unsupported SIMD vector type"
      ParseUnusableUnnamedDecl unnamedId -> PP.hsep [
          "Unusable unnamed declaration "
        , prettyForTrace unnamedId
        ]
      ParseDeclarationNotVisible kind name -> PP.hcat [
            "Declaration of '"
          , PP.text (C.tagKindPrefix kind)
          , " "
          , PP.text name
          , "' will not be visible outside of this function"
          ]
      ParseExpectedFunctionType ty -> PP.hsep [
          "Expected function type, but got"
        , PP.string ty
        ]
      ParseMacroDefinitionNoMacroName ->
        "A macro definition has no name, even though macros can not be unnamed"
      ParseUnexpectedComplexType ty ->
        unexpected $ "complex type " >< PP.show ty
      ParseUnexpectedCursorKind x ->
        unexpected $ "cursor kind " >< either PP.show PP.show x
      ParseUnexpectedLinkage linkage -> PP.hcat [
          "Unexpected linkage: "
        , PP.show linkage
        ]
      ParseUnexpectedTypeKind x ->
        unexpected $ "type kind " >< either PP.show PP.show x
      ParseUnexpectedVisibility visibility -> PP.hcat [
          "Unexpected visibility: "
        , PP.show visibility
        ]
      ParseNoMainHeadersException why whr -> PP.hsep [
          "Could not determine main headers:"
        , PP.string why
        , "at"
        , PP.string $ getRealPath whr
        ]
    where
      unexpected :: PP.CtxDoc -> PP.CtxDoc
      unexpected msg = PP.vcat [
            "Unexpected " >< msg
          , PP.string pleaseReport
          ]

-- | Unsupported features are warnings
instance IsTrace Level DelayedParseMsg where
  getDefaultLogLevel = \case
      ParseUnderlyingTypeFailed _ x        -> getDefaultLogLevel x
      ParseImplicitFieldFailed    x        -> getDefaultLogLevel x
      ParseMacroEmpty{}                    -> Info
      ParseMacroErrorParse{}               -> Info
      ParsePotentialDuplicateSymbol{}      -> Notice
      ParseDeclarationNotVisible{}         -> Warning
      ParseFunctionOfTypeTypedef{}         -> Warning
      ParseInvalidLinkage                  -> Warning
      ParseInvalidVisibility               -> Warning
      ParseNestedDeclsFailed{}             -> Warning
      ParseNonPublicVisibility{}           -> Warning
      ParseUnknownCursorAvailability{}     -> Warning
      ParseUnknownStorageClass{}           -> Warning
      ParseUnexposedType                   -> Warning
      ParseUnsupportedUnnamedInExtern{}    -> Warning
      ParseUnsupportedUnnamedInSignature{} -> Warning
      ParseUnsupportedBuiltin{}            -> Warning
      ParseUnsupportedFloatType{}          -> Warning
      ParseUnsupportedLinkage{}            -> Warning
      ParseUnsupportedTLS{}                -> Warning
      ParseUnsupportedVariadicFunction     -> Warning
      ParseUnsupportedVector               -> Warning
      ParseUnusableUnnamedDecl{}           -> Warning
      ParseExpectedFunctionType{}          -> Bug
      ParseMacroDefinitionNoMacroName{}    -> Bug
      ParseUnexpectedComplexType{}         -> Bug
      ParseUnexpectedCursorKind{}          -> Bug
      ParseUnexpectedLinkage{}             -> Bug
      ParseUnexpectedTypeKind{}            -> Bug
      ParseUnexpectedVisibility{}          -> Bug
      ParseNoMainHeadersException{}        -> Error
  getSource  = const HsBindgen
  getTraceId = \case
      ParseImplicitFieldFailed x        -> "parse-" <> getTraceId x
      ParseMacroDefinitionNoMacroName{} -> "parse-macro"
      ParseMacroEmpty{}                 -> "parse-macro"
      ParseMacroErrorParse{}            -> "parse-macro"
      _ -> "parse"

{-------------------------------------------------------------------------------
  Delayed parse messages: implicit fields
-------------------------------------------------------------------------------}

data ParseImplicitFieldsMsg =
    -- | Unsupported empty anonymous nested struct or union
    --
    -- Anonymous nested structs and unions need to have at least one named
    -- member for parsing of implicit fields to succeed.
    UnsupportedEmptyAnon
    -- | An unexpected exception was thrown when using @clang_Type_getOffsetOf@
    --
    -- This is likely a bug in implicit field parsing.
  | UnexpectedClangOffsetOfException Text String
  deriving stock (Show, Eq, Ord, Generic)


instance PrettyForTrace ParseImplicitFieldsMsg where
  prettyForTrace = \case
      UnsupportedEmptyAnon -> PP.hsep [
          "Unsupported empty nested anonymous union or struct:"
        , "it should have at least one named field"
        ]
      UnexpectedClangOffsetOfException field exc -> PP.hsep [
          "Unexpected exception when using clang_Type_getOffsetOf"
        , "with field name", PP.text field, ":", PP.string exc
        ]

-- | Unsupported features are warnings
instance IsTrace Level ParseImplicitFieldsMsg where
  getDefaultLogLevel = \case
      UnsupportedEmptyAnon{}              -> Warning
      UnexpectedClangOffsetOfException{}  -> Bug
  getSource  = const HsBindgen
  getTraceId = const "implicit-fields"