Skip to content

Commit bf12e63

Browse files
committed
Suggestions for missing implicit parameters
1 parent 99e6ed6 commit bf12e63

File tree

2 files changed

+74
-19
lines changed

2 files changed

+74
-19
lines changed

ghcide/src/Development/IDE/Plugin/CodeAction.hs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ import Safe (atMay)
6363
import Bag (isEmptyBag)
6464
import qualified Data.HashSet as Set
6565
import Control.Concurrent.Extra (threadDelay, readVar)
66+
import Development.IDE.GHC.Util (printRdrName)
6667

6768
plugin :: Plugin c
6869
plugin = codeActionPluginWithRules rules codeAction <> Plugin mempty setHandlersCodeLens
@@ -178,6 +179,7 @@ suggestExactAction ::
178179
suggestExactAction df ps x =
179180
concat
180181
[ suggestConstraint df (astA ps) x
182+
, suggestImplicitParameter (astA ps) x
181183
]
182184

183185
suggestAction
@@ -740,7 +742,10 @@ suggestConstraint df parsedModule diag@Diagnostic {..}
740742
findMissingConstraint :: T.Text -> Maybe T.Text
741743
findMissingConstraint t =
742744
let regex = "(No instance for|Could not deduce) \\((.+)\\) arising from" -- a use of / a do statement
743-
in matchRegexUnifySpaces t regex <&> last
745+
regexImplicitParams = "Could not deduce: (\\?.+) arising from a use of"
746+
match = matchRegexUnifySpaces t regex
747+
matchImplicitParams = matchRegexUnifySpaces t regexImplicitParams
748+
in match <|> matchImplicitParams <&> last
744749

745750
-- | Suggests a constraint for an instance declaration for which a constraint is missing.
746751
suggestInstanceConstraint :: DynFlags -> ParsedSource -> Diagnostic -> T.Text -> [(T.Text, Rewrite)]
@@ -784,6 +789,19 @@ suggestInstanceConstraint df (L _ HsModule {hsmodDecls}) Diagnostic {..} missing
784789
actionTitle constraint = "Add `" <> constraint
785790
<> "` to the context of the instance declaration"
786791

792+
suggestImplicitParameter ::
793+
ParsedSource ->
794+
Diagnostic ->
795+
[(T.Text, Rewrite)]
796+
suggestImplicitParameter (L _ HsModule {hsmodDecls}) Diagnostic {_message, _range}
797+
| Just [implicitT] <- matchRegexUnifySpaces _message "Unbound implicit parameter \\(([^:]+::.+)\\) arising",
798+
Just (L _ (ValD _ FunBind {fun_id = L _ funId})) <- findDeclContainingLoc (_start _range) hsmodDecls,
799+
Just (TypeSig _ _ HsWC {hswc_body = HsIB {hsib_body}}) <- findSigOfDecl (== funId) hsmodDecls
800+
=
801+
[( "Add " <> implicitT <> " to the context of " <> T.pack (printRdrName funId)
802+
, appendConstraint (T.unpack implicitT) hsib_body)]
803+
| otherwise = []
804+
787805
findTypeSignatureName :: T.Text -> Maybe T.Text
788806
findTypeSignatureName t = matchRegexUnifySpaces t "([^ ]+) :: " <&> head
789807

ghcide/test/exe/Main.hs

Lines changed: 55 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -687,6 +687,7 @@ codeActionTests = testGroup "code actions"
687687
, removeRedundantConstraintsTests
688688
, addTypeAnnotationsToLiteralsTest
689689
, exportUnusedTests
690+
, addImplicitParamsConstraintTests
690691
]
691692

692693
codeActionHelperFunctionTests :: TestTree
@@ -2050,59 +2051,95 @@ addFunctionConstraintTests = let
20502051
, " return ()"
20512052
]
20522053

