Skip to content

Commit 4af6ade

Browse files
authored
Merge pull request #98 from zliu41/extensions
Make applyRefactoring take GHC extensions
2 parents 396e6c5 + 1cd6bae commit 4af6ade

File tree

5 files changed

+174
-70
lines changed

5 files changed

+174
-70
lines changed

apply-refact.cabal

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ library
3535
, refact >= 0.2
3636
, ghc-exactprint >= 0.6.3.3
3737
, ghc >= 8.6
38+
, ghc-boot-th
3839
, containers >= 0.6.0.1 && < 0.7
3940
, extra >= 1.7.3
4041
, syb >= 0.7.1
@@ -43,6 +44,9 @@ library
4344
, filemanip >= 0.3.6.3 && < 0.4
4445
, unix-compat >= 0.5.2
4546
, directory >= 1.3
47+
if impl(ghc >= 8.8)
48+
build-depends:
49+
ghc-lib-parser-ex >= 8.10.0.16
4650
hs-source-dirs: src
4751
default-language: Haskell2010
4852

@@ -76,6 +80,9 @@ executable refactor
7680
, unix-compat
7781
, filepath
7882
, transformers
83+
if impl(ghc >= 8.8)
84+
build-depends:
85+
ghc-lib-parser-ex >= 8.10.0.16
7986

8087
Test-Suite test
8188
type: exitcode-stdio-1.0
@@ -110,3 +117,6 @@ Test-Suite test
110117
, filepath
111118
, silently
112119
, transformers
120+
if impl(ghc >= 8.8)
121+
build-depends:
122+
ghc-lib-parser-ex >= 8.10.0.16

cabal.project

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
packages: *.cabal
2+
constraints: ghc-lib-parser-ex -auto +no-ghc-lib

src/Refact/Apply.hs

