packages feed

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

module HsBindgen.Frontend.Pass.Select (
    selectDecls
  ) where

import Data.Foldable qualified as Foldable
import Data.List qualified as List
import Data.List.NonEmpty qualified as NonEmpty
import Data.Map.Strict qualified as Map
import Data.Maybe (listToMaybe, maybeToList)
import Data.Set ((\\))
import Data.Set qualified as Set

import Clang.HighLevel.Types

import HsBindgen.Errors (panicPure)
import HsBindgen.Frontend.Analysis.DeclIndex (DeclIndex, Entry (..),
                                              Success (..), UnusableEntry (..),
                                              UnusableReason (..),
                                              UsableEntry (..))
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.IncludeGraph (IncludeGraph)
import HsBindgen.Frontend.Analysis.IncludeGraph qualified as IncludeGraph
import HsBindgen.Frontend.Analysis.UseDeclGraph (UseDeclGraph)
import HsBindgen.Frontend.Analysis.UseDeclGraph qualified as UseDeclGraph
import HsBindgen.Frontend.DeclMeta
import HsBindgen.Frontend.Pass.ConstructTranslationUnit.IsPass
import HsBindgen.Frontend.Pass.Parse.Msg
import HsBindgen.Frontend.Pass.Select.IsPass
import HsBindgen.Frontend.Pass.TranslateTypes.IsPass
import HsBindgen.Frontend.Predicate
import HsBindgen.Frontend.TranslationUnit qualified as C
import HsBindgen.Imports
import HsBindgen.IR.C qualified as C
import HsBindgen.IR.Pass
import HsBindgen.IR.Translation
import HsBindgen.Util.Tracer

{-------------------------------------------------------------------------------
  Data types
-------------------------------------------------------------------------------}

-- Declaration itself.
type Decl l = C.Decl l Select

-- | Internal data type! This data type
-- 'HsBindgen.Frontend.Pass.Select.Unselectable' refers to declarations that are
-- not selectable _from the perspective of `hs-bindgen`_; and, in particular,
-- not from the perspective of the user (they can change the select predicate).
--
-- Also, (and in contrast to 'UsableEntry'/'UnusableEntry'), selectability _is
-- concerned with transitivity_. All transitive dependencies of a selectable
-- declaration must also be selectable.
data Unselectable =
    -- | We (i.e., `hs-bindgen`) can not select a declaration selected because
    --   it or one of its dependencies is unusable.
    Unselectable UnusableEntry
    -- | We (i.e., `hs-bindgen`) can not select a declaration because one of its
    --   dependencies has not been selected by the user.
  | UnselectableDependencyNotSelected
  deriving stock (Show)

-- | We have to treat with two notions of usability here:
--
-- 1. A declaration can be usable because it is in the list of declarations
--    attached to the translation unit.
--
-- 2. A declaration is available if the declaration itself and all of its
--    transitive dependencies are available.
--
-- @TransitiveSelectability@ deals with the second type.
--
-- (We avoid the term available, because it is overloaded with Clang's
-- CXAvailabilityKind).
data TransitiveSelectability =
    TransitivelySelectable
    -- | For each transitive dependency, we try to give the root cause of
    -- unavailability.
    --
    -- We should use a "non-empty" map here.
  | TransitivelyUnselectable (Map C.DeclId Unselectable)
  deriving stock (Show)

{-------------------------------------------------------------------------------
  Select
-------------------------------------------------------------------------------}

selectDecls ::
     forall l. HasCallStack
  => IsMainHeader
  -> IsInMainHeaderDir
  -> SelectConfig
  -> C.TranslationUnit l TranslateTypes
  -> (C.TranslationUnit l Select, [AnnMsg Select])
