packages feed

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

-- | Name and signature extraction from GHC AST nodes.
--
-- Provides functions to extract declaration names, type signatures,
-- and related metadata from the various GHC declaration types. Used
-- by the main conversion module and by 'Scrod.Convert.FromGhc.Constructors'.
module Scrod.Convert.FromGhc.Names where

import qualified Data.List as List
import qualified Data.List.NonEmpty as NonEmpty
import qualified Data.Maybe as Maybe
import qualified Data.Text as Text
import GHC.Hs ()
import qualified GHC.Hs.Extension as Ghc
import qualified GHC.Types.SrcLoc as SrcLoc
import qualified GHC.Utils.Outputable as Outputable
import qualified Language.Haskell.Syntax as Syntax
import qualified Scrod.Convert.FromGhc.Internal as Internal
import qualified Scrod.Convert.FromGhc.SigArguments as SigArguments
import qualified Scrod.Core.ItemName as ItemName

-- | Extract declaration name.
extractDeclName :: Syntax.LHsDecl Ghc.GhcPs -> Maybe ItemName.ItemName
extractDeclName lDecl = case SrcLoc.unLoc lDecl of
  Syntax.TyClD _ tyClDecl -> extractTyClDeclName tyClDecl
  Syntax.ValD _ bind -> extractBindName bind
  Syntax.SigD _ sig -> extractSigName sig
  Syntax.InstD _ inst -> extractInstDeclName inst
  Syntax.DerivD _ derivDecl -> extractDerivDeclName derivDecl
  Syntax.KindSigD _ kindSig -> Just $ extractStandaloneKindSigName kindSig
  Syntax.ForD _ foreignDecl -> Just $ extractForeignDeclName foreignDecl
  _ -> Nothing

-- | Extract name from a standalone kind signature.
extractStandaloneKindSigName :: Syntax.StandaloneKindSig Ghc.GhcPs -> ItemName.ItemName
extractStandaloneKindSigName (Syntax.StandaloneKindSig _ lName _) = Internal.extractIdPName lName

-- | Extract signature from a standalone kind signature.
-- Only returns the kind type, not the name.
-- For example, @type X :: a -> a@ produces @"a -> a"@.
extractKindSigSignature :: Syntax.StandaloneKindSig Ghc.GhcPs -> Text.Text
extractKindSigSignature (Syntax.StandaloneKindSig _ _ lSigType) =
  Text.pack . Internal.showSDocShort . Outputable.ppr $ lSigType

-- | Extract name from a type/class declaration.
-- For class declarations, this includes type variables in the name
-- (e.g., @class C a@ produces @"C a"@).
extractTyClDeclName :: Syntax.TyClDecl Ghc.GhcPs -> Maybe ItemName.ItemName
extractTyClDeclName tyClDecl = case tyClDecl of
  Syntax.FamDecl _ famDecl -> Just $ extractFamilyDeclName famDecl
  Syntax.SynDecl {Syntax.tcdLName = lName} -> Just $ Internal.extractIdPName lName
  Syntax.DataDecl {Syntax.tcdLName = lName} -> Just $ Internal.extractIdPName lName
  Syntax.ClassDecl {Syntax.tcdLName = lName, Syntax.tcdTyVars = tyVars} ->
    Just . ItemName.MkItemName . Text.pack . Outputable.showSDocUnsafe $ case Syntax.hsQTvExplicit tyVars of
      [] -> Outputable.ppr lName
      tvs -> Outputable.hsep (Outputable.ppr lName : fmap Outputable.ppr tvs)

-- | Extract the fully applied parent type text from a data declaration.
-- For @data Maybe a@, this produces @"Maybe a"@.
extractParentTypeText :: Syntax.TyClDecl Ghc.GhcPs -> Maybe Text.Text
extractParentTypeText tyClDecl = case tyClDecl of
  Syntax.DataDecl {Syntax.tcdLName = lName, Syntax.tcdTyVars = tyVars} ->
    Just . Text.pack . Internal.showSDocShort $ case Syntax.hsQTvExplicit tyVars of
      [] -> Outputable.ppr lName
      tvs -> Outputable.hsep (Outputable.ppr lName : fmap Outputable.ppr tvs)
  _ -> Nothing

-- | Extract type variable bindings from a type\/class declaration.
-- For @data T a b@, this produces @Just "a b"@.
-- For @class C a@, this produces @Just "a"@.
-- Returns 'Nothing' if there are no type variables.
extractTyClDeclTyVars :: Syntax.TyClDecl Ghc.GhcPs -> Maybe Text.Text
extractTyClDeclTyVars tyClDecl = case tyClDecl of
  Syntax.DataDecl {Syntax.tcdTyVars = tyVars} -> tyVarsToText tyVars
  _ -> Nothing

