hs-bindgen-1.0.0.0: src-internal/HsBindgen/Frontend/Pass/MangleNames/ResolveNames.hs
module HsBindgen.Frontend.Pass.MangleNames.ResolveNames (
resolveNames
) where
import Control.Applicative (asum)
import Control.Monad.Except (ExceptT, MonadError (..), runExceptT)
import Control.Monad.Reader (Reader, ask, local, runReader)
import Data.Either (partitionEithers)
import Data.List.NonEmpty qualified as NonEmpty
import Data.Map qualified as Map
import Data.Proxy
import Data.Set qualified as Set
import HsBindgen.BindingSpec
import HsBindgen.Frontend.Pass.MangleNames.CreateNames
import HsBindgen.Frontend.Pass.MangleNames.Error
import HsBindgen.Frontend.Pass.MangleNames.IsPass
import HsBindgen.Frontend.Pass.MangleNames.Names
import HsBindgen.Frontend.Pass.TypecheckMacros.IsPass
import HsBindgen.Imports
import HsBindgen.IR.C qualified as C
import HsBindgen.IR.Translation
import HsBindgen.Language.Haskell qualified as Hs
import HsBindgen.Macro.Type qualified as Macro
import Doxygen.Parser.Types qualified as Doxy
{-------------------------------------------------------------------------------
Traversal 3: Resolve names
Rewrite every 'C.DeclId' reference into a 'DeclIdPair' and every
'C.ScopedName' reference into a 'ScopedNamePair' using the 'NameMap', copying
annotations across unchanged. Declarations flagged by 'detectClashes' are
dropped (their failure is already recorded); a reference to an unmangleable
declaration drops the referring declaration with a
'ResolveNamesUnderlyingDeclNotMangled' failure. A reference to an unmangleable
scoped name drops the referring declaration with a
'ResolveNamesScopedNameNotMangled' failure.
-------------------------------------------------------------------------------}
type ResolveM = Reader NameMap
type ResolveE a = ExceptT MangleNamesResolutionError ResolveM a
resolveNames ::
forall l. Macro.HasTypes l
=> NameMap
-> Set C.DeclId
-> [C.Decl l CreateNames]
-> ([C.Decl l MangleNames], [MangleNamesFailure])
resolveNames nameMap dropped decls = (oks, failures)
where
failures :: [MangleNamesFailure]
oks :: [C.Decl l MangleNames]
(failures, oks) = partitionEithers $ mapMaybe resolveOne decls
resolveOne ::
C.Decl l CreateNames
-> Maybe (Either MangleNamesFailure (C.Decl l MangleNames))
resolveOne decl
| Set.member decl.info.id dropped = Nothing
| otherwise = Just $
case runReader (runExceptT (resolveDecl decl)) nameMap of
Left err -> Left $ toFailure decl.info (MangleNamesResolutionError err)
Right decl' -> Right decl'
resolveDecl ::
Macro.HasTypes l
=> C.Decl l CreateNames
-> ResolveE (C.Decl l MangleNames)
resolveDecl decl =
withDeclNamespace decl.kind $ \nsProxy -> do
info' <- resolveDeclInfo nsProxy decl.info
kind' <- resolveDeclKind decl.info decl.kind
pure C.Decl{
info = info'
, kind = kind'
, ann = decl.ann
}
resolveDeclInfo ::
forall ns. Hs.SingNamespace ns
=> Proxy ns
-> C.DeclInfo CreateNames
-> ResolveE (C.DeclInfo MangleNames)
resolveDeclInfo nsProxy info = do
hsName <- resolveDeclName nsProxy info.id
comment' <- traverse (resolve info) info.comment
enclosing' <- mapM resolveEnclosingRef info.enclosing
pure C.DeclInfo{
loc = info.loc
, id = DeclIdPair{
cName = info.id
, hsName = Hs.demoteNs hsName
}
, sourceOrderIndex = info.sourceOrderIndex
, origin = info.origin
, availability = info.availability
, comment = comment'
, enclosing = enclosing'
}
resolveFieldInfo ::
C.DeclInfo CreateNames
-> C.FieldInfo CreateNames
-> ResolveE (C.FieldInfo MangleNames)
resolveFieldInfo info fieldInfo = do
name' <- lookupScopedNamePairR info.id fieldInfo.name
comment' <- traverse (resolve info) fieldInfo.comment
pure C.FieldInfo{
loc = fieldInfo.loc
, name = name'
, comment = comment'
}
resolveDeclName ::
forall ns. Hs.SingNamespace ns
=> Proxy ns -> C.DeclId -> ResolveE (Hs.Name ns)
resolveDeclName _ declId = case Hs.singNamespace @ns of
Hs.SNsTypeConstr -> lookupTypeR declId
Hs.SNsConstr -> lookupDataR declId
Hs.SNsVar -> lookupVarR declId
resolveEnclosingRef ::
C.EnclosingRef CreateNames
-> ResolveE (C.EnclosingRef MangleNames)
resolveEnclosingRef = \case
C.EnclosingRef e -> do
-- We must not propagate potential name mangler failures of the enclosing
-- declaration, which is a backwards reference. Instead, we mark the
-- reference "unusable".
r <- tryError $ lookupTypePairR e
pure $ either (const (C.UnusableEnclosingRef e)) C.EnclosingRef r
C.UnusableEnclosingRef e ->
pure $ C.UnusableEnclosingRef e
resolveDeclKind ::
Macro.HasTypes l
=> C.DeclInfo CreateNames
-> C.DeclKind l CreateNames
-> ResolveE (C.DeclKind l MangleNames)
resolveDeclKind info = \case
C.DeclStruct x -> C.DeclStruct <$> resolve info x
C.DeclUnion x -> C.DeclUnion <$> resolve info x
C.DeclEnum x -> C.DeclEnum <$> resolve info x
C.DeclUntaggedEnumConstant x -> C.DeclUntaggedEnumConstant <$> resolve info x
C.DeclTypedef x -> C.DeclTypedef <$> resolve info x
C.DeclFunction x -> C.DeclFunction <$> resolve info x
C.DeclMacro x -> C.DeclMacro <$> resolveMacro x
C.DeclGlobal x -> C.DeclGlobal <$> resolve info x
C.DeclOpaque mSize -> pure (C.DeclOpaque mSize)
{-------------------------------------------------------------------------------
Traversal 3: name-map lookups
-------------------------------------------------------------------------------}
lookupTypeR :: C.DeclId -> ResolveE (Hs.Name Hs.NsTypeConstr)
lookupTypeR declId = do
nameMap <- ask
case lookupType declId nameMap of
Nothing ->
throwError $
ResolveNamesUnderlyingDeclNotMangled declId (NonEmpty.singleton Hs.NsTypeConstr)
Just hsNm -> pure hsNm
lookupDataR :: C.DeclId -> ResolveE (Hs.Name Hs.NsConstr)
lookupDataR declId = do
nameMap <- ask
case lookupData declId nameMap of
Nothing ->
throwError $
ResolveNamesUnderlyingDeclNotMangled declId (NonEmpty.singleton Hs.NsConstr)
Just hsNm -> pure hsNm
lookupVarR :: C.DeclId -> ResolveE (Hs.Name Hs.NsVar)
lookupVarR declId = do
nameMap <- ask
case lookupVar declId nameMap of
Nothing ->
throwError $
ResolveNamesUnderlyingDeclNotMangled declId (NonEmpty.singleton Hs.NsVar)
Just hsNm -> pure hsNm
lookupScopedNameR :: C.DeclId -> C.ScopedName -> ResolveE Hs.SomeName
lookupScopedNameR declId scopedName = do
nameMap <- ask
case lookupScopedName declId scopedName nameMap of
Nothing ->
throwError $
ResolveNamesScopedNameNotMangled declId scopedName
Just hsNm -> pure hsNm
lookupTypePairR :: C.DeclId -> ResolveE DeclIdPair
lookupTypePairR declId =
(\hsName -> DeclIdPair declId (Hs.demoteNs hsName)) <$> lookupTypeR declId
lookupVarPairR :: C.DeclId -> ResolveE DeclIdPair
lookupVarPairR declId =
(\hsName -> DeclIdPair declId (Hs.demoteNs hsName)) <$> lookupVarR declId
lookupScopedNamePairR :: C.DeclId -> C.ScopedName -> ResolveE ScopedNamePair
lookupScopedNamePairR declId scopedName =
ScopedNamePair scopedName <$> lookupScopedNameR declId scopedName
{-------------------------------------------------------------------------------
Traversal 3: Resolve instances
-------------------------------------------------------------------------------}
class Resolve a where
resolve :: C.DeclInfo CreateNames -> a CreateNames -> ResolveE (a MangleNames)
instance Resolve C.Struct where
resolve info struct = do
fields <- mapM (resolve info) struct.fields
flam <- C.traverseFlamField (resolve info) struct.flam
pure C.Struct{
fields = fields
, flam = flam
, ann = struct.ann
, sizeof = struct.sizeof
, alignment = struct.alignment
}
instance Resolve C.Union where
resolve info union = do
fields <- mapM (resolve info) union.fields
pure C.Union{
fields = fields
, ann = union.ann
, sizeof = union.sizeof
, alignment = union.alignment
}
instance Resolve C.Field where
resolve info = \case
C.FieldRegular field -> C.FieldRegular <$> (resolve info) field
C.FieldImplicit field -> C.FieldImplicit <$> (resolve info) field
instance Resolve C.RegularField where
resolve info field = do
fieldInfo' <- resolveFieldInfo info field.info
typ' <- (resolve info) field.typ
pure C.RegularField{
info = fieldInfo'
, typ = typ'
, offset = field.offset
, width = field.width
, ann = field.ann
}
instance Resolve C.ImplicitField where
resolve info field = do
fieldInfo' <- resolveFieldInfo info field.info
typRef' <- resolve info field.typRef
indirect' <- mapM (resolveIndirectField info field.typRef) field.indirect
pure C.ImplicitField{
info = fieldInfo'
, typRef = typRef'
, offset = field.offset
, indirect = indirect'
, ann = field.ann
}
instance Resolve C.AnonRef where
resolve info = \case
C.AnonRef ref -> C.AnonRef <$> lookupTypePairR ref
C.AnonExtBinding ext -> C.AnonExtBinding <$> resolveExtBindingRef info ext
resolveIndirectField ::
C.DeclInfo CreateNames
-> C.AnonRef CreateNames
-> C.IndirectField CreateNames
-> ResolveE (C.IndirectField MangleNames)
resolveIndirectField info anonRef field = do
fieldInfo' <- resolveFieldInfo info field.info
typ' <- resolve info field.typ
path' <- mapM (resolve info) field.path
ann' <- IndirectFieldNames <$> lookupScopedNamePairR anonDeclId field.info.name
pure C.IndirectField{
info = fieldInfo'
, typ = typ'
, offset = field.offset
, width = field.width
, path = path'
, ann = ann'
}
where
anonDeclId = case anonRef of
C.AnonRef declId -> declId
C.AnonExtBinding ext -> ext.name.cName
instance Resolve C.Enum where
resolve info enum = do
typ' <- (resolve info) enum.typ
constants' <- mapM (resolve info) enum.constants
pure C.Enum{
typ = typ'
, constants = constants'
, ann = enum.ann
, sizeof = enum.sizeof
, alignment = enum.alignment
}
instance Resolve C.EnumConstant where
resolve info constant = do
fieldInfo' <- resolveFieldInfo info constant.info
pure C.EnumConstant{
info = fieldInfo'
, value = constant.value
}
instance Resolve C.UntaggedEnumConstant where
resolve info (C.UntaggedEnumConstant primTyp constant) = do
constant' <- (resolve info) constant
pure C.UntaggedEnumConstant{
typ = primTyp
, constant = constant'
}
instance Resolve C.Typedef where
resolve info typedef = do
typ' <- (resolve info) typedef.typ
pure C.Typedef{
typ = typ'
, ann = typedef.ann
}
instance Resolve C.Function where
resolve info function = do
args <- mapM resolveArg function.args
res' <- (resolve info) function.res
pure C.Function{
args = args
, res = res'
, attrs = function.attrs
, ann = function.ann
}
where
resolveArg ::
C.FunctionArg CreateNames
-> ResolveE (C.FunctionArg MangleNames)
resolveArg arg = do
name' <- mapM (lookupScopedNamePairR info.id) arg.name
typ' <- resolve info arg.typ
pure C.FunctionArg{
name = name'
, typ = typ'
, ann = arg.ann
}
instance Resolve C.Global where
resolve info global = do
typ' <- resolve info global.typ
pure C.Global{
typ = typ'
, ann = global.ann
}
resolveMacro ::
Macro.HasTypes l
=> TypecheckedMacro CreateNames l
-> ResolveE (TypecheckedMacro MangleNames l)
resolveMacro = \case
MacroType typ -> MacroType <$> resolveMacroType typ
MacroValue val -> MacroValue <$> resolveMacroValue val
resolveMacroType ::
Macro.HasTypes l
=> TypecheckedMacroType l CreateNames
-> ResolveE (TypecheckedMacroType l MangleNames)
resolveMacroType macroType = do
body' <- traverse resolveMacroTypeVar macroType.body
pure TypecheckedMacroType{
body = body'
, deps = macroType.deps
, ann = macroType.ann
}
where
resolveMacroTypeVar ::
MacroTypeBodyVar CreateNames
-> ResolveE (MacroTypeBodyVar MangleNames)
resolveMacroTypeVar = \case
MacroTypeExtBinding ext -> pure $ MacroTypeExtBinding ext
MacroTypeBodyVar declId -> MacroTypeBodyVar <$> lookupTypePairR declId
resolveMacroValue ::
Macro.HasTypes l
=> TypecheckedMacroValue l CreateNames
-> ResolveE (TypecheckedMacroValue l MangleNames)
resolveMacroValue macroValue = do
body' <- traverse lookupVarPairR macroValue.body
pure $ TypecheckedMacroValue body' macroValue.deps
instance Resolve C.Comment where
resolve info (C.Comment comment) = C.Comment <$> mapM (resolve info) comment
instance Resolve C.CommentRef where
resolve _info = \case
C.CommentRef name Nothing mKind -> do
nameMap <- ask
pure $ C.CommentRef name (searchNameMap nameMap name mKind) mKind
C.CommentRef name (Just declId) mKind ->
(\pair -> C.CommentRef name (Just pair) mKind) <$> lookupAnyPair declId
instance Resolve C.Type where
resolve info = \case
-- Interesting cases
C.TypeRef declId -> fmap C.TypeRef $
lookupTypePairR declId
C.TypeEnum ref -> fmap C.TypeEnum $
C.Ref <$> lookupTypePairR ref.name <*> resolve info ref.underlying
C.TypeMacro ref -> fmap C.TypeMacro $
C.MacroRef <$> lookupTypePairR ref.name <*> resolve info ref.underlying
C.TypeTypedef ref -> fmap C.TypeTypedef $
C.Ref <$> lookupTypePairR ref.name <*> resolve info ref.underlying
-- Recursive cases
C.TypePointers n typ -> C.TypePointers n <$> resolve info typ
C.TypeFun args res -> C.TypeFun <$> mapM (resolve info) args <*> resolve info res
C.TypeConstArray n typ -> C.TypeConstArray n <$> resolve info typ
C.TypeIncompleteArray typ -> C.TypeIncompleteArray <$> resolve info typ
C.TypeBlock typ -> C.TypeBlock <$> resolve info typ
C.TypeQual qual typ -> C.TypeQual qual <$> resolve info typ
C.TypeExtBinding ref -> C.TypeExtBinding <$> resolveExtBindingRef info ref
-- The other entries do not need any name mangling
C.TypePrim prim -> pure $ C.TypePrim prim
C.TypeVoid -> pure C.TypeVoid
C.TypeComplex prim -> pure $ C.TypeComplex prim
instance Resolve C.TypeFunArg where
resolve info arg = C.TypeFunArgF <$> resolve info arg.typ <*> pure arg.ann
resolveExtBindingRef ::
C.DeclInfo CreateNames
-> C.ExtBindingRef CreateNames
-> ResolveE (C.ExtBindingRef MangleNames)
resolveExtBindingRef info (C.Ref ext uTy) =
-- The underlying type may reference the external binding itself (e.g.
-- the typedef name that was replaced). We extend the 'NameMap' with the
-- external binding so that such references can be resolved.
C.Ref ext <$>
local
( #typeConstrs %~ Map.insert ext.cName ext.hsName.name )
( resolve info uTy )
{-------------------------------------------------------------------------------
Resolving comment references
-------------------------------------------------------------------------------}
-- | Dispatch on the Doxygen @kindref@ attribute to choose which 'C.NameKind'
-- values to try. Per-kind lookup is handled by 'lookupByKind', which narrows
-- sub-map searches:
--
-- * Compound → only tagged kinds, only 'typeConstrs' (3 lookups)
-- * Member → ordinary + macro kinds, 'typeConstrs' + 'vars' (4 lookups)
-- * Nothing → all kinds, per-kind dispatch (7 lookups, fallback)
searchNameMap :: NameMap -> Text -> Maybe Doxy.RefKind -> Maybe DeclIdPair
searchNameMap nameMap name = \case
Just Doxy.RefCompound ->
searchKinds
[ C.NameKindTagged C.TagKindStruct
, C.NameKindTagged C.TagKindUnion
, C.NameKindTagged C.TagKindEnum ]
Just Doxy.RefMember ->
searchKinds [C.NameKindOrdinary, C.NameKindMacro]
Nothing ->
searchKinds [minBound .. maxBound]
where
-- | For each 'C.NameKind', construct a 'C.DeclId' and try the appropriate
-- sub-maps via 'lookupByKind'. Return the first match.
searchKinds :: [C.NameKind] -> Maybe DeclIdPair
searchKinds kinds = asum
[ lookupByKind kind (C.DeclId (C.DeclName name kind) False) nameMap
| kind <- kinds
]
-- | Dispatch to the appropriate sub-map(s) based on 'C.NameKind'.
--
-- Tagged names can only be type constructors; ordinary and macro names can be
-- type constructors or variables.
lookupByKind :: C.NameKind -> C.DeclId -> NameMap -> Maybe DeclIdPair
lookupByKind = \case
C.NameKindTagged{} -> lookupTypeNs
_ -> lookupTypeOrVarNs
where
lookupTypeNs :: C.DeclId -> NameMap -> Maybe DeclIdPair
lookupTypeNs declId nameMap =
DeclIdPair declId . Hs.demoteNs <$> lookupType declId nameMap
lookupTypeOrVarNs :: C.DeclId -> NameMap -> Maybe DeclIdPair
lookupTypeOrVarNs declId nameMap =
DeclIdPair declId <$> asum
[ Hs.demoteNs <$> lookupType declId nameMap
, Hs.demoteNs <$> lookupVar declId nameMap
]
lookupAnyPair :: C.DeclId -> ResolveE DeclIdPair
lookupAnyPair declId = do
nameMap <- ask
case lookupByKind declId.name.kind declId nameMap of
Nothing ->
throwError $
ResolveNamesUnderlyingDeclNotMangled declId $ case declId.name.kind of
C.NameKindTagged{} -> NonEmpty.singleton Hs.NsTypeConstr
_ -> NonEmpty.fromList [Hs.NsTypeConstr, Hs.NsVar]
Just pair ->
pure pair
{-------------------------------------------------------------------------------
Internal auxiliary
-------------------------------------------------------------------------------}
-- The function 'tryError' is only available in `mtl` versions 2.3 and newer.
tryError :: MonadError e m => m a -> m (Either e a)
tryError action = (Right <$> action) `catchError` (pure . Left)