packages feed

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

-- | Declaration index
--
-- Intended for qualified import.
--
-- > import HsBindgen.Frontend.Analysis.DeclIndex (DeclIndex)
-- > import HsBindgen.Frontend.Analysis.DeclIndex qualified as DeclIndex
module HsBindgen.Frontend.Analysis.DeclIndex (
    DeclIndex -- opaque
    -- * Entry
  , UsableEntry(..)
  , UnusableEntry(..)
  , UnusableReason(..)
  , Success(..)
  , Squashed(..)
  , Entry(..)
  , entryToLoc
  , entryToAvailability
    -- * Construction
  , fromParseResults
    -- * Filter
  , filter
  , restrictKeys
  , withoutKeys
    -- * Query parse successes
  , lookup
  , getDecls
    -- * Other queries
  , lookupEntry
  , toList
  , lookupLoc
  , lookupAmbiguity
  , unusableToLoc
  , keysSet
  , getOmitted
  , getSquashed
  , getUnusables
    -- * Support for macro failures
  , registerMacroTypecheckFailure
  , registerDelayedParseMsg
    -- * Support for the @PrepareReparse@ pass
  , registerDelayedPrepareReparseMsg
    -- * Support for the @ReparseMacroExpansions@ pass
  , registerDelayedReparseMacroExpansionsMsg
    -- * Support for binding specifications
  , registerOmittedDeclarations
  , registerExternalDeclarations
    -- * Support for name mangle failures
  , registerSquashedDeclarations
  , registerMangleNamesFailure
    -- * Support for the @TranslateTypes@ pass
  , registerDelayedTranslateTypesMsg
  ) where

import Prelude hiding (filter, lookup)

import Control.Monad.State
import Data.Foldable qualified as Foldable
import Data.Map.Strict qualified as Map
import Data.Set qualified as Set
import Optics.Core (traverseOf)

import Clang.HighLevel.Types

import HsBindgen.Errors
import HsBindgen.Frontend.Analysis.DeclIndex.ResolveMacro
import HsBindgen.Frontend.Pass.ConstructTranslationUnit.IsPass
import HsBindgen.Frontend.Pass.EnrichComments.IsPass
import HsBindgen.Frontend.Pass.MangleNames.Error
import HsBindgen.Frontend.Pass.Parse.Msg
import HsBindgen.Frontend.Pass.Parse.Result
import HsBindgen.Frontend.Pass.PrepareReparse.IsPass.Msg
import HsBindgen.Frontend.Pass.ReparseMacroExpansions.IsPass.Msg (DelayedReparseMacroExpansionsMsg)
import HsBindgen.Frontend.Pass.TranslateTypes.IsPass.Msg (DelayedTranslateTypesMsg)
import HsBindgen.Frontend.Pass.TypecheckMacros.IsPass
import HsBindgen.Frontend.Predicate (IsMainHeader)
import HsBindgen.Imports hiding (toList)
import HsBindgen.IR.C qualified as C
import HsBindgen.IR.Pass (IsPass)
import HsBindgen.Language.Haskell qualified as Hs
import HsBindgen.Macro.Error
import HsBindgen.Macro.Interface qualified as Macro
import HsBindgen.Macro.Type qualified as Macro
import HsBindgen.Macro.UniqueExpansion qualified as UniqueExpansion
import HsBindgen.Macro.UniqueExpansion.Types qualified as UniqueExpansion
import HsBindgen.Util.Tracer

type In  = EnrichComments
type Out = ConstructTranslationUnit

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

