Erroneous Template Haskell splice is still run
Please examine this curiosity:
{-# LANGUAGE TemplateHaskell #-}
{-# OPTIONS_GHC -Wincomplete-patterns -Werror #-}
module Bug where
import Language.Haskell.TH
$( do line <- runIO $ getLine
case line of "hello" -> return [] )
Compiling this program waits for user input. Regardless of what the user enters, compilation then fails with an error (NB: -Werror
) about the incomplete pattern-match.
What I find exceedingly strange (and somewhat dangerous) is that my compile-time code was run, even though it gives rise to a fatal warning. If, for example, I had {-# WARNING launchTheRockets #-}
somewhere and relied on -Werror=warnings-deprecations
, I could have a launched rocket on my hands. No no no.
Even worse, I was able (with some effort) to get GHC to properly go off the rails:
{-# LANGUAGE TemplateHaskell, ExplicitForAll, PolyKinds, TypeApplications #-}
module Bug where
import GHC.Exts
import Language.Haskell.TH
$( let levfun :: forall (r :: RuntimeRep) (a :: TYPE r). a -> ()
levfun = error "e1" -- NB: this, so far, is OK: no levity-polymorphic binder
in levfun (error @Any "e2") -- but this is very naughty: levity-polymorphic argument
`seq` return [] )
This example produces
[1 of 1] Compiling Bug ( Bug.hs, Bug.o, Bug.dyn_o )
ghc: internal error: stg_ap_ppv_ret
(GHC version 8.10.1 for x86_64_apple_darwin)
Please report this as a GHC bug: https://www.haskell.org/ghc/reportabug
Abort trap: 6
The fact that GHC fails to run my compile-time program isn't a surprise, given that which exception to throw depends on the strictness of error @Any "e2"
... but that expression has Any
representation, and so we can't know its strictness. This is why GHC normally disallows levity-polymorphic arguments.
Levity-polymorphism checks and pattern-match completeness checks are both run in the desugarer. Currently, GHC.Tc.Gen.Splice.runMeta'
just desugars an expression and then runs it. Any error or warning messages produced by desugaring are propagated. The problem is that we try to compile and run the expression before examining these messages.
The fix is straightforward: don't do this. Instead, abort if there are any errors or fatal warnings.
Open question: what to do on non-fatal warnings? Should they be reported eagerly, before the splice code is run? Or should they be reported with all the other messages at the end of compilation? Reporting the warnings eagerly might be helpful in debugging. Consider this example:
{-# LANGUAGE TemplateHaskell #-}
{-# OPTIONS_GHC -Wname-shadowing #-}
module Bug where
import GHC.Exts
import Language.Haskell.TH
$( do let x = IntegerL 5
let x = StringL "hello"
return [ SigD (mkName "y") (ConT ''Integer)
, ValD (VarP (mkName "y")) (NormalB (LitE x)) [] ] )
This produces
y :: Integer
y = "hello"
which fails with a type error. However, more helpful would be to learn that the second x
shadows the first -- and I've even asked for this warning to be printed, but it's suppressed by the type error, even though the type error happens in a later stage than the warning.
@adinapoli might be interested, as this all plays into the error-plumbing work he and I are engaging in; it's yet another bug that came to light only through a careful redesign of this mechanism.