Skip to content

Commit 4a98c22

Browse files
committed
Distinguish between wildcard expansion and traditional record syntax conversion
1 parent 14e94b7 commit 4a98c22

File tree

2 files changed

+83
-56
lines changed

2 files changed

+83
-56
lines changed

plugins/hls-explicit-record-fields-plugin/src/Ide/Plugin/ExplicitFields.hs

Lines changed: 35 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -149,25 +149,42 @@ descriptor recorder plId =
149149
, pluginRules = collectRecordsRule recorder *> collectNamesRule
150150
}
151151

152+
data RecordConversionType
153+
= RecordWildcardExpansion
154+
| RecordTraditionalSyntaxConversion
155+
156+
data RecordConversion =
157+
RecordConversion
158+
Int -- ^ uid
159+
RecordConversionType
160+
161+
-- | Given a record, determine whether it is a case of wildcard expansion
162+
-- or a conversion to the traditional record syntax.
163+
getConversionType :: RecordInfo -> Maybe RecordConversionType
164+
getConversionType = \case
165+
-- Only fully saturated constructor applications can be converted to
166+
-- the record syntax through the code action
167+
RecordInfoApp _ (RecordAppExpr Unsaturated _ _) -> Nothing
168+
RecordInfoApp {} -> Just RecordTraditionalSyntaxConversion
169+
_ -> Just RecordWildcardExpansion
170+
152171
codeActionProvider :: PluginMethodHandler IdeState 'Method_TextDocumentCodeAction
153172
codeActionProvider ideState _ (CodeActionParams _ _ docId range _) = do
154173
nfp <- getNormalizedFilePathE (docId ^. L.uri)
155174
CRR {crCodeActions, crCodeActionResolve, enabledExtensions} <- runActionE "ExplicitFields.CollectRecords" ideState $ useE CollectRecords nfp
156175
-- All we need to build a code action is the list of extensions, and a int to
157176
-- allow us to resolve it later.
158-
let recordUids = [ uid
177+
let recordUids = [ RecordConversion uid conversionType
159178
| uid <- RangeMap.filterByRange range crCodeActions
160179
, Just record <- [IntMap.lookup uid crCodeActionResolve]
161-
-- Only fully saturated constructor applications can be
162-
-- converted to the record syntax through the code action
163-
, isConvertible record
180+
, Just conversionType <- [getConversionType record]
164181
]
165182
let actions = map (mkCodeAction enabledExtensions) recordUids
166183
pure $ InL actions
167184
where
168-
mkCodeAction :: [Extension] -> Int -> Command |? CodeAction
169-
mkCodeAction exts uid = InR CodeAction
170-
{ _title = mkTitle exts -- TODO: `Expand positional record` without NamedFieldPuns if RecordInfoApp
185+
mkCodeAction :: [Extension] -> RecordConversion -> Command |? CodeAction
186+
mkCodeAction exts (RecordConversion uid conversionType) = InR CodeAction
187+
{ _title = mkTitle exts conversionType
171188
, _kind = Just CodeActionKind_RefactorRewrite
172189
, _diagnostics = Nothing
173190
, _isPreferred = Nothing
@@ -177,11 +194,6 @@ codeActionProvider ideState _ (CodeActionParams _ _ docId range _) = do
177194
, _data_ = Just $ toJSON uid
178195
}
179196

180-
isConvertible :: RecordInfo -> Bool
181-
isConvertible = \case
182-
RecordInfoApp _ (RecordAppExpr Unsaturated _ _) -> False
183-
_ -> True
184-
185197
codeActionResolveProvider :: ResolveFunction IdeState Int 'Method_CodeActionResolve
186198
codeActionResolveProvider ideState pId ca uri uid = do
187199
nfp <- getNormalizedFilePathE uri
@@ -246,7 +258,7 @@ inlayHintDotdotProvider _ state pId InlayHintParams {_textDocument = TextDocumen
246258
, _label = InR label
247259
, _kind = Nothing -- neither a type nor a parameter
248260
, _textEdits = Just textEdits -- same as CodeAction
249-
, _tooltip = Just $ InL (mkTitle enabledExtensions) -- same as CodeAction
261+
, _tooltip = Just $ InL (mkTitle enabledExtensions RecordWildcardExpansion) -- same as CodeAction
250262
, _paddingLeft = Just True -- padding after dotdot
251263
, _paddingRight = Nothing
252264
, _data_ = Nothing
@@ -282,20 +294,23 @@ inlayHintPosRecProvider _ state _pId InlayHintParams {_textDocument = TextDocume
282294
, _label = InR $ pure (mkInlayHintLabelPart name fieldDefLoc)
283295
, _kind = Nothing -- neither a type nor a parameter
284296
, _textEdits = Just (maybeToList te) -- same as CodeAction
285-
, _tooltip = Just $ InL "Expand positional record" -- same as CodeAction
297+
, _tooltip = Just $ InL (mkTitle [] RecordTraditionalSyntaxConversion) -- same as CodeAction
286298
, _paddingLeft = Nothing
287299
, _paddingRight = Nothing
288300
, _data_ = Nothing
289301
}
290302

291303
mkInlayHintLabelPart name loc = InlayHintLabelPart (printFieldName (pprNameUnqualified name) <> "=") Nothing loc Nothing
292304

293-
mkTitle :: [Extension] -> Text
294-
mkTitle exts = "Expand record wildcard"
295-
<> if NamedFieldPuns `elem` exts
296-
then mempty
297-
else " (needs extension: NamedFieldPuns)"
298-
305+
mkTitle :: [Extension] -> RecordConversionType -> Text
306+
mkTitle exts = \case
307+
RecordWildcardExpansion ->
308+
"Expand record wildcard"
309+
<> if NamedFieldPuns `elem` exts
310+
then mempty
311+
else " (needs extension: NamedFieldPuns)"
312+
RecordTraditionalSyntaxConversion ->
313+
"Convert to traditional record syntax"
299314

300315
pragmaEdit :: [Extension] -> NextPragmaInfo -> Maybe TextEdit
301316
pragmaEdit exts pragma = if NamedFieldPuns `elem` exts

plugins/hls-explicit-record-fields-plugin/test/Main.hs

Lines changed: 48 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -22,22 +22,22 @@ plugin = mkPluginTestDescriptor ExplicitFields.descriptor "explicit-fields"
2222
test :: TestTree
2323
test = testGroup "explicit-fields"
2424
[ testGroup "code actions"
25-
[ mkTest "WildcardOnly" "WildcardOnly" 12 10 12 20
26-
, mkTest "Unused" "Unused" 12 10 12 20
27-
, mkTest "Unused2" "Unused2" 12 10 12 20
28-
, mkTest "WithPun" "WithPun" 13 10 13 25
29-
, mkTest "WithExplicitBind" "WithExplicitBind" 12 10 12 32
30-
, mkTest "Mixed" "Mixed" 14 10 14 37
31-
, mkTest "Construction" "Construction" 16 5 16 15
32-
, mkTest "PositionalConstruction" "PositionalConstruction" 15 5 15 15
33-
, mkTest "HsExpanded1" "HsExpanded1" 17 10 17 20
34-
, mkTest "HsExpanded2" "HsExpanded2" 23 10 23 22
25+
[ mkExpansionTest "WildcardOnly" "WildcardOnly" 12 10 12 20
26+
, mkExpansionTest "Unused" "Unused" 12 10 12 20
27+
, mkExpansionTest "Unused2" "Unused2" 12 10 12 20
28+
, mkExpansionTest "WithPun" "WithPun" 13 10 13 25
29+
, mkExpansionTest "WithExplicitBind" "WithExplicitBind" 12 10 12 32
30+
, mkExpansionTest "Mixed" "Mixed" 14 10 14 37
31+
, mkExpansionTest "Construction" "Construction" 16 5 16 15
32+
, mkConversionTest "PositionalConstruction" "PositionalConstruction" 15 5 15 15
33+
, mkExpansionTest "HsExpanded1" "HsExpanded1" 17 10 17 20
34+
, mkExpansionTest "HsExpanded2" "HsExpanded2" 23 10 23 22
3535
, mkTestNoAction "ExplicitBinds" "ExplicitBinds" 11 10 11 52
3636
, mkTestNoAction "Puns" "Puns" 12 10 12 31
3737
, mkTestNoAction "Infix" "Infix" 11 11 11 31
3838
, mkTestNoAction "Prefix" "Prefix" 10 11 10 28
3939
, mkTestNoAction "PartiallyAppliedCon" "PartiallyAppliedCon" 7 8 7 12
40-
, mkTest "PolymorphicRecordConstruction" "PolymorphicRecordConstruction" 15 5 15 15
40+
, mkConversionTest "PolymorphicRecordConstruction" "PolymorphicRecordConstruction" 15 5 15 15
4141
]
4242
, testGroup "inlay hints"
4343
[ mkInlayHintsTest "Construction" Nothing 16 $ \ih -> do
@@ -84,19 +84,19 @@ test = testGroup "explicit-fields"
8484
[ defInlayHint { _position = Position 15 11
8585
, _label = InR [ foo ]
8686
, _textEdits = Just [ mkLineTextEdit "MyRec { foo = a, bar = b, baz = c }" 15 5 16 ]
87-
, _tooltip = Just $ InL "Expand positional record"
87+
, _tooltip = Just $ InL "Convert to traditional record syntax"
8888
, _paddingLeft = Nothing
8989
}
9090
, defInlayHint { _position = Position 15 13
9191
, _label = InR [ bar ]
9292
, _textEdits = Just [ mkLineTextEdit "MyRec { foo = a, bar = b, baz = c }" 15 5 16 ]
93-
, _tooltip = Just $ InL "Expand positional record"
93+
, _tooltip = Just $ InL "Convert to traditional record syntax"
9494
, _paddingLeft = Nothing
9595
}
9696
, defInlayHint { _position = Position 15 15
9797
, _label = InR [ baz ]
9898
, _textEdits = Just [ mkLineTextEdit "MyRec { foo = a, bar = b, baz = c }" 15 5 16 ]
99-
, _tooltip = Just $ InL "Expand positional record"
99+
, _tooltip = Just $ InL "Convert to traditional record syntax"
100100
, _paddingLeft = Nothing
101101
}
102102
]
@@ -109,19 +109,19 @@ test = testGroup "explicit-fields"
109109
[ defInlayHint { _position = Position 15 11
110110
, _label = InR [ foo ]
111111
, _textEdits = Just [ mkLineTextEdit "MyRec { foo = a, bar = b, baz = c }" 15 5 16 ]
112-
, _tooltip = Just $ InL "Expand positional record"
112+
, _tooltip = Just $ InL "Convert to traditional record syntax"
113113
, _paddingLeft = Nothing
114114
}
115115
, defInlayHint { _position = Position 15 13
116116
, _label = InR [ bar ]
117117
, _textEdits = Just [ mkLineTextEdit "MyRec { foo = a, bar = b, baz = c }" 15 5 16 ]
118-
, _tooltip = Just $ InL "Expand positional record"
118+
, _tooltip = Just $ InL "Convert to traditional record syntax"
119119
, _paddingLeft = Nothing
120120
}
121121
, defInlayHint { _position = Position 15 15
122122
, _label = InR [ baz ]
123123
, _textEdits = Just [ mkLineTextEdit "MyRec { foo = a, bar = b, baz = c }" 15 5 16 ]
124-
, _tooltip = Just $ InL "Expand positional record"
124+
, _tooltip = Just $ InL "Convert to traditional record syntax"
125125
, _paddingLeft = Nothing
126126
}
127127
]
@@ -142,7 +142,7 @@ test = testGroup "explicit-fields"
142142
[defInlayHint { _position = Position 13 21
143143
, _label = InR [ foo ]
144144
, _textEdits = Just [ mkLineTextEdit "MyRec { foo = 5 }" 13 15 22 ]
145-
, _tooltip = Just $ InL "Expand positional record"
145+
, _tooltip = Just $ InL "Convert to traditional record syntax"
146146
, _paddingLeft = Nothing
147147
}]
148148
, mkInlayHintsTest "HsExpanded1DuplicateRecordFields" (Just " (positional)") 13 $ \ih -> do
@@ -152,7 +152,7 @@ test = testGroup "explicit-fields"
152152
[defInlayHint { _position = Position 13 21
153153
, _label = InR [ foo ]
154154
, _textEdits = Just [ mkLineTextEdit "MyRec { foo = 5 }" 13 15 22 ]
155-
, _tooltip = Just $ InL "Expand positional record"
155+
, _tooltip = Just $ InL "Convert to traditional record syntax"
156156
, _paddingLeft = Nothing
157157
}]
158158
, mkInlayHintsTest "HsExpanded2" Nothing 23 $ \ih -> do
@@ -172,7 +172,7 @@ test = testGroup "explicit-fields"
172172
[defInlayHint { _position = Position 16 21
173173
, _label = InR [ foo ]
174174
, _textEdits = Just [ mkLineTextEdit "MyRec { foo = 5 }" 16 15 22 ]
175-
, _tooltip = Just $ InL "Expand positional record"
175+
, _tooltip = Just $ InL "Convert to traditional record syntax"
176176
, _paddingLeft = Nothing
177177
}]
178178
, mkInlayHintsTest "Mixed" Nothing 14 $ \ih -> do
@@ -276,19 +276,19 @@ test = testGroup "explicit-fields"
276276
[ defInlayHint { _position = Position 15 11
277277
, _label = InR [ foo ]
278278
, _textEdits = Just [ mkLineTextEdit "MyRec { foo = a, bar = b, baz = c }" 15 5 16 ]
279-
, _tooltip = Just $ InL "Expand positional record"
279+
, _tooltip = Just $ InL "Convert to traditional record syntax"
280280
, _paddingLeft = Nothing
281281
}
282282
, defInlayHint { _position = Position 15 13
283283
, _label = InR [ bar ]
284284
, _textEdits = Just [ mkLineTextEdit "MyRec { foo = a, bar = b, baz = c }" 15 5 16 ]
285-
, _tooltip = Just $ InL "Expand positional record"
285+
, _tooltip = Just $ InL "Convert to traditional record syntax"
286286
, _paddingLeft = Nothing
287287
}
288288
, defInlayHint { _position = Position 15 15
289289
, _label = InR [ baz ]
290290
, _textEdits = Just [ mkLineTextEdit "MyRec { foo = a, bar = b, baz = c }" 15 5 16 ]
291-
, _tooltip = Just $ InL "Expand positional record"
291+
, _tooltip = Just $ InL "Convert to traditional record syntax"
292292
, _paddingLeft = Nothing
293293
}
294294
]
@@ -310,35 +310,47 @@ mkTestNoAction title fp x1 y1 x2 y2 =
310310
testCase title $
311311
runSessionWithServer def plugin (testDataDir </> "noop") $ do
312312
doc <- openDoc (fp <.> "hs") "haskell"
313-
actions <- getExplicitFieldsActions doc x1 y1 x2 y2
313+
actions <- getActions isExplicitFieldsCodeAction doc x1 y1 x2 y2
314314
liftIO $ actions @?= []
315315

316-
mkTestWithCount :: Int -> TestName -> FilePath -> UInt -> UInt -> UInt -> UInt -> TestTree
317-
mkTestWithCount cnt title fp x1 y1 x2 y2 =
316+
mkTestWithCountAndMessage :: (CodeAction -> Bool) -> Int -> TestName -> FilePath -> UInt -> UInt -> UInt -> UInt -> TestTree
317+
mkTestWithCountAndMessage msgPredicate cnt title fp x1 y1 x2 y2 =
318318
goldenWithHaskellAndCaps def codeActionResolveCaps plugin title testDataDir fp "expected" "hs" $ \doc -> do
319-
acts@(act:_) <- getExplicitFieldsActions doc x1 y1 x2 y2
319+
acts@(act:_) <- getActions msgPredicate doc x1 y1 x2 y2
320320
liftIO $ length acts @?= cnt
321321
executeCodeAction act
322322

323-
mkTest :: TestName -> FilePath -> UInt -> UInt -> UInt -> UInt -> TestTree
324-
mkTest = mkTestWithCount 1
323+
mkExpansionTest :: TestName -> FilePath -> UInt -> UInt -> UInt -> UInt -> TestTree
324+
mkExpansionTest = mkTestWithCountAndMessage isRecWildcardExpansionCodeAction 1
325325

326-
getExplicitFieldsActions
327-
:: TextDocumentIdentifier
326+
mkConversionTest :: TestName -> FilePath -> UInt -> UInt -> UInt -> UInt -> TestTree
327+
mkConversionTest = mkTestWithCountAndMessage isRecLiteralConversionCodeAction 1
328+
329+
getActions
330+
:: (CodeAction -> Bool) -> TextDocumentIdentifier
328331
-> UInt -> UInt -> UInt -> UInt
329332
-> Session [CodeAction]
330-
getExplicitFieldsActions doc x1 y1 x2 y2 =
331-
findExplicitFieldsAction <$> getAndResolveCodeActions doc range
333+
getActions p doc x1 y1 x2 y2 =
334+
findAction p <$> getAndResolveCodeActions doc range
332335
where
333336
range = Range (Position x1 y1) (Position x2 y2)
334337

335-
findExplicitFieldsAction :: [a |? CodeAction] -> [CodeAction]
336-
findExplicitFieldsAction = filter isExplicitFieldsCodeAction . rights . map toEither
338+
findAction :: (CodeAction -> Bool) -> [a |? CodeAction] -> [CodeAction]
339+
findAction p = filter p . rights . map toEither
337340

338341
isExplicitFieldsCodeAction :: CodeAction -> Bool
339-
isExplicitFieldsCodeAction CodeAction {_title} =
342+
isExplicitFieldsCodeAction act =
343+
isRecWildcardExpansionCodeAction act
344+
|| isRecLiteralConversionCodeAction act
345+
346+
isRecWildcardExpansionCodeAction :: CodeAction -> Bool
347+
isRecWildcardExpansionCodeAction CodeAction {_title} =
340348
"Expand record wildcard" `T.isPrefixOf` _title
341349

350+
isRecLiteralConversionCodeAction :: CodeAction -> Bool
351+
isRecLiteralConversionCodeAction CodeAction {_title} =
352+
"Convert to traditional record syntax" `T.isPrefixOf` _title
353+
342354
defInlayHint :: InlayHint
343355
defInlayHint =
344356
InlayHint

0 commit comments

Comments
 (0)