-- | The declaration index, parameterized by the pass at which the indexed
-- declarations are represented.
--
-- The public 'DeclIndex' fixes this to 'ConstructTranslationUnit' (macros
-- resolved). The polymorphic form is used internally to build the index at
-- 'EnrichComments' before resolving macros (see 'fromParseResults').
--
-- The declaration index indexes C types (not Haskell types); as such, it
-- contains declarations in the source code, and never contains external
-- declarations.
--
-- When we replace a declaration by an external one while resolving binding
-- specifications, it is not deleted from the declaration index but reclassified
-- as 'UsableExternal'. In the "HsBindgen.Frontend.Analysis.UseDeclGraph",
-- dependency edges from use sites to the replaced declaration are deleted,
-- because the use sites now depend on the external Haskell type.
--
-- For example, assume the C code
--
-- @
-- typedef struct {    // D1
--   int x;
-- } foo_t;            // D2
--
-- typedef foo_t foo;  // D3
-- @
--
-- - D1 declares an untagged @struct@
-- - D2 declares the typedef @foo_t@ depending on D1
-- - D3 declares a typedef @foo@ depending on D2
--
-- The use-decl graph is
--
-- D1 <- D2 <- D3
--
-- Further, assume we have an external binding specification for D2. After
-- resolving external binding specifications, the use-decl graph will be
--
-- D1 <- D2    D3
--      (R2) <-|
--
-- The edge from D3 to D2 was removed, since D3 now depends on a Haskell type
-- R3, which is not part of the use-decl graph.
data DeclIndex l = DeclIndex {
      map             :: Map C.DeclId (Entry l)
      -- | Macros whose expansion depends on where they are invoked
      --
      -- See "HsBindgen.Macro.UniqueExpansion". Ambiguity is independent of the
      -- entries: a macro may be ambiguous whether or not it is usable, and
      -- replacing or removing its entry does not change the verdict.
    , ambiguousMacros :: Set UniqueExpansion.Name
    }
  deriving stock (Show, Generic)

{-------------------------------------------------------------------------------
  Entry
-------------------------------------------------------------------------------}

-- | Usable declaration
--
-- At each stage in the `hs-bindgen` pipeline, a "usable" declaration (i.e.,
-- 'UsableEntry') is a declaration we think we can generate bindings for. Passes
-- may replace "usable" declarations with
--
-- - other "usable" declarations such as external declarations
--   ("ResolveBindingSpecs") or squashed declarations (in "MangleNames"); or
--
-- - "unusable" declarations (i.e, 'UnusableEntry'), for example when macro
--   typechecking fails ("TypecheckMacros") or name mangling fails
--   ("MangleNames").
--
-- At the end of the `hs-bindgen` pipeline, we can generate bindings for
-- "usable" declarations.
--
-- However, usability is not concerned with _transitivity_. Usable declarations
-- may have unusable transitive dependencies. Even though we can generate
-- bindings for usable declarations, they may not be functional because they
-- miss transitive dependencies.
--
-- (We avoid the term available, because it is overloaded with Clang's
-- CXAvailabilityKind).
data UsableEntry l =
    UsableSuccess (Success l Out)
  | UsableExternal C.DeclLocs
    -- Squashed declarations are always "usable" because we only squash
    -- declaration in the list of declarations attached to the declaration unit.
  | UsableSquashed Squashed
  deriving stock (Show, Generic)

usableToLoc :: UsableEntry l -> C.DeclLocs
usableToLoc = \case
    UsableSuccess  x    -> C.DeclLoc x.decl.info.loc
    UsableExternal locs -> locs
    UsableSquashed x    -> C.DeclLoc x.typedefLoc

-- | Unusable declaration
--
-- A declaration is unusable if we cannot generate bindings for it.
--
-- See 'UsableEntry'.
--
-- (We avoid the term available, because it is overloaded with Clang's
-- CXAvailabilityKind).
data UnusableEntry =
    UnusableReason (SingleLoc C.DeclPath) UnusableReason
  | UnusableConflict C.Conflict
  deriving stock (Show, Generic)

instance PrettyForTrace UnusableEntry where
  prettyForTrace = \case
    UnusableReason   _loc unusable -> prettyForTrace unusable
    UnusableConflict      conflict -> prettyForTrace conflict

data UnusableReason =
    UnusableUnavailable
  | UnusableOmitted
  | UnusableParseFailure           DelayedParseMsg
  | UnusableMacroTypecheckFailure  MacroTypecheckError
  | UnusableMacroResolutionFailure MacroResolutionError
  | UnusableMangleNamesFailure     MangleNamesError
  deriving stock (Show, Generic)

