packages feed

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

-- | Resolve instance parent relationships.
--
-- Associates class instances and standalone deriving declarations with
-- their parent types or classes when those are defined in the same
-- module. For @instance C T@, if @T@ is defined locally, the instance
-- is parented under @T@. Otherwise, if @C@ is defined locally, the
-- instance is parented under @C@.
module Scrod.Convert.FromGhc.InstanceParents where

import qualified Data.Map as Map
import qualified Data.Maybe as Maybe
import GHC.Hs ()
import qualified GHC.Hs.Extension as Ghc
import qualified GHC.Parser.Annotation as Annotation
import qualified GHC.Types.SrcLoc as SrcLoc
import qualified Language.Haskell.Syntax as Syntax
import qualified Scrod.Convert.FromGhc.Internal as Internal
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.Location as Location

-- | Extract the head type name for each instance or standalone deriving
-- declaration, keyed by source location.
extractInstanceHeadTypeNames ::
  SrcLoc.Located (Syntax.HsModule Ghc.GhcPs) ->
  Map.Map Location.Location ItemName.ItemName
extractInstanceHeadTypeNames lHsModule =
  let hsModule = SrcLoc.unLoc lHsModule
      decls = Syntax.hsmodDecls hsModule
   in Map.fromList $ Maybe.mapMaybe extractDeclInstanceHeadType decls

-- | Extract the class name for each instance or standalone deriving
-- declaration, keyed by source location.
extractInstanceClassNames ::
  SrcLoc.Located (Syntax.HsModule Ghc.GhcPs) ->
  Map.Map Location.Location ItemName.ItemName
extractInstanceClassNames lHsModule =
  let hsModule = SrcLoc.unLoc lHsModule
      decls = Syntax.hsmodDecls hsModule
   in Map.fromList $ Maybe.mapMaybe extractDeclInstanceClass decls

-- | Extract the class name from a single declaration, if it is an
-- instance or standalone deriving declaration.
extractDeclInstanceClass ::
  Syntax.LHsDecl Ghc.GhcPs ->
  Maybe (Location.Location, ItemName.ItemName)
extractDeclInstanceClass lDecl = case SrcLoc.unLoc lDecl of
  Syntax.InstD _ inst -> case inst of
    Syntax.ClsInstD _ clsInst -> do
      location <- Internal.locationFromSrcSpan (Annotation.getLocA lDecl)
      className <- extractClassName . Syntax.sig_body . SrcLoc.unLoc $ Syntax.cid_poly_ty clsInst
      Just (location, className)
    _ -> Nothing
  Syntax.DerivD _ derivDecl -> do
    location <- Internal.locationFromSrcSpan (Annotation.getLocA lDecl)
    className <- extractClassName . Syntax.sig_body . SrcLoc.unLoc . Syntax.hswc_body $ Syntax.deriv_type derivDecl
    Just (location, className)
  _ -> Nothing

-- | Extract the head type name from a single declaration, if it is an
-- instance or standalone deriving declaration.
extractDeclInstanceHeadType ::
  Syntax.LHsDecl Ghc.GhcPs ->
  Maybe (Location.Location, ItemName.ItemName)
extractDeclInstanceHeadType lDecl = case SrcLoc.unLoc lDecl of
  Syntax.InstD _ inst -> case inst of
    Syntax.ClsInstD _ clsInst -> do
      location <- Internal.locationFromSrcSpan (Annotation.getLocA lDecl)
      headType <- extractHeadTypeName . Syntax.sig_body . SrcLoc.unLoc $ Syntax.cid_poly_ty clsInst
      Just (location, headType)
    _ -> Nothing
  Syntax.DerivD _ derivDecl -> do
    location <- Internal.locationFromSrcSpan (Annotation.getLocA lDecl)
    headType <- extractHeadTypeName . Syntax.sig_body . SrcLoc.unLoc . Syntax.hswc_body $ Syntax.deriv_type derivDecl
    Just (location, headType)
  _ -> Nothing

-- | Extract the head type constructor name from the last argument of a
-- type application. For @C T@ this returns @T@; for @C (Maybe a)@ this
-- returns @Maybe@.
extractHeadTypeName :: Syntax.LHsType Ghc.GhcPs -> Maybe ItemName.ItemName
extractHeadTypeName lTy = case SrcLoc.unLoc lTy of
  Syntax.HsAppTy _ _ arg -> extractOutermostTyCon arg
  Syntax.HsQualTy _ _ body -> extractHeadTypeName body
  Syntax.HsForAllTy _ _ body -> extractHeadTypeName body
  Syntax.HsParTy _ inner -> extractHeadTypeName inner
  _ -> Nothing

