@@ -56,6 +56,9 @@ import Outputable (ppr, showSDocUnsafe)
56
56
import DynFlags (xFlags , FlagSpec (.. ))
57
57
import GHC.LanguageExtensions.Type (Extension )
58
58
import System.Time.Extra (showDuration , duration )
59
+ import Data.Function
60
+ import Control.Arrow ((>>>) )
61
+ import Data.Functor
59
62
60
63
plugin :: Plugin c
61
64
plugin = codeActionPluginWithRules rules codeAction <> Plugin mempty setHandlersCodeLens
@@ -155,6 +158,7 @@ suggestAction dflags packageExports ideOptions parsedModule text diag = concat
155
158
, suggestModuleTypo diag
156
159
, suggestReplaceIdentifier text diag
157
160
, suggestSignature True diag
161
+ , suggestConstraint text diag
158
162
] ++ concat
159
163
[ suggestNewDefinition ideOptions pm text diag
160
164
++ suggestRemoveRedundantImport pm text diag
@@ -404,6 +408,140 @@ suggestSignature isQuickFix Diagnostic{_range=_range@Range{..},..}
404
408
405
409
suggestSignature _ _ = []
406
410
411
+ -- | Suggests a constraint for a declaration for which a constraint is missing.
412
+ suggestConstraint :: Maybe T. Text -> Diagnostic -> [(T. Text , [TextEdit ])]
413
+ suggestConstraint mContents diag@ Diagnostic {.. }
414
+ | Just contents <- mContents
415
+ , Just missingConstraint <- findMissingConstraint _message
416
+ = let codeAction = if _message =~ (" the type signature for:" :: String )
417
+ then suggestFunctionConstraint
418
+ else suggestInstanceConstraint
419
+ in codeAction contents diag missingConstraint
420
+ | otherwise = []
421
+ where
422
+ findMissingConstraint :: T. Text -> Maybe T. Text
423
+ findMissingConstraint t =
424
+ let regex = " (No instance for|Could not deduce) \\ ((.+)\\ ) arising from a use of"
425
+ in matchRegex t regex <&> last
426
+
427
+ normalizeConstraints :: T. Text -> T. Text -> T. Text
428
+ normalizeConstraints existingConstraints constraint =
429
+ let constraintsInit = if " (" `T.isPrefixOf` existingConstraints
430
+ then T. dropEnd 1 existingConstraints
431
+ else " (" <> existingConstraints
432
+ in constraintsInit <> " , " <> constraint <> " )"
433
+
434
+ -- | Suggests a constraint for an instance declaration for which a constraint is missing.
435
+ suggestInstanceConstraint :: T. Text -> Diagnostic -> T. Text -> [(T. Text , [TextEdit ])]
436
+ suggestInstanceConstraint contents Diagnostic {.. } missingConstraint
437
+ -- Suggests a constraint for an instance declaration with no existing constraints.
438
+ -- • No instance for (Eq a) arising from a use of ‘==’
439
+ -- Possible fix: add (Eq a) to the context of the instance declaration
440
+ -- • In the expression: x == y
441
+ -- In an equation for ‘==’: (Wrap x) == (Wrap y) = x == y
442
+ -- In the instance declaration for ‘Eq (Wrap a)’
443
+ | Just [instanceDeclaration] <- matchRegex _message " In the instance declaration for ‘([^`]*)’"
444
+ = let instanceLine = contents
445
+ & T. splitOn (" instance " <> instanceDeclaration)
446
+ & head & T. lines & length
447
+ startOfConstraint = Position instanceLine (length (" instance " :: String ))
448
+ range = Range startOfConstraint startOfConstraint
449
+ newConstraint = missingConstraint <> " => "
450
+ in [(actionTitle missingConstraint, [TextEdit range newConstraint])]
451
+
452
+ -- Suggests a constraint for an instance declaration with one or more existing constraints.
453
+ -- • Could not deduce (Eq b) arising from a use of ‘==’
454
+ -- from the context: Eq a
455
+ -- bound by the instance declaration at /path/to/Main.hs:7:10-32
456
+ -- Possible fix: add (Eq b) to the context of the instance declaration
457
+ -- • In the second argument of ‘(&&)’, namely ‘x' == y'’
458
+ -- In the expression: x == y && x' == y'
459
+ -- In an equation for ‘==’:
460
+ -- (Pair x x') == (Pair y y') = x == y && x' == y'
461
+ | Just [instanceLineStr, constraintFirstCharStr]
462
+ <- matchRegex _message " bound by the instance declaration at .+:([0-9]+):([0-9]+)"
463
+ = let existingConstraints = findExistingConstraints _message
464
+ newConstraints = normalizeConstraints existingConstraints missingConstraint
465
+ instanceLine = readPositionNumber instanceLineStr
466
+ constraintFirstChar = readPositionNumber constraintFirstCharStr
467
+ startOfConstraint = Position instanceLine constraintFirstChar
468
+ endOfConstraint = Position instanceLine $
469
+ constraintFirstChar + T. length existingConstraints
470
+ range = Range startOfConstraint endOfConstraint
471
+ in [(actionTitle missingConstraint, [TextEdit range newConstraints])]
472
+ | otherwise = []
473
+ where
474
+ findExistingConstraints :: T. Text -> T. Text
475
+ findExistingConstraints t =
476
+ T. replace " from the context: " " " . T. strip $ T. lines t !! 1
477
+
478
+ readPositionNumber :: T. Text -> Int
479
+ readPositionNumber = T. unpack >>> read >>> pred
480
+
481
+ actionTitle :: T. Text -> T. Text
482
+ actionTitle constraint = " Add `" <> constraint
483
+ <> " ` to the context of the instance declaration"
484
+
485
+ findTypeSignatureName :: T. Text -> Maybe T. Text
486
+ findTypeSignatureName t = matchRegex t " ([^ ]+) :: " <&> head
487
+
488
+ findTypeSignatureLine :: T. Text -> T. Text -> Int
489
+ findTypeSignatureLine contents typeSignatureName =
490
+ T. splitOn (typeSignatureName <> " :: " ) contents & head & T. lines & length
491
+
492
+ -- | Suggests a constraint for a type signature for which a constraint is missing.
493
+ suggestFunctionConstraint :: T. Text -> Diagnostic -> T. Text -> [(T. Text , [TextEdit ])]
494
+ suggestFunctionConstraint contents Diagnostic {.. } missingConstraint
495
+ -- Suggests a constraint for a type signature with any number of existing constraints.
496
+ -- • No instance for (Eq a) arising from a use of ‘==’
497
+ -- Possible fix:
498
+ -- add (Eq a) to the context of
499
+ -- the type signature for:
500
+ -- eq :: forall a. a -> a -> Bool
501
+ -- • In the expression: x == y
502
+ -- In an equation for ‘eq’: eq x y = x == y
503
+
504
+ -- • Could not deduce (Eq b) arising from a use of ‘==’
505
+ -- from the context: Eq a
506
+ -- bound by the type signature for:
507
+ -- eq :: forall a b. Eq a => Pair a b -> Pair a b -> Bool
508
+ -- at Main.hs:5:1-42
509
+ -- Possible fix:
510
+ -- add (Eq b) to the context of
511
+ -- the type signature for:
512
+ -- eq :: forall a b. Eq a => Pair a b -> Pair a b -> Bool
513
+ -- • In the second argument of ‘(&&)’, namely ‘y == y'’
514
+ -- In the expression: x == x' && y == y'
515
+ -- In an equation for ‘eq’:
516
+ -- eq (Pair x y) (Pair x' y') = x == x' && y == y'
517
+ | Just typeSignatureName <- findTypeSignatureName _message
518
+ = let mExistingConstraints = findExistingConstraints _message
519
+ newConstraint = buildNewConstraints missingConstraint mExistingConstraints
520
+ typeSignatureLine = findTypeSignatureLine contents typeSignatureName
521
+ typeSignatureFirstChar = T. length $ typeSignatureName <> " :: "
522
+ startOfConstraint = Position typeSignatureLine typeSignatureFirstChar
523
+ endOfConstraint = Position typeSignatureLine $
524
+ typeSignatureFirstChar + maybe 0 T. length mExistingConstraints
525
+ range = Range startOfConstraint endOfConstraint
526
+ in [(actionTitle missingConstraint typeSignatureName, [TextEdit range newConstraint])]
527
+ | otherwise = []
528
+ where
529
+ findExistingConstraints :: T. Text -> Maybe T. Text
530
+ findExistingConstraints message =
531
+ if message =~ (" from the context:" :: String )
532
+ then fmap (T. strip . head ) $ matchRegex message " \\ . ([^=]+)"
533
+ else Nothing
534
+
535
+ buildNewConstraints :: T. Text -> Maybe T. Text -> T. Text
536
+ buildNewConstraints constraint mExistingConstraints =
537
+ case mExistingConstraints of
538
+ Just existingConstraints -> normalizeConstraints existingConstraints constraint
539
+ Nothing -> constraint <> " => "
540
+
541
+ actionTitle :: T. Text -> T. Text -> T. Text
542
+ actionTitle constraint typeSignatureName = " Add `" <> constraint
543
+ <> " ` to the context of the type signature for `" <> typeSignatureName <> " `"
544
+
407
545
-------------------------------------------------------------------------------------------------
408
546
409
547
suggestNewImport :: PackageExportsMap -> ParsedModule -> Diagnostic -> [(T. Text , [TextEdit ])]
0 commit comments