instance PrettyForTrace UnusableReason where
  prettyForTrace = \case
    UnusableUnavailable{} ->
      "Declaration is 'unavailable' on this platform"
    UnusableOmitted{} ->
      "Omitted by prescriptive binding specification"
    UnusableParseFailure{} ->
      "Parse failed"
    UnusableMangleNamesFailure{} ->
      "Name mangler failure"
    UnusableMacroTypecheckFailure{} ->
      "Macro type-checking failed"
    UnusableMacroResolutionFailure{} ->
      "Macro name resolution failed"

unusableToLoc :: UnusableEntry -> C.DeclLocs
unusableToLoc = \case
  UnusableReason   loc _ -> C.DeclLoc          loc
  UnusableConflict     c -> C.DeclLocsConflict c

data Success l p = Success {
    decl                              :: C.Decl l p
  , delayedParseMsgs                  :: [DelayedParseMsg]
  , delayedPrepareReparseMsgs         :: [DelayedPrepareReparseMsg]
  , delayedReparseMacroExpansionsMsgs :: [DelayedReparseMacroExpansionsMsg]
  , delayedTranslateTypesMsgs         :: [DelayedTranslateTypesMsg]
  }
  deriving stock (Generic)

deriving stock instance ( IsPass p
                        , Macro.HasTypes l
                        ) => Show (Success l p)

data Squashed = Squashed {
    -- | The location of the squashed typedef (i.e., _not_ the target)
    typedefLoc   :: SingleLoc C.DeclPath
  , targetNameC  :: C.DeclId
  , targetNameHs :: Hs.Name Hs.NsTypeConstr
  }
  deriving stock (Show, Generic)

-- | Entry of declaration index
data Entry l =
    UsableEntry   (UsableEntry l)
  | UnusableEntry UnusableEntry
  deriving stock (Show, Generic)

entryToLoc :: Entry l -> C.DeclLocs
entryToLoc = \case
  UnusableEntry e -> unusableToLoc e
  UsableEntry   e -> usableToLoc e

entryToAvailability :: Entry l -> C.Availability
entryToAvailability = \case
    UsableEntry e   -> case e of
      UsableSuccess success -> success.decl.info.availability
      UsableExternal{}      -> C.Available
      UsableSquashed{}      -> C.Available
    UnusableEntry{} -> C.Available

{-------------------------------------------------------------------------------
  Construction
-------------------------------------------------------------------------------}

-- | Construct the declaration index, resolving macro names.
--
-- Macro resolution needs the set of all declaration IDs, which is only known
-- once all parse results are in. We therefore first resolve the macros in each
-- successful declaration ('resolveMacros'), and then build the index from the
-- resolved results ('buildIndex'), detecting conflicts even with macros that we
-- cannot resolve.
--
-- The analysis of the macro definitions decides whether a macro redefinition
-- is a conflict, and is recorded in the index as the set of ambiguous macros.
fromParseResults ::
     forall l. Macro.HasTypes l
  => Macro.Lang l
  -> UniqueExpansion.Analysis
  -> IsMainHeader
  -> [ParseResult l In]
  -> DeclIndex l
fromParseResults macroLang macroAnalysis isMainHeader parseResults = DeclIndex{
      map             = buildIndex macroAnalysis isMainHeader $
                          resolveMacros macroLang declIds parseResults
    , ambiguousMacros = UniqueExpansion.ambiguous macroAnalysis
    }
  where
    declIds :: Set C.DeclId
    declIds = getDeclIds parseResults

    getDeclIds :: [ParseResult l In] -> Set C.DeclId
    getDeclIds = Set.fromList . map (.id)

{-------------------------------------------------------------------------------
  Macro resolution
-------------------------------------------------------------------------------}

-- | Result of resolving macro names in a single parse result.
data ResolvedResult l =
    -- | Macro names were resolved successfully (or the declaration was not a
    -- macro, or was not a parse success to begin with).
    Resolved (ParseResult l Out)
    -- | Macro names could not be resolved.
  | Unresolved C.DeclId (SingleLoc C.DeclPath) MacroResolutionError