-- | Extract the class name from an instance head. For @C T@ this
-- returns @C@; for @Functor F@ this returns @Functor@. For nullary
-- classes (e.g., @instance C@) this returns @C@.
extractClassName :: Syntax.LHsType Ghc.GhcPs -> Maybe ItemName.ItemName
extractClassName lTy = case SrcLoc.unLoc lTy of
  Syntax.HsAppTy _ fun _ -> extractOutermostTyCon fun
  Syntax.HsAppKindTy _ fun _ -> extractOutermostTyCon fun
  Syntax.HsTyVar {} -> extractOutermostTyCon lTy
  Syntax.HsQualTy _ _ body -> extractClassName body
  Syntax.HsForAllTy _ _ body -> extractClassName body
  Syntax.HsParTy _ inner -> extractClassName inner
  _ -> Nothing

-- | Extract the outermost type constructor name from a type. For @T@
-- this returns @T@; for @Maybe a@ this returns @Maybe@.
extractOutermostTyCon :: Syntax.LHsType Ghc.GhcPs -> Maybe ItemName.ItemName
extractOutermostTyCon lTy = case SrcLoc.unLoc lTy of
  Syntax.HsTyVar _ _ lName -> Just . ItemName.MkItemName $ Internal.extractRdrName lName
  Syntax.HsAppTy _ fun _ -> extractOutermostTyCon fun
  Syntax.HsAppKindTy _ fun _ -> extractOutermostTyCon fun
  Syntax.HsParTy _ inner -> extractOutermostTyCon inner
  _ -> Nothing

-- | Associate instances and standalone deriving declarations with their
-- parent types or classes when those are defined in the same module.
associateInstanceParents ::
  Map.Map Location.Location ItemName.ItemName ->
  Map.Map Location.Location ItemName.ItemName ->
  [Located.Located Item.Item] ->
  [Located.Located Item.Item]
associateInstanceParents headTypeNames classNames items =
  let typeNameToKey = buildTypeNameToKeyMap items
   in fmap (resolveInstanceParent headTypeNames classNames typeNameToKey) items

-- | Build a map from type/class names to their item keys.
-- For items whose names contain type variables (indicated by a space),
-- both the full name and the base name (first word) are indexed.
buildTypeNameToKeyMap ::
  [Located.Located Item.Item] ->
  Map.Map ItemName.ItemName ItemKey.ItemKey
buildTypeNameToKeyMap =
  Map.fromList . concatMap getTypeNameAndKey
  where
    getTypeNameAndKey locItem =
      let val = Located.value locItem
       in case Item.parentKey val of
            Just _ -> []
            Nothing ->
              if isTypeOrClassKind (Item.kind val)
                then case Item.name val of
                  Nothing -> []
                  Just n ->
                    let k = Item.key val
                        full = ItemName.unwrap n
                     in (n, k) : [(ItemName.MkItemName base, k) | Just base <- [Internal.baseItemName full]]
                else []

-- | Check if an item kind represents a type or class definition.
isTypeOrClassKind :: ItemKind.ItemKind -> Bool
isTypeOrClassKind kind = case kind of
  ItemKind.DataType -> True
  ItemKind.Newtype -> True
  ItemKind.TypeData -> True
  ItemKind.TypeSynonym -> True
  ItemKind.Class -> True
  _ -> False

-- | Try to resolve an instance's parent from the head type maps.
-- First tries the head type name (e.g., @T@ from @instance C T@),
-- then falls back to the class name (e.g., @C@).
resolveInstanceParent ::
  Map.Map Location.Location ItemName.ItemName ->
  Map.Map Location.Location ItemName.ItemName ->
  Map.Map ItemName.ItemName ItemKey.ItemKey ->
  Located.Located Item.Item ->
  Located.Located Item.Item
resolveInstanceParent headTypeNames classNames typeNameToKey locItem =
  let val = Located.value locItem
   in case Item.parentKey val of
        Just _ -> locItem
        Nothing ->
          if Item.kind val == ItemKind.ClassInstance || Item.kind val == ItemKind.StandaloneDeriving
            then case lookupParentKey (Located.location locItem) headTypeNames typeNameToKey of
              Just parentKey ->
                Internal.setParentKey parentKey locItem
              Nothing -> case lookupParentKey (Located.location locItem) classNames typeNameToKey of
                Just parentKey ->
                  Internal.setParentKey parentKey locItem
                Nothing -> locItem
            else locItem

-- | Look up a parent key by first resolving a location to a name,
-- then resolving that name to a key.
lookupParentKey ::
  Location.Location ->
  Map.Map Location.Location ItemName.ItemName ->
  Map.Map ItemName.ItemName ItemKey.ItemKey ->
  Maybe ItemKey.ItemKey
lookupParentKey location nameMap keyMap = do
  name <- Map.lookup location nameMap
  Map.lookup name keyMap