Skip to content

Semantic tokens: add module name support and improve performance and accuracy by traversing the hieAst along with source code #3958

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 84 commits into from
Jan 29, 2024
Merged
Show file tree
Hide file tree
Changes from 79 commits
Commits
Show all changes
84 commits
Select commit Hold shift + click to select a range
e60c7ae
add module name support for semantic tokens
soulomoon Jan 15, 2024
1379382
cleanup
soulomoon Jan 15, 2024
b2cbb8c
cleanup
soulomoon Jan 15, 2024
7896b67
fix docName
soulomoon Jan 15, 2024
422fc95
regenerate the config test result
soulomoon Jan 15, 2024
b2e0f31
stylish
soulomoon Jan 15, 2024
388ff4b
mend
soulomoon Jan 17, 2024
4b78555
rename
soulomoon Jan 17, 2024
445a825
fix module name
soulomoon Jan 17, 2024
04d08a5
add test for qualified names
soulomoon Jan 17, 2024
2cca23d
fix test
soulomoon Jan 17, 2024
c0bd67a
strip ()
soulomoon Jan 17, 2024
7a68ccd
stylish
soulomoon Jan 17, 2024
9758193
remove wrap '' () from tokens and add test
soulomoon Jan 17, 2024
8d27484
fix doc and rename
soulomoon Jan 17, 2024
6e69acb
improve test TQualifiedName
soulomoon Jan 17, 2024
10d92e6
rename
soulomoon Jan 17, 2024
8fe342e
add doc
soulomoon Jan 17, 2024
481a404
fix test
soulomoon Jan 17, 2024
16f7086
fix test
soulomoon Jan 17, 2024
72e8eee
fix test
soulomoon Jan 17, 2024
2a8855f
fix test
soulomoon Jan 17, 2024
511e670
stylish
soulomoon Jan 17, 2024
9072e7b
add tokenize
soulomoon Jan 22, 2024
6f588ca
clean up
soulomoon Jan 22, 2024
4236ca6
clean up
soulomoon Jan 22, 2024
65d493d
stylish
soulomoon Jan 22, 2024
017c848
Merge branch 'master' into add-module-name-support
soulomoon Jan 22, 2024
bb7f50d
cleanup
soulomoon Jan 22, 2024
d5dfaca
Merge branch 'master' into add-module-name-support
soulomoon Jan 22, 2024
d596876
cleaup
soulomoon Jan 22, 2024
d113778
add type signatures
soulomoon Jan 23, 2024
1ea88fb
cleanup
soulomoon Jan 23, 2024
5cc0c4d
cleanup
soulomoon Jan 23, 2024
b8a8ed3
cleanup
soulomoon Jan 23, 2024
84c8bcb
add type sig
soulomoon Jan 23, 2024
77b6210
fix
soulomoon Jan 23, 2024
0cd9540
fix
soulomoon Jan 23, 2024
a83a072
cleanup
soulomoon Jan 23, 2024
68a8d6b
Merge branch 'master' into add-module-name-support
soulomoon Jan 23, 2024
e296422
Merge branch 'master' into add-module-name-support
soulomoon Jan 23, 2024
b5e93e9
cleanup
soulomoon Jan 23, 2024
23d0d76
remove lengthFS
soulomoon Jan 23, 2024
a6e941a
cleanup
soulomoon Jan 23, 2024
c6f32d8
Merge branch 'master' into add-module-name-support
soulomoon Jan 24, 2024
68c187a
Merge branch 'master' into add-module-name-support
soulomoon Jan 24, 2024
19823dd
Merge branch 'master' into add-module-name-support
soulomoon Jan 24, 2024
622ac85
make ast traversing explicit
soulomoon Jan 24, 2024
b1f3fa0
cleanup
soulomoon Jan 24, 2024
811d0eb
optimize, splitRangeByText should not revert the work of focusTokenAt
soulomoon Jan 24, 2024
df48d37
clean up
soulomoon Jan 24, 2024
6fdff82
fix doc
soulomoon Jan 24, 2024
f76829e
rename Name to Id to fit the change
soulomoon Jan 24, 2024
c65d151
fix doc
soulomoon Jan 24, 2024
a0a1fb6
clean up
soulomoon Jan 24, 2024
43b70b9
clean up
soulomoon Jan 24, 2024
d77788e
clean up
soulomoon Jan 24, 2024
863ef26
only handle the leaf node with single column token
soulomoon Jan 24, 2024
e0a3ba7
cleanup
soulomoon Jan 24, 2024
f6ad967
fix
soulomoon Jan 24, 2024
7bd7514
clean up
soulomoon Jan 26, 2024
e87a9ba
fix sub
soulomoon Jan 26, 2024
ade5574
fix test for ghc92
soulomoon Jan 26, 2024
d89ced6
Merge branch 'master' into add-module-name-support
soulomoon Jan 26, 2024
06f55bc
Merge branch 'master' into add-module-name-support
soulomoon Jan 26, 2024
d9960e1
use strict map and make range and split explicit
soulomoon Jan 28, 2024
63fffa9
use strict map
soulomoon Jan 28, 2024
11e36ea
use strict map
soulomoon Jan 28, 2024
b473abc
cleanup
soulomoon Jan 28, 2024
c6d0023
add doc
soulomoon Jan 28, 2024
01c8099
handle more DerivedOccName
soulomoon Jan 28, 2024
14d6be8
use strict map
soulomoon Jan 28, 2024
dce593a
use strict map
soulomoon Jan 28, 2024
281850d
make field strict
soulomoon Jan 28, 2024
bef5a2a
cleanup
soulomoon Jan 28, 2024
47338cc
make rope strict in PTokenState
soulomoon Jan 28, 2024
72467a8
make map strict in PTokenState
soulomoon Jan 28, 2024
eea7f9f
use fromSet for importedIdSemanticMap
soulomoon Jan 28, 2024
eb5f9f0
add guard
soulomoon Jan 29, 2024
c605b26
cleanup
soulomoon Jan 29, 2024
fa8ca2c
fix
soulomoon Jan 29, 2024
73c9bbe
rename
soulomoon Jan 29, 2024
e2dd71c
Merge remote-tracking branch 'upstream/master' into add-module-name-s…
soulomoon Jan 29, 2024
9b717db
fix
soulomoon Jan 29, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions hls-plugin-api/src/Ide/Plugin/Properties.hs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
module Ide.Plugin.Properties
( PropertyType (..),
ToHsType,
NotElem,
MetaData (..),
PropertyKey (..),
SPropertyKey (..),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ source-repository head
location: https://github.com/haskell/haskell-language-server.git

library
ghc-options: -Wall
ghc-options: -Wall -Wincomplete-patterns -Werror=incomplete-patterns
buildable: True
exposed-modules:
Ide.Plugin.SemanticTokens
Expand All @@ -30,13 +30,15 @@ library
Ide.Plugin.SemanticTokens.Query
Ide.Plugin.SemanticTokens.SemanticConfig
Ide.Plugin.SemanticTokens.Utils
Ide.Plugin.SemanticTokens.Tokenize
Ide.Plugin.SemanticTokens.Internal

hs-source-dirs: src
build-depends:
, aeson
, base >=4.12 && <5
, containers
, text-rope
, extra
, hiedb
, mtl >= 2.2
Expand All @@ -61,7 +63,7 @@ library

test-suite tests
type: exitcode-stdio-1.0
ghc-options: -Wall
ghc-options: -Wall -Wincomplete-patterns -Werror=incomplete-patterns
default-language: Haskell2010
hs-source-dirs: test
main-is: Main.hs
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedLabels #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
Expand All @@ -21,8 +23,9 @@ import Control.Monad.Except (ExceptT, liftEither,
withExceptT)
import Control.Monad.Trans (lift)
import Control.Monad.Trans.Except (runExceptT)
import Data.Aeson (ToJSON (toJSON))
import qualified Data.Map as Map
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as M
import qualified Data.Set as S
import Development.IDE (Action,
GetDocMap (GetDocMap),
GetHieAst (GetHieAst),
Expand All @@ -34,7 +37,6 @@ import Development.IDE (Action,
cmapWithPrio, define,
fromNormalizedFilePath,
hieKind, logPriority,
usePropertyAction,
use_)
import Development.IDE.Core.PluginUtils (runActionE,
useWithStaleE)
Expand All @@ -54,6 +56,7 @@ import Ide.Plugin.Error (PluginError (PluginIn
import Ide.Plugin.SemanticTokens.Mappings
import Ide.Plugin.SemanticTokens.Query
import Ide.Plugin.SemanticTokens.SemanticConfig (mkSemanticConfigFunctions)
import Ide.Plugin.SemanticTokens.Tokenize (hieAstSpanIdentifiers)
import Ide.Plugin.SemanticTokens.Types
import Ide.Types
import qualified Language.LSP.Protocol.Lens as L
Expand Down Expand Up @@ -91,37 +94,37 @@ semanticTokensFull recorder state pid param = do
-- Local names token type from 'hieAst'
-- Name locations from 'hieAst'
-- Visible names from 'tmrRenamed'

--
-- It then combines this information to compute the semantic tokens for the file.
getSemanticTokensRule :: Recorder (WithPriority SemanticLog) -> Rules ()
getSemanticTokensRule recorder =
define (cmapWithPrio LogShake recorder) $ \GetSemanticTokens nfp -> handleError recorder $ do
(HAR {..}) <- lift $ use_ GetHieAst nfp
(DKMap {getTyThingMap}, _) <- lift $ useWithStale_ GetDocMap nfp
ast <- handleMaybe (LogNoAST $ show nfp) $ getAsts hieAst Map.!? (HiePath . mkFastString . fromNormalizedFilePath) nfp
ast <- handleMaybe (LogNoAST $ show nfp) $ getAsts hieAst M.!? (HiePath . mkFastString . fromNormalizedFilePath) nfp
virtualFile <- handleMaybeM LogNoVF $ getVirtualFile nfp
-- get current location from the old ones
let spanNamesMap = hieAstSpanNames virtualFile ast
let names = nameSetElemsStable $ unionNameSets $ Map.elems spanNamesMap
let localSemanticMap = mkLocalNameSemanticFromAst names (hieKindFunMasksKind hieKind) refMap
let spanIdMap = M.filter (not . null) $ hieAstSpanIdentifiers virtualFile ast
let names = S.unions $ M.elems spanIdMap
let localSemanticMap = mkLocalIdSemanticFromAst names (hieKindFunMasksKind hieKind) refMap
-- get imported name semantic map
let importedNameSemanticMap = foldr (getTypeExclude localSemanticMap getTyThingMap) emptyNameEnv names
let sMap = plusNameEnv_C (<>) importedNameSemanticMap localSemanticMap
let rangeTokenType = extractSemanticTokensFromNames sMap spanNamesMap
let importedIdSemanticMap = M.mapMaybe id
$ M.fromSet (getTypeExclude getTyThingMap) (names `S.difference` M.keysSet localSemanticMap)
let sMap = M.unionWith (<>) importedIdSemanticMap localSemanticMap
let rangeTokenType = extractSemanticTokensFromNames sMap spanIdMap
return $ RangeHsSemanticTokenTypes rangeTokenType
where
-- ignore one already in discovered in local
getTypeExclude ::
NameEnv a ->
NameEnv TyThing ->
Name ->
NameEnv HsSemanticTokenType ->
NameEnv HsSemanticTokenType
getTypeExclude localEnv tyThingMap n nameMap
| n `elemNameEnv` localEnv = nameMap
| otherwise =
let tyThing = lookupNameEnv tyThingMap n
in maybe nameMap (extendNameEnv nameMap n) (tyThing >>= tyThingSemantic)
Identifier ->
Maybe HsSemanticTokenType
getTypeExclude tyThingMap n
| (Right name) <- n =
let tyThing = lookupNameEnv tyThingMap name
in (tyThing >>= tyThingSemantic)
| otherwise = Nothing

-- | Persistent rule to ensure that semantic tokens doesn't block on startup
persistentGetSemanticTokensRule :: Rules ()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ module Ide.Plugin.SemanticTokens.Mappings where

import qualified Data.Array as A
import Data.List.Extra (chunksOf, (!?))
import qualified Data.Map as Map
import qualified Data.Map.Strict as Map
import Data.Maybe (mapMaybe)
import qualified Data.Set as Set
import Data.Text (Text, unpack)
Expand Down Expand Up @@ -45,6 +45,7 @@ toLspTokenType conf tk = case tk of
TTypeFamily -> stTypeFamily conf
TRecordField -> stRecordField conf
TPatternSynonym -> stPatternSynonym conf
TModule -> stModule conf

lspTokenReverseMap :: SemanticTokensConfig -> Map.Map SemanticTokenTypes HsSemanticTokenType
lspTokenReverseMap config
Expand Down Expand Up @@ -114,15 +115,15 @@ recoverFunMaskArray flattened = unflattened
-- The recursion in 'unflattened' is crucial - it's what gives us sharing
-- function indicator check.
unflattened :: A.Array TypeIndex Bool
unflattened = fmap (\flatTy -> go (fmap (unflattened A.!) flatTy)) flattened
unflattened = fmap (go . fmap (unflattened A.!)) flattened

-- Unfold an 'HieType' whose subterms have already been unfolded
-- Unfold an 'HieType' whose sub-terms have already been unfolded
go :: HieType Bool -> Bool
go (HTyVarTy _name) = False
go (HAppTy _f _x) = False
go (HLitTy _lit) = False
go (HForAllTy ((_n, _k), _af) b) = b
go (HFunTy _ _ _) = True
go (HFunTy {}) = True
go (HQualTy _constraint b) = b
go (HCastTy b) = b
go HCoercionTy = False
Expand Down
Original file line number Diff line number Diff line change
@@ -1,106 +1,75 @@
{-# LANGUAGE ExplicitNamespaces #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}

-- |
-- The query module is used to query the semantic tokens from the AST
module Ide.Plugin.SemanticTokens.Query where

import Data.Either (rights)
import Data.Foldable (fold)
import qualified Data.Map as M
import qualified Data.Map as Map
import Data.Maybe (fromMaybe, listToMaybe,
mapMaybe)
import qualified Data.Set as S
import qualified Data.Map.Strict as M
import Data.Maybe (listToMaybe, mapMaybe)
import Data.Set (Set)
import qualified Data.Set as Set
import Data.Text (Text)
import Development.IDE.Core.PositionMapping (PositionMapping,
toCurrentRange)
import Development.IDE.GHC.Compat
import Development.IDE.GHC.Error (realSrcSpanToCodePointRange)
import Ide.Plugin.SemanticTokens.Mappings
import Ide.Plugin.SemanticTokens.Types (HieFunMaskKind,
HsSemanticTokenType,
NameSemanticMap,
HsSemanticTokenType (TModule),
IdSemanticMap,
RangeIdSetMap,
SemanticTokensConfig)
import Language.LSP.Protocol.Types
import Language.LSP.VFS (VirtualFile,
codePointRangeToRange)
import Prelude hiding (span)
import Language.LSP.Protocol.Types (Position (Position),
Range (Range),
SemanticTokenAbsolute (SemanticTokenAbsolute),
SemanticTokens,
defaultSemanticTokensLegend,
makeSemanticTokens)
import Prelude hiding (length, span)

---------------------------------------------------------

-- * extract semantic map from HieAst for local variables

---------------------------------------------------------

mkLocalNameSemanticFromAst :: [Name] -> HieFunMaskKind a -> RefMap a -> NameSemanticMap
mkLocalNameSemanticFromAst names hieKind rm = mkNameEnv (mapMaybe (nameNameSemanticFromHie hieKind rm) names)
mkLocalIdSemanticFromAst :: Set Identifier -> HieFunMaskKind a -> RefMap a -> IdSemanticMap
mkLocalIdSemanticFromAst names hieKind rm = M.mapMaybe (idIdSemanticFromHie hieKind rm) $ M.fromSet id names

nameNameSemanticFromHie :: forall a. HieFunMaskKind a -> RefMap a -> Name -> Maybe (Name, HsSemanticTokenType)
nameNameSemanticFromHie hieKind rm ns = do
st <- nameSemanticFromRefMap rm ns
return (ns, st)
idIdSemanticFromHie :: forall a. HieFunMaskKind a -> RefMap a -> Identifier -> Maybe HsSemanticTokenType
idIdSemanticFromHie _ _ (Left _) = Just TModule
idIdSemanticFromHie hieKind rm ns = do
idSemanticFromRefMap rm ns
where
nameSemanticFromRefMap :: RefMap a -> Name -> Maybe HsSemanticTokenType
nameSemanticFromRefMap rm' name' = do
spanInfos <- Map.lookup (Right name') rm'
idSemanticFromRefMap :: RefMap a -> Identifier -> Maybe HsSemanticTokenType
idSemanticFromRefMap rm' name' = do
spanInfos <- M.lookup name' rm'
let typeTokenType = foldMap (typeSemantic hieKind) $ listToMaybe $ mapMaybe (identType . snd) spanInfos
contextInfoTokenType <- foldMap (contextInfosMaybeTokenType . identInfo . snd) spanInfos
fold [typeTokenType, Just contextInfoTokenType]

contextInfosMaybeTokenType :: Set.Set ContextInfo -> Maybe HsSemanticTokenType
contextInfosMaybeTokenType details = foldMap infoTokenType (Set.toList details)

-----------------------------------

-- * extract location from HieAST a

-----------------------------------

-- | get only visible names from HieAST
-- we care only the leaf node of the AST
-- and filter out the derived and evidence names
hieAstSpanNames :: VirtualFile -> HieAST a -> M.Map Range NameSet
hieAstSpanNames vf ast =
if null (nodeChildren ast)
then getIds ast
else M.unionsWith unionNameSet $ map (hieAstSpanNames vf) (nodeChildren ast)
where
getIds ast' = fromMaybe mempty $ do
range <- codePointRangeToRange vf $ realSrcSpanToCodePointRange $ nodeSpan ast'
return $ M.singleton range (getNodeIds' ast')
getNodeIds' =
Map.foldl' combineNodeIds mempty
. Map.filterWithKey (\k _ -> k == SourceInfo)
. getSourcedNodeInfo
. sourcedNodeInfo
combineNodeIds :: NameSet -> NodeInfo a -> NameSet
ad `combineNodeIds` (NodeInfo _ _ bd) = ad `unionNameSet` xs
where
xs = mkNameSet $ rights $ M.keys $ M.filterWithKey inclusion bd
inclusion :: Identifier -> IdentifierDetails a -> Bool
inclusion a b = not $ exclusion a b
exclusion :: Identifier -> IdentifierDetails a -> Bool
exclusion idt IdentifierDetails {identInfo = infos} = case idt of
Left _ -> True
Right _ -> any isEvidenceContext (S.toList infos)

-------------------------------------------------

-- * extract semantic tokens from NameSemanticMap
-- * extract semantic tokens from IdSemanticMap

-------------------------------------------------

extractSemanticTokensFromNames :: NameSemanticMap -> M.Map Range NameSet -> M.Map Range HsSemanticTokenType
extractSemanticTokensFromNames nsm = Map.mapMaybe (foldMap (lookupNameEnv nsm) . nameSetElemsStable)
extractSemanticTokensFromNames :: IdSemanticMap -> RangeIdSetMap -> M.Map Range HsSemanticTokenType
extractSemanticTokensFromNames nsm = M.mapMaybe (foldMap (`M.lookup` nsm))

rangeSemanticMapSemanticTokens :: SemanticTokensConfig -> PositionMapping -> M.Map Range HsSemanticTokenType -> Either Text SemanticTokens
rangeSemanticMapSemanticTokens stc mapping =
makeSemanticTokens defaultSemanticTokensLegend
. mapMaybe (\(range, ty) -> flip toAbsSemanticToken ty <$> range)
. Map.toAscList
. M.toAscList
. M.mapKeys (toCurrentRange mapping)
where
toAbsSemanticToken :: Range -> HsSemanticTokenType -> SemanticTokenAbsolute
Expand Down
Loading