resolvedResultId :: ResolvedResult l -> C.DeclId
resolvedResultId = \case
    Resolved result       -> result.id
    Unresolved declId _ _ -> declId

resolvedResultLoc :: ResolvedResult l -> SingleLoc C.DeclPath
resolvedResultLoc = \case
    Resolved result    -> result.loc
    Unresolved _ loc _ -> loc

resolvedResultToEntry :: ResolvedResult l -> Entry l
resolvedResultToEntry = \case
    Resolved result      -> parseResultToEntry result
    Unresolved _ loc err -> UnusableEntry $ UnusableReason loc (UnusableMacroResolutionFailure err)
  where
    parseResultToEntry :: ParseResult l Out -> Entry l
    parseResultToEntry result = case result.classification of
      ParseResultSuccess r ->
        UsableEntry $ UsableSuccess (parseSuccessToSuccess r)
      ParseResultUnavailable ->
        UnusableEntry $ UnusableReason result.loc UnusableUnavailable
      ParseResultFailure r ->
        UnusableEntry $ UnusableReason result.loc $ UnusableParseFailure r

parseSuccessToSuccess :: ParseSuccess l Out -> Success l Out
parseSuccessToSuccess success = Success {
      decl = success.decl
    , delayedParseMsgs = success.delayedParseMsgs
    , delayedPrepareReparseMsgs = []
    , delayedReparseMacroExpansionsMsgs = []
    , delayedTranslateTypesMsgs = []
    }

-- | Resolve macro names in every successful declaration.
--
-- A declaration whose macro names cannot be resolved becomes 'Unresolved', which
-- 'buildIndex' turns into an 'UnusableMacroResolutionFailure' entry.
resolveMacros ::
     forall l.
     Macro.Lang l
  -> Set C.DeclId
  -> [ParseResult l In]
  -> [ResolvedResult l]
resolveMacros macroLang allDeclIds = map resolveParseResult
  where
    resolveParseResult :: ParseResult l In -> ResolvedResult l
    resolveParseResult result =
      case traverseOf #classification resolveParseClassification result of
        Right resolved -> Resolved resolved
        Left  err      -> Unresolved result.id result.loc err

    resolveParseClassification ::
         ParseClassification l In
      -> Either MacroResolutionError (ParseClassification l Out)
    resolveParseClassification = \case
      ParseResultSuccess success ->
        case resolveMacroWith macroLang allDeclIds success.decl of
          Right resolvedDecl -> Right $
            ParseResultSuccess ParseSuccess{
              decl             = resolvedDecl
            , delayedParseMsgs = success.delayedParseMsgs
            }
          Left err -> Left err
      ParseResultUnavailable ->
        Right $ ParseResultUnavailable
      ParseResultFailure x ->
        Right $ ParseResultFailure x

{-------------------------------------------------------------------------------
  Build from resolved macros
-------------------------------------------------------------------------------}

-- This function checks for conflicts between ordinary declarations and macro
-- declarations, which must be done specially because they are in separate
-- namespaces. This is done here because we need to detect conflicts even with
-- macros that we cannot typecheck, which are thrown out in the
-- @TypecheckMacros@ pass.
buildIndex ::
     forall l. Macro.HasTypes l
  => UniqueExpansion.Analysis
  -> IsMainHeader
  -> [ResolvedResult l]
  -> Map C.DeclId (Entry l)