selectDecls isMainHeader isInMainHeaderDir config unit =
    let -- Directly match the selection predicate on the 'DeclIndex', obtaining
        -- information about succeeded _and failed_ selection roots.
        selectionRootsIndex :: DeclIndex l
        selectionRootsIndex = selectDeclIndex unit.meta.declUseGraph match index

        -- Identifiers of selection roots. Some of them may be unavailable
        -- (i.e., not in the 'succeeded' map, and hence, not in the list of
        -- declarations attached to the translation unit).
        rootIds :: Set C.DeclId
        rootIds = DeclIndex.keysSet selectionRootsIndex

        -- Identifiers of transitive dependencies including selection roots.
        rootAndTransDepIds :: Set C.DeclId
        rootAndTransDepIds = UseDeclGraph.getTransitiveDeps useDeclGraph rootIds

        -- Identifiers of transitive dependencies excluding selection roots.
        strictTransDepIds :: Set C.DeclId
        strictTransDepIds = rootAndTransDepIds \\ rootIds

        -- Identifiers of all selected declarations.
        selectedIds :: Set C.DeclId
        -- Identifiers of (additional) transitive dependencies selected due to
        -- program slicing. This is the only point where we differentiate
        -- between selection with or without program slicing.
        additionalSelectedTransDepIds :: Set C.DeclId
        (selectedIds, additionalSelectedTransDepIds) = case config.programSlicing of
          DisableProgramSlicing -> (rootIds        , Set.empty)
          EnableProgramSlicing  -> (rootAndTransDepIds, strictTransDepIds)

        additionalSelectedTransDepsIndex :: DeclIndex l
        additionalSelectedTransDepsIndex = DeclIndex.restrictKeys index additionalSelectedTransDepIds

        notSelectedIndex :: DeclIndex l
        notSelectedIndex = DeclIndex.withoutKeys index selectedIds

        getTransitiveSelectability :: C.DeclId -> TransitiveSelectability
        getTransitiveSelectability x
          | Map.null unusabilityReasons = TransitivelySelectable
          | otherwise                   = TransitivelyUnselectable unusabilityReasons
          where
            transDeps :: Set C.DeclId
            transDeps =
              UseDeclGraph.getStrictTransitiveDeps
                useDeclGraph
                (Set.singleton x)

            unusables :: Map C.DeclId Unselectable
            unusables =
              Unselectable <$> DeclIndex.getUnusables index transDeps

            nonselected :: Map C.DeclId Unselectable
            nonselected  =
              Map.fromSet (const UnselectableDependencyNotSelected) $
                transDeps \\ selectedIds

            unusabilityReasons :: Map C.DeclId Unselectable
            unusabilityReasons =
              Map.unionWith
                getMostNaturalUnselectable
                unusables
                nonselected

            -- Get the reason that is most useful to the user about why a
            -- declaration is unselectable.
            getMostNaturalUnselectable ::
              Unselectable -> Unselectable -> Unselectable
            getMostNaturalUnselectable l r =
              case (l,r) of
                (UnselectableDependencyNotSelected, Unselectable u) ->
                  case u of
                    UnusableReason _ UnusableUnavailable -> r
                    UnusableReason _ UnusableOmitted     -> r
                    _otherReason                         -> l
                (Unselectable u, UnselectableDependencyNotSelected) ->
                  case u of
                    UnusableReason _ UnusableUnavailable -> l
                    UnusableReason _ UnusableOmitted{}   -> l
                    _otherReason                         -> r
                (_, _) -> l

        selectDecl :: Decl l -> Maybe (Decl l)
        selectDecl =
          selectDeclWith
            selectedIds
            getTransitiveSelectability

        unitDecls :: [Decl l]
        unitDecls = map coercePass unit.decls

        selectedUnitDecls  :: [Decl l]
        selectedUnitDecls = mapMaybe selectDecl unitDecls

        selectMsgs :: [AnnMsg Select]
        selectMsgs =
          getSelectMsgs
            rootIds
            additionalSelectedTransDepIds
            getTransitiveSelectability
            index

        unitSelect :: C.TranslationUnit l Select
        unitSelect = C.TranslationUnit {
                decls        = selectedUnitDecls
              , includeGraph = unit.includeGraph
              , meta         = unit.meta
              }

        -- If there were no predicate matches we issue a warning to the user.
        noDeclarationsMatchedMsg :: [AnnMsg Select]
        noDeclarationsMatchedMsg = [
            withCallStack C.WithLocationInfo{
                loc = C.LocationUnavailable
              , msg = SelectNoDeclarationsMatched
              }
          | Set.null rootIds
          ]

        selectionRootMsgs :: [AnnMsg Select]
        selectionRootMsgs = getDelayedMsgsSelectionRoots selectionRootsIndex

        -- Number of selected macros that hs-bindgen dropped.
        droppedMacroCount :: Int
        droppedMacroCount =
          length $ filter (\m -> isDroppedMacro m.traceMsg.msg) selectionRootMsgs

        -- Single summary of dropped macros.
        macrosDroppedSummaryMsg :: [AnnMsg Select]
        macrosDroppedSummaryMsg = [
            withCallStack C.WithLocationInfo{
                loc = C.LocationUnavailable
              , msg = SelectMacrosDropped droppedMacroCount
              }
          | droppedMacroCount > 0
          ]

        msgs :: [AnnMsg Select]
        msgs =
          concat [
              selectMsgs
            , selectionRootMsgs
            , getDelayedMsgsAdditionalSelectedTransDeps additionalSelectedTransDepsIndex
            , getBugMsgsNotSelected notSelectedIndex
            , noDeclarationsMatchedMsg
            ]

    in  ( unitSelect
        , sortSelectMsgs unit.includeGraph msgs ++ macrosDroppedSummaryMsg
        )
  where
    index :: DeclIndex l
    index = unit.meta.declIndex

    useDeclGraph :: UseDeclGraph
    useDeclGraph = unit.meta.useDeclGraph

    match :: Match
    match name loc availability =
      matchSelect
        isMainHeader
        isInMainHeaderDir
        (singleLocPath loc)
        name
        availability
        config.selectionPredicate