-- | Pretty-print explicit type variable binders as text.
-- Returns 'Nothing' if the list is empty.
tyVarsToText :: Syntax.LHsQTyVars Ghc.GhcPs -> Maybe Text.Text
tyVarsToText tyVars = case Syntax.hsQTvExplicit tyVars of
  [] -> Nothing
  tvs -> Just . Text.pack . Internal.showSDocShort $ Outputable.hsep (fmap Outputable.ppr tvs)

-- | Extract the signature for a type synonym declaration.
-- For @type T = ()@, this produces @Just "= ()"@.
-- For @type T a = [a]@, this produces @Just "a = [a]"@.
extractSynDeclSignature :: Syntax.TyClDecl Ghc.GhcPs -> Maybe Text.Text
extractSynDeclSignature tyClDecl = case tyClDecl of
  Syntax.SynDecl {Syntax.tcdTyVars = tyVars, Syntax.tcdRhs = rhs} ->
    let rhsText = Text.pack . Internal.showSDocShort $ Outputable.ppr rhs
     in Just $ maybe (Text.pack "= " <> rhsText) (\tvs -> tvs <> Text.pack " = " <> rhsText) (tyVarsToText tyVars)
  _ -> Nothing

-- | Extract name from a family declaration.
extractFamilyDeclName :: Syntax.FamilyDecl Ghc.GhcPs -> ItemName.ItemName
extractFamilyDeclName famDecl = Internal.extractIdPName $ Syntax.fdLName famDecl

-- | Extract name from a foreign declaration.
extractForeignDeclName :: Syntax.ForeignDecl Ghc.GhcPs -> ItemName.ItemName
extractForeignDeclName foreignDecl = Internal.extractIdPName $ Syntax.fd_name foreignDecl

-- | Extract signature from a foreign declaration.
extractForeignDeclSignature :: Syntax.ForeignDecl Ghc.GhcPs -> Text.Text
extractForeignDeclSignature foreignDecl =
  Text.pack . Internal.showSDocShort . Outputable.ppr $ Syntax.fd_sig_ty foreignDecl

-- | Extract name from a binding.
extractBindName :: Syntax.HsBindLR Ghc.GhcPs Ghc.GhcPs -> Maybe ItemName.ItemName
extractBindName bind = case bind of
  Syntax.FunBind {Syntax.fun_id = lId} -> Just $ Internal.extractIdPName lId
  Syntax.PatBind {} -> Nothing
  Syntax.VarBind {} -> Nothing
  Syntax.PatSynBind _ patSyn -> Just $ extractPatSynName patSyn

-- | Extract name from a pattern synonym binding.
extractPatSynName :: Syntax.PatSynBind Ghc.GhcPs Ghc.GhcPs -> ItemName.ItemName
extractPatSynName patSyn = Internal.extractIdPName $ Syntax.psb_id patSyn

-- | Extract argument names from a function binding's patterns.
--
-- For each argument position, scans across all equations and picks
-- the first variable pattern name found (skipping wildcards,
-- constructor patterns, and literals). Returns one entry per
-- argument position; 'Nothing' when no variable name was found.
extractBindArgNames :: Syntax.HsBindLR Ghc.GhcPs Ghc.GhcPs -> [Maybe Text.Text]
extractBindArgNames bind = case bind of
  Syntax.FunBind {Syntax.fun_matches = mg} ->
    let lMatches = SrcLoc.unLoc (Syntax.mg_alts mg)
        patNameLists = fmap extractMatchPatNames lMatches
     in mergePatNames patNameLists
  _ -> []

-- | Extract the variable name (if any) from each pattern in a match.
extractMatchPatNames ::
  SrcLoc.GenLocated l (Syntax.Match Ghc.GhcPs body) ->
  [Maybe Text.Text]
extractMatchPatNames lMatch =
  let match = SrcLoc.unLoc lMatch
   in fmap (extractPatVarName . SrcLoc.unLoc) (SrcLoc.unLoc (Syntax.m_pats match))

