packages feed

libclang-bindings-0.1.0.0: src/Clang/HighLevel/Declaration.hs

module Clang.HighLevel.Declaration (
    -- * Declaration
    DeclarationClassification(..)
  , classifyDeclaration
    -- * Other
  , classifyTentativeDefinition
  ) where

import Control.Monad.IO.Class

import Clang.Enum.Simple
import Clang.LowLevel.Core (CXCursor, CX_StorageClass (..))
import Clang.LowLevel.Core qualified as LowLevel

{-------------------------------------------------------------------------------
  Declaration
-------------------------------------------------------------------------------}

-- | Declaration classification
--
-- This classification function is suitable for declarations of functions,
-- variables, enums, structs, and unions.
--
-- Forward declarations and redeclarations can be classified as either
-- 'DefinitionElsewhere' or 'DefinitionUnavailable'.
--
-- <https://en.cppreference.com/w/c/language/struct.html#Forward_declaration>
--
-- <https://en.cppreference.com/w/c/language/declarations.html#Redeclaration>
--
-- Despite the name, a tentative definition is /not/ classified as a
-- 'Definition'. Use 'classifyTentativeDefinition' to detect whether a
-- declaration is a tentative definition.
--
-- <https://en.cppreference.com/w/c/language/extern.html#Tentative_definitions>
data DeclarationClassification =
    -- | A declaration together with a definition.
    --
    -- > int foo (void) { return 1; }; // cursor positioned here
    --
    -- <https://en.cppreference.com/w/c/language/declarations.html#Definitions>
    Definition

    -- | A declaration without definition, but the definition is available
    -- elsewhere in the translation unit.
    --
    -- > struct X; // cursor positioned here
    -- > struct X { int n; };
  | DefinitionElsewhere CXCursor

    -- | A declaration without a definition, and there is no definition
    -- available elsewhere in the translation unit.
    --
    -- > extern int x; // cursor positioned here
  | DefinitionUnavailable
  deriving stock (Show, Eq)

-- | Classify a declaration
classifyDeclaration ::
     MonadIO m
  => CXCursor  -- ^ Declaration
  -> m DeclarationClassification
classifyDeclaration cursor = do
    defnCursor <- LowLevel.clang_getCursorDefinition cursor
    isDefnNull <- LowLevel.clang_equalCursors defnCursor LowLevel.nullCursor
    if isDefnNull
      then return DefinitionUnavailable
      else do
        isCursorDefn <- LowLevel.clang_equalCursors cursor defnCursor
        return $
          if isCursorDefn
            then Definition
            else DefinitionElsewhere defnCursor

{-------------------------------------------------------------------------------
  Other
-------------------------------------------------------------------------------}

-- | Classify whether a declaration of a global variable is a tentative
-- definition.
--
-- NOTE: this function assumes that the cursor points to a global variable
-- declaration.
--
-- A tentative definition is an external declaration without an initializer,
-- and either without a storage-class specifier or with the specifier static.
--
-- A tentative definition is a declaration that may or may not act as a
-- definition. If an actual external definition is found earlier or later in the
-- same translation unit, then the tentative definition just acts as a
-- declaration.
--
-- <https://en.cppreference.com/w/c/language/extern.html#Tentative_definitions>
classifyTentativeDefinition ::
     MonadIO m
  => CXCursor
  -> m Bool
classifyTentativeDefinition cursor = do
    initrCursor <- LowLevel.clang_Cursor_getVarDeclInitializer cursor
    isInitrNull <- LowLevel.clang_equalCursors initrCursor LowLevel.nullCursor
    if isInitrNull
      then do
        storage <- LowLevel.clang_Cursor_getStorageClass cursor
        case fromSimpleEnum storage of
          Right CX_SC_Static -> pure True
          Right CX_SC_None -> pure True
          _ -> pure False
      else pure False