{-------------------------------------------------------------------------------
  Filter list of declarations
-------------------------------------------------------------------------------}

selectDeclWith ::
  -- | Selected declaration IDs.
     Set C.DeclId
  -> (C.DeclId -> TransitiveSelectability)
  -> Decl l
  -> Maybe (Decl l)
selectDeclWith
  selectedIds
  getTransitiveSelectability
  decl =
    case (isSelected, transitiveSelectability) of
      (True, TransitivelySelectable) -> Just decl
      _otherwise                     -> Nothing
  where
    declId :: C.DeclId
    declId = decl.info.id.cName

    isSelected :: Bool
    isSelected = Set.member declId selectedIds

    transitiveSelectability :: TransitiveSelectability
    transitiveSelectability = getTransitiveSelectability declId

{-------------------------------------------------------------------------------
  Select traces
-------------------------------------------------------------------------------}

getSelectMsgs ::
     forall l. HasCallStack
  => Set C.DeclId
  -- ^ Selection roots.
  -> Set C.DeclId
  -- ^ Additionally selected transitive dependencies (non-empty when program
  --   slicing is enabled).
  ->(C.DeclId -> TransitiveSelectability)
  -> DeclIndex l
  -> [AnnMsg Select]
getSelectMsgs
  rootIds
  additionalSelectedTransDepIds
  getTransitiveSelectability
  declIndex
  = concatMap (uncurry aux) $ DeclIndex.toList declIndex
  where
    aux ::
         HasCallStack
      => C.DeclId
      -> Entry l
      -> [AnnMsg Select]
    aux =
      getSelectMsgsDeclId
        getTransitiveSelectability
        declIndex
        rootIds
        additionalSelectedTransDepIds

getSelectMsgsDeclId ::
    HasCallStack
  => (C.DeclId -> TransitiveSelectability)
  -> DeclIndex l
  -- | Selection roots.
  -> Set C.DeclId
  -- | Additionally selected transitive dependencies (non-empty when program
  --   slicing is enabled).
  -> Set C.DeclId
  -> C.DeclId
  -> Entry l
  -> [AnnMsg Select]
getSelectMsgsDeclId
  getTransitiveSelectability
  declIndex
  rootIds
  additionalSelectedTransDepIds
  declId
  entry
  = case ( isSelectedRoot
         , isAdditionalSelectedTransDep
         , transitiveSelectability ) of
      -- Declaration is a selection root.
      (True, False, TransitivelySelectable) ->
        getMsgsFor SelectionRoot
      (True, False, TransitivelyUnselectable rs) ->
        maybeToList $ getUnavailMsg SelectionRoot rs
      -- Declaration is an additionally selected transitive dependency.
      (False, True, TransitivelySelectable) ->
        getMsgsFor TransitiveDependency
      (False, True, TransitivelyUnselectable rs) ->
        maybeToList $ getUnavailMsg TransitiveDependency rs
      -- Declaration is not selected.
      (False, False, _) ->
        [withLoc $ SelectStatusInfo NotSelected]
      -- Declaration is a selection root and a transitive dependency. This
      -- should be impossible and we consider it a bug.
      (True, True, _) ->
        panicPure $
          "Declaration is selection root and transitive dependency: "
          ++ show (withLoc declId)
  where
    -- We check three conditions:
    isSelectedRoot = Set.member declId rootIds
    -- These are also always strict transitive dependencies.
    isAdditionalSelectedTransDep =
      Set.member declId additionalSelectedTransDepIds
    transitiveSelectability = getTransitiveSelectability declId

    getMsgsFor :: HasCallStack => SelectReason -> [AnnMsg Select]
    getMsgsFor selectReason = concat [
          [ withLoc $ SelectStatusInfo (Selected selectReason) ]
        , [ withLoc $ SelectDeprecated selectReason | isDeprecated ]
        ]

    withLoc :: HasCallStack => a -> WithCallStack (C.WithLocationInfo a)
    withLoc x = withCallStack C.WithLocationInfo{
                  loc = C.declIdLocationInfo declId $
                          C.declLocsToList $
                            DeclIndex.entryToLoc entry
                , msg = x
                }

    getUnavailMsg ::
         HasCallStack
      => SelectReason
      -> Map C.DeclId Unselectable
      -> Maybe (AnnMsg Select)
    getUnavailMsg selectReason unavailReasons =
        if null msgs then
          Nothing
        else
          Just $ withLoc $ TransitiveDependenciesMissing selectReason msgs
      where
        msgs = [
               case r of
                 Unselectable u ->
                   TransitiveDependencyUnusable i u
                 UnselectableDependencyNotSelected ->
                   TransitiveDependencyNotSelected i locs
             | (i, r) <- Map.toList unavailReasons
             , let locs = case DeclIndex.lookupLoc i declIndex of
                     Nothing -> []
                     Just xs -> C.declLocsToList xs
             ]

    isDeprecated :: Bool
    isDeprecated = DeclIndex.entryToAvailability entry == C.Deprecated