buildIndex macroAnalysis isMainHeader results =
    execState (mapM_ aux results) Map.empty
  where
    aux :: ResolvedResult l -> State (Map C.DeclId (Entry l)) ()
    aux new = modify' $ \index ->
      let declId :: C.DeclId
          declId = resolvedResultId new

          mConflict :: Maybe (IsConflict l)
          mConflict = Foldable.asum [
              do
                old <- Map.lookup declId index
                pure $ checkIsConflict macroAnalysis isMainHeader new (declId, old)
            , case declId.name.kind of
                C.NameKindOrdinary -> do
                  let altDeclId = C.DeclId{
                          name   = C.DeclName{
                              text = declId.name.text
                            , kind = C.NameKindMacro
                            }
                        , isUnnamed = False
                        }
                  old <- Map.lookup altDeclId index
                  pure $ checkIsConflict macroAnalysis isMainHeader new (altDeclId, old)
                C.NameKindMacro    -> do
                  let altDeclId = C.DeclId{
                          name   = C.DeclName{
                              text = declId.name.text
                            , kind = C.NameKindOrdinary
                            }
                        , isUnnamed = False
                        }
                  old <- Map.lookup altDeclId index
                  pure $ checkIsConflict macroAnalysis isMainHeader new (altDeclId, old)
                C.NameKindTagged{} -> Nothing
            ]
      in  case mConflict of
            Nothing ->
              Map.insert declId (resolvedResultToEntry new) index
            Just (Redefinition entry) ->
              Map.insert declId entry index
            Just (SingleConflict ids conflict) ->
              Map.union
                ( Map.fromList
                    [ (x, UnusableEntry (UnusableConflict conflict))
                    | x <- Set.toList ids
                    ]
                )
                index

{-------------------------------------------------------------------------------
  Conflicts
-------------------------------------------------------------------------------}

data IsConflict l =
    Redefinition (Entry l)
  | SingleConflict (Set C.DeclId) C.Conflict

-- | Check whether a new declaration conflicts with an existing entry
--
-- A macro that is defined more than once is not a conflict if the analysis of
-- the macro definitions deems the redefinition benign. Two ordinary
-- declarations are not a conflict if they are successfully parsed to the same
-- kind of declaration.
checkIsConflict ::
     forall l. Macro.HasTypes l
  => UniqueExpansion.Analysis
  -> IsMainHeader
  -> ResolvedResult l
  -> (C.DeclId, Entry l)
  -> IsConflict l
checkIsConflict macroAnalysis isMainHeader new (oldId, old)
    | C.NameKindMacro <- newId.name.kind
    , C.NameKindMacro <- oldId.name.kind
    = case (redefinition, old) of
        -- E.g., the macro conflicts with an ordinary declaration
        (_, UnusableEntry UnusableConflict{}) -> singleConflict
        (UniqueExpansion.Benign, _)           -> Redefinition macroRedefinition
        (UniqueExpansion.NotBenign, _)        -> singleConflict
    | Resolved new' <- new
    , ParseResultSuccess newSuccess <- new'.classification
    , UsableEntry (UsableSuccess oldSuccess) <- old
    , newSuccess.decl.kind == oldSuccess.decl.kind
    = Redefinition $ UsableEntry $ UsableSuccess $
        mergeRedefinition keep (parseSuccessToSuccess newSuccess) oldSuccess
    | otherwise
    = singleConflict
  where
    newId :: C.DeclId
    newId = resolvedResultId new

    newLoc :: SingleLoc C.DeclPath
    newLoc = resolvedResultLoc new

    redefinition :: UniqueExpansion.Redefinition
    redefinition =
      UniqueExpansion.redefinition macroAnalysis $
        UniqueExpansion.Name newId.name.text

    -- The definitions are identical, so the macro language fared the same with
    -- both.
    macroRedefinition :: Entry l
    macroRedefinition = case (resolvedResultToEntry new, old) of
      ( UsableEntry (UsableSuccess newSuccess)
        , UsableEntry (UsableSuccess oldSuccess) ) ->
          UsableEntry $ UsableSuccess $
            mergeRedefinition keep newSuccess oldSuccess
      (newEntry, _) -> case keep of
        KeepNew -> newEntry
        KeepOld -> old

    keep :: Keep
    keep = keepRedefinition isMainHeader newLoc old

    singleConflict :: IsConflict l
    singleConflict =
      let conflict :: C.Conflict
          conflict = case old of
            UnusableEntry (UnusableConflict c) ->
              C.conflictInsert c newLoc
            _otherwise ->
              C.conflictFromList $
                newLoc : C.declLocsToList (entryToLoc old)
      in SingleConflict (Set.fromList [newId, oldId]) conflict