2053-
check :: String -> T.Text -> T.Text -> T.Text -> TestTree
2054-
check testName actionTitle originalCode expectedCode = testSession testName $ do
2055-
doc <- createDoc "Testing.hs" "haskell" originalCode
2056-
_ <- waitForDiagnostics
2057-
actionsOrCommands <- getCodeActions doc (Range (Position 6 0) (Position 6 maxBound))
2058-
chosenAction <- liftIO $ pickActionWithTitle actionTitle actionsOrCommands
2059-
executeCodeAction chosenAction
2060-
modifiedCode <- documentContents doc
2061-
liftIO $ expectedCode @=? modifiedCode
2062-
20632054
in testGroup "add function constraint"
2064-
[ check
2055+
[ checkCodeAction
20652056
"no preexisting constraint"
20662057
"Add `Eq a` to the context of the type signature for `eq`"
20672058
(missingConstraintSourceCode "")
20682059
(missingConstraintSourceCode "Eq a => ")
2069-
, check
2060+
, checkCodeAction
20702061
"no preexisting constraint, with forall"
20712062
"Add `Eq a` to the context of the type signature for `eq`"
20722063
(missingConstraintWithForAllSourceCode "")
20732064
(missingConstraintWithForAllSourceCode "Eq a => ")
2074-
, check
2065+
, checkCodeAction
20752066
"preexisting constraint, no parenthesis"
20762067
"Add `Eq b` to the context of the type signature for `eq`"
20772068
(incompleteConstraintSourceCode "Eq a")
20782069
(incompleteConstraintSourceCode "(Eq a, Eq b)")
2079-
, check
2070+
, checkCodeAction
20802071
"preexisting constraints in parenthesis"
20812072
"Add `Eq c` to the context of the type signature for `eq`"
20822073
(incompleteConstraintSourceCode2 "(Eq a, Eq b)")
20832074
(incompleteConstraintSourceCode2 "(Eq a, Eq b, Eq c)")
2084-
, check
2075+
, checkCodeAction
20852076
"preexisting constraints with forall"
20862077
"Add `Eq b` to the context of the type signature for `eq`"
20872078
(incompleteConstraintWithForAllSourceCode "Eq a")
20882079
(incompleteConstraintWithForAllSourceCode "(Eq a, Eq b)")
2089-
, check
2080+
, checkCodeAction
20902081
"preexisting constraint, with extra spaces in context"
20912082
"Add `Eq b` to the context of the type signature for `eq`"
20922083
(incompleteConstraintSourceCodeWithExtraCharsInContext "Eq a")
20932084
(incompleteConstraintSourceCodeWithExtraCharsInContext "Eq a, Eq b")
2094-
, check
2085+
, checkCodeAction
20952086
"preexisting constraint, with newlines in type signature"
20962087
"Add `Eq b` to the context of the type signature for `eq`"
20972088
(incompleteConstraintSourceCodeWithNewlinesInTypeSignature "Eq a")
20982089
(incompleteConstraintSourceCodeWithNewlinesInTypeSignature "Eq a, Eq b")
2099-
, check
2090+
, checkCodeAction
21002091
"missing Monad constraint"
21012092
"Add `Monad m` to the context of the type signature for `f`"
21022093
(missingMonadConstraint "")
21032094
(missingMonadConstraint "Monad m => ")
21042095
]
21052096

2097+
checkCodeAction :: String -> T.Text -> T.Text -> T.Text -> TestTree
2098+
checkCodeAction testName actionTitle originalCode expectedCode = testSession testName $ do
2099+
doc <- createDoc "Testing.hs" "haskell" originalCode
2100+
_ <- waitForDiagnostics
2101+
actionsOrCommands <- getCodeActions doc (Range (Position 6 0) (Position 6 maxBound))
2102+
chosenAction <- liftIO $ pickActionWithTitle actionTitle actionsOrCommands
2103+
executeCodeAction chosenAction
2104+
modifiedCode <- documentContents doc
2105+
liftIO $ expectedCode @=? modifiedCode
2106+
2107+
addImplicitParamsConstraintTests :: TestTree
2108+
addImplicitParamsConstraintTests =
2109+
testGroup
2110+
"add missing implicit params constraints"
2111+
[ testGroup
2112+
"introduced"
2113+
[ let ex ctxtA = exampleCode "?a" ctxtA ""
2114+
in checkCodeAction "at top level" "Add ?a::() to the context of fBase" (ex "") (ex "?a::()"),
2115+
let ex ctxA = exampleCode "x where x = ?a" ctxA ""
2116+
in checkCodeAction "in nested def" "Add ?a::() to the context of fBase" (ex "") (ex "?a::()")
2117+
],
2118+
testGroup
2119+
"inherited"
2120+
[ let ex = exampleCode "()" "?a::()"
2121+
in checkCodeAction
2122+
"with preexisting context"
2123+
"Add `?a::()` to the context of the type signature for `fCaller`"
2124+
(ex "Eq ()")
2125+
(ex "Eq (), ?a::()"),
2126+
let ex = exampleCode "()" "?a::()"
2127+
in checkCodeAction "without preexisting context" "Add ?a::() to the context of fCaller" (ex "") (ex "?a::()")
2128+
]
2129+
]
2130+
where
2131+
mkContext "" = ""
2132+
mkContext contents = "(" <> contents <> ") => "
2133+
2134+
exampleCode bodyBase contextBase contextCaller =
2135+
T.unlines
2136+
[ "{-# LANGUAGE FlexibleContexts, ImplicitParams #-}",
2137+
"module Testing where",
2138+
"fBase :: " <> mkContext contextBase <> "()",
2139+
"fBase = " <> bodyBase,
2140+
"fCaller :: " <> mkContext contextCaller <> "()",
2141+
"fCaller = fBase"
2142+
]
21062143
removeRedundantConstraintsTests :: TestTree
21072144
removeRedundantConstraintsTests = let
21082145
header =

0 commit comments

Comments
 (0)