{-------------------------------------------------------------------------------
  Delayed traces
-------------------------------------------------------------------------------}

mkSuccessMessages ::
     HasCallStack
  => C.DeclId
  -> Success l ConstructTranslationUnit
  -> [AnnMsg Select]
mkSuccessMessages declId success = concat [
      fmap (mkAnnMsg . SelectDelayedParseMsg)
        delayedParseMsgs
    , fmap (mkAnnMsg . SelectDelayedPrepareReparseMsg)
        delayedPrepareReparseMsgs
    , fmap (mkAnnMsg . SelectDelayedReparseMacroExpansionsMsg)
        delayedReparseMacroExpansionsMsgs
    , fmap (mkAnnMsg . SelectDelayedTranslateTypesMsg)
        delayedTranslateTypesMsgs
    ]
  where
    DeclIndex.Success _
      delayedParseMsgs
      delayedPrepareReparseMsgs
      delayedReparseMacroExpansionsMsgs
      delayedTranslateTypesMsgs = success

    mkAnnMsg :: HasCallStack => SelectMsg -> AnnMsg Select
    mkAnnMsg msg = withCallStack C.WithLocationInfo{
          loc = C.declIdLocationInfo declId [success.decl.info.loc]
        , msg = msg
        }

getDelayedMsgsSelectionRoots :: HasCallStack => DeclIndex l -> [AnnMsg Select]
getDelayedMsgsSelectionRoots = concatMap (uncurry aux) . DeclIndex.toList
  where
    aux ::
         HasCallStack
      => C.DeclId
      -> Entry l
      -> [AnnMsg Select]
    aux declId = \case
      UsableEntry e -> case e of
        UsableSuccess success ->
          mkSuccessMessages declId success
        UsableExternal{} -> []
        -- Parse messages are unavailable for squashed entries. We are OK with
        -- this; instead we have issued a notice that the @typedef@ was squashed.
        UsableSquashed x ->
          List.singleton $ withCallStack C.WithLocationInfo{
                loc = C.declIdLocationInfo declId [x.typedefLoc]
              , msg = SelectMangleNamesSquashed x
              }
      UnusableEntry e -> case e of
        UnusableReason loc s ->
            List.singleton $ withCallStack C.WithLocationInfo{
                  loc = C.declIdLocationInfo declId [loc]
                , msg = SelectUnusable s
                }
        UnusableConflict c ->
          List.singleton $ withCallStack C.WithLocationInfo{
              loc = C.declIdLocationInfo declId $
                NonEmpty.toList $ C.conflictToList c
            , msg = SelectConflict
            }

getDelayedMsgsAdditionalSelectedTransDeps ::
     HasCallStack
  => DeclIndex l
  -> [AnnMsg Select]
