Skip to content

Commit 8a90def

Browse files
xsebekOndrej Sebek
and
Ondrej Sebek
authored
Avoid extra parens for wildcard type signature (#2764)
+ avoid parens in simple cases (a, Char, [a], (),...) - change one test - (Int)/Int Co-authored-by: Ondrej Sebek <ondrej.sebek@mavenir.com>
1 parent 9f62337 commit 8a90def

File tree

2 files changed

+79
-73
lines changed

2 files changed

+79
-73
lines changed

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

+15-7
Original file line numberDiff line numberDiff line change
@@ -1529,14 +1529,22 @@ mkRenameEdit contents range name =
15291529
curr <- textInRange range <$> contents
15301530
pure $ "`" `T.isPrefixOf` curr && "`" `T.isSuffixOf` curr
15311531

1532+
1533+
-- | Extract the type and surround it in parentheses except in obviously safe cases.
1534+
--
1535+
-- Inferring when parentheses are actually needed around the type signature would
1536+
-- require understanding both the precedence of the context of the hole and of
1537+
-- the signature itself. Inserting them (almost) unconditionally is ugly but safe.
15321538
extractWildCardTypeSignature :: T.Text -> T.Text
1533-
extractWildCardTypeSignature =
1534-
-- inferring when parens are actually needed around the type signature would
1535-
-- require understanding both the precedence of the context of the _ and of
1536-
-- the signature itself. Inserting them unconditionally is ugly but safe.
1537-
("(" `T.append`) . (`T.append` ")") .
1538-
T.takeWhile (/='') . T.dropWhile (=='') . T.dropWhile (/='') .
1539-
snd . T.breakOnEnd "standing for "
1539+
extractWildCardTypeSignature msg = (if enclosed || not application then id else bracket) signature
1540+
where
1541+
msgSigPart = snd $ T.breakOnEnd "standing for " msg
1542+
signature = T.takeWhile (/='') . T.dropWhile (=='') . T.dropWhile (/='') $ msgSigPart
1543+
-- parenthesize type applications, e.g. (Maybe Char)
1544+
application = any isSpace . T.unpack $ signature
1545+
-- do not add extra parentheses to lists, tuples and already parenthesized types
1546+
enclosed = not (T.null signature) && (T.head signature, T.last signature) `elem` [('(',')'), ('[',']')]
1547+
bracket = ("(" `T.append`) . (`T.append` ")")
15401548

15411549
extractRenamableTerms :: T.Text -> [T.Text]
15421550
extractRenamableTerms msg

ghcide/test/exe/Main.hs

+64-66
Original file line numberDiff line numberDiff line change
@@ -1188,73 +1188,71 @@ renameActionTests = testGroup "rename actions"
11881188

11891189
typeWildCardActionTests :: TestTree
11901190
typeWildCardActionTests = testGroup "type wildcard actions"
1191-
[ testSession "global signature" $ do
1192-
let content = T.unlines
1193-
[ "module Testing where"
1194-
, "func :: _"
1195-
, "func x = x"
1196-
]
1197-
doc <- createDoc "Testing.hs" "haskell" content
1198-
_ <- waitForDiagnostics
1199-
actionsOrCommands <- getAllCodeActions doc
1200-
let [addSignature] = [action | InR action@CodeAction { _title = actionTitle } <- actionsOrCommands
1201-
, "Use type signature" `T.isInfixOf` actionTitle
1202-
]
1203-
executeCodeAction addSignature
1204-
contentAfterAction <- documentContents doc
1205-
let expectedContentAfterAction = T.unlines
1206-
[ "module Testing where"
1207-
, "func :: (p -> p)"
1208-
, "func x = x"
1209-
]
1210-
liftIO $ expectedContentAfterAction @=? contentAfterAction
1211-
, testSession "multi-line message" $ do
1212-
let content = T.unlines
1213-
[ "module Testing where"
1214-
, "func :: _"
1215-
, "func x y = x + y"
1216-
]
1217-
doc <- createDoc "Testing.hs" "haskell" content
1218-
_ <- waitForDiagnostics
1219-
actionsOrCommands <- getAllCodeActions doc
1220-
let [addSignature] = [action | InR action@CodeAction { _title = actionTitle } <- actionsOrCommands
1221-
, "Use type signature" `T.isInfixOf` actionTitle
1222-
]
1223-
executeCodeAction addSignature
1224-
contentAfterAction <- documentContents doc
1225-
let expectedContentAfterAction = T.unlines
1226-
[ "module Testing where"
1227-
, "func :: (Integer -> Integer -> Integer)"
1228-
, "func x y = x + y"
1229-
]
1230-
liftIO $ expectedContentAfterAction @=? contentAfterAction
1231-
, testSession "local signature" $ do
1232-
let content = T.unlines
1233-
[ "module Testing where"
1234-
, "func :: Int -> Int"
1235-
, "func x ="
1236-
, " let y :: _"
1237-
, " y = x * 2"
1238-
, " in y"
1239-
]
1240-
doc <- createDoc "Testing.hs" "haskell" content
1241-
_ <- waitForDiagnostics
1242-
actionsOrCommands <- getAllCodeActions doc
1243-
let [addSignature] = [action | InR action@CodeAction { _title = actionTitle } <- actionsOrCommands
1244-
, "Use type signature" `T.isInfixOf` actionTitle
1245-
]
1246-
executeCodeAction addSignature
1247-
contentAfterAction <- documentContents doc
1248-
let expectedContentAfterAction = T.unlines
1249-
[ "module Testing where"
1250-
, "func :: Int -> Int"
1251-
, "func x ="
1252-
, " let y :: (Int)"
1253-
, " y = x * 2"
1254-
, " in y"
1255-
]
1256-
liftIO $ expectedContentAfterAction @=? contentAfterAction
1191+
[ testUseTypeSignature "global signature"
1192+
[ "func :: _"
1193+
, "func x = x"
1194+
]
1195+
[ "func :: (p -> p)"
1196+
, "func x = x"
1197+
]
1198+
, testUseTypeSignature "local signature"
1199+
[ "func :: Int -> Int"
1200+
, "func x ="
1201+
, " let y :: _"
1202+
, " y = x * 2"
1203+
, " in y"
1204+
]
1205+
[ "func :: Int -> Int"
1206+
, "func x ="
1207+
, " let y :: Int"
1208+
, " y = x * 2"
1209+
, " in y"
1210+
]
1211+
, testUseTypeSignature "multi-line message"
1212+
[ "func :: _"
1213+
, "func x y = x + y"
1214+
]
1215+
[ "func :: (Integer -> Integer -> Integer)"
1216+
, "func x y = x + y"
1217+
]
1218+
, testUseTypeSignature "type in parentheses"
1219+
[ "func :: a -> _"
1220+
, "func x = (x, const x)"
1221+
]
1222+
[ "func :: a -> (a, b -> a)"
1223+
, "func x = (x, const x)"
1224+
]
1225+
, testUseTypeSignature "type in brackets"
1226+
[ "func :: _ -> Maybe a"
1227+
, "func xs = head xs"
1228+
]
1229+
[ "func :: [Maybe a] -> Maybe a"
1230+
, "func xs = head xs"
1231+
]
1232+
, testUseTypeSignature "unit type"
1233+
[ "func :: IO _"
1234+
, "func = putChar 'H'"
1235+
]
1236+
[ "func :: IO ()"
1237+
, "func = putChar 'H'"
1238+
]
12571239
]
1240+
where
1241+
-- | Test session of given name, checking action "Use type signature..."
1242+
-- on a test file with given content and comparing to expected result.
1243+
testUseTypeSignature name textIn textOut = testSession name $ do
1244+
let fileStart = "module Testing where"
1245+
content = T.unlines $ fileStart : textIn
1246+
expectedContentAfterAction = T.unlines $ fileStart : textOut
1247+
doc <- createDoc "Testing.hs" "haskell" content
1248+
_ <- waitForDiagnostics
1249+
actionsOrCommands <- getAllCodeActions doc
1250+
let [addSignature] = [action | InR action@CodeAction { _title = actionTitle } <- actionsOrCommands
1251+
, "Use type signature" `T.isInfixOf` actionTitle
1252+
]
1253+
executeCodeAction addSignature
1254+
contentAfterAction <- documentContents doc
1255+
liftIO $ expectedContentAfterAction @=? contentAfterAction
12581256

12591257
{-# HLINT ignore "Use nubOrd" #-}
12601258
removeImportTests :: TestTree

0 commit comments

Comments
 (0)