88module Ide.Plugin.Class.Types where
99
1010import Control.DeepSeq (rwhnf )
11- import Control.Monad.Extra (mapMaybeM , whenMaybe )
11+ import Control.Monad.Extra (mapMaybeM )
1212import Control.Monad.IO.Class (liftIO )
13- import Control.Monad.Trans.Maybe (MaybeT ( MaybeT , runMaybeT ) )
13+ import Control.Monad.Trans.Maybe (runMaybeT )
1414import Data.Aeson
1515import qualified Data.IntMap as IntMap
16- import Data.List.Extra (firstJust )
17- import Data.Maybe (catMaybes , mapMaybe ,
18- maybeToList )
16+ import Data.Maybe (fromMaybe , listToMaybe ,
17+ mapMaybe , maybeToList )
1918import qualified Data.Text as T
2019import Data.Unique (hashUnique , newUnique )
2120import Development.IDE
@@ -26,6 +25,7 @@ import Development.IDE.GHC.Compat.Util (bagToList)
2625import Development.IDE.Graph.Classes
2726import GHC.Generics
2827import Ide.Plugin.Class.Utils
28+ import qualified Ide.Plugin.RangeMap as RangeMap
2929import Ide.Types
3030import Language.LSP.Protocol.Types (TextEdit ,
3131 VersionedTextDocumentIdentifier )
@@ -49,27 +49,33 @@ data AddMinimalMethodsParams = AddMinimalMethodsParams
4949 }
5050 deriving (Show , Eq , Generic , ToJSON , FromJSON )
5151
52- -- | The InstanceBindTypeSigs Rule collects the instance bindings type
53- -- signatures (both name and type). It is used by both the code actions and the
54- -- code lenses
55- data GetInstanceBindTypeSigs = GetInstanceBindTypeSigs
52+ -- | Indexes the instances declared in a module by their source span, giving
53+ -- each instance's class and the full instantiated type of every class method.
54+ -- Both the placeholder code action (to enumerate missing methods) and the
55+ -- code lens (to display inferred signatures) consume this rule.
56+ data GetClassInstances = GetClassInstances
5657 deriving (Generic , Show , Eq , Ord , Hashable , NFData )
5758
58- data InstanceBindTypeSig = InstanceBindTypeSig
59- { bindName :: Name
60- , bindType :: Type
59+ data InstanceInfo = InstanceInfo
60+ { instSpan :: SrcSpan
61+ -- ^ Source span of the instance declaration.
62+ , instClass :: Class
63+ , instMethods :: [(Name , Type )]
64+ -- ^ Each class method paired with its type instantiated for this
65+ -- instance, including any instance context (e.g. @Eq a@ for
66+ -- @instance Eq a => C [a]@).
6167 }
6268
63- newtype InstanceBindTypeSigsResult =
64- InstanceBindTypeSigsResult [ InstanceBindTypeSig ]
69+ newtype ClassInstancesResult =
70+ ClassInstancesResult ( RangeMap. RangeMap InstanceInfo )
6571
66- instance Show InstanceBindTypeSigsResult where
67- show _ = " <InstanceBindTypeSigs >"
72+ instance Show ClassInstancesResult where
73+ show _ = " <ClassInstances >"
6874
69- instance NFData InstanceBindTypeSigsResult where
75+ instance NFData ClassInstancesResult where
7076 rnf = rwhnf
7177
72- type instance RuleResult GetInstanceBindTypeSigs = InstanceBindTypeSigsResult
78+ type instance RuleResult GetClassInstances = ClassInstancesResult
7379
7480-- | The necessary data to execute our code lens
7581data InstanceBindLensCommand = InstanceBindLensCommand
@@ -80,11 +86,10 @@ data InstanceBindLensCommand = InstanceBindLensCommand
8086 , commandEdit :: TextEdit }
8187 deriving (Generic , FromJSON , ToJSON )
8288
83- -- | The InstanceBindLens rule is specifically for code lenses. It relies on
84- -- the InstanceBindTypeSigs rule, filters out irrelevant matches and signatures
85- -- that can't be matched to a source span. It provides all the signatures linked
86- -- to a unique ID to aid in resolving. It also provides a list of enabled
87- -- extensions.
89+ -- | The InstanceBindLens rule is specifically for code lenses. It correlates
90+ -- user-written instance method bindings (those without an explicit signature)
91+ -- to the instance's 'InstanceInfo', and emits range/name/type triples with
92+ -- unique IDs for resolve.
8893data GetInstanceBindLens = GetInstanceBindLens
8994 deriving (Generic , Show , Eq , Ord , Hashable , NFData )
9095
@@ -123,11 +128,14 @@ instance Pretty Log where
123128 <+> pretty (showSDoc dflags $ ppr methods)
124129 LogShake log -> pretty log
125130
131+ -- | A user-written instance binding without an explicit signature. The 'Name'
132+ -- is the renamer-level method name (e.g. @(==)@), used to look up the
133+ -- instance-level type in 'instMethods'.
126134data BindInfo = BindInfo
127- { bindSpan :: SrcSpan
135+ { bindSpan :: SrcSpan
128136 -- ^ SrcSpan of the whole binding
129- , bindNameSpan :: SrcSpan
130- -- ^ SrcSpan of the binding name
137+ , bindFunName :: Name
138+ -- ^ The renamed method name of the binding.
131139 }
132140
133141getInstanceBindLensRule :: Recorder (WithPriority Log ) -> Rules ()
@@ -138,35 +146,34 @@ getInstanceBindLensRule recorder = do
138146#else
139147 tmr@ (tmrRenamed -> (hs_tyclds -> tycls, _, _, _)) <- useMT TypeCheck nfp
140148#endif
141- (InstanceBindTypeSigsResult allBinds) <- useMT GetInstanceBindTypeSigs nfp
142-
143- let -- declared instance methods without signatures
144- bindInfos = [ bind
145- | instds <- map group_instds tycls -- class instance decls
146- , instd <- instds
147- , inst <- maybeToList $ getClsInstD (unLoc instd)
148- , bind <- getBindSpanWithoutSig inst
149- ]
150- targetSigs = matchBind bindInfos allBinds
151- rangeIntNameType <- liftIO $ mapMaybeM getRangeWithSig targetSigs
149+ ClassInstancesResult instMap <- useMT GetClassInstances nfp
150+
151+ let -- Correlate renamed ClsInstDecls with their InstanceInfo by source
152+ -- span (the only link between the renamed tree and tcg_insts), then
153+ -- collect user-written bindings that lack an explicit signature.
154+ entries =
155+ [ (bind, ty)
156+ | instds <- map group_instds tycls
157+ , instd <- instds
158+ , inst <- maybeToList $ getClsInstD (unLoc instd)
159+ , info <- maybeToList $ do
160+ instdRange <- srcSpanToRange (getLocA instd)
161+ listToMaybe (RangeMap. elementsInRange instdRange instMap)
162+ , bind <- getBindSpanWithoutSig inst
163+ , ty <- maybeToList (lookup (bindFunName bind) (instMethods info))
164+ ]
165+ rangeIntNameType <- liftIO $ mapMaybeM tagEntry entries
152166 let lensRange = (\ (range, int, _, _) -> (range, int)) <$> rangeIntNameType
153- lensDetails = IntMap. fromList $ (\ (range, int, name, typ) -> (int, (range, name, typ))) <$> rangeIntNameType
167+ lensDetails = IntMap. fromList $
168+ (\ (range, int, name, typ) -> (int, (range, name, typ))) <$> rangeIntNameType
154169 lensEnabledExtensions = getExtensions $ tmrParsed tmr
155170 pure $ InstanceBindLensResult $ InstanceBindLens {.. }
156171 where
157- -- Match Binds with their signatures
158- -- We try to give every `InstanceBindTypeSig` a `SrcSpan`,
159- -- hence we can display signatures for `InstanceBindTypeSig` with span later.
160- matchBind :: [BindInfo ] -> [InstanceBindTypeSig ] -> [Maybe (InstanceBindTypeSig , SrcSpan )]
161- matchBind existedBinds allBindWithSigs =
162- [firstJust (go bindSig) existedBinds | bindSig <- allBindWithSigs]
163- where
164- go :: InstanceBindTypeSig -> BindInfo -> Maybe (InstanceBindTypeSig , SrcSpan )
165- go bindSig bind = do
166- range <- (srcSpanToRange . bindNameSpan) bind
167- if inRange range (getSrcSpan $ bindName bindSig)
168- then Just (bindSig, bindSpan bind)
169- else Nothing
172+ tagEntry (bind, ty) = case srcSpanToRange (bindSpan bind) of
173+ Nothing -> pure Nothing
174+ Just r -> do
175+ uniqueID <- hashUnique <$> newUnique
176+ pure $ Just (r, uniqueID, bindFunName bind, ty)
170177
171178 getClsInstD (ClsInstD _ d) = Just d
172179 getClsInstD _ = Nothing
@@ -183,52 +190,54 @@ getInstanceBindLensRule recorder = do
183190 cid_binds
184191 go (L l bind) = case bind of
185192 FunBind {.. }
186- -- `Generated` tagged for Template Haskell,
187- -- here we filter out nonsense generated bindings
188- -- that are nonsense for displaying code lenses.
189- --
190- -- See https://github.com/haskell/haskell-language-server/issues/3319
191- | not $ isGenerated (groupOrigin fun_matches)
192- -> Just $ L l fun_id
193- _ -> Nothing
193+ -- `Generated` tagged for Template Haskell,
194+ -- here we filter out nonsense generated bindings
195+ -- that are nonsense for displaying code lenses.
196+ --
197+ -- See https://github.com/haskell/haskell-language-server/issues/3319
198+ | not $ isGenerated (groupOrigin fun_matches)
199+ -> Just $ L l fun_id
200+ _ -> Nothing
194201 -- Existed signatures' name
195- sigNames = concat $ mapMaybe (\ (L _ r) -> getSigName r) cid_sigs
196- toBindInfo (L l (L l' _)) = BindInfo
197- (locA l) -- bindSpan
198- (locA l') -- bindNameSpan
199- in toBindInfo <$> filter (\ (L _ name) -> unLoc name `notElem` sigNames) bindNames
200-
201- -- Get bind definition range with its rendered signature text
202- getRangeWithSig :: Maybe (InstanceBindTypeSig , SrcSpan ) -> IO (Maybe (Range , Int , Name , Type ))
203- getRangeWithSig (Just (bind, span )) = runMaybeT $ do
204- range <- MaybeT . pure $ srcSpanToRange span
205- uniqueID <- liftIO $ hashUnique <$> newUnique
206- pure (range, uniqueID, bindName bind, bindType bind)
207- getRangeWithSig Nothing = pure Nothing
208-
209-
210- getInstanceBindTypeSigsRule :: Recorder (WithPriority Log ) -> Rules ()
211- getInstanceBindTypeSigsRule recorder = do
212- defineNoDiagnostics (cmapWithPrio LogShake recorder) $ \ GetInstanceBindTypeSigs nfp -> runMaybeT $ do
213- (tmrTypechecked -> gblEnv ) <- useMT TypeCheck nfp
202+ existingSigNames = concat $ mapMaybe (\ (L _ r) -> getSigName r) cid_sigs
203+ toBindInfo (L l (L _ n)) = BindInfo (locA l) n
204+ in toBindInfo <$> filter (\ (L _ name) -> unLoc name `notElem` existingSigNames) bindNames
205+
206+ getClassInstancesRule :: Recorder (WithPriority Log ) -> Rules ()
207+ getClassInstancesRule recorder = do
208+ defineNoDiagnostics (cmapWithPrio LogShake recorder) $ \ GetClassInstances nfp -> runMaybeT $ do
209+ (tmrTypechecked -> gblEnv) <- useMT TypeCheck nfp
214210 (hscEnv -> hsc) <- useMT GhcSession nfp
215- let binds = collectHsBindsBinders $ tcg_binds gblEnv
216- (_, maybe [] catMaybes -> instanceBinds) <- liftIO $
211+ (_, mInfos) <- liftIO $
217212 initTcWithGbl hsc gblEnv ghostSpan
218213#if MIN_VERSION_ghc(9,7,0)
219214 $ liftZonkM
220215#endif
221- $ traverse bindToSig binds
222- pure $ InstanceBindTypeSigsResult instanceBinds
216+ $ mkInfos gblEnv
217+ pure $ ClassInstancesResult $ RangeMap. fromList'
218+ $ mapMaybe (\ i -> (,i) <$> srcSpanToRange (instSpan i)) (fromMaybe [] mInfos)
223219 where
224- bindToSig id = do
225- let name = idName id
226- whenMaybe (isBindingName name) $ do
227- env <- tcInitTidyEnv
220+ mkInfos gblEnv = do
221+ env <- tcInitTidyEnv
222+ pure $ map (mkInfo env) (tcg_insts gblEnv)
223+
224+ mkInfo env inst = do
225+ -- forall tvs. theta => cls tys
226+ let (_tvs, theta, cls, tys) = instanceSig inst
227+ -- Canonicalise internal GHC type-variable names (e.g. a_1 -> a).
228+ tidy ty =
228229#if MIN_VERSION_ghc(9,11,0)
229- let ty =
230+ tidyOpenType env ty
230231#else
231- let (_, ty) =
232+ snd (tidyOpenType env ty)
232233#endif
233- tidyOpenType env (idType id )
234- pure $ InstanceBindTypeSig name ty
234+ -- `instantiateMethod` substitutes the instance head types into
235+ -- the method's type and drops the leading class predicate, but
236+ -- not the instance's own constraints (`theta`). Re-prepend them
237+ -- so we get every method's full instantiated type.
238+ mkMeth m = (idName m, tidy (mkInvisFunTys theta (instantiateMethod cls m tys)))
239+ InstanceInfo
240+ { instSpan = getSrcSpan (is_dfun inst)
241+ , instClass = cls
242+ , instMethods = map mkMeth (classMethods cls)
243+ }
0 commit comments