Skip to content

Commit ed362a4

Browse files
author
Santiago Weight
committed
refact: extract AddArgument logic
1 parent e770424 commit ed362a4

File tree

4 files changed

+206
-179
lines changed

4 files changed

+206
-179
lines changed

plugins/hls-refactor-plugin/hls-refactor-plugin.cabal

+2
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ library
3434
other-modules: Development.IDE.Plugin.CodeAction.Args
3535
Development.IDE.Plugin.CodeAction.ExactPrint
3636
Development.IDE.Plugin.CodeAction.PositionIndexed
37+
Development.IDE.Plugin.Plugins.AddArgument
38+
Development.IDE.Plugin.Plugins.Diagnostic
3739
default-extensions:
3840
BangPatterns
3941
CPP

plugins/hls-refactor-plugin/src/Development/IDE/Plugin/CodeAction.hs

+5-179
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ import Data.Ord (comparing)
3838
import qualified Data.Set as S
3939
import qualified Data.Text as T
4040
import qualified Data.Text.Utf16.Rope as Rope
41-
import Data.Tuple.Extra (first)
4241
import Development.IDE.Core.Rules
4342
import Development.IDE.Core.RuleTypes
4443
import Development.IDE.Core.Service
@@ -57,6 +56,7 @@ import Development.IDE.Plugin.CodeAction.ExactPrint
5756
import Development.IDE.Plugin.CodeAction.PositionIndexed
5857
import Development.IDE.Plugin.CodeAction.Util
5958
import Development.IDE.Plugin.Completions.Types
59+
import qualified Development.IDE.Plugin.Plugins.AddArgument
6060
import Development.IDE.Plugin.TypeLenses (suggestSignature)
6161
import Development.IDE.Types.Exports
6262
import Development.IDE.Types.Location
@@ -65,8 +65,7 @@ import Development.IDE.Types.Logger hiding
6565
import Development.IDE.Types.Options
6666
import GHC.Exts (fromList)
6767
import qualified GHC.LanguageExtensions as Lang
68-
import Ide.PluginUtils (makeDiffTextEdit,
69-
subRange)
68+
import Ide.PluginUtils (subRange)
7069
import Ide.Types
7170
import qualified Language.LSP.Server as LSP
7271
import Language.LSP.Types (ApplyWorkspaceEditParams (..),
@@ -92,15 +91,8 @@ import Language.LSP.VFS (VirtualFile,
9291
import qualified Text.Fuzzy.Parallel as TFP
9392
import Text.Regex.TDFA (mrAfter,
9493
(=~), (=~~))
95-
#if MIN_VERSION_ghc(9,2,1)
96-
import Data.Either.Extra (maybeToEither)
97-
import GHC.Types.SrcLoc (generatedSrcSpan)
98-
import Language.Haskell.GHC.ExactPrint (noAnnSrcSpanDP1,
99-
runTransformT)
100-
#endif
10194
#if MIN_VERSION_ghc(9,2,0)
102-
import Control.Monad.Except (lift)
103-
import Debug.Trace
95+
import Development.IDE.Plugin.Plugins.Diagnostic
10496
import GHC (AddEpAnn (AddEpAnn),
10597
Anchor (anchor_op),
10698
AnchorOperation (..),
@@ -109,17 +101,7 @@ import GHC (AddEpAnn (Ad
109101
EpAnn (..),
110102
EpaLocation (..),
111103
LEpaComment,
112-
LocatedA,
113-
SrcSpanAnn' (SrcSpanAnn),
114-
SrcSpanAnnA,
115-
SrcSpanAnnN,
116-
TrailingAnn (..),
117-
addTrailingAnnToA,
118-
emptyComments,
119-
noAnn)
120-
import GHC.Hs (IsUnicodeSyntax (..))
121-
import Language.Haskell.GHC.ExactPrint.Transform (d1)
122-
104+
LocatedA)
123105
#else
124106
import Language.Haskell.GHC.ExactPrint.Types (Annotation (annsDP),
125107
DeltaPos,
@@ -190,7 +172,7 @@ bindingsPluginDescriptor recorder plId = mkExactprintPluginDescriptor recorder $
190172
#endif
191173
, wrap suggestNewDefinition
192174
#if MIN_VERSION_ghc(9,2,1)
193-
, wrap suggestAddArgument
175+
, wrap Development.IDE.Plugin.Plugins.AddArgument.plugin
194176
#endif
195177
, wrap suggestDeleteUnusedBinding
196178
]
@@ -905,34 +887,6 @@ suggestReplaceIdentifier contents Diagnostic{_range=_range,..}
905887
= [ ("Replace with ‘" <> name <> "", [mkRenameEdit contents _range name]) | name <- renameSuggestions ]
906888
| otherwise = []
907889

908-
matchVariableNotInScope :: T.Text -> Maybe (T.Text, Maybe T.Text)
909-
matchVariableNotInScope message
910-
-- * Variable not in scope:
911-
-- suggestAcion :: Maybe T.Text -> Range -> Range
912-
-- * Variable not in scope:
913-
-- suggestAcion
914-
| Just (name, typ) <- matchVariableNotInScopeTyped message = Just (name, Just typ)
915-
| Just name <- matchVariableNotInScopeUntyped message = Just (name, Nothing)
916-
| otherwise = Nothing
917-
where
918-
matchVariableNotInScopeTyped message
919-
| Just [name, typ] <- matchRegexUnifySpaces message "Variable not in scope: ([^ ]+) :: ([^*•]+)" =
920-
Just (name, typ)
921-
| otherwise = Nothing
922-
matchVariableNotInScopeUntyped message
923-
| Just [name] <- matchRegexUnifySpaces message "Variable not in scope: ([^ ]+)" =
924-
Just name
925-
| otherwise = Nothing
926-
927-
matchFoundHole :: T.Text -> Maybe (T.Text, T.Text)
928-
matchFoundHole message
929-
| Just [name, typ] <- matchRegexUnifySpaces message "Found hole: _([^ ]+) :: ([^*•]+) Or perhaps" =
930-
Just (name, typ)
931-
| otherwise = Nothing
932-
933-
matchFoundHoleIncludeUnderscore :: T.Text -> Maybe (T.Text, T.Text)
934-
matchFoundHoleIncludeUnderscore message = first ("_" <>) <$> matchFoundHole message
935-
936890
suggestNewDefinition :: IdeOptions -> ParsedModule -> Maybe T.Text -> Diagnostic -> [(T.Text, [TextEdit])]
937891
suggestNewDefinition ideOptions parsedModule contents Diagnostic {_message, _range}
938892
| Just (name, typ) <- matchVariableNotInScope message =
@@ -962,121 +916,6 @@ newDefinitionAction IdeOptions {..} parsedModule Range {_start} name typ
962916
sig = name <> colon <> T.dropWhileEnd isSpace (fromMaybe "_" typ)
963917
ParsedModule {pm_parsed_source = L _ HsModule {hsmodDecls}} = parsedModule
964918

965-
#if MIN_VERSION_ghc(9,2,1)
966-
-- When GHC tells us that a variable is not bound, it will tell us either:
967-
-- - there is an unbound variable with a given type
968-
-- - there is an unbound variable (GHC provides no type suggestion)
969-
--
970-
-- When we receive either of these errors, we produce a text edit that will add a new argument (as a new pattern in the
971-
-- last position of each LHS of the top-level bindings for this HsDecl).
972-
--
973-
-- NOTE When adding a new argument to a declaration, the corresponding argument's type in declaration's signature might
974-
-- not be the last type in the signature, such as:
975-
-- foo :: a -> b -> c -> d
976-
-- foo a b = \c -> ...
977-
-- In this case a new argument would have to add its type between b and c in the signature.
978-
suggestAddArgument :: ParsedModule -> Diagnostic -> Either ResponseError [(T.Text, [TextEdit])]
979-
suggestAddArgument parsedModule Diagnostic {_message, _range}
980-
| Just (name, typ) <- matchVariableNotInScope message = addArgumentAction parsedModule _range name typ
981-
| Just (name, typ) <- matchFoundHoleIncludeUnderscore message = addArgumentAction parsedModule _range name (Just typ)
982-
| otherwise = pure []
983-
where
984-
message = unifySpaces _message
985-
986-
-- Given a name for the new binding, add a new pattern to the match in the last position,
987-
-- returning how many patterns there were in this match prior to the transformation:
988-
-- addArgToMatch "foo" `bar arg1 arg2 = ...`
989-
-- => (`bar arg1 arg2 foo = ...`, 2)
990-
addArgToMatch :: T.Text -> GenLocated l (Match GhcPs body) -> (GenLocated l (Match GhcPs body), Int)
991-
addArgToMatch name (L locMatch (Match xMatch ctxMatch pats rhs)) =
992-
let unqualName = mkRdrUnqual $ mkVarOcc $ T.unpack name
993-
newPat = L (noAnnSrcSpanDP1 generatedSrcSpan) $ VarPat NoExtField (noLocA unqualName)
994-
in (L locMatch (Match xMatch ctxMatch (pats <> [newPat]) rhs), length pats)
995-
996-
-- Attempt to insert a binding pattern into each match for the given LHsDecl; succeeds only if the function is a FunBind.
997-
-- Also return:
998-
-- - the declaration's name
999-
-- - the number of bound patterns in the declaration's matches prior to the transformation
1000-
--
1001-
-- For example:
1002-
-- insertArg "new_pat" `foo bar baz = 1`
1003-
-- => (`foo bar baz new_pat = 1`, Just ("foo", 2))
1004-
appendFinalPatToMatches :: T.Text -> LHsDecl GhcPs -> TransformT (Either ResponseError) (LHsDecl GhcPs, Maybe (GenLocated SrcSpanAnnN RdrName, Int))
1005-
appendFinalPatToMatches name = \case
1006-
(L locDecl (ValD xVal (FunBind xFunBind idFunBind mg coreFunBind))) -> do
1007-
(mg', numPatsMay) <- modifyMgMatchesT' mg (pure . second Just . addArgToMatch name) Nothing combineMatchNumPats
1008-
numPats <- lift $ maybeToEither (responseError "Unexpected empty match group in HsDecl") numPatsMay
1009-
let decl' = L locDecl (ValD xVal (FunBind xFunBind idFunBind mg' coreFunBind))
1010-
pure (decl', Just (idFunBind, numPats))
1011-
decl -> pure (decl, Nothing)
1012-
where
1013-
combineMatchNumPats Nothing other = pure other
1014-
combineMatchNumPats other Nothing = pure other
1015-
combineMatchNumPats (Just l) (Just r)
1016-
| l == r = pure (Just l)
1017-
| otherwise = Left $ responseError "Unexpected different numbers of patterns in HsDecl MatchGroup"
1018-
1019-
-- The add argument works as follows:
1020-
-- 1. Attempt to add the given name as the last pattern of the declaration that contains `range`.
1021-
-- 2. If such a declaration exists, use that declaration's name to modify the signature of said declaration, if it
1022-
-- has a type signature.
1023-
--
1024-
-- NOTE For the following situation, the type signature is not updated (it's unclear what should happen):
1025-
-- type FunctionTySyn = () -> Int
1026-
-- foo :: FunctionTySyn
1027-
-- foo () = new_def
1028-
--
1029-
-- TODO instead of inserting a typed hole; use GHC's suggested type from the error
1030-
addArgumentAction :: ParsedModule -> Range -> T.Text -> Maybe T.Text -> Either ResponseError [(T.Text, [TextEdit])]
1031-
addArgumentAction (ParsedModule _ moduleSrc _ _) range name _typ = do
1032-
(newSource, _, _) <- runTransformT $ do
1033-
(moduleSrc', join -> matchedDeclNameMay) <- addNameAsLastArgOfMatchingDecl (makeDeltaAst moduleSrc)
1034-
case matchedDeclNameMay of
1035-
Just (matchedDeclName, numPats) -> modifySigWithM (unLoc matchedDeclName) (addTyHoleToTySigArg numPats) moduleSrc'
1036-
Nothing -> pure moduleSrc'
1037-
let diff = makeDiffTextEdit (T.pack $ exactPrint moduleSrc) (T.pack $ exactPrint newSource)
1038-
pure [("Add argument ‘" <> name <> "’ to function", fromLspList diff)]
1039-
where
1040-
addNameAsLastArgOfMatchingDecl = modifySmallestDeclWithM spanContainsRangeOrErr addNameAsLastArg
1041-
addNameAsLastArg = fmap (first (:[])) . appendFinalPatToMatches name
1042-
1043-
spanContainsRangeOrErr = maybeToEither (responseError "SrcSpan was not valid range") . (`spanContainsRange` range)
1044-
1045-
-- Transform an LHsType into a list of arguments and return type, to make transformations easier.
1046-
hsTypeToFunTypeAsList :: LHsType GhcPs -> ([(SrcSpanAnnA, XFunTy GhcPs, HsArrow GhcPs, LHsType GhcPs)], LHsType GhcPs)
1047-
hsTypeToFunTypeAsList = \case
1048-
L spanAnnA (HsFunTy xFunTy arrow lhs rhs) ->
1049-
let (rhsArgs, rhsRes) = hsTypeToFunTypeAsList rhs
1050-
in ((spanAnnA, xFunTy, arrow, lhs):rhsArgs, rhsRes)
1051-
ty -> ([], ty)
1052-
1053-
-- The inverse of `hsTypeToFunTypeAsList`
1054-
hsTypeFromFunTypeAsList :: ([(SrcSpanAnnA, XFunTy GhcPs, HsArrow GhcPs, LHsType GhcPs)], LHsType GhcPs) -> LHsType GhcPs
1055-
hsTypeFromFunTypeAsList (args, res) =
1056-
foldr (\(spanAnnA, xFunTy, arrow, argTy) res -> L spanAnnA $ HsFunTy xFunTy arrow argTy res) res args
1057-
1058-
-- Add a typed hole to a type signature in the given argument position:
1059-
-- 0 `foo :: ()` => foo :: _ -> ()
1060-
-- 2 `foo :: FunctionTySyn` => foo :: FunctionTySyn
1061-
-- 1 `foo :: () -> () -> Int` => foo :: () -> _ -> () -> Int
1062-
addTyHoleToTySigArg :: Int -> LHsSigType GhcPs -> (LHsSigType GhcPs)
1063-
addTyHoleToTySigArg loc (L annHsSig (HsSig xHsSig tyVarBndrs lsigTy)) =
1064-
let (args, res) = hsTypeToFunTypeAsList lsigTy
1065-
wildCardAnn = SrcSpanAnn (EpAnn genAnchor1 (AnnListItem [AddRarrowAnn d1]) emptyComments) generatedSrcSpan
1066-
newArg = (SrcSpanAnn mempty generatedSrcSpan, noAnn, HsUnrestrictedArrow NormalSyntax, L wildCardAnn $ HsWildCardTy noExtField)
1067-
-- NOTE if the location that the argument wants to be placed at is not one more than the number of arguments
1068-
-- in the signature, then we return the original type signature.
1069-
-- This situation most likely occurs due to a function type synonym in the signature
1070-
insertArg n _ | n < 0 = error "Not possible"
1071-
insertArg 0 as = newArg:as
1072-
insertArg _ [] = []
1073-
insertArg n (a:as) = a : insertArg (n - 1) as
1074-
lsigTy' = hsTypeFromFunTypeAsList (insertArg loc args, res)
1075-
in L annHsSig (HsSig xHsSig tyVarBndrs lsigTy')
1076-
1077-
fromLspList :: List a -> [a]
1078-
fromLspList (List a) = a
1079-
#endif
1080919

1081920
suggestFillTypeWildcard :: Diagnostic -> [(T.Text, TextEdit)]
1082921
suggestFillTypeWildcard Diagnostic{_range=_range,..}
@@ -2169,29 +2008,16 @@ rangesForBinding' b (L (locA -> l) (IEThingWith _ thing _ inners))
21692008
#endif
21702009
rangesForBinding' _ _ = []
21712010

2172-
-- | 'matchRegex' combined with 'unifySpaces'
2173-
matchRegexUnifySpaces :: T.Text -> T.Text -> Maybe [T.Text]
2174-
matchRegexUnifySpaces message = matchRegex (unifySpaces message)
2175-
21762011
-- | 'allMatchRegex' combined with 'unifySpaces'
21772012
allMatchRegexUnifySpaces :: T.Text -> T.Text -> Maybe [[T.Text]]
21782013
allMatchRegexUnifySpaces message =
21792014
allMatchRegex (unifySpaces message)
21802015

2181-
-- | Returns Just (the submatches) for the first capture, or Nothing.
2182-
matchRegex :: T.Text -> T.Text -> Maybe [T.Text]
2183-
matchRegex message regex = case message =~~ regex of
2184-
Just (_ :: T.Text, _ :: T.Text, _ :: T.Text, bindings) -> Just bindings
2185-
Nothing -> Nothing
2186-
21872016
-- | Returns Just (all matches) for the first capture, or Nothing.
21882017
allMatchRegex :: T.Text -> T.Text -> Maybe [[T.Text]]
21892018
allMatchRegex message regex = message =~~ regex
21902019

21912020

2192-
unifySpaces :: T.Text -> T.Text
2193-
unifySpaces = T.unwords . T.words
2194-
21952021
-- functions to help parse multiple import suggestions
21962022

21972023
-- | Returns the first match if found

0 commit comments

Comments
 (0)