Skip to content

Commit a162e81

Browse files
jacgcocreature
authored andcommitted
Defer type errors (#47)
* TEST: Degrade type error to warning It will be upgraded again later, but for the time being we want to see whether the proposed mechanism for deferring type errors works at all. As it turns out the first, most obvious approach, does not work: this is documented in the next commit. A second approach was found that does work, and appears in the commit after the next. This test is failing until the second approach is implemented. * Defer type errors (first approach: FAILED) The idea is to set the `-fdefer-type-errors` and `-fwarn-deferred-type-errors` flags, by setting options programatically inside the `Ghc` monad. Deferral of type errors was not observed with this approach. The (less obvious) approach used in the next commit seems to be more successful. * Defer type errors (second approach: SUCCESS) This approach modifies the `ParsedModule` which is passed to `GHC.typecheckedModule` by hie-core's `typecheckModule`. Type warning deferral is now observed at run time, and the tests pass. * TEST: Reinstate severity of type errors So far, type errors have been deferred and reported as warnings. The next step is to ensure that the deferred type errors are reported as errors rather than warnings, once again. This test fails until the implementation arrives in the next commit. * Upgrade severity of deferred Type Errors after typecheck ... and make the test pass again. * Hide helper functions in local scopes * Stop setting Opt_WarnDeferredTypeErrors ... and the tests still pass, thereby confirming @hsenag's hypothesis that this flag is not needed. * TEST: Check that typed holes are reported as errors * TEST: Downgrade severity of typed holes Error -> Warning This test fails, thereby falsifying the hypothesis that `Opt_DeferTypeErrors` implies `Opt_DeferTypedHoles`. * Defer typed holes ... and pass the failing test. * TEST: Reinstate severity of typed holes ... failing the test until the implementation catches up in the next commit. * Upgrade severity of deferred Typed Holes after typecheck ... and pass the test once again. * TEST: Degrade variable out of scope from Error to Warning ... test fails until next commit. * Defer out of scope variables ... passing the test which was changed in the last commit. * TEST: Reinstate severity of out of scope variables ... failing the test, and forcing the implementation to catch up. * Upgrade severity of deferred out of scope vars after typecheck ... passing the test once again. * Add explicit tests for deferrals * Add IdeOption for deferral switching * Improve documentation of optDefer * Add IdeDefer newtype
1 parent 819bd42 commit a162e81

File tree

5 files changed

+84
-10
lines changed

5 files changed

+84
-10
lines changed

src/Development/IDE/Core/Compile.hs

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -82,19 +82,22 @@ computePackageDeps env pkg = do
8282

8383
-- | Typecheck a single module using the supplied dependencies and packages.
8484
typecheckModule
85-
:: HscEnv
85+
:: IdeDefer
86+
-> HscEnv
8687
-> [TcModuleResult]
8788
-> ParsedModule
8889
-> IO ([FileDiagnostic], Maybe TcModuleResult)
89-
typecheckModule packageState deps pm =
90+
typecheckModule (IdeDefer defer) packageState deps pm =
91+
let demoteIfDefer = if defer then demoteTypeErrorsToWarnings else id
92+
in
9093
fmap (either (, Nothing) (second Just)) $
9194
runGhcEnv packageState $
9295
catchSrcErrors "typecheck" $ do
9396
setupEnv deps
9497
(warnings, tcm) <- withWarnings "typecheck" $ \tweak ->
95-
GHC.typecheckModule pm{pm_mod_summary = tweak $ pm_mod_summary pm}
98+
GHC.typecheckModule $ demoteIfDefer pm{pm_mod_summary = tweak $ pm_mod_summary pm}
9699
tcm2 <- mkTcModuleResult tcm
97-
return (warnings, tcm2)
100+
return (map unDefer warnings, tcm2)
98101

99102
-- | Compile a single type-checked module to a 'CoreModule' value, or
100103
-- provide errors.
@@ -126,8 +129,32 @@ compileModule packageState deps tmr =
126129
(cg_binds tidy)
127130
(mg_safe_haskell desugar)
128131

129-
return (warnings, core)
132+
return (map snd warnings, core)
133+
134+
demoteTypeErrorsToWarnings :: ParsedModule -> ParsedModule
135+
demoteTypeErrorsToWarnings =
136+
(update_pm_mod_summary . update_hspp_opts) demoteTEsToWarns where
137+
138+
demoteTEsToWarns :: DynFlags -> DynFlags
139+
demoteTEsToWarns = (`gopt_set` Opt_DeferTypeErrors)
140+
. (`gopt_set` Opt_DeferTypedHoles)
141+
. (`gopt_set` Opt_DeferOutOfScopeVariables)
142+
143+
update_hspp_opts :: (DynFlags -> DynFlags) -> ModSummary -> ModSummary
144+
update_hspp_opts up ms = ms{ms_hspp_opts = up $ ms_hspp_opts ms}
145+
146+
update_pm_mod_summary :: (ModSummary -> ModSummary) -> ParsedModule -> ParsedModule
147+
update_pm_mod_summary up pm =
148+
pm{pm_mod_summary = up $ pm_mod_summary pm}
149+
150+
unDefer :: (WarnReason, FileDiagnostic) -> FileDiagnostic
151+
unDefer (Reason Opt_WarnDeferredTypeErrors , fd) = upgradeWarningToError fd
152+
unDefer (Reason Opt_WarnTypedHoles , fd) = upgradeWarningToError fd
153+
unDefer (Reason Opt_WarnDeferredOutOfScopeVariables, fd) = upgradeWarningToError fd
154+
unDefer ( _ , fd) = fd
130155

156+
upgradeWarningToError :: FileDiagnostic -> FileDiagnostic
157+
upgradeWarningToError (nfp, fd) = (nfp, fd{_severity = Just DsError})
131158

132159
addRelativeImport :: ParsedModule -> DynFlags -> DynFlags
133160
addRelativeImport modu dflags = dflags

src/Development/IDE/Core/Rules.hs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,8 @@ typeCheckRule =
312312
tms <- uses_ TypeCheck (transitiveModuleDeps deps)
313313
setPriority priorityTypeCheck
314314
packageState <- hscEnv <$> use_ GhcSession file
315-
liftIO $ typecheckModule packageState tms pm
315+
IdeOptions{ optDefer = defer} <- getIdeOptions
316+
liftIO $ typecheckModule defer packageState tms pm
316317

317318

318319
generateCoreRule :: Rules ()

src/Development/IDE/GHC/Warnings.hs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,14 @@ import Development.IDE.GHC.Error
2525
-- https://github.com/ghc/ghc/blob/5f1d949ab9e09b8d95319633854b7959df06eb58/compiler/main/GHC.hs#L623-L640
2626
-- which basically says that log_action is taken from the ModSummary when GHC feels like it.
2727
-- The given argument lets you refresh a ModSummary log_action
28-
withWarnings :: GhcMonad m => T.Text -> ((ModSummary -> ModSummary) -> m a) -> m ([FileDiagnostic], a)
28+
withWarnings :: GhcMonad m => T.Text -> ((ModSummary -> ModSummary) -> m a) -> m ([(WarnReason, FileDiagnostic)], a)
2929
withWarnings diagSource action = do
3030
warnings <- liftIO $ newVar []
3131
oldFlags <- getDynFlags
3232
let newAction :: DynFlags -> WarnReason -> Severity -> SrcSpan -> PprStyle -> SDoc -> IO ()
33-
newAction dynFlags _ _ loc style msg = do
34-
let d = diagFromErrMsg diagSource dynFlags $ mkWarnMsg dynFlags loc (queryQual style) msg
35-
modifyVar_ warnings $ return . (d:)
33+
newAction dynFlags wr _ loc style msg = do
34+
let wr_d = fmap (wr,) $ diagFromErrMsg diagSource dynFlags $ mkWarnMsg dynFlags loc (queryQual style) msg
35+
modifyVar_ warnings $ return . (wr_d:)
3636
setLogAction newAction
3737
res <- action $ \x -> x{ms_hspp_opts = (ms_hspp_opts x){log_action = newAction}}
3838
setLogAction $ log_action oldFlags

src/Development/IDE/Types/Options.hs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
module Development.IDE.Types.Options
88
( IdeOptions(..)
99
, IdeReportProgress(..)
10+
, IdeDefer(..)
1011
, clientSupportsProgress
1112
, IdePkgLocationOptions(..)
1213
, defaultIdeOptions
@@ -44,9 +45,16 @@ data IdeOptions = IdeOptions
4445
-- ^ the ```language to use
4546
, optNewColonConvention :: Bool
4647
-- ^ whether to use new colon convention
48+
, optDefer :: IdeDefer
49+
-- ^ Whether to defer type errors, typed holes and out of scope
50+
-- variables. Deferral allows the IDE to continue to provide
51+
-- features such as diagnostics and go-to-definition, in
52+
-- situations in which they would become unavailable because of
53+
-- the presence of type errors, holes or unbound variables.
4754
}
4855

4956
newtype IdeReportProgress = IdeReportProgress Bool
57+
newtype IdeDefer = IdeDefer Bool
5058

5159
clientSupportsProgress :: LSP.ClientCapabilities -> IdeReportProgress
5260
clientSupportsProgress caps = IdeReportProgress $ fromMaybe False $
@@ -63,6 +71,7 @@ defaultIdeOptions session = IdeOptions
6371
,optReportProgress = IdeReportProgress False
6472
,optLanguageSyntax = "haskell"
6573
,optNewColonConvention = False
74+
,optDefer = IdeDefer True
6675
}
6776

6877

test/exe/Main.hs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,43 @@ diagnosticTests = testGroup "diagnostics"
8585
, [(DsError, (2, 14), "Couldn't match type '[Char]' with 'Int'")]
8686
)
8787
]
88+
, testSession "typed hole" $ do
89+
let content = T.unlines
90+
[ "module Testing where"
91+
, "foo :: Int -> String"
92+
, "foo a = _ a"
93+
]
94+
_ <- openDoc' "Testing.hs" "haskell" content
95+
expectDiagnostics
96+
[ ( "Testing.hs"
97+
, [(DsError, (2, 8), "Found hole: _ :: Int -> String")]
98+
)
99+
]
100+
101+
, testGroup "deferral" $
102+
let sourceA a = T.unlines
103+
[ "module A where"
104+
, "a :: Int"
105+
, "a = " <> a]
106+
sourceB = T.unlines
107+
[ "module B where"
108+
, "import A"
109+
, "b :: Float"
110+
, "b = True"]
111+
bMessage = "Couldn't match expected type 'Float' with actual type 'Bool'"
112+
expectedDs aMessage =
113+
[ ("A.hs", [(DsError, (2,4), aMessage)])
114+
, ("B.hs", [(DsError, (3,4), bMessage)])]
115+
deferralTest title binding message = testSession title $ do
116+
_ <- openDoc' "A.hs" "haskell" $ sourceA binding
117+
_ <- openDoc' "B.hs" "haskell" sourceB
118+
expectDiagnostics $ expectedDs message
119+
in
120+
[ deferralTest "type error" "True" "Couldn't match expected type"
121+
, deferralTest "typed hole" "_" "Found hole"
122+
, deferralTest "out of scope var" "unbound" "Variable not in scope"
123+
]
124+
88125
, testSession "remove required module" $ do
89126
let contentA = T.unlines [ "module ModuleA where" ]
90127
docA <- openDoc' "ModuleA.hs" "haskell" contentA

0 commit comments

Comments
 (0)