packages feed

scrod-0.2026.2.21: source/library/Scrod/Convert/FromGhc/Visibility.hs

-- | Compute visibility for each item based on the module's export list.
module Scrod.Convert.FromGhc.Visibility (computeVisibility) where

import qualified Data.Map as Map
import qualified Data.Maybe as Maybe
import qualified Data.Set as Set
import qualified Data.Text as Text
import qualified Scrod.Convert.FromGhc.Internal as Internal
import qualified Scrod.Core.Export as Export
import qualified Scrod.Core.ExportIdentifier as ExportIdentifier
import qualified Scrod.Core.ExportName as ExportName
import qualified Scrod.Core.Item as Item
import qualified Scrod.Core.ItemKey as ItemKey
import qualified Scrod.Core.ItemKind as ItemKind
import qualified Scrod.Core.ItemName as ItemName
import qualified Scrod.Core.Located as Located
import qualified Scrod.Core.Subordinates as Subordinates
import qualified Scrod.Core.Visibility as Visibility

-- | Whether an item kind is always visible regardless of the export list.
isAlwaysVisible :: ItemKind.ItemKind -> Bool
isAlwaysVisible k = case k of
  ItemKind.ClassInstance -> True
  ItemKind.CompletePragma -> True
  ItemKind.StandaloneDeriving -> True
  ItemKind.DerivedInstance -> True
  ItemKind.Rule -> True
  ItemKind.Default -> True
  ItemKind.Annotation -> True
  ItemKind.Splice -> True
  _ -> False

-- | Compute visibility for each item based on the module's export list.
computeVisibility ::
  Maybe [Export.Export] ->
  [Located.Located Item.Item] ->
  [Located.Located Item.Item]
computeVisibility Nothing items = fmap setImplicit items
computeVisibility (Just exports) items =
  let exportedNames = extractExportedNames exports
      wildcardParentKeys = extractWildcardParentKeys exports items
      exportedParentSubs = extractExportedParentSubs exports items
   in fmap (classifyItem exportedNames wildcardParentKeys exportedParentSubs) items
  where
    classifyItem ::
      Set.Set Text.Text ->
      Set.Set ItemKey.ItemKey ->
      Map.Map ItemKey.ItemKey (Maybe Subordinates.Subordinates) ->
      Located.Located Item.Item ->
      Located.Located Item.Item
    classifyItem exportedNames wildcardKeys parentSubs li =
      let item = Located.value li
       in if isAlwaysVisible (Item.kind item)
            then setVisibility Visibility.Implicit li
            else case Item.parentKey item of
              Just pk
                | Set.member pk wildcardKeys ->
                    setVisibility Visibility.Exported li
                | Just subs <- Map.lookup pk parentSubs ->
                    classifyChild subs item li
              _ ->
                case Item.name item of
                  Just n
                    | nameIsExported (ItemName.unwrap n) exportedNames ->
                        setVisibility Visibility.Exported li
                  _ -> setVisibility Visibility.Unexported li

    -- Check if a name (possibly containing type variables) matches
    -- an exported name. Tries the full name first, then the base
    -- name (first word) for names with type variables.
    nameIsExported :: Text.Text -> Set.Set Text.Text -> Bool
    nameIsExported full names =
      Set.member full names
        || maybe False (`Set.member` names) (Internal.baseItemName full)

    -- Classify a child of an exported parent based on subordinate
    -- restrictions. Non-traditional subordinates (e.g. associated type
    -- families) are always exported. Traditional subordinates
    -- (constructors, record fields, class methods) follow the export
    -- list's subordinate restrictions.
    classifyChild ::
      Maybe Subordinates.Subordinates ->
      Item.Item ->
      Located.Located Item.Item ->
      Located.Located Item.Item
    classifyChild subs item li
      | not (ItemKind.isTraditionalSubordinate (Item.kind item)) =
          setVisibility Visibility.Exported li
      | otherwise = case subs of
          Nothing -> setVisibility Visibility.Unexported li
          Just (Subordinates.MkSubordinates True _) ->
            setVisibility Visibility.Exported li
          Just (Subordinates.MkSubordinates False explicit) ->
            case Item.name item of
              Just n
                | Set.member (ItemName.unwrap n) explicitNames ->
                    setVisibility Visibility.Exported li
                where
                  explicitNames = Set.fromList $ fmap ExportName.name explicit
              _ -> setVisibility Visibility.Unexported li

