Skip to content

Commit 7f919cb

Browse files
committed
Improve quick-fix support for "term-level use of type constructors"
1 parent 4d0131d commit 7f919cb

6 files changed

Lines changed: 81 additions & 31 deletions

File tree

src/GHC/Diagnostic.hs

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ module GHC.Diagnostic (
1818
, extractIdentifiers
1919
, qualifiedName
2020
, analyzeAnnotation
21+
, takeGhcSourceSpan
2122
#endif
2223
) where
2324

@@ -66,6 +67,7 @@ formatSolutions start = zipWith formatNumbered [start..] >>> reverse >>> \ case
6667
ImportName module_ qualification name -> importStatement module_ qualification [name] <> faint package
6768
where
6869
package = " (" <> Builder.fromText module_.package.name <> ")"
70+
AddToImportList module_ text _span -> "Add " <> Builder.fromText text <> " to import list of " <> Builder.fromText module_
6971

7072
faint :: Builder -> Builder
7173
faint = Builder.withSGR [SetConsoleIntensity FaintIntensity]
@@ -96,10 +98,10 @@ annotate getAvailableImports diagnostic = getAvailableImports >>= \ case
9698
++ maybe [] (analyzeAnnotation availableImports) annotation
9799

98100
analyzeHints :: [Text] -> [Solution]
99-
analyzeHints = concat . mapMaybe analyzeHint
101+
analyzeHints = concatMap analyzeHint
100102