getDelayedMsgsAdditionalSelectedTransDeps = concatMap (uncurry aux) . DeclIndex.toList
  where
    aux ::
         HasCallStack
      => C.DeclId
      -> Entry l
      -> [AnnMsg Select]
    aux declId = \case
      UsableEntry e -> case e of
        UsableSuccess success ->
          mkSuccessMessages declId success
        UsableExternal{} -> []
        -- Parse messages are unavailable for squashed entries. We are OK with
        -- this; instead we have issued a notice that the @typedef@ was squashed.
        UsableSquashed x ->
          [ withCallStack C.WithLocationInfo{
                loc = C.declIdLocationInfo declId [x.typedefLoc]
              , msg = SelectMangleNamesSquashed x
              }
          ]
      -- Messages for unusable transitive dependencies are already attached to
      -- the traces of the reverse transitive dependencies.
      UnusableEntry _ -> []

-- NOTE: We emit delayed BUG-level parse messages even for declarations that are
-- not selected. We do not have a test for this; please ensure delayed BUG-level
-- parse messages are emitted for all declarations also in the future.
getBugMsgsNotSelected :: HasCallStack => DeclIndex l -> [AnnMsg Select]
getBugMsgsNotSelected = concatMap (uncurry aux) . DeclIndex.toList
  where
    aux ::
         HasCallStack
      => C.DeclId
      -> Entry l
      -> [AnnMsg Select]
    aux declId = \case
      UsableEntry e -> case e of
        UsableSuccess success ->
          let isBugLevel x = getDefaultLogLevel x == Bug
          in  filter isBugLevel $ mkSuccessMessages declId success
        UsableExternal{} -> []
        UsableSquashed{} -> []
      UnusableEntry e -> case e of
        UnusableReason loc reason ->
          if isBug reason then
              List.singleton $ withCallStack C.WithLocationInfo{
                  loc = C.declIdLocationInfo declId [loc]
                , msg = SelectUnusable reason
                }
          else
            []
        UnusableConflict{} -> []

    isBug :: UnusableReason -> Bool
    isBug = \case
      UnusableUnavailable                -> False
      UnusableOmitted                    -> False
      UnusableParseFailure           err -> getDefaultLogLevel err == Bug
      UnusableMangleNamesFailure     err -> getDefaultLogLevel err == Bug
      UnusableMacroTypecheckFailure  err -> getDefaultLogLevel err == Bug
      UnusableMacroResolutionFailure err -> getDefaultLogLevel err == Bug

-- | Does this selection-root message represent a macro that hs-bindgen dropped?
--
isDroppedMacro :: SelectMsg -> Bool
isDroppedMacro = \case
    SelectUnusable reason -> case reason of
      UnusableMacroTypecheckFailure  _ -> True
      UnusableMacroResolutionFailure _ -> True
      -- A parse failure counts as a dropped macro iff it is a macro parse
      -- failure.
      UnusableParseFailure p           -> isMacroParseFailure p
      UnusableUnavailable              -> False
      UnusableOmitted                  -> False
      UnusableMangleNamesFailure _     -> False
    _otherwise                         -> False
  where
    isMacroParseFailure :: DelayedParseMsg -> Bool
    isMacroParseFailure = \case
      ParseMacroDefinitionNoMacroName{} -> True
      ParseMacroEmpty{}                 -> False
      ParseMacroErrorParse{}            -> True
      _otherwise                        -> False

{-------------------------------------------------------------------------------
  Sort traces
-------------------------------------------------------------------------------}

-- | Location a trace message is sorted by
--
-- Only conflicting declarations carry more than one location; we sort by the
-- first.
msgLoc :: AnnMsg Select -> Maybe (SingleLoc C.DeclPath)
msgLoc msg = listToMaybe $ C.locationInfoLocs msg.traceMsg.loc

-- | Sort key of a trace message
--
-- The constructor order /is/ the specification: messages not attached to a
-- declaration sort to the back. Do not reorder.
data MsgSortKey =
    MsgAt {
        msgSource :: IncludeGraph.IncludeOrderIx
      , msgLine   :: Int
      , msgColumn :: Int
      }
  | MsgNoLocation
  deriving stock (Eq, Ord)

msgSortKey :: IncludeGraph.IncludeOrder -> AnnMsg Select -> MsgSortKey
msgSortKey order msg = case msgLoc msg of
    Nothing  -> MsgNoLocation
    Just loc -> MsgAt {
        msgSource = IncludeGraph.lookupIncludeOrder order (singleLocPath loc)
      , msgLine   = singleLocLine   loc
      , msgColumn = singleLocColumn loc
      }

