hs-bindgen-1.0.0.0: src-internal/HsBindgen/Frontend/Pass/ReparseMacroExpansions.hs
module HsBindgen.Frontend.Pass.ReparseMacroExpansions (
reparseMacroExpansions
) where
import Control.Monad.Reader (MonadReader (..), Reader, runReader)
import Control.Monad.State (MonadState, StateT, modify, runStateT)
import Data.Foldable qualified as Foldable
import Data.Map qualified as Map
import Data.Set qualified as Set
import Clang.CStandard (ClangCStandard)
import HsBindgen.Frontend.Analysis.DeclIndex (DeclIndex)
import HsBindgen.Frontend.Analysis.DeclIndex qualified as DeclIndex
import HsBindgen.Frontend.Analysis.DeclUseGraph (DeclUseGraph)
import HsBindgen.Frontend.Analysis.DeclUseGraph qualified as DeclUseGraph
import HsBindgen.Frontend.Analysis.UseDeclGraph qualified as UseDeclGraph
import HsBindgen.Frontend.DeclMeta
import HsBindgen.Frontend.LanguageC qualified as LanC
import HsBindgen.Frontend.Pass.Parse.IsPass
import HsBindgen.Frontend.Pass.PrepareReparse.IsPass
import HsBindgen.Frontend.Pass.ReparseMacroExpansions.ForgetAnn (ForgetAnn (forgetAnn))
import HsBindgen.Frontend.Pass.ReparseMacroExpansions.IsPass (ReparseMacroExpansions)
import HsBindgen.Frontend.Pass.ReparseMacroExpansions.IsPass.Msg
import HsBindgen.Frontend.Pass.ReparseMacroExpansions.LanC (LanC)
import HsBindgen.Frontend.Pass.ReparseMacroExpansions.Zip
import HsBindgen.Frontend.Pass.TypecheckMacros.IsPass
import HsBindgen.Frontend.TranslationUnit qualified as C
import HsBindgen.Imports
import HsBindgen.IR.C qualified as C
import HsBindgen.IR.Pass
{-------------------------------------------------------------------------------
Top-level
-------------------------------------------------------------------------------}
-- | Reparse declarations that have macro expansions in their type positions,
-- using the known-types environment from 'typecheckMacros'.
reparseMacroExpansions ::
forall l.
ClangCStandard
-> Map LanC.CName (C.Type PrepareReparse)
-- ^ Known non-macro names and their types (see 'ReparseEnv')
-> Set LanC.CName
-- ^ Known macro names (see 'ReparseEnv')
-> C.TranslationUnit l PrepareReparse
-> C.TranslationUnit l ReparseMacroExpansions
reparseMacroExpansions cStd knownNonMacroTypes knownMacros unit =
let (reparsedDecls, reparseState) =
runM
cStd
(Map.map coercePass knownNonMacroTypes)
knownMacros
(mapM reparseDecl unit.decls)
in C.TranslationUnit{
decls = reparsedDecls
, includeGraph = unit.includeGraph
, meta = updateMeta reparseState.reparseWarnings reparsedDecls unit.meta
}
updateMeta ::
forall l.
[(C.DeclId, DelayedReparseMacroExpansionsMsg)]
-> [C.Decl l ReparseMacroExpansions]
-> DeclMeta l
-> DeclMeta l
updateMeta msgs decls meta = DeclMeta{
declIndex = updatedDeclIndex
, useDeclGraph = UseDeclGraph.fromDeclUseGraph updatedDeclUseGraph
, declUseGraph = updatedDeclUseGraph
}
where
updatedDeclIndex :: DeclIndex l
updatedDeclIndex =
-- We use @foldr@ here to establish the original order of reparse
-- warnings.
foldr
DeclIndex.registerDelayedReparseMacroExpansionsMsg
meta.declIndex
[ (declId, msg)
| (declId, msg) <- msgs
]
updatedDeclUseGraph :: DeclUseGraph
updatedDeclUseGraph =
Foldable.foldl'
(flip updateDeps)
meta.declUseGraph
decls
-- | Dependencies before reparse may point to underlying types. These have to be
-- replaced with their actual dependencies after reparse+zip.
--
-- For example,
--
-- @
-- // c_header.h
-- typedef int A;
-- #define B A
-- void foo(B x);
-- @
--
-- Before reparse, @foo@ directly depends on @A@. After reparse+zip, we know
-- that @foo@ depends on @B@. We have to cut the dependency to @A@ and
-- replace it with the dependency to @B@.
updateDeps ::
C.Decl l ReparseMacroExpansions
-> DeclUseGraph
-> DeclUseGraph
updateDeps decl graph =
DeclUseGraph.insertDepsOfDecl decl $
DeclUseGraph.deleteDeps (Set.singleton decl.info.id) graph
{-------------------------------------------------------------------------------
Reparse declarations with macro expansions
-------------------------------------------------------------------------------}
reparseDecl ::
C.Decl l PrepareReparse
-> M (C.Decl l ReparseMacroExpansions)
reparseDecl decl = case decl.kind of
C.DeclMacro macro -> processMacro info' macro
C.DeclTypedef typedef -> processTypedef info' typedef
C.DeclStruct struct -> processStruct info' struct
C.DeclUnion union -> processUnion info' union
C.DeclEnum enum -> processEnum info' enum
C.DeclUntaggedEnumConstant enumConst -> processUntaggedEnumConstant info' enumConst
C.DeclOpaque mSize -> processOpaque info' (C.DeclOpaque mSize)
C.DeclFunction fun -> processFunction info' fun
C.DeclGlobal global -> processGlobal info' global
where
info' :: C.DeclInfo ReparseMacroExpansions
info' = coercePass decl.info
{-------------------------------------------------------------------------------
Function for each kind of declaration
-------------------------------------------------------------------------------}
-- | Macros have already been type-checked; just coerce the pass annotation.
processMacro ::
C.DeclInfo ReparseMacroExpansions
-> TypecheckedMacro PrepareReparse l
-> M (C.Decl l ReparseMacroExpansions)
processMacro info macro =
pure C.Decl{
info = info
, ann = NoAnn
, kind = C.DeclMacro (coercePassParam macro)
}
processStruct ::
C.DeclInfo ReparseMacroExpansions
-> C.Struct PrepareReparse
-> M (C.Decl l ReparseMacroExpansions)
processStruct info struct = do
mkDecl
<$> mapM (processField info.id) struct.fields
<*> C.traverseFlamField (processRegularField info.id) struct.flam
where
mkDecl ::
[C.Field ReparseMacroExpansions]
-> C.Flam ReparseMacroExpansions
-> C.Decl l ReparseMacroExpansions
mkDecl fields flam = C.Decl{
info = info
, ann = NoAnn
, kind = C.DeclStruct C.Struct{
fields = fields
, flam = flam
, sizeof = struct.sizeof
, alignment = struct.alignment
, ann = struct.ann
}
}
processUnion ::
C.DeclInfo ReparseMacroExpansions
-> C.Union PrepareReparse
-> M (C.Decl l ReparseMacroExpansions)
processUnion info union = do
combineFields <$> mapM (processField info.id) union.fields
where
combineFields ::
[C.Field ReparseMacroExpansions]
-> C.Decl l ReparseMacroExpansions
combineFields fields = C.Decl{
info = info
, ann = NoAnn
, kind = C.DeclUnion C.Union{
fields = fields
, sizeof = union.sizeof
, alignment = union.alignment
, ann = union.ann
}
}
-- | Reconstruct a 'C.FieldInfo' after reparsing.
--
-- When @mName@ is 'Nothing', the original name is preserved; when it is
-- 'Just', the reparsed name replaces it.
mkFieldInfo ::
C.FieldInfo PrepareReparse
-> Maybe Text
-> C.FieldInfo LanC
mkFieldInfo info mName = C.FieldInfo{
name = maybe info.name C.ScopedName mName
, comment = fmap coercePass info.comment
, loc = info.loc
}
processField ::
C.DeclId
-> C.Field PrepareReparse
-> M (C.Field ReparseMacroExpansions)
processField declId = \case
C.FieldRegular field -> C.FieldRegular <$> processRegularField declId field
C.FieldImplicit field -> C.FieldImplicit <$> processImplicitField declId field
processRegularField ::
C.DeclId
-> C.RegularField PrepareReparse
-> M (C.RegularField ReparseMacroExpansions)
processRegularField declId field =
reparseWith declId LanC.reparseField field.ann withoutReparse withReparse
where
withoutReparse :: C.RegularField PrepareReparse
withoutReparse = field
withReparse :: (C.Type LanC, Text) -> M (C.RegularField LanC)
withReparse (ty, name) = pure C.RegularField{
info = mkFieldInfo field.info (Just name)
, typ = ty
, offset = field.offset
, width = field.width
, ann = NoAnn
}
processImplicitField ::
C.DeclId
-> C.ImplicitField PrepareReparse
-> M (C.ImplicitField ReparseMacroExpansions)
processImplicitField declId field = do
indirect' <- mapM (processIndirectField declId) field.indirect
pure C.ImplicitField {
info = coercePass field.info
, typRef = coercePass field.typRef
, offset = field.offset
, indirect = indirect'
, ann = field.ann
}
processIndirectField ::
C.DeclId
-> C.IndirectField PrepareReparse
-> M (C.IndirectField ReparseMacroExpansions)
processIndirectField declId field =
reparseWith declId LanC.reparseField field.ann withoutReparse withReparse
where
withoutReparse :: C.IndirectField PrepareReparse
withoutReparse = field
withReparse :: (C.Type LanC, Text) -> M (C.IndirectField LanC)
withReparse (ty, name) = pure C.IndirectField{
info = mkFieldInfo field.info (Just name)
, typ = ty
, offset = field.offset
, width = field.width
, path = fmap coercePass field.path
, ann = NoAnn
}
processOpaque ::
C.DeclInfo ReparseMacroExpansions
-> C.DeclKind l ReparseMacroExpansions
-> M (C.Decl l ReparseMacroExpansions)
processOpaque info kind =
pure C.Decl{
info = info
, kind = kind
, ann = NoAnn
}
processEnum ::
C.DeclInfo ReparseMacroExpansions
-> C.Enum PrepareReparse
-> M (C.Decl l ReparseMacroExpansions)
processEnum info enum =
mkDecl <$> mapM processEnumConstant enum.constants
where
mkDecl ::
[C.EnumConstant ReparseMacroExpansions]
-> C.Decl l ReparseMacroExpansions
mkDecl enumerators = C.Decl{
info = info
, ann = NoAnn
, kind = C.DeclEnum C.Enum{
typ = coercePass enum.typ
, constants = enumerators
, sizeof = enum.sizeof
, alignment = enum.alignment
, ann = NoAnn
}
}
processUntaggedEnumConstant ::
C.DeclInfo ReparseMacroExpansions
-> C.UntaggedEnumConstant PrepareReparse
-> M (C.Decl l ReparseMacroExpansions)
processUntaggedEnumConstant info enumConst =
mkDecl <$> processEnumConstant enumConst.constant
where
mkDecl ::
C.EnumConstant ReparseMacroExpansions
-> C.Decl l ReparseMacroExpansions
mkDecl constant = C.Decl{
info = info
, ann = NoAnn
, kind = C.DeclUntaggedEnumConstant C.UntaggedEnumConstant{
typ = enumConst.typ
, constant = constant
}
}
processEnumConstant ::
C.EnumConstant PrepareReparse
-> M (C.EnumConstant ReparseMacroExpansions)
processEnumConstant constant =
pure C.EnumConstant{
value = constant.value
, info = C.FieldInfo{
comment = fmap coercePass constant.info.comment
, name = constant.info.name
, loc = constant.info.loc
}
}
processTypedef ::
C.DeclInfo ReparseMacroExpansions
-> C.Typedef PrepareReparse
-> M (C.Decl l ReparseMacroExpansions)
processTypedef info typedef = do
typedef' <- reparseWith info.id LanC.reparseTypedef typedef.ann withoutReparse withReparse
pure C.Decl{
info = info
, ann = NoAnn
, kind = C.DeclTypedef typedef'
}
where
withoutReparse :: C.Typedef PrepareReparse
withoutReparse = typedef
withReparse ::
C.Type LanC
-> M (C.Typedef LanC)
withReparse typ' = pure C.Typedef{
typ = typ'
, ann = NoAnn
}
processFunction ::
C.DeclInfo ReparseMacroExpansions
-> C.Function PrepareReparse
-> M (C.Decl l ReparseMacroExpansions)
processFunction info function = do
function' <- reparseWith info.id LanC.reparseFunDecl function.ann withoutReparse withReparse
pure C.Decl {
info = info
, ann = NoAnn
, kind = C.DeclFunction function'
}
where
withoutReparse :: C.Function PrepareReparse
withoutReparse = function
withReparse ::
(([(Maybe Text, C.Type LanC)] , C.Type LanC) , Text)
-> M (C.Function LanC)
withReparse ((tys, ty), _name) = pure C.Function{
args = map (uncurry mkFunctionArg) tys
, res = ty
, attrs = function.attrs
, ann = NoAnn
}
mkFunctionArg :: Maybe Text -> C.Type LanC -> C.FunctionArg LanC
mkFunctionArg mname typ = C.FunctionArg{
name = C.ScopedName <$> mname
, typ = typ
, ann = NoAnn
}
-- | Globals (externs or constants)
processGlobal ::
C.DeclInfo ReparseMacroExpansions
-> C.Global PrepareReparse
-> M (C.Decl l ReparseMacroExpansions)
processGlobal info global = do
global' <- reparseWith info.id LanC.reparseGlobal global.ann withoutReparse withReparse
pure C.Decl {
info = info
, ann = NoAnn
, kind = C.DeclGlobal global'
}
where
withoutReparse :: C.Global PrepareReparse
withoutReparse = global
withReparse :: C.Type LanC -> M (C.Global LanC)
withReparse ty = pure C.Global{
typ = ty
, ann = NoAnn
}
{-------------------------------------------------------------------------------
Internal: monad used for reparsing
-------------------------------------------------------------------------------}
newtype M a = WrapM { _unwrapM :: StateT ReparseState (Reader ReparseEnv) a }
deriving newtype (
Functor
, Applicative
, Monad
, MonadReader ReparseEnv
, MonadState ReparseState
)
-- | Environment used when reparsing declarations with macro expansions.
data ReparseEnv = ReparseEnv {
-- | Known non-macro names and their types (i.e., @typedef@s, @struct@s,
-- @union@s and @enum@s).
knownTypes :: Map LanC.CName (C.Type LanC)
-- | Known macro names; kept separate so we can restrict the reparse
-- environment to macros actually /expanded/.
, knownMacros :: Set LanC.CName
}
data ReparseState = ReparseState {
-- | Delayed parse messages collected during reparse
--
-- Stored in reverse order.
reparseWarnings :: [(C.DeclId, DelayedReparseMacroExpansionsMsg)]
}
deriving stock (Generic)
runM ::
ClangCStandard
-> Map LanC.CName (C.Type LanC)
-> Set LanC.CName
-> M a
-> (a, ReparseState)
runM cStd knownTypes knownMacros (WrapM ma) = runReader (runStateT ma s) e
where
e :: ReparseEnv
e = ReparseEnv {
-- Add the bespoke types as a fallback (note, 'Map.union' is
-- left-biased).
knownTypes = knownTypes `Map.union` LanC.bespokeTypes cStd
, knownMacros = knownMacros
}
s :: ReparseState
s = ReparseState{
reparseWarnings = []
}
{-------------------------------------------------------------------------------
Reparsing declarations with macro expansions
-------------------------------------------------------------------------------}
-- | Run reparser if needed; use fallback on failure or if not needed.
--
-- On reparsing failure, records @(declId, ReparseMacroExpansionsLanC e)@ in
-- 'ReparseState.reparseWarnings' and uses the fallback value.
--
-- On reparsing success and zip failure, records @(declId,
-- ReparseMacroExpansionsZip e)@ in 'ReparseState.reparseWarnings' and uses the
-- fallback value.
--
reparseWith ::
forall a r. (ZipReparsed r, ForgetAnn r)
=> C.DeclId
-> LanC.Parser a
-> ReparseInfo FlatTokens
-> r PrepareReparse
-> (a -> M (r LanC))
-> M (r ReparseMacroExpansions)
reparseWith declId parser reparseInfo fallback onSuccess = case reparseInfo of
ReparseNotNeeded ->
pure fallback'
ReparseNeeded tokens macroInvs -> do
env <- ask
let
usedMacros :: Set Text
usedMacros = invokedMacros macroInvs
usedKnownMacros :: Set LanC.CName
usedKnownMacros = Set.intersection env.knownMacros usedMacros
usedUnknownMacros :: Set LanC.CName
usedUnknownMacros = Set.difference usedMacros env.knownMacros
forM_ usedUnknownMacros $ \u ->
modify $ #reparseWarnings %~
((declId, ReparseMacroExpansionUnknownType u) :)
let reparseEnv :: LanC.ReparseEnv
reparseEnv = LanC.ReparseEnv env.knownTypes usedKnownMacros
case parser reparseEnv tokens of
Right a -> do
b <- onSuccess a
case zipEither fallback b of
Left es -> do
forM_ es $ \e ->
modify $ #reparseWarnings %~ ((declId, e) :)
pure fallback'
Right c -> pure c
Left e -> do
modify $ #reparseWarnings %~ ((declId, ReparseMacroExpansionsLanC e) :)
pure fallback'
where
fallback' :: r ReparseMacroExpansions
fallback' = forgetAnn fallback