{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Plugin.GhcTags.Generate
( GhcTag (..)
, GhcTags
, GhcKind (..)
, TagField (..)
, ghcKindToChar
, charToGhcKind
, getGhcTags
) where
import Data.List (find)
import Data.Maybe (mapMaybe, maybeToList)
import Data.Foldable (foldl')
import Data.Text (Text)
import qualified Data.Text as Text
import BasicTypes ( SourceText (..)
)
import FastString ( FastString (..)
)
import FieldLabel ( FieldLbl (..)
)
import HsBinds ( HsBindLR (..)
, PatSynBind (..)
, Sig (..)
)
import HsDecls ( ForeignImport (..)
, ClsInstDecl (..)
, ConDecl (..)
, DataFamInstDecl (..)
, FamEqn (..)
, FamilyDecl (..)
, FamilyInfo (..)
, ForeignDecl (..)
, LHsDecl
, HsConDeclDetails
, HsDecl (..)
, HsDataDefn (..)
, InstDecl (..)
, TyClDecl (..)
, TyFamInstDecl (..)
)
import HsImpExp ( IE (..)
, IEWildcard (..)
, ieWrappedName
)
import HsSyn ( FieldOcc (..)
, GhcPs
, HsModule (..)
, LFieldOcc
)
import HsTypes ( ConDeclField (..)
, HsConDetails (..)
, HsImplicitBndrs (..)
, HsType (..)
, LConDeclField
, LHsType
)
import SrcLoc ( GenLocated (..)
, Located
, SrcSpan (..)
, unLoc
)
import RdrName ( RdrName (..)
, rdrNameOcc
)
import Name ( nameOccName
, occNameFS
)
data GhcKind = TkTerm
| TkFunction
| TkTypeConstructor
| TkDataConstructor
| TkGADTConstructor
| TkRecordField
| TkTypeSynonym
| TkTypeSignature
| TkPatternSynonym
| TkTypeClass
| TkTypeClassMember
| TkTypeClassInstance
| TkTypeFamily
| TkTypeFamilyInstance
| TkDataTypeFamily
| TkDataTypeFamilyInstance
| TkForeignImport
| TkForeignExport
deriving (Ord, Eq, Show)
ghcKindToChar :: GhcKind -> Char
ghcKindToChar tagKind = case tagKind of
TkTerm -> '`'
TkFunction -> 'λ'
TkTypeConstructor -> 'Λ'
TkDataConstructor -> 'c'
TkGADTConstructor -> 'g'
TkRecordField -> 'r'
TkTypeSynonym -> '≡'
TkTypeSignature -> '⊢'
TkPatternSynonym -> 'p'
TkTypeClass -> 'C'
TkTypeClassMember -> 'm'
TkTypeClassInstance -> 'i'
TkTypeFamily -> 'f'
TkTypeFamilyInstance -> 'F'
TkDataTypeFamily -> 'd'
TkDataTypeFamilyInstance -> 'D'
TkForeignImport -> 'I'
TkForeignExport -> 'E'
charToGhcKind :: Char -> Maybe GhcKind
charToGhcKind c = case c of
'`' -> Just TkTerm
'λ' -> Just TkFunction
'Λ' -> Just TkTypeConstructor
'c' -> Just TkDataConstructor
'g' -> Just TkGADTConstructor
'r' -> Just TkRecordField
'≡' -> Just TkTypeSynonym
'⊢' -> Just TkTypeSignature
'p' -> Just TkPatternSynonym
'C' -> Just TkTypeClass
'm' -> Just TkTypeClassMember
'i' -> Just TkTypeClassInstance
'f' -> Just TkTypeFamily
'F' -> Just TkTypeFamilyInstance
'd' -> Just TkDataTypeFamily
'D' -> Just TkDataTypeFamilyInstance
'I' -> Just TkForeignImport
'E' -> Just TkForeignExport
_ -> Nothing
data TagField = TagField {
fieldName :: Text,
fieldValue :: Text
}
deriving (Eq, Ord, Show)
fileField :: TagField
fileField = TagField { fieldName = "file", fieldValue = "" }
data GhcTag = GhcTag {
gtSrcSpan :: !SrcSpan
, gtTag :: !FastString
, gtKind :: !GhcKind
, gtFields :: ![TagField]
}
deriving Show
appendField :: TagField -> GhcTag -> GhcTag
appendField f gt = gt { gtFields = f : gtFields gt }
type GhcTags = [GhcTag]
getFileTagField :: Maybe [IE GhcPs] -> Located RdrName -> Maybe TagField
getFileTagField Nothing _name = Nothing
getFileTagField (Just ies) name =
maybe (Just fileField) (const Nothing) $ find (\a -> ieName a == Just (unLoc name)) ies
where
ieName :: IE GhcPs -> Maybe RdrName
ieName (IEVar _ (L _ n)) = Just $ ieWrappedName n
ieName (IEThingAbs _ (L _ n)) = Just $ ieWrappedName n
ieName (IEThingWith _ (L _ n) _ _ _) = Just $ ieWrappedName n
ieName (IEThingAll _ (L _ n)) = Just $ ieWrappedName n
ieName _ = Nothing
getFileTagFieldForMember :: Maybe [IE GhcPs]
-> Located RdrName
-> Located RdrName
-> Maybe TagField
getFileTagFieldForMember Nothing _memberName _className = Nothing
getFileTagFieldForMember (Just ies) memberName className =
maybe (Just fileField) (const Nothing) $ find go ies
where
go :: IE GhcPs -> Bool
go (IEVar _ (L _ n)) = ieWrappedName n == unLoc memberName
go (IEThingAbs _ _) = False
go (IEThingAll _ (L _ n)) = ieWrappedName n == unLoc className
go (IEThingWith _ _ IEWildcard{} _ _) = True
go (IEThingWith _ (L _ n) NoIEWildcard ns lfls) =
ieWrappedName n == unLoc className
&& (isInWrappedNames || isInFieldLbls)
where
isInWrappedNames = any ((== occNameFS (rdrNameOcc (unLoc memberName))) . occNameFS . rdrNameOcc . ieWrappedName . unLoc) ns
isInFieldLbls = any ((== occNameFS (rdrNameOcc (unLoc memberName))) . occNameFS . rdrNameOcc . flSelector. unLoc) lfls
go _ = False
mkGhcTag :: Located RdrName
-> GhcKind
-> [TagField]
-> GhcTag
mkGhcTag (L gtSrcSpan rdrName) gtKind gtFields =
case rdrName of
Unqual occName ->
GhcTag { gtTag = occNameFS occName
, gtSrcSpan
, gtKind
, gtFields
}
Qual _ occName ->
GhcTag { gtTag = occNameFS occName
, gtSrcSpan
, gtKind
, gtFields
}
Orig _ occName ->
GhcTag { gtTag = occNameFS occName
, gtSrcSpan
, gtKind
, gtFields
}
Exact eName ->
GhcTag { gtTag = occNameFS $ nameOccName eName
, gtSrcSpan
, gtKind
, gtFields
}
getGhcTags :: Located (HsModule GhcPs)
-> GhcTags
getGhcTags (L _ HsModule { hsmodDecls, hsmodExports }) =
reverse $ foldl' go [] hsmodDecls
where
mies :: Maybe [IE GhcPs]
mies = map unLoc . unLoc <$> hsmodExports
mkGhcTag' :: Located RdrName
-> GhcKind
-> GhcTag
mkGhcTag' a k = mkGhcTag a k (maybeToList $ getFileTagField mies a)
mkGhcTagForMember :: Located RdrName
-> Located RdrName
-> GhcKind
-> GhcTag
mkGhcTagForMember memberName className kind =
mkGhcTag memberName kind (maybeToList $ getFileTagFieldForMember mies memberName className)
go :: GhcTags -> LHsDecl GhcPs -> GhcTags
go tags (L _ hsDecl) = case hsDecl of
TyClD _ tyClDecl ->
case tyClDecl of
FamDecl { tcdFam } ->
case mkFamilyDeclTags tcdFam Nothing of
Just tag -> tag : tags
Nothing -> tags
SynDecl { tcdLName } ->
mkGhcTag' tcdLName TkTypeSynonym : tags
DataDecl { tcdLName, tcdDataDefn } ->
case tcdDataDefn of
HsDataDefn { dd_cons } ->
mkGhcTag' tcdLName TkTypeConstructor
: (mkConsTags tcdLName . unLoc) `concatMap` dd_cons
++ tags
XHsDataDefn {} -> tags
ClassDecl { tcdLName, tcdSigs, tcdMeths, tcdATs } ->
mkGhcTag' tcdLName TkTypeClass
: (mkClsMemberTags tcdLName . unLoc) `concatMap` tcdSigs
++ foldl' (\tags' hsBind -> mkHsBindLRTags (unLoc hsBind) ++ tags')
tags
tcdMeths
++ (flip mkFamilyDeclTags (Just tcdLName) . unLoc) `mapMaybe` tcdATs
XTyClDecl {} -> tags
InstD _ instDecl ->
case instDecl of
ClsInstD { cid_inst } ->
case cid_inst of
XClsInstDecl {} -> tags
ClsInstDecl { cid_poly_ty, cid_tyfam_insts, cid_datafam_insts } ->
case cid_poly_ty of
XHsImplicitBndrs {} ->
tyFamTags ++ dataFamTags ++ tags
HsIB { hsib_body } ->
case mkLHsTypeTag hsib_body of
Nothing -> tyFamTags ++ dataFamTags ++ tags
Just tag -> tag : tyFamTags ++ dataFamTags ++ tags
where
dataFamTags = (mkDataFamInstDeclTag . unLoc) `concatMap` cid_datafam_insts
tyFamTags = (mkTyFamInstDeclTag . unLoc) `mapMaybe` cid_tyfam_insts
DataFamInstD { dfid_inst } ->
mkDataFamInstDeclTag dfid_inst ++ tags
TyFamInstD { tfid_inst } ->
case mkTyFamInstDeclTag tfid_inst of
Nothing -> tags
Just tag -> tag : tags
XInstDecl {} -> tags
DerivD {} -> tags
ValD _ hsBind -> mkHsBindLRTags hsBind ++ tags
SigD _ sig -> mkSigTags sig ++ tags
DefD {} -> tags
ForD _ foreignDecl ->
case foreignDecl of
ForeignImport { fd_name, fd_fi = CImport _ _ _mheader _ (L _ sourceText) } ->
case sourceText of
NoSourceText -> tag
SourceText s -> TagField "ffi" (Text.pack s) `appendField` tag
: tags
where
tag = mkGhcTag' fd_name TkForeignImport
ForeignExport { fd_name } ->
mkGhcTag' fd_name TkForeignExport
: tags
XForeignDecl {} -> tags
WarningD {} -> tags
AnnD {} -> tags
RuleD {} -> tags
SpliceD {} -> tags
DocD {} -> tags
RoleAnnotD {} -> tags
XHsDecl {} -> tags
mkConsTags :: Located RdrName
-> ConDecl GhcPs
-> GhcTags
mkConsTags tyName ConDeclGADT { con_names, con_args } =
(\n -> mkGhcTagForMember n tyName TkGADTConstructor)
`map` con_names
++ mkHsConDeclDetails con_args
mkConsTags tyName ConDeclH98 { con_name, con_args } =
mkGhcTagForMember con_name tyName TkDataConstructor
: mkHsConDeclDetails con_args
mkConsTags _ XConDecl {} = []
mkHsConDeclDetails :: HsConDeclDetails GhcPs -> GhcTags
mkHsConDeclDetails (RecCon (L _ fields)) = foldl' f [] fields
where
f :: GhcTags -> LConDeclField GhcPs -> GhcTags
f ts (L _ ConDeclField { cd_fld_names }) = foldl' g ts cd_fld_names
f ts _ = ts
g :: GhcTags -> LFieldOcc GhcPs -> GhcTags
g ts (L _ FieldOcc { rdrNameFieldOcc }) =
mkGhcTag' rdrNameFieldOcc TkRecordField
: ts
g ts _ = ts
mkHsConDeclDetails _ = []
mkHsBindLRTags :: HsBindLR GhcPs GhcPs -> GhcTags
mkHsBindLRTags hsBind =
case hsBind of
FunBind { fun_id } -> [mkGhcTag' fun_id TkFunction]
PatBind {} -> []
VarBind { var_id, var_rhs = L srcSpan _ } -> [mkGhcTag' (L srcSpan var_id) TkTerm]
AbsBinds {} -> []
PatSynBind _ PSB { psb_id } -> [mkGhcTag' psb_id TkPatternSynonym]
PatSynBind _ XPatSynBind {} -> []
XHsBindsLR {} -> []
mkClsMemberTags :: Located RdrName -> Sig GhcPs -> GhcTags
mkClsMemberTags clsName (TypeSig _ lhs _) =
(\n -> mkGhcTagForMember n clsName TkTypeSignature)
`map` lhs
mkClsMemberTags clsName (PatSynSig _ lhs _) =
(\n -> mkGhcTagForMember n clsName TkPatternSynonym)
`map` lhs
mkClsMemberTags clsName (ClassOpSig _ _ lhs _) =
(\n -> mkGhcTagForMember n clsName TkTypeClassMember)
`map` lhs
mkClsMemberTags _ _ = []
mkSigTags :: Sig GhcPs -> GhcTags
mkSigTags (TypeSig _ lhs _) = flip mkGhcTag' TkTypeSignature `map` lhs
mkSigTags (PatSynSig _ lhs _) = flip mkGhcTag' TkPatternSynonym `map` lhs
mkSigTags (ClassOpSig _ _ lhs _) = flip mkGhcTag' TkTypeClassMember `map` lhs
mkSigTags IdSig {} = []
mkSigTags FixSig {} = []
mkSigTags InlineSig {} = []
mkSigTags SpecSig {} = []
mkSigTags SpecInstSig {} = []
mkSigTags MinimalSig {} = []
mkSigTags SCCFunSig {} = []
mkSigTags CompleteMatchSig {} = []
mkSigTags XSig {} = []
mkFamilyDeclTags :: FamilyDecl GhcPs
-> Maybe (Located RdrName)
-> Maybe GhcTag
mkFamilyDeclTags FamilyDecl { fdLName, fdInfo } assocClsName =
case assocClsName of
Nothing -> Just $ mkGhcTag' fdLName tk
Just clsName -> Just $ mkGhcTagForMember fdLName clsName tk
where
tk = case fdInfo of
DataFamily -> TkDataTypeFamily
OpenTypeFamily -> TkTypeFamily
ClosedTypeFamily {} -> TkTypeFamily
mkFamilyDeclTags XFamilyDecl {} _ = Nothing
mkLHsTypeTag :: LHsType GhcPs -> Maybe GhcTag
mkLHsTypeTag (L _ hsType) = (\a -> mkGhcTag a TkTypeClassInstance []) <$> hsTypeTagName hsType
hsTypeTagName :: HsType GhcPs -> Maybe (Located RdrName)
hsTypeTagName hsType =
case hsType of
HsForAllTy {hst_body} -> hsTypeTagName (unLoc hst_body)
HsQualTy {hst_body} -> hsTypeTagName (unLoc hst_body)
HsTyVar _ _ a -> Just $ a
HsAppTy _ a _ -> hsTypeTagName (unLoc a)
HsOpTy _ _ a _ -> Just $ a
HsKindSig _ a _ -> hsTypeTagName (unLoc a)
_ -> Nothing
mkDataFamInstDeclTag :: DataFamInstDecl GhcPs -> GhcTags
mkDataFamInstDeclTag DataFamInstDecl { dfid_eqn } =
case dfid_eqn of
XHsImplicitBndrs {} -> []
HsIB { hsib_body = FamEqn { feqn_tycon, feqn_rhs } } ->
case feqn_rhs of
HsDataDefn { dd_cons } ->
mkGhcTag' feqn_tycon TkDataTypeFamilyInstance
: (mkConsTags feqn_tycon . unLoc) `concatMap` dd_cons
XHsDataDefn {} ->
mkGhcTag' feqn_tycon TkDataTypeFamilyInstance : []
HsIB { hsib_body = XFamEqn {} } -> []
mkTyFamInstDeclTag :: TyFamInstDecl GhcPs -> Maybe GhcTag
mkTyFamInstDeclTag TyFamInstDecl { tfid_eqn } =
case tfid_eqn of
XHsImplicitBndrs {} -> Nothing
HsIB { hsib_body = FamEqn { feqn_tycon } } ->
Just $ mkGhcTag' feqn_tycon TkTypeFamilyInstance
HsIB { hsib_body = XFamEqn {} } -> Nothing