-- | When there is no export list, tag implicit items and leave everything
-- else as 'Exported' (the default set by 'mkItemM').
setImplicit :: Located.Located Item.Item -> Located.Located Item.Item
setImplicit li =
  let item = Located.value li
   in if isAlwaysVisible (Item.kind item)
        then setVisibility Visibility.Implicit li
        else li

-- | Set the visibility field on a located item.
setVisibility :: Visibility.Visibility -> Located.Located Item.Item -> Located.Located Item.Item
setVisibility v li =
  li {Located.value = (Located.value li) {Item.visibility = v}}

-- | Extract the set of names that are directly exported (including
-- explicitly named subordinates like @Foo(Bar, Baz)@).
extractExportedNames :: [Export.Export] -> Set.Set Text.Text
extractExportedNames = foldMap go
  where
    go :: Export.Export -> Set.Set Text.Text
    go (Export.Identifier ident) =
      let name = ExportName.name (ExportIdentifier.name ident)
          explicitSubs = case ExportIdentifier.subordinates ident of
            Nothing -> Set.empty
            Just (Subordinates.MkSubordinates _ explicit) ->
              Set.fromList $ fmap ExportName.name explicit
       in Set.insert name explicitSubs
    go _ = Set.empty

-- | Extract the set of parent item keys whose children are exported via
-- a @(..)@ wildcard.
extractWildcardParentKeys ::
  [Export.Export] ->
  [Located.Located Item.Item] ->
  Set.Set ItemKey.ItemKey
extractWildcardParentKeys exports items =
  let wildcardNames =
        Set.fromList
          [ ExportName.name (ExportIdentifier.name ident)
          | Export.Identifier ident <- exports,
            Just (Subordinates.MkSubordinates True _) <- [ExportIdentifier.subordinates ident]
          ]
   in Set.fromList
        . Maybe.mapMaybe (\n -> Map.lookup n (topLevelNameToKey items))
        $ Set.toList wildcardNames

-- | Extract a map from exported parent item keys to their subordinate
-- restrictions. The value is 'Nothing' when the parent is exported with
-- no subordinates at all (e.g. @Foo@), or 'Just' when subordinates are
-- present (e.g. @Foo(..)@ or @Foo(Bar)@).
extractExportedParentSubs ::
  [Export.Export] ->
  [Located.Located Item.Item] ->
  Map.Map ItemKey.ItemKey (Maybe Subordinates.Subordinates)
extractExportedParentSubs exports items =
  let exportSubs =
        [ (ExportName.name (ExportIdentifier.name ident), ExportIdentifier.subordinates ident)
        | Export.Identifier ident <- exports
        ]
      nk = topLevelNameToKey items
   in Map.fromList
        . Maybe.mapMaybe (\(n, subs) -> (\k -> (k, subs)) <$> Map.lookup n nk)
        $ exportSubs

-- | Build a map from top-level item names to their keys.
-- For items whose names contain type variables (indicated by a space),
-- both the full name and the base name (first word) are indexed.
topLevelNameToKey :: [Located.Located Item.Item] -> Map.Map Text.Text ItemKey.ItemKey
topLevelNameToKey items =
  Map.fromList
    [ entry
    | li <- items,
      Maybe.isNothing (Item.parentKey (Located.value li)),
      Just n <- [Item.name (Located.value li)],
      let full = ItemName.unwrap n,
      let k = Item.key (Located.value li),
      entry <- (full, k) : [(base, k) | Just base <- [Internal.baseItemName full]]
    ]