-- TODO <https://github.com/well-typed/hs-bindgen/issues/2282>
--
-- We should "keep" more information in a redefinition so we can apply the
-- select predicate to any of the declarations.

-- | Which of two definitions of the same declaration to keep
data Keep = KeepNew | KeepOld

-- | Keep the new definition if it is in a main header and the old one is not
--
-- The location of the kept definition decides whether the default selection
-- predicate selects it, and its header determines the generated @#include@.
keepRedefinition :: IsMainHeader -> SingleLoc C.DeclPath -> Entry l -> Keep
keepRedefinition isMainHeader newLoc old
    | inMainHeader newLoc
    , not $ any inMainHeader $ C.declLocsToList (entryToLoc old)
    = KeepNew
    | otherwise
    = KeepOld
  where
    inMainHeader :: SingleLoc C.DeclPath -> Bool
    inMainHeader = maybe False isMainHeader . C.declPathRealPath . singleLocPath

-- | Merge two definitions, keeping the delayed parse messages of both
mergeRedefinition :: Keep -> Success l Out -> Success l Out -> Success l Out
mergeRedefinition keep newSuccess oldSuccess =
    -- TODO <https://github.com/well-typed/hs-bindgen/issues/2099?
    --
    -- We should make sure that we do not report delayed messages multiple
    -- times (e.g., use 'Data.List.nub').
    let delayedParseMsgs =
          newSuccess.delayedParseMsgs ++ oldSuccess.delayedParseMsgs
        delayedPrepareReparseMsgs =
          if not (null oldSuccess.delayedPrepareReparseMsgs) then
            panicPure "expected empty prepare reparse messages"
          else
            []
        kept = case keep of
          KeepNew -> newSuccess
          KeepOld -> oldSuccess
    in kept{
           delayedParseMsgs          = delayedParseMsgs
         , delayedPrepareReparseMsgs = delayedPrepareReparseMsgs
         }

{-------------------------------------------------------------------------------
  Filter
-------------------------------------------------------------------------------}

filter ::
     (C.DeclId -> Entry l -> Bool)
  -> DeclIndex l
  -> DeclIndex l
filter p = #map %~ Map.filterWithKey p

restrictKeys :: DeclIndex l -> Set C.DeclId -> DeclIndex l
restrictKeys index xs = index & #map %~ (`Map.restrictKeys` xs)

withoutKeys :: DeclIndex l -> Set C.DeclId -> DeclIndex l
withoutKeys index xs = index & #map %~ (`Map.withoutKeys` xs)

{-------------------------------------------------------------------------------
  Query parse successes
-------------------------------------------------------------------------------}

-- | Lookup parse success.
lookup :: C.DeclId -> DeclIndex l -> Maybe (C.Decl l Out)
lookup declId index = case Map.lookup declId index.map of
  Nothing                          -> Nothing
  Just (UsableEntry (UsableSuccess x)) -> Just $ x.decl
  _                                -> Nothing

-- | Get all parse successes.
getDecls :: DeclIndex l -> [C.Decl l Out]
getDecls index = mapMaybe toDecl $ Map.elems index.map
  where
    toDecl = \case
      UsableEntry (UsableSuccess x) -> Just x.decl
      _otherEntries             -> Nothing

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

-- | Lookup an entry of a declaration index.
lookupEntry :: C.DeclId -> DeclIndex l -> Maybe (Entry l)
lookupEntry x index = Map.lookup x index.map

-- | Get all entries of a declaration index.
toList :: DeclIndex l -> [(C.DeclId, Entry l)]
toList index = Map.toList index.map

-- | Get the source locations of a declaration.
--
-- 'Nothing' indicates the declaration was /not found/.
lookupLoc :: C.DeclId -> DeclIndex l -> Maybe C.DeclLocs
lookupLoc d i = entryToLoc <$> lookupEntry d i

