From ebd8918b7c50ae51921664e24fac0de4376ffcf9 Mon Sep 17 00:00:00 2001 From: Oleg Grenrus <oleg.grenrus@iki.fi> Date: Mon, 27 Mar 2023 20:55:15 +0300 Subject: [PATCH] Allow generation of TTH syntax with TH In other words allow generation of typed splices and brackets with Untyped Template Haskell. That is useful in cases where a library is build with TTH in mind, but we still want to generate some auxiliary declarations, where TTH cannot help us, but untyped TH can. Such example is e.g. `staged-sop` which works with TTH, but we would like to derive `Generic` declarations with TH. An alternative approach is to use `unsafeCodeCoerce`, but then the derived `Generic` instances would be type-checked only at use sites, i.e. much later. Also `-ddump-splices` output is quite ugly: user-written instances would use TTH brackets, not `unsafeCodeCoerce`. This commit doesn't allow generating of untyped template splices and brackets with untyped TH, as I don't know why one would want to do that (instead of merging the splices, e.g.) --- compiler/GHC/ThToHs.hs | 4 ++++ libraries/template-haskell/Language/Haskell/TH/Lib.hs | 2 +- .../Language/Haskell/TH/Lib/Internal.hs | 6 ++++++ libraries/template-haskell/Language/Haskell/TH/Ppr.hs | 2 ++ .../template-haskell/Language/Haskell/TH/Syntax.hs | 2 ++ libraries/template-haskell/changelog.md | 4 ++++ testsuite/tests/th/TH_typed1.hs | 7 +++++++ testsuite/tests/th/TH_typed1.stdout | 1 + testsuite/tests/th/TH_typed2.hs | 7 +++++++ testsuite/tests/th/TH_typed2.stdout | 1 + testsuite/tests/th/TH_typed3.hs | 10 ++++++++++ testsuite/tests/th/TH_typed3.stderr | 9 +++++++++ testsuite/tests/th/TH_typed4.hs | 7 +++++++ testsuite/tests/th/TH_typed4.stderr | 10 ++++++++++ testsuite/tests/th/TH_typed5.hs | 10 ++++++++++ testsuite/tests/th/TH_typed5.stdout | 2 ++ testsuite/tests/th/all.T | 5 +++++ 17 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 testsuite/tests/th/TH_typed1.hs create mode 100644 testsuite/tests/th/TH_typed1.stdout create mode 100644 testsuite/tests/th/TH_typed2.hs create mode 100644 testsuite/tests/th/TH_typed2.stdout create mode 100644 testsuite/tests/th/TH_typed3.hs create mode 100644 testsuite/tests/th/TH_typed3.stderr create mode 100644 testsuite/tests/th/TH_typed4.hs create mode 100644 testsuite/tests/th/TH_typed4.stderr create mode 100644 testsuite/tests/th/TH_typed5.hs create mode 100644 testsuite/tests/th/TH_typed5.stdout diff --git a/compiler/GHC/ThToHs.hs b/compiler/GHC/ThToHs.hs index 724f15f60210..39da7e0c5155 100644 --- a/compiler/GHC/ThToHs.hs +++ b/compiler/GHC/ThToHs.hs @@ -1157,6 +1157,10 @@ cvtl e = wrapLA (cvt e) (L noSrcSpanA (DotFieldOcc noAnn (L noSrcSpanA (FieldLabelString (fsLit f))))) } cvt (ProjectionE xs) = return $ HsProjection noAnn $ fmap (L noSrcSpanA . DotFieldOcc noAnn . L noSrcSpanA . FieldLabelString . fsLit) xs + cvt (TypedSpliceE e) = do { e' <- parenthesizeHsExpr appPrec <$> cvtl e + ; return $ HsTypedSplice (noAnn, noAnn) e' } + cvt (TypedBracketE e) = do { e' <- cvtl e + ; return $ HsTypedBracket noAnn e' } {- | #16895 Ensure an infix expression's operator is a variable/constructor. Consider this example: diff --git a/libraries/template-haskell/Language/Haskell/TH/Lib.hs b/libraries/template-haskell/Language/Haskell/TH/Lib.hs index b52de5b0d39a..d6107f9dac0b 100644 --- a/libraries/template-haskell/Language/Haskell/TH/Lib.hs +++ b/libraries/template-haskell/Language/Haskell/TH/Lib.hs @@ -44,7 +44,7 @@ module Language.Haskell.TH.Lib ( appE, appTypeE, uInfixE, parensE, infixE, infixApp, sectionL, sectionR, lamE, lam1E, lamCaseE, lamCasesE, tupE, unboxedTupE, unboxedSumE, condE, multiIfE, letE, caseE, appsE, listE, sigE, recConE, recUpdE, stringE, - fieldExp, getFieldE, projectionE, + fieldExp, getFieldE, projectionE, typedSpliceE, typedBracketE, -- **** Ranges fromE, fromThenE, fromToE, fromThenToE, diff --git a/libraries/template-haskell/Language/Haskell/TH/Lib/Internal.hs b/libraries/template-haskell/Language/Haskell/TH/Lib/Internal.hs index 8d0cf5adde74..eeeff941fa29 100644 --- a/libraries/template-haskell/Language/Haskell/TH/Lib/Internal.hs +++ b/libraries/template-haskell/Language/Haskell/TH/Lib/Internal.hs @@ -380,6 +380,12 @@ getFieldE e f = do projectionE :: Quote m => NonEmpty String -> m Exp projectionE xs = pure (ProjectionE xs) +typedSpliceE :: Quote m => m Exp -> m Exp +typedSpliceE = fmap TypedSpliceE + +typedBracketE :: Quote m => m Exp -> m Exp +typedBracketE = fmap TypedBracketE + -- ** 'arithSeqE' Shortcuts fromE :: Quote m => m Exp -> m Exp fromE x = do { a <- x; pure (ArithSeqE (FromR a)) } diff --git a/libraries/template-haskell/Language/Haskell/TH/Ppr.hs b/libraries/template-haskell/Language/Haskell/TH/Ppr.hs index 034d2687b3b3..dbe3cb85dfd5 100644 --- a/libraries/template-haskell/Language/Haskell/TH/Ppr.hs +++ b/libraries/template-haskell/Language/Haskell/TH/Ppr.hs @@ -232,6 +232,8 @@ pprExp _ (LabelE s) = text "#" <> text s pprExp _ (ImplicitParamVarE n) = text ('?' : n) pprExp _ (GetFieldE e f) = pprExp appPrec e <> text ('.': f) pprExp _ (ProjectionE xs) = parens $ hcat $ map ((char '.'<>) . text) $ toList xs +pprExp _ (TypedBracketE e) = text "[||" <> ppr e <> text "||]" +pprExp _ (TypedSpliceE e) = text "$$" <> pprExp appPrec e pprFields :: [(Name,Exp)] -> Doc pprFields = sep . punctuate comma . map (\(s,e) -> pprName' Applied s <+> equals <+> ppr e) diff --git a/libraries/template-haskell/Language/Haskell/TH/Syntax.hs b/libraries/template-haskell/Language/Haskell/TH/Syntax.hs index 6d96d414c636..8398bafd5371 100644 --- a/libraries/template-haskell/Language/Haskell/TH/Syntax.hs +++ b/libraries/template-haskell/Language/Haskell/TH/Syntax.hs @@ -2385,6 +2385,8 @@ data Exp | ImplicitParamVarE String -- ^ @{ ?x }@ ( Implicit parameter ) | GetFieldE Exp String -- ^ @{ exp.field }@ ( Overloaded Record Dot ) | ProjectionE (NonEmpty String) -- ^ @(.x)@ or @(.x.y)@ (Record projections) + | TypedBracketE Exp -- ^ @[|| e ||]@ + | TypedSpliceE Exp -- ^ @$$e@ deriving( Show, Eq, Ord, Data, Generic ) type FieldExp = (Name,Exp) diff --git a/libraries/template-haskell/changelog.md b/libraries/template-haskell/changelog.md index f6ed4d6b5f11..5a62f6e12408 100644 --- a/libraries/template-haskell/changelog.md +++ b/libraries/template-haskell/changelog.md @@ -8,6 +8,10 @@ This change enables TemplateHaskell support for `DuplicateRecordFields`. + * Add support for generating typed splices and brackets in untyped Template Haskell + Introduces `typedSpliceE :: Quote m => m Exp -> m Exp` and + `typedBracketE :: Quote m => m Exp -> m Exp` + ## 2.20.0.0 * The `Ppr.pprInfixT` function has gained a `Precedence` argument. diff --git a/testsuite/tests/th/TH_typed1.hs b/testsuite/tests/th/TH_typed1.hs new file mode 100644 index 000000000000..f50131f88ba6 --- /dev/null +++ b/testsuite/tests/th/TH_typed1.hs @@ -0,0 +1,7 @@ +{-# LANGUAGE TemplateHaskell #-} + +module Main where + +import Language.Haskell.TH + +main = print $$( $(typedBracketE [| 'x' |]) ) diff --git a/testsuite/tests/th/TH_typed1.stdout b/testsuite/tests/th/TH_typed1.stdout new file mode 100644 index 000000000000..44cf16f8da03 --- /dev/null +++ b/testsuite/tests/th/TH_typed1.stdout @@ -0,0 +1 @@ +'x' diff --git a/testsuite/tests/th/TH_typed2.hs b/testsuite/tests/th/TH_typed2.hs new file mode 100644 index 000000000000..67f32766ceb3 --- /dev/null +++ b/testsuite/tests/th/TH_typed2.hs @@ -0,0 +1,7 @@ +{-# LANGUAGE TemplateHaskell #-} + +module Main where + +import Language.Haskell.TH + +main = print $( typedSpliceE $ typedBracketE [| 'y' |] ) diff --git a/testsuite/tests/th/TH_typed2.stdout b/testsuite/tests/th/TH_typed2.stdout new file mode 100644 index 000000000000..5b548bb8b21d --- /dev/null +++ b/testsuite/tests/th/TH_typed2.stdout @@ -0,0 +1 @@ +'y' diff --git a/testsuite/tests/th/TH_typed3.hs b/testsuite/tests/th/TH_typed3.hs new file mode 100644 index 000000000000..b9477b27f006 --- /dev/null +++ b/testsuite/tests/th/TH_typed3.hs @@ -0,0 +1,10 @@ +{-# LANGUAGE TemplateHaskell #-} + +module Main where + +import Language.Haskell.TH + +-- test parenthesis around splice +main = do + print $( typedSpliceE $ typedBracketE [| 'z' |] ) + print $( typedSpliceE $ appE [| id |] (typedBracketE [| 'z' |]) ) diff --git a/testsuite/tests/th/TH_typed3.stderr b/testsuite/tests/th/TH_typed3.stderr new file mode 100644 index 000000000000..bf5d8ec7c981 --- /dev/null +++ b/testsuite/tests/th/TH_typed3.stderr @@ -0,0 +1,9 @@ +TH_typed3.hs:9:12-53: Splicing expression + typedSpliceE $ typedBracketE [| 'z' |] ======> $$[|| 'z' ||] +TH_typed3.hs:10:12-69: Splicing expression + typedSpliceE $ appE [| id |] (typedBracketE [| 'z' |]) + ======> + $$(id [|| 'z' ||]) +TH_typed3.hs:9:12-53: Splicing expression [|| 'z' ||] ======> 'z' +TH_typed3.hs:10:12-69: Splicing expression + id [|| 'z' ||] ======> 'z' diff --git a/testsuite/tests/th/TH_typed4.hs b/testsuite/tests/th/TH_typed4.hs new file mode 100644 index 000000000000..622b20bd2a82 --- /dev/null +++ b/testsuite/tests/th/TH_typed4.hs @@ -0,0 +1,7 @@ +{-# LANGUAGE TemplateHaskell #-} + +module Main where + +import Language.Haskell.TH + +main = print $$( $$(unsafeCodeCoerce $ typedBracketE $ litE $ charL 'a' :: Code Q (Code Q Char)) ) diff --git a/testsuite/tests/th/TH_typed4.stderr b/testsuite/tests/th/TH_typed4.stderr new file mode 100644 index 000000000000..9852f09b42af --- /dev/null +++ b/testsuite/tests/th/TH_typed4.stderr @@ -0,0 +1,10 @@ +TH_typed4.hs:7:20-96: Splicing expression + unsafeCodeCoerce $ typedBracketE $ litE $ charL 'a' :: + Code Q (Code Q Char) + ======> + [|| 'a' ||] +TH_typed4.hs:7:16-98: Splicing expression + $$(unsafeCodeCoerce $ typedBracketE $ litE $ charL 'a' :: + Code Q (Code Q Char)) + ======> + 'a' diff --git a/testsuite/tests/th/TH_typed5.hs b/testsuite/tests/th/TH_typed5.hs new file mode 100644 index 000000000000..e04b129c5070 --- /dev/null +++ b/testsuite/tests/th/TH_typed5.hs @@ -0,0 +1,10 @@ +{-# LANGUAGE TemplateHaskell #-} + +module Main where + +import Language.Haskell.TH +import Language.Haskell.TH.Ppr + +main = do + putStrLn =<< fmap pprint (typedSpliceE $ typedBracketE [| 'z' |]) + putStrLn =<< fmap pprint (typedSpliceE $ appE [| id |] (typedBracketE [| 'z' |])) diff --git a/testsuite/tests/th/TH_typed5.stdout b/testsuite/tests/th/TH_typed5.stdout new file mode 100644 index 000000000000..62698d2161de --- /dev/null +++ b/testsuite/tests/th/TH_typed5.stdout @@ -0,0 +1,2 @@ +$$[||'z'||] +$$(GHC.Base.id [||'z'||]) diff --git a/testsuite/tests/th/all.T b/testsuite/tests/th/all.T index 20586f17b843..60f02a9c2e21 100644 --- a/testsuite/tests/th/all.T +++ b/testsuite/tests/th/all.T @@ -559,3 +559,8 @@ test('T22819', normal, compile, ['-v0']) test('TH_fun_par', normal, compile, ['']) test('T23036', normal, compile, ['-v0 -ddump-splices -dsuppress-uniques']) test('T23203', normal, compile, ['-v0 -ddump-splices -dsuppress-uniques']) +test('TH_typed1', normal, compile_and_run, ['']) +test('TH_typed2', normal, compile_and_run, ['']) +test('TH_typed3', normal, compile, ['-v0 -ddump-splices -dsuppress-uniques']) +test('TH_typed4', normal, compile, ['-v0 -ddump-splices -dsuppress-uniques']) +test('TH_typed5', normal, compile_and_run, ['']) -- GitLab