Skip to content

Commit 9272bfe

Browse files
authored
Code action add default type annotation to remove -Wtype-defaults warning (#680)
* Code action to add default type annotation to satisfy the contraints this is useful when using `traceShow` with with OverloadedStrings and type-defaults warning enabled Handle the following cases: - there is one literal and one contraint to be satisfied - there are mulitple literals and/or multiple constraints Adding type annotations to expressions that trigger type-defaults warning is not part of this changes * Simplify older test * Fix hlint issue
1 parent f32f666 commit 9272bfe

File tree

2 files changed

+165
-6
lines changed

2 files changed

+165
-6
lines changed

src/Development/IDE/Plugin/CodeAction.hs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ import GHC.LanguageExtensions.Type (Extension)
5656
import Data.Function
5757
import Control.Arrow ((>>>))
5858
import Data.Functor
59+
import Control.Applicative ((<|>))
5960

6061
plugin :: Plugin c
6162
plugin = codeActionPluginWithRules rules codeAction <> Plugin mempty setHandlersCodeLens
@@ -146,6 +147,7 @@ suggestAction dflags packageExports ideOptions parsedModule text diag = concat
146147
, suggestReplaceIdentifier text diag
147148
, suggestSignature True diag
148149
, suggestConstraint text diag
150+
, suggestAddTypeAnnotationToSatisfyContraints text diag
149151
] ++ concat
150152
[ suggestNewDefinition ideOptions pm text diag
151153
++ suggestRemoveRedundantImport pm text diag
@@ -200,6 +202,61 @@ suggestDeleteTopBinding ParsedModule{pm_parsed_source = L _ HsModule{hsmodDecls}
200202
matchesBindingName b (SigD (TypeSig (L _ x:_) _)) = showSDocUnsafe (ppr x) == b
201203
matchesBindingName _ _ = False
202204

205+
206+
suggestAddTypeAnnotationToSatisfyContraints :: Maybe T.Text -> Diagnostic -> [(T.Text, [TextEdit])]
207+
suggestAddTypeAnnotationToSatisfyContraints sourceOpt Diagnostic{_range=_range,..}
208+
-- File.hs:52:41: warning:
209+
-- * Defaulting the following constraint to type ‘Integer’
210+
-- Num p0 arising from the literal ‘1’
211+
-- * In the expression: 1
212+
-- In an equation for ‘f’: f = 1
213+
-- File.hs:52:41: warning:
214+
-- * Defaulting the following constraints to type ‘[Char]’
215+
-- (Show a0)
216+
-- arising from a use of ‘traceShow’
217+
-- at A.hs:228:7-25
218+
-- (IsString a0)
219+
-- arising from the literal ‘"debug"’
220+
-- at A.hs:228:17-23
221+
-- * In the expression: traceShow "debug" a
222+
-- In an equation for ‘f’: f a = traceShow "debug" a
223+
-- File.hs:52:41: warning:
224+
-- * Defaulting the following constraints to type ‘[Char]’
225+
-- (Show a0)
226+
-- arising from a use of ‘traceShow’
227+
-- at A.hs:255:28-43
228+
-- (IsString a0)
229+
-- arising from the literal ‘"test"’
230+
-- at /Users/serhiip/workspace/ghcide/src/Development/IDE/Plugin/CodeAction.hs:255:38-43
231+
-- * In the fourth argument of ‘seq’, namely ‘(traceShow "test")’
232+
-- In the expression: seq "test" seq "test" (traceShow "test")
233+
-- In an equation for ‘f’:
234+
-- f = seq "test" seq "test" (traceShow "test")
235+
| Just [ty, lit] <- matchRegex _message (pat False False True)
236+
<|> matchRegex _message (pat False False False)
237+
= codeEdit ty lit (makeAnnotatedLit ty lit)
238+
| Just source <- sourceOpt
239+
, Just [ty, lit] <- matchRegex _message (pat True True False)
240+
= let lit' = makeAnnotatedLit ty lit;
241+
tir = textInRange _range source
242+
in codeEdit ty lit (T.replace lit lit' tir)
243+
| otherwise = []
244+
where
245+
makeAnnotatedLit ty lit = "(" <> lit <> " :: " <> ty <> ")"
246+
pat multiple at inThe = T.concat [ ".*Defaulting the following constraint"
247+
, if multiple then "s" else ""
248+
, " to type ‘([^ ]+)’ "
249+
, ".*arising from the literal ‘(.+)’"
250+
, if inThe then ".+In the.+argument" else ""
251+
, if at then ".+at" else ""
252+
, ".+In the expression"
253+
]
254+
codeEdit ty lit replacement =
255+
let title = "Add type annotation ‘" <> ty <> "’ to ‘" <> lit <> ""
256+
edits = [TextEdit _range replacement]
257+
in [( title, edits )]
258+
259+
203260
suggestReplaceIdentifier :: Maybe T.Text -> Diagnostic -> [(T.Text, [TextEdit])]
204261
suggestReplaceIdentifier contents Diagnostic{_range=_range,..}
205262
-- File.hs:52:41: error:

test/exe/Main.hs

Lines changed: 108 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -483,6 +483,7 @@ codeActionTests = testGroup "code actions"
483483
, deleteUnusedDefinitionTests
484484
, addInstanceConstraintTests
485485
, addFunctionConstraintTests
486+
, addTypeAnnotationsToLiteralsTest
486487
]
487488

488489
codeLensesTests :: TestTree
@@ -1209,9 +1210,104 @@ deleteUnusedDefinitionTests = testGroup "delete unused definition action"
12091210
liftIO $ contentAfterAction @?= expectedResult
12101211

12111212
extractCodeAction docId actionPrefix = do
1212-
Just (CACodeAction action@CodeAction { _title = actionTitle })
1213-
<- find (\(CACodeAction CodeAction{_title=x}) -> actionPrefix `T.isPrefixOf` x)
1214-
<$> getCodeActions docId (R 0 0 0 0)
1213+
[action@CodeAction { _title = actionTitle }] <- findCodeActionsByPrefix docId (R 0 0 0 0) [actionPrefix]
1214+
return (action, actionTitle)
1215+
1216+
addTypeAnnotationsToLiteralsTest :: TestTree
1217+
addTypeAnnotationsToLiteralsTest = testGroup "add type annotations to literals to satisfy contraints"
1218+
[
1219+
testSession "add default type to satisfy one contraint" $
1220+
testFor
1221+
(T.unlines [ "{-# OPTIONS_GHC -Wtype-defaults #-}"
1222+
, "module A () where"
1223+
, ""
1224+
, "f = 1"
1225+
])
1226+
[ (DsWarning, (3, 4), "Defaulting the following constraint") ]
1227+
"Add type annotation ‘Integer’ to ‘1’"
1228+
(T.unlines [ "{-# OPTIONS_GHC -Wtype-defaults #-}"
1229+
, "module A () where"
1230+
, ""
1231+
, "f = (1 :: Integer)"
1232+
])
1233+
1234+
, testSession "add default type to satisfy one contraint with duplicate literals" $
1235+
testFor
1236+
(T.unlines [ "{-# OPTIONS_GHC -Wtype-defaults #-}"
1237+
, "{-# LANGUAGE OverloadedStrings #-}"
1238+
, "module A () where"
1239+
, ""
1240+
, "import Debug.Trace"
1241+
, ""
1242+
, "f = seq \"debug\" traceShow \"debug\""
1243+
])
1244+
[ (DsWarning, (6, 8), "Defaulting the following constraint")
1245+
, (DsWarning, (6, 16), "Defaulting the following constraint")
1246+
]
1247+
"Add type annotation ‘[Char]’ to ‘\"debug\""
1248+
(T.unlines [ "{-# OPTIONS_GHC -Wtype-defaults #-}"
1249+
, "{-# LANGUAGE OverloadedStrings #-}"
1250+
, "module A () where"
1251+
, ""
1252+
, "import Debug.Trace"
1253+
, ""
1254+
, "f = seq (\"debug\" :: [Char]) traceShow \"debug\""
1255+
])
1256+
, testSession "add default type to satisfy two contraints" $
1257+
testFor
1258+
(T.unlines [ "{-# OPTIONS_GHC -Wtype-defaults #-}"
1259+
, "{-# LANGUAGE OverloadedStrings #-}"
1260+
, "module A () where"
1261+
, ""
1262+
, "import Debug.Trace"
1263+
, ""
1264+
, "f a = traceShow \"debug\" a"
1265+
])
1266+
[ (DsWarning, (6, 6), "Defaulting the following constraint") ]
1267+
"Add type annotation ‘[Char]’ to ‘\"debug\""
1268+
(T.unlines [ "{-# OPTIONS_GHC -Wtype-defaults #-}"
1269+
, "{-# LANGUAGE OverloadedStrings #-}"
1270+
, "module A () where"
1271+
, ""
1272+
, "import Debug.Trace"
1273+
, ""
1274+
, "f a = traceShow (\"debug\" :: [Char]) a"
1275+
])
1276+
, testSession "add default type to satisfy two contraints with duplicate literals" $
1277+
testFor
1278+
(T.unlines [ "{-# OPTIONS_GHC -Wtype-defaults #-}"
1279+
, "{-# LANGUAGE OverloadedStrings #-}"
1280+
, "module A () where"
1281+
, ""
1282+
, "import Debug.Trace"
1283+
, ""
1284+
, "f = seq (\"debug\" :: [Char]) (seq (\"debug\" :: [Char]) (traceShow \"debug\"))"
1285+
])
1286+
[ (DsWarning, (6, 54), "Defaulting the following constraint") ]
1287+
"Add type annotation ‘[Char]’ to ‘\"debug\""
1288+
(T.unlines [ "{-# OPTIONS_GHC -Wtype-defaults #-}"
1289+
, "{-# LANGUAGE OverloadedStrings #-}"
1290+
, "module A () where"
1291+
, ""
1292+
, "import Debug.Trace"
1293+
, ""
1294+
, "f = seq (\"debug\" :: [Char]) (seq (\"debug\" :: [Char]) (traceShow (\"debug\" :: [Char])))"
1295+
])
1296+
]
1297+
where
1298+
testFor source diag expectedTitle expectedResult = do
1299+
docId <- createDoc "A.hs" "haskell" source
1300+
expectDiagnostics [ ("A.hs", diag) ]
1301+
1302+
(action, title) <- extractCodeAction docId "Add type annotation"
1303+
1304+
liftIO $ title @?= expectedTitle
1305+
executeCodeAction action
1306+
contentAfterAction <- documentContents docId
1307+
liftIO $ contentAfterAction @?= expectedResult
1308+
1309+
extractCodeAction docId actionPrefix = do
1310+
[action@CodeAction { _title = actionTitle }] <- findCodeActionsByPrefix docId (R 0 0 0 0) [actionPrefix]
12151311
return (action, actionTitle)
12161312

12171313

@@ -2684,19 +2780,25 @@ openTestDataDoc path = do
26842780
createDoc path "haskell" source
26852781

26862782
findCodeActions :: TextDocumentIdentifier -> Range -> [T.Text] -> Session [CodeAction]
2687-
findCodeActions doc range expectedTitles = do
2783+
findCodeActions = findCodeActions' (==) "is not a superset of"
2784+
2785+
findCodeActionsByPrefix :: TextDocumentIdentifier -> Range -> [T.Text] -> Session [CodeAction]
2786+
findCodeActionsByPrefix = findCodeActions' T.isPrefixOf "is not prefix of"
2787+
2788+
findCodeActions' :: (T.Text -> T.Text -> Bool) -> String -> TextDocumentIdentifier -> Range -> [T.Text] -> Session [CodeAction]
2789+
findCodeActions' op errMsg doc range expectedTitles = do
26882790
actions <- getCodeActions doc range
26892791
let matches = sequence
26902792
[ listToMaybe
26912793
[ action
26922794
| CACodeAction action@CodeAction { _title = actionTitle } <- actions
2693-
, actionTitle == expectedTitle ]
2795+
, expectedTitle `op` actionTitle]
26942796
| expectedTitle <- expectedTitles]
26952797
let msg = show
26962798
[ actionTitle
26972799
| CACodeAction CodeAction { _title = actionTitle } <- actions
26982800
]
2699-
++ " is not a superset of "
2801+
++ " " <> errMsg <> " "
27002802
++ show expectedTitles
27012803
liftIO $ case matches of
27022804
Nothing -> assertFailure msg

0 commit comments

Comments
 (0)