-- | Does the expansion of a macro depend on where it is invoked?
--
-- An identifier that is not defined as a macro is
-- 'UniqueExpansion.Unambiguous'.
lookupAmbiguity :: UniqueExpansion.Name -> DeclIndex l -> UniqueExpansion.Ambiguity
lookupAmbiguity name index
    | name `Set.member` index.ambiguousMacros =
        UniqueExpansion.Ambiguous
    | otherwise =
        UniqueExpansion.Unambiguous

-- | Get the identifiers of all declarations in the index.
keysSet :: DeclIndex l -> Set C.DeclId
keysSet index = Map.keysSet index.map

-- | Get omitted entries.
getOmitted :: DeclIndex l -> Map C.DeclId RealPath
getOmitted index = Map.mapMaybe toOmitted index.map
  where
    toOmitted :: Entry l -> Maybe RealPath
    toOmitted = \case
      UsableEntry   (UsableSquashed e) ->
        lookupEntry e.targetNameC index >>= toOmitted
      UsableEntry{} ->
        Nothing
      UnusableEntry (UnusableReason loc UnusableOmitted) ->
        C.declPathRealPath (singleLocPath loc)
      UnusableEntry{} ->
        Nothing

-- | Get squashed entries.
--
-- TODO <https://github.com/well-typed/hs-bindgen/issues/1549>
-- We may no longer need `getSquashed` once we properly record lists of aliases.
getSquashed ::
     DeclIndex l
  -> Set C.DeclId
  -> Map C.DeclId (RealPath, Hs.Name Hs.NsTypeConstr)
getSquashed index targets = Map.mapMaybe onlySquashedTargetingSet index.map
  where
    onlySquashedTargetingSet ::
         Entry l
      -> Maybe (RealPath, Hs.Name Hs.NsTypeConstr)
    onlySquashedTargetingSet = \case
      UsableEntry (UsableSquashed e) ->
        if Set.member e.targetNameC targets then
          (, e.targetNameHs) <$> C.declPathRealPath (singleLocPath e.typedefLoc)
        else
          Nothing
      _otherwise  -> Nothing

-- | Restrict the declaration index to unusable declarations in a given set.
getUnusables :: DeclIndex l -> Set C.DeclId -> Map C.DeclId UnusableEntry
getUnusables index = Map.mapMaybe onlyUnusable . (.map) . restrictKeys index
  where
    onlyUnusable :: Entry l -> Maybe UnusableEntry
    onlyUnusable = \case
      UsableEntry   _ -> Nothing
      UnusableEntry e -> Just e

{-------------------------------------------------------------------------------
  Support for delayed parse messages
-------------------------------------------------------------------------------}

-- | Append a delayed parse message to an existing 'UsableSuccess' entry.
--
-- Has no effect if the declaration is not a parse success (e.g., if it is a
-- parse failure or a macro failure).
registerDelayedParseMsg ::
     (C.DeclId, DelayedParseMsg)
  -> DeclIndex l
  -> DeclIndex l
registerDelayedParseMsg (declId, msg) =
    #map %~ Map.adjust addMsg declId
  where
    addMsg :: Entry l -> Entry l
    addMsg (UsableEntry (UsableSuccess ps)) =
      UsableEntry $ UsableSuccess ps {
          HsBindgen.Frontend.Analysis.DeclIndex.delayedParseMsgs =
            ps.delayedParseMsgs ++ [msg]
        }
    addMsg entry = entry

{-------------------------------------------------------------------------------
  Support for macro failures
-------------------------------------------------------------------------------}

registerMacroTypecheckFailure ::
     DeclIndex l
  -> (C.DeclInfo TypecheckMacros, MacroTypecheckError)
  -> DeclIndex l
registerMacroTypecheckFailure index (info, err) = index
    & #map %~ Map.insert info.id (UnusableEntry $ UnusableReason info.loc $
        UnusableMacroTypecheckFailure err)

{-------------------------------------------------------------------------------
  Support for @PrepareReparse@ pass
-------------------------------------------------------------------------------}

