Skip to content

Commit 139dcf5

Browse files
simmsbmichaelpj
andauthored
Add an assist for importing record fields when using OverloadedRecordDot (#3642)
* Add an assist for OverloadedRecordDot * Add a test --------- Co-authored-by: Michael Peyton Jones <me@michaelpj.com>
1 parent 408a2af commit 139dcf5

File tree

2 files changed

+58
-5
lines changed

2 files changed

+58
-5
lines changed

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

+31-5
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,8 @@ import GHC (AddEpAnn (Ad
106106
DeltaPos (..),
107107
EpAnn (..),
108108
EpaLocation (..),
109-
hsmodAnn,
110-
LEpaComment)
109+
LEpaComment,
110+
hsmodAnn)
111111
#else
112112
import Language.Haskell.GHC.ExactPrint.Types (Annotation (annsDP),
113113
DeltaPos,
@@ -150,6 +150,7 @@ iePluginDescriptor recorder plId =
150150
, wrap suggestNewOrExtendImportForClassMethod
151151
, wrap suggestHideShadow
152152
, wrap suggestNewImport
153+
, wrap suggestAddRecordFieldImport
153154
]
154155
plId
155156
in mkExactprintPluginDescriptor recorder $ old {pluginHandlers = pluginHandlers old <> mkPluginHandler STextDocumentCodeAction codeAction }
@@ -1211,6 +1212,25 @@ suggestFixConstructorImport Diagnostic{_range=_range,..}
12111212
in [("Fix import of " <> fixedImport, TextEdit _range fixedImport)]
12121213
| otherwise = []
12131214

1215+
suggestAddRecordFieldImport :: ExportsMap -> DynFlags -> Annotated ParsedSource -> T.Text -> Diagnostic -> [(T.Text, CodeActionKind, TextEdit)]
1216+
suggestAddRecordFieldImport exportsMap df ps fileContents Diagnostic {..}
1217+
| Just fieldName <- findMissingField _message
1218+
, Just (range, indent) <- newImportInsertRange ps fileContents
1219+
= let qis = qualifiedImportStyle df
1220+
suggestions = nubSortBy simpleCompareImportSuggestion (constructNewImportSuggestions exportsMap (Nothing, NotInScopeThing fieldName) Nothing qis)
1221+
in map (\(ImportSuggestion _ kind (unNewImport -> imp)) -> (imp, kind, TextEdit range (imp <> "\n" <> T.replicate indent " "))) suggestions
1222+
| otherwise = []
1223+
where
1224+
findMissingField :: T.Text -> Maybe T.Text
1225+
findMissingField t =
1226+
let
1227+
hasfieldRegex = "((.+\\.)?HasField) \"(.+)\" ([^ ]+) ([^ ]+)"
1228+
regex = "(No instance for|Could not deduce):? (\\(" <> hasfieldRegex <> "\\)|‘" <> hasfieldRegex <> "’|" <> hasfieldRegex <> ")"
1229+
match = filter (/="") <$> matchRegexUnifySpaces t regex
1230+
in case match of
1231+
Just [_, _, _, _, fieldName, _, _] -> Just fieldName
1232+
_ -> Nothing
1233+
12141234
-- | Suggests a constraint for a declaration for which a constraint is missing.
12151235
suggestConstraint :: DynFlags -> ParsedSource -> Diagnostic -> [(T.Text, Rewrite)]
12161236
suggestConstraint df (makeDeltaAst -> parsedModule) diag@Diagnostic {..}
@@ -1608,10 +1628,11 @@ findPositionAfterModuleName ps hsmodName' = do
16081628
epaLocationToLine :: EpaLocation -> Maybe Int
16091629
#if MIN_VERSION_ghc(9,5,0)
16101630
epaLocationToLine (EpaSpan sp _)
1631+
= Just . srcLocLine . realSrcSpanEnd $ sp
16111632
#else
16121633
epaLocationToLine (EpaSpan sp)
1613-
#endif
16141634
= Just . srcLocLine . realSrcSpanEnd $ sp
1635+
#endif
16151636
epaLocationToLine (EpaDelta (SameLine _) priorComments) = Just $ sumCommentsOffset priorComments
16161637
-- 'priorComments' contains the comments right before the current EpaLocation
16171638
-- Summing line offset of priorComments is necessary, as 'line' is the gap between the last comment and
@@ -1852,16 +1873,21 @@ textInRange (Range (Position (fromIntegral -> startRow) (fromIntegral -> startCo
18521873

18531874
-- | Returns the ranges for a binding in an import declaration
18541875
rangesForBindingImport :: ImportDecl GhcPs -> String -> [Range]
1855-
rangesForBindingImport ImportDecl{
18561876
#if MIN_VERSION_ghc(9,5,0)
1877+
rangesForBindingImport ImportDecl{
18571878
ideclImportList = Just (Exactly, L _ lies)
1879+
} b =
1880+
concatMap (mapMaybe srcSpanToRange . rangesForBinding' b') lies
1881+
where
1882+
b' = wrapOperatorInParens b
18581883
#else
1884+
rangesForBindingImport ImportDecl{
18591885
ideclHiding = Just (False, L _ lies)
1860-
#endif
18611886
} b =
18621887
concatMap (mapMaybe srcSpanToRange . rangesForBinding' b') lies
18631888
where
18641889
b' = wrapOperatorInParens b
1890+
#endif
18651891
rangesForBindingImport _ _ = []
18661892

18671893
wrapOperatorInParens :: String -> String

plugins/hls-refactor-plugin/test/Main.hs

+27
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,7 @@ codeActionTests = testGroup "code actions"
310310
, removeImportTests
311311
, suggestImportClassMethodTests
312312
, suggestImportTests
313+
, suggestAddRecordFieldImportTests
313314
, suggestHideShadowTests
314315
, fixConstructorImportTests
315316
, fixModuleImportTypoTests
@@ -1730,6 +1731,32 @@ suggestImportTests = testGroup "suggest import actions"
17301731
else
17311732
liftIO $ [_title | InR CodeAction{_title} <- actions, _title == newImp ] @?= []
17321733

1734+
suggestAddRecordFieldImportTests :: TestTree
1735+
suggestAddRecordFieldImportTests = testGroup "suggest imports of record fields when using OverloadedRecordDot"
1736+
[ testGroup "The field is suggested when an instance resolution failure occurs"
1737+
[ ignoreFor (BrokenForGHC [GHC810, GHC90, GHC94, GHC96]) "Extension not present <9.2, and the assist is derived from the help message in >=9.4" theTest
1738+
]
1739+
]
1740+
where
1741+
theTest = testSessionWithExtraFiles "hover" def $ \dir -> do
1742+
configureCheckProject False
1743+
let before = T.unlines $ "module A where" : ["import B (Foo)", "getFoo :: Foo -> Int", "getFoo x = x.foo"]
1744+
after = T.unlines $ "module A where" : ["import B (Foo, foo)", "getFoo :: Foo -> Int", "getFoo x = x.foo"]
1745+
cradle = "cradle: {direct: {arguments: [-hide-all-packages, -package, base, -package, text, -package-env, -, A, B]}}"
1746+
liftIO $ writeFileUTF8 (dir </> "hie.yaml") cradle
1747+
liftIO $ writeFileUTF8 (dir </> "B.hs") $ unlines ["module B where", "data Foo = Foo { foo :: Int }"]
1748+
doc <- createDoc "Test.hs" "haskell" before
1749+
waitForProgressDone
1750+
_ <- waitForDiagnostics
1751+
let defLine = fromIntegral $ 1 + 2
1752+
range = Range (Position defLine 0) (Position defLine maxBound)
1753+
actions <- getCodeActions doc range
1754+
action <- liftIO $ pickActionWithTitle "Add foo to the import list of B" actions
1755+
executeCodeAction action
1756+
contentAfterAction <- documentContents doc
1757+
liftIO $ after @=? contentAfterAction
1758+
1759+
17331760
suggestImportDisambiguationTests :: TestTree
17341761
suggestImportDisambiguationTests = testGroup "suggest import disambiguation actions"
17351762
[ testGroup "Hiding strategy works"

0 commit comments

Comments
 (0)