-- | Extract a variable name from a pattern, unwrapping wrapper nodes.
--
-- Handles 'VarPat' directly, and recurses through 'AsPat' (using the
-- as-binding name), 'BangPat', 'LazyPat', 'ParPat', and 'SigPat'.
-- Returns 'Nothing' for wildcards, constructor patterns, literals, etc.
extractPatVarName :: Syntax.Pat Ghc.GhcPs -> Maybe Text.Text
extractPatVarName pat = case pat of
  Syntax.VarPat _ lId -> Just $ Internal.extractRdrName lId
  Syntax.AsPat _ lId _ -> Just $ Internal.extractRdrName lId
  Syntax.BangPat _ lPat -> extractPatVarName $ SrcLoc.unLoc lPat
  Syntax.LazyPat _ lPat -> extractPatVarName $ SrcLoc.unLoc lPat
  Syntax.ParPat _ lPat -> extractPatVarName $ SrcLoc.unLoc lPat
  Syntax.SigPat _ lPat _ -> extractPatVarName $ SrcLoc.unLoc lPat
  _ -> Nothing

-- | Merge pattern names across multiple equations.
--
-- For each argument position, takes the first 'Just' name found
-- across the equations. This handles cases like:
--
-- @
-- or True _ = True
-- or _ x = x
-- @
--
-- where position 0 yields 'Nothing' (no variable in either equation)
-- and position 1 yields @Just "x"@ (from the second equation).
mergePatNames :: [[Maybe Text.Text]] -> [Maybe Text.Text]
mergePatNames patNameLists = case patNameLists of
  [] -> []
  _ ->
    let maxLen = maximum (fmap length patNameLists)
        padded = fmap (\ns -> ns <> replicate (maxLen - length ns) Nothing) patNameLists
     in fmap (Maybe.listToMaybe . Maybe.catMaybes) (List.transpose padded)

-- | Extract name from a signature.
extractSigName :: Syntax.Sig Ghc.GhcPs -> Maybe ItemName.ItemName
extractSigName sig = case sig of
  Syntax.TypeSig _ (lName : _) _ -> Just $ Internal.extractIdPName lName
  Syntax.PatSynSig _ (lName : _) _ -> Just $ Internal.extractIdPName lName
  Syntax.ClassOpSig _ _ (lName : _) _ -> Just $ Internal.extractIdPName lName
  _ -> Nothing

-- | Extract signature text from a Sig. Only returns the type part, not the
-- name. For example, @x :: Int@ produces @"Int"@.
-- Strips 'HsDocTy' nodes so that embedded doc comments do not appear in the
-- pretty-printed output.
extractSigSignature :: Syntax.Sig Ghc.GhcPs -> Maybe Text.Text
extractSigSignature sig = case sig of
  Syntax.TypeSig _ _ ty ->
    Just . Text.pack . Internal.showSDocShort . Outputable.ppr $ SigArguments.stripHsSigWcType ty
  Syntax.PatSynSig _ _ ty ->
    Just . Text.pack . Internal.showSDocShort . Outputable.ppr $ SigArguments.stripHsSigType ty
  Syntax.ClassOpSig _ _ _ ty ->
    Just . Text.pack . Internal.showSDocShort . Outputable.ppr $ SigArguments.stripHsSigType ty
  _ -> Nothing

-- | Extract name from an instance declaration.
extractInstDeclName :: Syntax.InstDecl Ghc.GhcPs -> Maybe ItemName.ItemName
extractInstDeclName inst = Just $ case inst of
  Syntax.ClsInstD _ clsInst ->
    ItemName.MkItemName . Text.pack . Internal.showSDocShort . Outputable.ppr $
      Syntax.cid_poly_ty clsInst
  Syntax.DataFamInstD _ dataFamInst ->
    ItemName.MkItemName . Text.pack . Internal.showSDocShort . Outputable.ppr $
      dataFamInst
  Syntax.TyFamInstD _ tyFamInst ->
    ItemName.MkItemName . Text.pack . Internal.showSDocShort . Outputable.ppr $
      tyFamInst

-- | Extract name from a standalone deriving declaration.
extractDerivDeclName :: Syntax.DerivDecl Ghc.GhcPs -> Maybe ItemName.ItemName
extractDerivDeclName =
  Just
    . ItemName.MkItemName
    . Text.pack
    . Internal.showSDocShort
    . Outputable.ppr
    . Syntax.hswc_body
    . Syntax.deriv_type

-- | Extract names from a constructor declaration.
-- GADT constructors can declare multiple names (e.g. @A, B :: Int -> T@).
extractConDeclNames :: Syntax.ConDecl Ghc.GhcPs -> NonEmpty.NonEmpty ItemName.ItemName
extractConDeclNames conDecl = case conDecl of
  Syntax.ConDeclH98 {Syntax.con_name = lName} -> pure $ Internal.extractIdPName lName
  Syntax.ConDeclGADT {Syntax.con_names = lNames} ->
    fmap Internal.extractIdPName lNames