Add -fkeep-going to make compiler continue despite errors (#15424)
authorKari Pahula <kaol@iki.fi>
Fri, 20 Sep 2019 07:11:53 +0000 (10:11 +0300)
committerMarge Bot <ben+marge-bot@smart-cactus.org>
Mon, 23 Sep 2019 21:54:42 +0000 (17:54 -0400)
Add a new optional failure handling for upsweep which continues
the compilation on other modules if any of them has errors.

15 files changed:
compiler/main/DynFlags.hs
compiler/main/GhcMake.hs
docs/users_guide/8.10.1-notes.rst
docs/users_guide/using.rst
testsuite/tests/ghci/prog019/A.hs [new file with mode: 0644]
testsuite/tests/ghci/prog019/B.hs [new file with mode: 0644]
testsuite/tests/ghci/prog019/B1.hs [new file with mode: 0644]
testsuite/tests/ghci/prog019/B2.hs [new file with mode: 0644]
testsuite/tests/ghci/prog019/C.hs [new file with mode: 0644]
testsuite/tests/ghci/prog019/D.hs [new file with mode: 0644]
testsuite/tests/ghci/prog019/E.hs [new file with mode: 0644]
testsuite/tests/ghci/prog019/prog019.T [new file with mode: 0644]
testsuite/tests/ghci/prog019/prog019.script [new file with mode: 0644]
testsuite/tests/ghci/prog019/prog019.stderr [new file with mode: 0644]
testsuite/tests/ghci/prog019/prog019.stdout [new file with mode: 0644]

index c2d0322..5bd8cb8 100644 (file)
@@ -653,6 +653,7 @@ data GeneralFlag
    -- response file and as such breaking apart.
    | Opt_SingleLibFolder
    | Opt_KeepCAFs
+   | Opt_KeepGoing
 
    -- output style opts
    | Opt_ErrorSpans -- Include full span info in error messages,
@@ -4206,6 +4207,7 @@ fFlagsDeps = [
   flagSpec "ignore-interface-pragmas"         Opt_IgnoreInterfacePragmas,
   flagGhciSpec "implicit-import-qualified"    Opt_ImplicitImportQualified,
   flagSpec "irrefutable-tuples"               Opt_IrrefutableTuples,
+  flagSpec "keep-going"                       Opt_KeepGoing,
   flagSpec "kill-absence"                     Opt_KillAbsence,
   flagSpec "kill-one-shot"                    Opt_KillOneShot,
   flagSpec "late-dmd-anal"                    Opt_LateDmdAnal,
index 81311c1..6e44a86 100644 (file)
@@ -1357,6 +1357,25 @@ upsweep mHscMessage old_hpt stable_mods cleanup sccs = do
  where
   done_holes = emptyUniqSet
 
+  keep_going this_mods old_hpt done mods mod_index nmods uids_to_check done_holes = do
+    let sum_deps ms (AcyclicSCC mod) =
+          if any (flip elem . map (unLoc . snd) $ ms_imps mod) ms
+            then ms_mod_name mod:ms
+            else ms
+        sum_deps ms _ = ms
+        dep_closure = foldl' sum_deps this_mods mods
+        dropped_ms = drop (length this_mods) (reverse dep_closure)
+        prunable (AcyclicSCC mod) = elem (ms_mod_name mod) dep_closure
+        prunable _ = False
+        mods' = filter (not . prunable) mods
+        nmods' = nmods - length dropped_ms
+
+    when (not $ null dropped_ms) $ do
+        dflags <- getSessionDynFlags
+        liftIO $ fatalErrorMsg dflags (keepGoingPruneErr dropped_ms)
+    (_, done') <- upsweep' old_hpt done mods' (mod_index+1) nmods' uids_to_check done_holes
+    return (Failed, done')
+
   upsweep'
     :: GhcMonad m
     => HomePackageTable
@@ -1374,10 +1393,13 @@ upsweep mHscMessage old_hpt stable_mods cleanup sccs = do
         return (Succeeded, done)
 
   upsweep' _old_hpt done
-     (CyclicSCC ms:_) _ _ _ _
+     (CyclicSCC ms:mods) mod_index nmods uids_to_check done_holes
    = do dflags <- getSessionDynFlags
         liftIO $ fatalErrorMsg dflags (cyclicModuleErr ms)
-        return (Failed, done)
+        if gopt Opt_KeepGoing dflags
+          then keep_going (map ms_mod_name ms) old_hpt done mods mod_index nmods
+                          uids_to_check done_holes
+          else return (Failed, done)
 
   upsweep' old_hpt done
      (AcyclicSCC mod:mods) mod_index nmods uids_to_check done_holes
@@ -1426,7 +1448,12 @@ upsweep mHscMessage old_hpt stable_mods cleanup sccs = do
                  return (Just mod_info)
 
         case mb_mod_info of
-          Nothing -> return (Failed, done)
+          Nothing -> do
+                dflags <- getSessionDynFlags
+                if gopt Opt_KeepGoing dflags
+                  then keep_going [ms_mod_name mod] old_hpt done mods mod_index nmods
+                                  uids_to_check done_holes
+                  else return (Failed, done)
           Just mod_info -> do
                 let this_mod = ms_mod_name mod
 
@@ -2652,6 +2679,12 @@ multiRootsErr dflags summs@(summ1:_)
     mod = ms_mod summ1
     files = map (expectJust "checkDup" . ml_hs_file . ms_location) summs
 
+keepGoingPruneErr :: [ModuleName] -> SDoc
+keepGoingPruneErr ms
+  = vcat (( text "-fkeep-going in use, removing the following" <+>
+            text "dependencies and continuing:"):
+          map (nest 6 . ppr) ms )
+
 cyclicModuleErr :: [ModSummary] -> SDoc
 -- From a strongly connected component we find
 -- a single cycle to report
index 8d70c7b..2ca9ce5 100644 (file)
@@ -119,6 +119,9 @@ Compiler
   the sample start event contains a timestamp of when the census occurred.
   The retainer profiling events are emitted using the standard events.
 
+- Add new flag :ghc-flag:`-fkeep-going` which makes the compiler
+  continue as far as it can despite errors.
+
 GHCi
 ~~~~
 
index d1c4538..8462a87 100644 (file)
@@ -1041,6 +1041,18 @@ messages and in GHCi:
     start at zero. This choice was made to follow existing convention
     (i.e. this is how Emacs does it).
 
+.. ghc-flag:: -fkeep-going
+    :shortdesc: Continue compilation as far as possible on errors
+    :type: dynamic
+    :category: verbosity
+
+    :since: 8.10.1
+
+    Causes GHC to continue the compilation if a module has an error.
+    Any reverse dependencies are pruned immediately and the whole
+    compilation is still flagged as an error.  This option has no
+    effect if parallel compilation (:ghc-flag:`-j[⟨n⟩]`) is in use.
+
 .. ghc-flag:: -freverse-errors
     :shortdesc: Output errors in reverse order
     :type: dynamic
diff --git a/testsuite/tests/ghci/prog019/A.hs b/testsuite/tests/ghci/prog019/A.hs
new file mode 100644 (file)
index 0000000..4126fd0
--- /dev/null
@@ -0,0 +1,8 @@
+-- Test for #15424
+
+module A where
+
+import B
+import C
+import D
+import E
diff --git a/testsuite/tests/ghci/prog019/B.hs b/testsuite/tests/ghci/prog019/B.hs
new file mode 100644 (file)
index 0000000..96a9cd4
--- /dev/null
@@ -0,0 +1,3 @@
+module B where
+
+import B1
diff --git a/testsuite/tests/ghci/prog019/B1.hs b/testsuite/tests/ghci/prog019/B1.hs
new file mode 100644 (file)
index 0000000..093c3b3
--- /dev/null
@@ -0,0 +1,3 @@
+module B1 where
+
+import B2
diff --git a/testsuite/tests/ghci/prog019/B2.hs b/testsuite/tests/ghci/prog019/B2.hs
new file mode 100644 (file)
index 0000000..ad504c3
--- /dev/null
@@ -0,0 +1,3 @@
+module B2 where
+
+asdf
diff --git a/testsuite/tests/ghci/prog019/C.hs b/testsuite/tests/ghci/prog019/C.hs
new file mode 100644 (file)
index 0000000..695cb14
--- /dev/null
@@ -0,0 +1,4 @@
+module C where
+
+foo :: Int
+foo = 1
diff --git a/testsuite/tests/ghci/prog019/D.hs b/testsuite/tests/ghci/prog019/D.hs
new file mode 100644 (file)
index 0000000..4edf1e1
--- /dev/null
@@ -0,0 +1,3 @@
+module D where
+
+roses are red
diff --git a/testsuite/tests/ghci/prog019/E.hs b/testsuite/tests/ghci/prog019/E.hs
new file mode 100644 (file)
index 0000000..114aeb9
--- /dev/null
@@ -0,0 +1,4 @@
+module E where
+
+bar :: String
+bar = "abc"
diff --git a/testsuite/tests/ghci/prog019/prog019.T b/testsuite/tests/ghci/prog019/prog019.T
new file mode 100644 (file)
index 0000000..e5bc51f
--- /dev/null
@@ -0,0 +1,4 @@
+test('prog019',
+     [extra_hc_opts('-fkeep-going'),
+      extra_files(['A.hs', 'B.hs', 'B1.hs', 'B2.hs', 'C.hs', 'D.hs', 'E.hs'])],
+     ghci_script, ['prog019.script'])
diff --git a/testsuite/tests/ghci/prog019/prog019.script b/testsuite/tests/ghci/prog019/prog019.script
new file mode 100644 (file)
index 0000000..e4fcd34
--- /dev/null
@@ -0,0 +1,4 @@
+:load A
+bar
+:module C
+foo
diff --git a/testsuite/tests/ghci/prog019/prog019.stderr b/testsuite/tests/ghci/prog019/prog019.stderr
new file mode 100644 (file)
index 0000000..b915e3d
--- /dev/null
@@ -0,0 +1,12 @@
+
+B2.hs:3:1: error:
+    Parse error: module header, import declaration
+    or top-level declaration expected.
+-fkeep-going in use, removing the following dependencies and continuing:
+      B1
+      B
+      A
+
+D.hs:3:1: error:
+    Parse error: module header, import declaration
+    or top-level declaration expected.
diff --git a/testsuite/tests/ghci/prog019/prog019.stdout b/testsuite/tests/ghci/prog019/prog019.stdout
new file mode 100644 (file)
index 0000000..64d6362
--- /dev/null
@@ -0,0 +1,2 @@
+"abc"
+1