sortSelectMsgs :: IncludeGraph -> [AnnMsg Select] -> [AnnMsg Select]
sortSelectMsgs includeGraph msgs =
    bugMsgs ++ List.sortOn (msgSortKey order) msgs
  where
    -- Compute the include order once.
    order :: IncludeGraph.IncludeOrder
    order = IncludeGraph.toIncludeOrder includeGraph

    -- We do not know where to sort a message whose source is not in the include
    -- graph, but a mis-sorted message is no reason to abort: report the bug and
    -- sort the message to the back.
    bugMsgs :: [AnnMsg Select]
    bugMsgs = [
        withCallStack C.WithLocationInfo{
            loc = C.LocationUnavailable
          , msg = SelectSourceNotInIncludeGraph path
          }
      | path <- Set.toList unknownPaths
      ]

    unknownPaths :: Set RealPath
    unknownPaths = Set.fromList [
        path
      | msg <- msgs
      , Just loc <- [msgLoc msg]
      , C.InHeader path <- [singleLocPath loc]
      , IncludeGraph.lookupIncludeOrder order (C.InHeader path)
          == IncludeGraph.NotInIncludeGraph
      ]

{-------------------------------------------------------------------------------
  Apply the selection predicate
-------------------------------------------------------------------------------}

-- Match function to find selection roots.
type Match = C.DeclName -> SingleLoc C.DeclPath -> C.Availability -> Bool

-- | Limit the declaration index to those entries that match the select
--   predicate. Do not include anything external nor omitted.
selectDeclIndex :: DeclUseGraph -> Match -> DeclIndex l -> DeclIndex l
selectDeclIndex declUseGraph predicate declIndex =
    DeclIndex.filter matchEntry declIndex
  where
    matchEntry :: C.DeclId -> Entry l -> Bool
    matchEntry declId entry =
        case entryInfo entry of
          Nothing ->
            -- External or omittted declarations have already been removed from
            -- the list of declarations
            False
          Just (locs, availability) ->
            if declId.isUnnamed
              then matchUnnamed declId
              else Foldable.any
                     (\loc -> predicate declId.name loc availability)
                     (C.declLocsToList locs)

    matchDeclId :: C.DeclId -> Bool
    matchDeclId declId =
        case DeclIndex.lookupEntry declId declIndex of
          Just entry -> matchEntry declId entry
          Nothing    -> panicPure $ "Unknown declaration ID: " ++ show declId

    -- Extract info from 'Entry' needed to match against the selection predicate
    --
    -- Returns 'Nothing' for external or omitted declarations.
    -- Returns 'Just _' for squashed declarations. Those can still be selected.
    -- Returns multiple locations only for conflicts.
    entryInfo :: Entry l -> Maybe (C.DeclLocs, C.Availability)
    entryInfo entry = case entry of
        UsableEntry UsableExternal{} ->
          Nothing
        UnusableEntry (UnusableReason _loc UnusableOmitted{}) ->
          Nothing
        _otherwise ->
          Just (DeclIndex.entryToLoc entry, DeclIndex.entryToAvailability entry)

    -- We match unnamed declarations based on their use sites.
    --
    -- Looking at the use sites means we treat unnamed declarations in a very
    -- similar way to auxiliary declarations. It's however worth spelling out
    -- why this is possible. There are two main classes of unnamed
    -- declarations. The first is unnamed declarations inside of typedefs:
    --
    -- > typedef struct { .. } foo;
    --
    -- Selecting the typedef but not the struct makes no sense; selecting the
    -- struct but not the typedef does, but is already captured by support for
    -- squashing typedefs.
    --
    -- The second class is unnamed declarations inside structs or unions:
    --
    -- > struct outer {
    -- >    struct { .. } field1, field2;
    -- >    struct { .. } field3;
    -- > }
    -- >
    --
    -- Like for typedefs, it doesn't really make sense to select the outer
    -- struct but not the inner struct. It could /in principle/ make sense to
    -- select the inner but not the outer, but it seems a pretty rare use case,
    -- and not a big deal if the outer struct will "come along" even if only the
    -- inner struct is needed.
    matchUnnamed :: C.DeclId -> Bool
    matchUnnamed unnamed =
       case DeclUseGraph.getUseSites declUseGraph unnamed of
         (declId, _) :_ ->
           -- The only way that unnamed declarations can have multiple use
           -- sites is when multiple fields are defined together:
           --
           -- > struct foo {
           -- >   struct { .. } x, y, z;
           -- > }
           --
           -- From the perspective of selection, any of these will do.
           matchDeclId declId
         [] ->
           -- An unnamed declaration can have no use sites if those
           -- dependencies were removed from the graph because the parent
           -- declaration is opaqued using a prescriptive binding specification.
           False