-
-
Notifications
You must be signed in to change notification settings - Fork 391
Best-effort support of Qualified Imports in GHC 9.4 #3712
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 12 commits
b92c423
7bf71e3
ff348a2
2eae0a7
c5a7b2d
cbc836a
1df314b
56b27a3
907704c
eaf396f
eb71c2f
b273e74
a8ecc92
02cc201
b2c5f78
d0e5765
9e1a230
bc57292
d96d470
e0eb90a
8f937fc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,6 +15,7 @@ module Development.IDE.Plugin.CodeAction | |
) where | ||
|
||
import Control.Applicative ((<|>)) | ||
import Control.Applicative.Combinators.NonEmpty (sepBy1) | ||
import Control.Arrow (second, | ||
(&&&), | ||
(>>>)) | ||
|
@@ -69,10 +70,12 @@ import Development.IDE.Types.Logger hiding | |
import Development.IDE.Types.Options | ||
import GHC.Exts (fromList) | ||
import qualified GHC.LanguageExtensions as Lang | ||
import qualified Text.Regex.Applicative as RE | ||
#if MIN_VERSION_ghc(9,4,0) | ||
import GHC.Parser.Annotation (TokenLocation (..)) | ||
#endif | ||
import Ide.PluginUtils (subRange) | ||
import Ide.PluginUtils (extractTextInRange, | ||
subRange) | ||
import Ide.Types | ||
import Language.LSP.Protocol.Message (ResponseError, | ||
SMethod (..)) | ||
|
@@ -1473,7 +1476,7 @@ suggestNewOrExtendImportForClassMethod packageExportsMap ps fileContents Diagnos | |
where moduleText = moduleNameText identInfo | ||
|
||
suggestNewImport :: DynFlags -> ExportsMap -> Annotated ParsedSource -> T.Text -> Diagnostic -> [(T.Text, CodeActionKind, TextEdit)] | ||
suggestNewImport df packageExportsMap ps fileContents Diagnostic{_message} | ||
suggestNewImport df packageExportsMap ps fileContents Diagnostic{_message, ..} | ||
konn marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| msg <- unifySpaces _message | ||
, Just thingMissing <- extractNotInScopeName msg | ||
, qual <- extractQualifiedModuleName msg | ||
|
@@ -1482,17 +1485,62 @@ suggestNewImport df packageExportsMap ps fileContents Diagnostic{_message} | |
>>= (findImportDeclByModuleName hsmodImports . T.unpack) | ||
>>= ideclAs . unLoc | ||
<&> T.pack . moduleNameString . unLoc | ||
, -- tentative workaround for detecting qualification in GHC 9.4 | ||
-- FIXME: We can delete this after dropping the support for GHC 9.4 | ||
qualGHC94 <- | ||
guard (ghcVersion == GHC94) | ||
fendor marked this conversation as resolved.
Show resolved
Hide resolved
|
||
*> extractQualifiedModuleNameFromMissingName (extractTextInRange _range fileContents) | ||
, Just (range, indent) <- newImportInsertRange ps fileContents | ||
, extendImportSuggestions <- matchRegexUnifySpaces msg | ||
"Perhaps you want to add ‘[^’]*’ to the import list in the import of ‘([^’]*)’" | ||
= let qis = qualifiedImportStyle df | ||
-- FIXME: we can use thingMissing once the support for GHC 9.4 is dropped. | ||
missing | ||
| GHC94 <- ghcVersion | ||
, isNothing (qual <|> qual') | ||
, Just q <- qualGHC94 = | ||
mapNotInScope ((q <> ".") <>) thingMissing | ||
konn marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| otherwise = thingMissing | ||
suggestions = nubSortBy simpleCompareImportSuggestion | ||
(constructNewImportSuggestions packageExportsMap (qual <|> qual', thingMissing) extendImportSuggestions qis) in | ||
(constructNewImportSuggestions packageExportsMap (qual <|> qual' <|> qualGHC94, missing) extendImportSuggestions qis) in | ||
map (\(ImportSuggestion _ kind (unNewImport -> imp)) -> (imp, kind, TextEdit range (imp <> "\n" <> T.replicate indent " "))) suggestions | ||
where | ||
L _ HsModule {..} = astA ps | ||
suggestNewImport _ _ _ _ _ = [] | ||
|
||
-- tentative workaround for detecting qualification in GHC 9.4 | ||
-- FIXME: We can delete this after dropping the support for GHC 9.4 | ||
extractQualifiedModuleNameFromMissingName :: T.Text -> Maybe T.Text | ||
konn marked this conversation as resolved.
Show resolved
Hide resolved
|
||
extractQualifiedModuleNameFromMissingName (T.strip -> missing) | ||
= T.pack <$> (T.unpack missing RE.=~ qualIdentP) | ||
where | ||
{- | ||
NOTE: Haskell 2010 allows /unicode/ upper & lower letters | ||
as a module name component; otoh, regex-tdfa only allows | ||
/ASCII/ letters to be matched with @[[:upper:]]@ and/or @[[:lower:]]@. | ||
Hence we use regex-applicative(-text) for finer-grained predicates. | ||
|
||
RULES (from [Section 10 of Haskell 2010 Report](https://www.haskell.org/onlinereport/haskell2010/haskellch10.html)): | ||
modid → {conid .} conid | ||
conid → large {small | large | digit | ' } | ||
small → ascSmall | uniSmall | _ | ||
ascSmall → a | b | … | z | ||
uniSmall → any Unicode lowercase letter | ||
large → ascLarge | uniLarge | ||
ascLarge → A | B | … | Z | ||
uniLarge → any uppercase or titlecase Unicode letter | ||
-} | ||
|
||
qualIdentP = parensQualOpP <|> qualVarP | ||
parensQualOpP = RE.sym '(' *> modNameP <* RE.sym '.' <* RE.anySym <* RE.few RE.anySym <* RE.sym ')' | ||
qualVarP = modNameP <* RE.sym '.' <* RE.some RE.anySym | ||
conIDP = RE.withMatched $ | ||
RE.psym isUpper | ||
*> RE.many | ||
(RE.psym $ \c -> c == '\'' || c == '_' || isUpper c || isLower c || isDigit c) | ||
modNameP = fmap snd $ RE.withMatched $ conIDP `sepBy1` RE.sym '.' | ||
|
||
|
||
constructNewImportSuggestions | ||
:: ExportsMap -> (Maybe T.Text, NotInScope) -> Maybe [T.Text] -> QualifiedImportStyle -> [ImportSuggestion] | ||
constructNewImportSuggestions exportsMap (qual, thingMissing) notTheseModules qis = nubOrdBy simpleCompareImportSuggestion | ||
|
@@ -1740,6 +1788,11 @@ data NotInScope | |
| NotInScopeThing T.Text | ||
deriving Show | ||
|
||
mapNotInScope :: (T.Text -> T.Text) -> NotInScope -> NotInScope | ||
mapNotInScope f (NotInScopeDataConstructor d) = NotInScopeDataConstructor (f d) | ||
mapNotInScope f (NotInScopeTypeConstructorOrClass d) = NotInScopeTypeConstructorOrClass (f d) | ||
mapNotInScope f (NotInScopeThing d) = NotInScopeThing (f d) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this function used for anything else? If not, we can likely turn it into a local function (e.g. in the where block) and remove the parameter f with the thing it is supposed to do (adding a ".") There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point! I made it local, changed to more speic name |
||
|
||
notInScope :: NotInScope -> T.Text | ||
notInScope (NotInScopeDataConstructor t) = t | ||
notInScope (NotInScopeTypeConstructorOrClass t) = t | ||
|
Uh oh!
There was an error while loading. Please reload this page.