Lines changed: 112 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,24 @@
1+
{-# LANGUAGE CPP #-}
12
{-# LANGUAGE TupleSections #-}
23

34
module Refact.Apply
4-
( runRefactoring
5-
, applyRefactorings
5+
( applyRefactorings
6+
, runRefactoring
7+
, parseExtensions
68
) where
79

8-
import Language.Haskell.GHC.ExactPrint.Parsers (parseModuleWithOptions)
10+
import Data.List
11+
import GHC.LanguageExtensions.Type (Extension(..))
912
import Refact.Fixity
1013
import Refact.Internal
1114
import Refact.Types
1215

16+
#if __GLASGOW_HASKELL__ <= 806
17+
import DynFlags (FlagSpec(flagSpecFlag, flagSpecName), xFlags)
18+
#else
19+
import Language.Haskell.GhclibParserEx.GHC.Driver.Session (impliedXFlags, readExtension)
20+
#endif
21+
1322
-- | Apply a set of refactorings as supplied by hlint
1423
applyRefactorings
1524
:: Maybe (Int, Int)
@@ -25,8 +34,106 @@ applyRefactorings
2534
-- prior to it which has an overlapping source span and is not filtered out.
2635
-> FilePath
2736
-- ^ Target file
37+
-> ([Extension], [Extension])
38+
-- ^ Enabled and disabled extensions. These are in addition to the @LANGUAGE@ pragmas
39+
-- in the target file. When they conflict with the @LANGUAGE@ pragmas, pragmas win.
2840
-> IO String
29-
applyRefactorings optionsPos inp file = do
41+
applyRefactorings optionsPos inp file exts = do
3042
(as, m) <- either (onError "apply") (uncurry applyFixities)
31-
=<< parseModuleWithOptions rigidLayout file
43+
=<< parseModuleWithArgs exts file
3244
apply optionsPos False ((mempty,) <$> inp) file Silent as m
45+
46+
-- | Parse the input into (enabled extensions, disabled extensions, invalid input).
47+
-- Implied extensions are automatically added. For example, @FunctionalDependencies@
48+
-- implies @MultiParamTypeClasses@, and @RebindableSyntax@ implies @NoImplicitPrelude@.
49+
--
50+
-- The input is processed from left to right. An extension (e.g., @StarIsType@)
51+
-- may be overridden later (e.g., by @NoStarIsType@).
52+
--
53+
-- Extensions that appear earlier in the input will appear later in the output.
54+
-- Implied extensions appear in the end. If an extension occurs multiple times in the input,
55+
-- the last one is used.
56+
--
57+
-- >>> parseExtensions ["GADTs", "RebindableSyntax", "StarIsType", "GADTs", "InvalidExtension", "NoStarIsType"]
58+
-- ([GADTs, RebindableSyntax, GADTSyntax, MonoLocalBinds], [StarIsType, ImplicitPrelude], ["InvalidExtension"])
59+
parseExtensions :: [String] -> ([Extension], [Extension], [String])
60+
parseExtensions = addImplied . foldl' f mempty
61+
where
62+
f :: ([Extension], [Extension], [String]) -> String -> ([Extension], [Extension], [String])
63+
f (ys, ns, is) ('N' : 'o' : s) | Just ext <- readExtension s =
64+
(delete ext ys, ext : delete ext ns, is)
65+
f (ys, ns, is) s | Just ext <- readExtension s =
66+
(ext : delete ext ys, delete ext ns, is)
67+
f (ys, ns, is) s = (ys, ns, s : is)
68+
69+
addImplied :: ([Extension], [Extension], [String]) -> ([Extension], [Extension], [String])
70+
addImplied (ys, ns, is) = (ys ++ impliedOn, ns ++ impliedOff, is)
71+
where
72+
impliedOn = [b | ext <- ys, (a, True, b) <- impliedXFlags, a == ext]
73+
impliedOff = [b | ext <- ys, (a, False, b) <- impliedXFlags, a == ext]
74+
75+
#if __GLASGOW_HASKELL__ <= 806
76+
readExtension :: String -> Maybe Extension
77+
readExtension s = flagSpecFlag <$> find ((== s) . flagSpecName) xFlags
78+
79+
-- | Copied from "Language.Haskell.GhclibParserEx.GHC.Driver.Session", in order to
80+
-- support GHC 8.6
81+
impliedXFlags :: [(Extension, Bool, Extension)]
82+
impliedXFlags
83+
-- See Note [Updating flag description in the User's Guide]
84+
= [ (RankNTypes, True, ExplicitForAll)
85+
, (QuantifiedConstraints, True, ExplicitForAll)
86+
, (ScopedTypeVariables, True, ExplicitForAll)
87+
, (LiberalTypeSynonyms, True, ExplicitForAll)
88+
, (ExistentialQuantification, True, ExplicitForAll)
89+
, (FlexibleInstances, True, TypeSynonymInstances)
90+
, (FunctionalDependencies, True, MultiParamTypeClasses)
91+
, (MultiParamTypeClasses, True, ConstrainedClassMethods) -- c.f. #7854
92+
, (TypeFamilyDependencies, True, TypeFamilies)
93+
94+
, (RebindableSyntax, False, ImplicitPrelude) -- NB: turn off!
95+
96+
, (DerivingVia, True, DerivingStrategies)
97+
98+
, (GADTs, True, GADTSyntax)
99+
, (GADTs, True, MonoLocalBinds)
100+
, (TypeFamilies, True, MonoLocalBinds)
101+
102+
, (TypeFamilies, True, KindSignatures) -- Type families use kind signatures
103+
, (PolyKinds, True, KindSignatures) -- Ditto polymorphic kinds
104+
105+
-- TypeInType is now just a synonym for a couple of other extensions.
106+
, (TypeInType, True, DataKinds)
107+
, (TypeInType, True, PolyKinds)
108+
, (TypeInType, True, KindSignatures)
109+
110+
-- AutoDeriveTypeable is not very useful without DeriveDataTypeable
111+
, (AutoDeriveTypeable, True, DeriveDataTypeable)
112+
113+
-- We turn this on so that we can export associated type
114+
-- type synonyms in subordinates (e.g. MyClass(type AssocType))
115+
, (TypeFamilies, True, ExplicitNamespaces)
116+
, (TypeOperators, True, ExplicitNamespaces)
117+
118+
, (ImpredicativeTypes, True, RankNTypes)
119+
120+
-- Record wild-cards implies field disambiguation
121+
-- Otherwise if you write (C {..}) you may well get
122+
-- stuff like " 'a' not in scope ", which is a bit silly
123+
-- if the compiler has just filled in field 'a' of constructor 'C'
124+
, (RecordWildCards, True, DisambiguateRecordFields)
125+
126+
, (ParallelArrays, True, ParallelListComp)
127+
128+
, (JavaScriptFFI, True, InterruptibleFFI)
129+
130+
, (DeriveTraversable, True, DeriveFunctor)
131+
, (DeriveTraversable, True, DeriveFoldable)
132+
133+
-- Duplicate record fields require field disambiguation
134+
, (DuplicateRecordFields, True, DisambiguateRecordFields)
135+
136+
, (TemplateHaskell, True, TemplateHaskellQuotes)
137+
, (Strict, True, StrictData)
138+
]
139+
#endif

src/Refact/Internal.hs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
module Refact.Internal
1313
( apply
1414
, runRefactoring
15+
, addExtensionsToFlags
16+
, parseModuleWithArgs
1517

1618
-- * Support for runPipe in the main process
1719
, Verbosity(..)
@@ -45,9 +47,16 @@ import qualified Data.Map as Map
4547
import Data.Maybe
4648
import Data.List
4749
import Data.Ord
50+
import DynFlags hiding (initDynFlags)
51+
import HeaderInfo (getOptions)
52+
import HscTypes (handleSourceError)
4853
import GHC.IO.Exception (IOErrorType(..))
54+
import GHC.LanguageExtensions.Type (Extension(..))
55+
import Panic (handleGhcException)
56+
import StringBuffer (stringToStringBuffer)
4957
import System.IO
5058
import System.IO.Error (mkIOError)
59+
import System.IO.Extra
5160

5261
import Debug.Trace
5362

@@ -649,3 +658,33 @@ doRename ss = everywhere (mkT rename)
649658
(s, n) = (GHC.occNameString v, GHC.occNameSpace v)
650659
s' = fromMaybe s (lookup s ss)
651660
-}
661+
662+
addExtensionsToFlags
663+
:: [Extension] -> [Extension] -> FilePath -> DynFlags
664+
-> IO (Either String DynFlags)
665+
addExtensionsToFlags es ds fp flags = catchErrors $ do
666+
(stringToStringBuffer -> buf) <- readFileUTF8' fp
667+
let opts = getOptions flags buf fp
668+
withExts = flip (foldl' xopt_unset) ds
669+
. flip (foldl' xopt_set) es
670+
$ flags
671+
(withPragmas, _, _) <- parseDynamicFilePragma withExts opts
672+
pure . Right $ withPragmas `gopt_set` Opt_KeepRawTokenStream
673+
where
674+
catchErrors = handleGhcException (pure . Left . show)
675+
. handleSourceError (pure . Left . show)
676+
677+
parseModuleWithArgs
678+
:: ([Extension], [Extension])
679+
-> FilePath
680+
-> IO (Either Errors (Anns, GHC.ParsedSource))
681+
parseModuleWithArgs (es, ds) fp = ghcWrapper $ do
682+
initFlags <- initDynFlags fp
683+
eflags <- liftIO $ addExtensionsToFlags es ds fp initFlags
684+
case eflags of
685+
-- TODO: report error properly.
686+
Left err -> pure . Left $ mkErr initFlags (UnhelpfulSpan mempty) err
687+
Right flags -> do
688+
_ <- GHC.setSessionDynFlags flags
689+
res <- parseModuleApiAnnsWithCppInternal defaultCppOptions flags fp
690+
pure $ postParseTransform res rigidLayout

src/Refact/Run.hs

Lines changed: 11 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,24 @@
11
{-# LANGUAGE RecordWildCards #-}
22
{-# LANGUAGE ScopedTypeVariables #-}
3-
{-# LANGUAGE ViewPatterns #-}
43

54
module Refact.Run (refactMain, runPipe) where
65

7-
import Language.Haskell.GHC.ExactPrint
8-
import qualified Language.Haskell.GHC.ExactPrint.Parsers as EP
9-
( defaultCppOptions
10-
, ghcWrapper
11-
, initDynFlags
12-
, parseModuleApiAnnsWithCppInternal
13-
, postParseTransform
14-
)
156
import Language.Haskell.GHC.ExactPrint.Utils
167

8+
import Refact.Apply (parseExtensions)
179
import qualified Refact.Types as R
1810
import Refact.Types hiding (SrcSpan)
1911
import Refact.Fixity
20-
import Refact.Internal (Errors, Verbosity(..), apply, onError, mkErr, rigidLayout)
12+
import Refact.Internal
13+
( Verbosity(..)
14+
, apply
15+
, onError
16+
, parseModuleWithArgs
17+
)
2118
import Refact.Options (Options(..), optionsWithHelp)
2219

23-
import DynFlags
24-
import HeaderInfo (getOptions)
25-
import HscTypes (handleSourceError)
26-
import qualified GHC (setSessionDynFlags, ParsedSource)
27-
import Panic (handleGhcException)
28-
import SrcLoc
29-
import StringBuffer (stringToStringBuffer)
30-
import GHC.LanguageExtensions.Type (Extension(..))
31-
3220
import Control.Monad
33-
import Control.Monad.IO.Class (MonadIO(..))
3421
import Data.List hiding (find)
35-
import qualified Data.List as List
3622
import Data.Maybe
3723
import Data.Version
3824
import Options.Applicative
@@ -87,49 +73,6 @@ filterFilename = do
8773
| "Setup.hs" `isInfixOf` x = False
8874
| otherwise = True
8975

90-
-- | Parse the input into a list of enabled extensions and a list of disabled extensions.
91-
parseExtensions :: [String] -> ([Extension], [Extension])
92-
parseExtensions = foldl' f ([], [])
93-
where
94-
f :: ([Extension], [Extension]) -> String -> ([Extension], [Extension])
95-
f (ys, ns) ('N' : 'o' : s) | Just ext <- readExtension s =
96-
(delete ext ys, ext : delete ext ns)
97-
f (ys, ns) s | Just ext <- readExtension s =
98-
(ext : delete ext ys, delete ext ns)
99-
-- ignore unknown extensions
100-
f (ys, ns) _ = (ys, ns)
101-
102-
readExtension :: String -> Maybe Extension
103-
readExtension s = flagSpecFlag <$> List.find ((== s) . flagSpecName) xFlags
104-
105-
addExtensionsToFlags
106-
:: [Extension] -> [Extension] -> FilePath -> DynFlags
107-
-> IO (Either String DynFlags)
108-
addExtensionsToFlags es ds fp flags = catchErrors $ do
109-
(stringToStringBuffer -> buf) <- readFileUTF8' fp
110-
let opts = getOptions flags buf fp
111-
withExts = flip (foldl' xopt_unset) ds
112-
. flip (foldl' xopt_set) es
113-
$ flags
114-
(withPragmas, _, _) <- parseDynamicFilePragma withExts opts
115-
pure . Right $ withPragmas `gopt_set` Opt_KeepRawTokenStream
116-
where
117-
catchErrors = handleGhcException (pure . Left . show)
118-
. handleSourceError (pure . Left . show)
119-
120-
parseModuleWithArgs :: [String] -> FilePath -> IO (Either Errors (Anns, GHC.ParsedSource))
121-
parseModuleWithArgs exts fp = EP.ghcWrapper $ do
122-
let (es, ds) = parseExtensions exts
123-
initFlags <- EP.initDynFlags fp
124-
eflags <- liftIO $ addExtensionsToFlags es ds fp initFlags
125-
case eflags of
126-
-- TODO: report error properly.
127-
Left err -> pure . Left $ mkErr initFlags (UnhelpfulSpan mempty) err
128-
Right flags -> do
129-
_ <- GHC.setSessionDynFlags flags
130-
res <- EP.parseModuleApiAnnsWithCppInternal EP.defaultCppOptions flags fp
131-
return $ EP.postParseTransform res rigidLayout
132-
13376
runPipe :: Options -> FilePath -> IO ()
13477
runPipe Options{..} file = do
13578
let verb = optionsVerbosity
@@ -141,8 +84,11 @@ runPipe Options{..} file = do
14184

14285
output <- if null inp then readFileUTF8' file else do
14386
when (verb == Loud) (traceM "Parsing module")
87+
let (enabledExts, disabledExts, invalidExts) = parseExtensions optionsLanguage
88+
unless (null invalidExts) . when (verb >= Normal) . putStrLn $
89+
"Invalid extensions: " ++ intercalate ", " invalidExts
14490
(as, m) <- either (onError "runPipe") (uncurry applyFixities)
145-
=<< parseModuleWithArgs optionsLanguage file
91+
=<< parseModuleWithArgs (enabledExts, disabledExts) file
14692
when optionsDebug (putStrLn (showAnnData as 0 m))
14793
apply optionsPos optionsStep inp file verb as m
14894

0 commit comments

Comments
 (0)