101-
analyzeHint :: Text -> Maybe [Solution]
102-
analyzeHint hint = asum [
103+
analyzeHint :: Text -> [Solution]
104+
analyzeHint hint = (fromMaybe [] $ asum [
103105
prefix "Perhaps you intended to use " <&> takeExtensions
104106

105107
, requiredFor GHC_910 $ prefix "Enable any of the following extensions: " <&>
@@ -108,7 +110,7 @@ analyzeHint hint = asum [
108110
, prefix "Perhaps use `" <&> return . takeIdentifier
109111
, prefix "Perhaps use variable `" <&> return . takeIdentifier
110112
, prefix "Perhaps use one of these:" <&> extractIdentifiers
111-
]
113+
]) <> maybeToList addToImportList
112114
where
113115
prefix :: Text -> Maybe Text
114116
prefix p = stripPrefix p hint
@@ -131,6 +133,37 @@ analyzeHint hint = asum [
131133
takeIdentifier :: Text -> Solution
132134
takeIdentifier = UseName . T.takeWhile (/= '\'')
133135

136+
addToImportList :: Maybe Solution
137+
addToImportList = case T.splitOn "' to the import list in the import of `" $ T.unwords $ T.lines $ hint of
138+
[T.takeWhileEnd (/= '`') -> name, T.splitOn "'" -> [module_, r]] -> AddToImportList module_ name <$> do
139+
takeGhcSourceSpan r
140+
_ -> Nothing
141+
142+
takeGhcSourceSpan :: Text -> Maybe Span
143+
takeGhcSourceSpan input = stripPrefix " (at " input <&> T.splitOn ":" >>= \ case
144+
file
145+
: (int -> Just line)
146+
: (T.splitOn "-" . (T.takeWhile (/= ')')) -> [int -> Just start, int -> Just end])
147+
: _ ->
148+
Just $ span file (line, start) (line, end)
149+
150+
file
151+
: (T.splitOn "-" -> [foo -> Just start, (takeFoo >>> foo) -> Just end])
152+
: _ ->
153+
Just $ span file start end
154+
_ -> Nothing
155+
where
156+
span :: Text -> (Int, Int) -> (Int, Int) -> Span
157+
span file start end = Span (T.unpack file) (uncurry Location start) (uncurry Location end)
158+
159+
int :: Text -> Maybe Int
160+
int = T.unpack >>> readMaybe
161+
162+
foo :: Text -> Maybe (Int, Int)
163+
foo = T.unpack >>> readMaybe
164+
165+
takeFoo t = T.take ((T.length $ T.takeWhile (/= ')') t) + 1) t
166+
134167
extractIdentifiers :: Text -> [Solution]
135168
extractIdentifiers input = case T.breakOn "`" >>> snd >>> T.breakOn "\'" $ input of
136169
(T.drop 1 -> identifier, rest)
@@ -324,6 +357,7 @@ edits annotated = case annotated.diagnostic.span of
324357
ReplaceImport old new -> ReplaceFirst span old new
325358
UseName name -> Replace span name
326359
ImportName module_ qualification name -> AddImport file module_ qualification [name]
360+
AddToImportList _ text loc -> Replace (Span loc.file loc.end loc.end) $ text <> "(..))" -- FIXME this is not correct and would need HIE lookup
327361

328362
file :: FilePath
329363
file = span.file

src/GHC/Diagnostic/Annotated.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ data Solution =
1616
| ReplaceImport Text Text
1717
| UseName Text
1818
| ImportName Module Qualification Text
19+
| AddToImportList Text Text Span
1920
deriving (Eq, Show)
2021

2122
data Annotation =

test/GHC/DiagnosticSpec.hs

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -307,13 +307,14 @@ spec = do
307307

308308
test "term-level-use-of-type-constructor" [] [r|
309309
module Foo where
310-
data Foo = Fooa | Fooi
311-
foo = Foo
312-
|] (Just $ TermLevelUseOfTypeConstructor Unqualified "Foo") [
313-
UseName "foo"
314-
, UseName "Fooa"
315-
, UseName "Fooi"
316-
]
310+
import Text.Markdown.Unlit (CodeBlock)
311+
data Foo = CodeBloc
312+
foo = CodeBlock
313+
|] (Just $ TermLevelUseOfTypeConstructor Unqualified "CodeBlock") [
314+
UseName "CodeBloc"
315+
, AddToImportList "Text.Markdown.Unlit" "CodeBlock" (Span "test/fixtures/term-level-use-of-type-constructor/Foo.hs" (Location 2 1) (Location 2 38))
316+
, importName (Module "markdown-unlit" "Text.Markdown.Unlit") "CodeBlock(..)"
317+
]
317318

318319
test "found-hole" [] [r|
319320
module Foo where
@@ -458,7 +459,7 @@ spec = do
458459
requiredFor GHC_910 "Perhaps you intended to use BlockArguments"
459460
, requiredFor GHC_912 "Perhaps you intended to use the `BlockArguments' extension"
460461
]
461-
for_ inputs \ input -> analyzeHint input `shouldBe` Just [
462+
for_ inputs \ input -> analyzeHint input `shouldBe` [
462463
EnableExtension "BlockArguments"
463464
]
464465

@@ -469,7 +470,7 @@ spec = do
469470
requiredFor GHC_910 "Enable any of the following extensions: TemplateHaskell, TemplateHaskellQuotes"
470471
, requiredFor GHC_912 "Perhaps you intended to use the `TemplateHaskellQuotes' extension (implied by `TemplateHaskell')"
471472
]
472-
for_ inputs \ input -> analyzeHint input `shouldBe` Just [
473+
for_ inputs \ input -> analyzeHint input `shouldBe` [
473474
EnableExtension "TemplateHaskellQuotes"
474475
, EnableExtension "TemplateHaskell"
475476
]
@@ -574,3 +575,12 @@ spec = do
574575
annotation = TypeNotInScope Unqualified "Option"
575576
analyzeAnnotation availableImports annotation `shouldBe` [
576577
]
578+
579+
fdescribe "takeGhcSourceSpan" do
580+
it "extracts single-line source spans" do
581+
takeGhcSourceSpan " (at Foo.hs:2:1-38).:.:." `shouldBe`
582+
Just (Span "Foo.hs" (Location 2 1) (Location 2 38))
583+
584+
it "extracts multi-line source spans" do
585+
takeGhcSourceSpan " (at Foo.hs:(2,1)-(5,12)).:.:." `shouldBe`
586+
Just (Span "Foo.hs" (Location 2 1) (Location 5 12))

test/fixtures/term-level-use-of-type-constructor/Foo.hs

Lines changed: 3 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
test/fixtures/term-level-use-of-type-constructor/Foo.hs:3:7: error: [GHC-01928]
2-
• Illegal term-level use of the type constructor `Foo'
3-
• defined at test/fixtures/term-level-use-of-type-constructor/Foo.hs:2:1
4-
• Perhaps use one of these:
5-
variable `foo' (line 3), `Fooa' (line 2), `Fooi' (line 2)
6-
• In the expression: Foo
7-
In an equation for `foo': foo = Foo
1+
test/fixtures/term-level-use-of-type-constructor/Foo.hs:4:7: error: [GHC-01928]
2+
• Illegal term-level use of the type constructor `CodeBlock'
3+
• imported from `Text.Markdown.Unlit' at test/fixtures/term-level-use-of-type-constructor/Foo.hs:2:29-37
4+
• Perhaps use `CodeBloc' (line 3)
5+
Add `CodeBlock' to the import list in the import of
6+
`Text.Markdown.Unlit'
7+
(at test/fixtures/term-level-use-of-type-constructor/Foo.hs:2:1-38).
8+
• In the expression: CodeBlock
9+
In an equation for `foo': foo = CodeBlock
810

test/fixtures/term-level-use-of-type-constructor/err.yaml

Lines changed: 11 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)