-- | Append a delayed @PrepareReparse@ message to an existing 'UsableSuccess'
-- entry.
--
-- Has no effect if the declaration is not a success
registerDelayedPrepareReparseMsg ::
     (C.DeclId, DelayedPrepareReparseMsg)
  -> DeclIndex l
  -> DeclIndex l
registerDelayedPrepareReparseMsg (declId, msg) =
    #map %~ Map.adjust addMsg declId
  where
    addMsg :: Entry l -> Entry l
    addMsg (UsableEntry (UsableSuccess ps)) =
      UsableEntry $ UsableSuccess ps{
          delayedPrepareReparseMsgs = ps.delayedPrepareReparseMsgs ++ [msg]
        }
    addMsg entry = entry


{-------------------------------------------------------------------------------
  Support for @ReparseMacroExpansions@ pass
-------------------------------------------------------------------------------}

-- | Append a delayed @ReparseMacroExpansions@ message to an existing
-- 'UsableSuccess' entry.
--
-- Has no effect if the declaration is not a success
registerDelayedReparseMacroExpansionsMsg ::
     (C.DeclId, DelayedReparseMacroExpansionsMsg)
  -> DeclIndex l
  -> DeclIndex l
registerDelayedReparseMacroExpansionsMsg (declId, msg) =
    #map %~ Map.adjust addMsg declId
  where
    addMsg :: Entry l -> Entry l
    addMsg (UsableEntry (UsableSuccess ps)) =
      UsableEntry $ UsableSuccess ps{
          delayedReparseMacroExpansionsMsgs = ps.delayedReparseMacroExpansionsMsgs ++ [msg]
        }
    addMsg entry = entry

{-------------------------------------------------------------------------------
  Support for binding specifications
-------------------------------------------------------------------------------}

registerOmittedDeclarations ::
     Map C.DeclId (SingleLoc C.DeclPath)
  -> DeclIndex l
  -> DeclIndex l
registerOmittedDeclarations xs =
    #map %~ Map.union (toOmitted <$> xs)
  where
    toOmitted loc = UnusableEntry $ UnusableReason loc UnusableOmitted

registerExternalDeclarations ::
     [(C.DeclId, C.DeclLocs)]
  -> DeclIndex l
  -> DeclIndex l
registerExternalDeclarations xs index = Foldable.foldl' insert index xs
  where
    insert :: DeclIndex l -> (C.DeclId, C.DeclLocs) -> DeclIndex l
    insert index' (declId, locs) =
      index' & #map %~ Map.insert declId (UsableEntry $ UsableExternal locs)

{-------------------------------------------------------------------------------
  Support for mangle names
-------------------------------------------------------------------------------}

registerSquashedDeclarations ::
     Map C.DeclId Squashed
  -> DeclIndex l
  -> DeclIndex l
registerSquashedDeclarations xs =
    #map %~ Map.union (UsableEntry . UsableSquashed <$> xs)

registerMangleNamesFailure ::
     Map C.DeclId (SingleLoc C.DeclPath, MangleNamesError)
  -> DeclIndex l
  -> DeclIndex l
registerMangleNamesFailure xs =
    #map %~ Map.union (toEntry <$> xs)
  where
    toEntry (loc, err) =
      UnusableEntry $ UnusableReason loc $ UnusableMangleNamesFailure err

{-------------------------------------------------------------------------------
  Support for @TranslateTypes@ pass
-------------------------------------------------------------------------------}

-- | Append a delayed @TranslateTypes@ message to an existing
-- 'UsableSuccess' entry.
--
-- Has no effect if the declaration is not a success
registerDelayedTranslateTypesMsg ::
     (C.DeclId, DelayedTranslateTypesMsg)
  -> DeclIndex l
  -> DeclIndex l
registerDelayedTranslateTypesMsg (declId, msg) =
    #map %~ Map.adjust addMsg declId
  where
    addMsg :: Entry l -> Entry l
    addMsg (UsableEntry (UsableSuccess ps)) =
      UsableEntry $ UsableSuccess ps{
          delayedTranslateTypesMsgs = ps.delayedTranslateTypesMsgs ++ [msg]
        }
    addMsg entry = entry