Brandon Chinn pushed to branch wip/interpolated-strings at Glasgow Haskell Compiler / GHC
Commits:
-
d114d7dd
by Brandon Chinn at 2025-04-30T08:45:58-07:00
29 changed files:
- compiler/GHC/Builtin/Names.hs
- compiler/GHC/Driver/Flags.hs
- compiler/GHC/Hs/Expr.hs
- compiler/GHC/Hs/Syn/Type.hs
- compiler/GHC/HsToCore/Expr.hs
- compiler/GHC/HsToCore/Quote.hs
- compiler/GHC/HsToCore/Ticks.hs
- compiler/GHC/Iface/Ext/Ast.hs
- compiler/GHC/Parser.y
- compiler/GHC/Parser/Lexer.x
- compiler/GHC/Parser/String.hs
- compiler/GHC/Rename/Expr.hs
- compiler/GHC/Rename/String.hs
- compiler/GHC/Tc/Types/Origin.hs
- compiler/GHC/Tc/Zonk/Type.hs
- compiler/GHC/ThToHs.hs
- compiler/Language/Haskell/Syntax/Expr.hs
- libraries/ghc-boot-th/GHC/Boot/TH/Ppr.hs
- libraries/ghc-experimental/src/Data/String/Interpolate/Experimental.hs
- libraries/ghc-internal/src/GHC/Internal/Data/String/Interpolate.hs
- libraries/ghc-internal/src/GHC/Internal/LanguageExtensions.hs
- libraries/ghc-internal/src/GHC/Internal/TH/Lib.hs
- libraries/ghc-internal/src/GHC/Internal/TH/Syntax.hs
- testsuite/tests/driver/T4437.hs
- testsuite/tests/parser/should_run/StringInterpolationOverloaded.hs
- + testsuite/tests/parser/should_run/StringInterpolationQualified.hs
- + testsuite/tests/parser/should_run/StringInterpolationQualified.stdout
- + testsuite/tests/parser/should_run/StringInterpolationQualified_SQL.hs
- testsuite/tests/parser/should_run/all.T
Changes:
... | ... | @@ -286,7 +286,7 @@ basicKnownKeyNames |
286 | 286 | fromStringName,
|
287 | 287 | |
288 | 288 | -- Interpolated strings
|
289 | - fromBuilderName, toBuilderName, interpolateName,
|
|
289 | + interpolateName,
|
|
290 | 290 | |
291 | 291 | -- Enum stuff
|
292 | 292 | enumFromName, enumFromThenName,
|
... | ... | @@ -1123,9 +1123,7 @@ minusName = varQual gHC_INTERNAL_NUM (fsLit "-") minusClassOpK |
1123 | 1123 | negateName = varQual gHC_INTERNAL_NUM (fsLit "negate") negateClassOpKey
|
1124 | 1124 | |
1125 | 1125 | -- Module GHC.Internal.Data.String.Interpolate
|
1126 | -toBuilderName, fromBuilderName, interpolateName :: Name
|
|
1127 | -toBuilderName = varQual gHC_INTERNAL_DATA_STRING_INTERPOLATE (fsLit "toBuilder") toBuilderKey
|
|
1128 | -fromBuilderName = varQual gHC_INTERNAL_DATA_STRING_INTERPOLATE (fsLit "fromBuilder") fromBuilderKey
|
|
1126 | +interpolateName :: Name
|
|
1129 | 1127 | interpolateName = varQual gHC_INTERNAL_DATA_STRING_INTERPOLATE (fsLit "interpolate") interpolateKey
|
1130 | 1128 | |
1131 | 1129 | ---------------------------------
|
... | ... | @@ -2502,9 +2500,7 @@ proxyHashKey :: Unique |
2502 | 2500 | proxyHashKey = mkPreludeMiscIdUnique 502
|
2503 | 2501 | |
2504 | 2502 | -- String interpolation
|
2505 | -toBuilderKey, fromBuilderKey, interpolateKey :: Unique
|
|
2506 | -toBuilderKey = mkPreludeMiscIdUnique 574
|
|
2507 | -fromBuilderKey = mkPreludeMiscIdUnique 575
|
|
2503 | +interpolateKey :: Unique
|
|
2508 | 2504 | interpolateKey = mkPreludeMiscIdUnique 576
|
2509 | 2505 | |
2510 | 2506 | ---------------- Template Haskell -------------------
|
... | ... | @@ -260,6 +260,7 @@ extensionName = \case |
260 | 260 | LangExt.ListTuplePuns -> "ListTuplePuns"
|
261 | 261 | LangExt.MultilineStrings -> "MultilineStrings"
|
262 | 262 | LangExt.StringInterpolation -> "StringInterpolation"
|
263 | + LangExt.QualifiedLiterals -> "QualifiedLiterals"
|
|
263 | 264 | LangExt.ExplicitLevelImports -> "ExplicitLevelImports"
|
264 | 265 | LangExt.ImplicitStagePersistence -> "ImplicitStagePersistence"
|
265 | 266 |
... | ... | @@ -868,8 +868,8 @@ ppr_expr (HsOverLabel s l) = case ghcPass @p of |
868 | 868 | ppr_expr (HsLit _ lit) = ppr lit
|
869 | 869 | ppr_expr (HsOverLit _ lit) = ppr lit
|
870 | 870 | |
871 | -ppr_expr (HsInterString _ strType parts) =
|
|
872 | - char 's' <> delim <> hcat (map pprInterPart parts) <> delim
|
|
871 | +ppr_expr (HsInterString _ mQualMod strType parts) =
|
|
872 | + prefix <> delim <> hcat (map pprInterPart parts) <> delim
|
|
873 | 873 | where
|
874 | 874 | pprInterPart = \case
|
875 | 875 | HsInterStringRaw st s ->
|
... | ... | @@ -880,6 +880,11 @@ ppr_expr (HsInterString _ strType parts) = |
880 | 880 | (HsStringTypeMulti, NoSourceText) -> pprHsStringMulti' (unpackFS s)
|
881 | 881 | HsInterStringExpr _ expr -> text "${" <> ppr_lexpr expr <> text "}"
|
882 | 882 | |
883 | + prefix =
|
|
884 | + case mQualMod of
|
|
885 | + Nothing -> char 's'
|
|
886 | + Just qualMod -> ppr qualMod <> char '.'
|
|
887 | + |
|
883 | 888 | delim =
|
884 | 889 | case strType of
|
885 | 890 | HsStringTypeSingle -> char '"'
|
... | ... | @@ -106,7 +106,7 @@ hsExprType (HsOverLabel v _) = dataConCantHappen v |
106 | 106 | hsExprType (HsIPVar v _) = dataConCantHappen v
|
107 | 107 | hsExprType (HsOverLit _ lit) = overLitType lit
|
108 | 108 | hsExprType (HsLit _ lit) = hsLitType lit
|
109 | -hsExprType (HsInterString _ _ _) = stringTy -- TODO: handle OverloadedStrings
|
|
109 | +hsExprType (HsInterString _ _ _ _) = stringTy -- TODO: handle OverloadedStrings + QualifiedLiterals
|
|
110 | 110 | hsExprType (HsLam _ _ (MG { mg_ext = match_group })) = matchGroupTcType match_group
|
111 | 111 | hsExprType (HsApp _ f _) = funResultTy $ lhsExprType f
|
112 | 112 | hsExprType (HsAppType x f _) = piResultTy (lhsExprType f) x
|
... | ... | @@ -554,7 +554,7 @@ dsExpr (HsOverLabel x _) = dataConCantHappen x |
554 | 554 | dsExpr (OpApp x _ _ _) = dataConCantHappen x
|
555 | 555 | dsExpr (SectionL x _ _) = dataConCantHappen x
|
556 | 556 | dsExpr (SectionR x _ _) = dataConCantHappen x
|
557 | -dsExpr (HsInterString x _ _) = dataConCantHappen x
|
|
557 | +dsExpr (HsInterString x _ _ _) = dataConCantHappen x
|
|
558 | 558 | |
559 | 559 | |
560 | 560 | {- *********************************************************************
|
... | ... | @@ -1576,11 +1576,11 @@ repE (HsOverLabel _ s) = repOverLabel s |
1576 | 1576 | -- HsOverlit can definitely occur
|
1577 | 1577 | repE (HsOverLit _ l) = do { a <- repOverloadedLiteral l; repLit a }
|
1578 | 1578 | repE (HsLit _ l) = do { a <- repLiteral l; repLit a }
|
1579 | -repE (HsInterString _ _ parts) = do
|
|
1579 | +repE (HsInterString _ mQualMod _ parts) = do
|
|
1580 | 1580 | parts' <- forM parts $ \case
|
1581 | 1581 | HsInterStringRaw _ s -> repInterStringRaw =<< coreStringLit s
|
1582 | 1582 | HsInterStringExpr _ e -> repInterStringExp =<< repLE e
|
1583 | - repInterString =<< coreListM interStringPartName parts'
|
|
1583 | + repInterString mQualMod =<< coreListM interStringPartName parts'
|
|
1584 | 1584 | repE (HsLam _ LamSingle (MG { mg_alts = L _ [m] })) = repLambda m
|
1585 | 1585 | repE e@(HsLam _ LamSingle (MG { mg_alts = L _ _ })) = pprPanic "repE: HsLam with multiple alternatives" (ppr e)
|
1586 | 1586 | repE (HsLam _ LamCase (MG { mg_alts = L _ ms }))
|
... | ... | @@ -2565,16 +2565,17 @@ repMDoE = repDoBlock mdoEName |
2565 | 2565 | |
2566 | 2566 | repDoBlock :: Name -> Maybe ModuleName -> Core [(M TH.Stmt)] -> MetaM (Core (M TH.Exp))
|
2567 | 2567 | repDoBlock doName maybeModName (MkC ss) = do
|
2568 | - MkC coreModName <- coreModNameM
|
|
2569 | - rep2 doName [coreModName, ss]
|
|
2570 | - where
|
|
2571 | - coreModNameM :: MetaM (Core (Maybe TH.ModName))
|
|
2572 | - coreModNameM = case maybeModName of
|
|
2573 | - Just m -> do
|
|
2574 | - MkC s <- coreStringLit (moduleNameFS m)
|
|
2575 | - mName <- rep2_nw mkModNameName [s]
|
|
2576 | - coreJust modNameTyConName mName
|
|
2577 | - _ -> coreNothing modNameTyConName
|
|
2568 | + MkC mCoreModName <- repMaybeModName maybeModName
|
|
2569 | + rep2 doName [mCoreModName, ss]
|
|
2570 | + |
|
2571 | +repMaybeModName :: Maybe ModuleName -> MetaM (Core (Maybe TH.ModName))
|
|
2572 | +repMaybeModName = \case
|
|
2573 | + Just m -> do
|
|
2574 | + MkC s <- coreStringLit (moduleNameFS m)
|
|
2575 | + mName <- rep2_nw mkModNameName [s]
|
|
2576 | + coreJust modNameTyConName mName
|
|
2577 | + Nothing ->
|
|
2578 | + coreNothing modNameTyConName
|
|
2578 | 2579 | |
2579 | 2580 | repComp :: Core [(M TH.Stmt)] -> MetaM (Core (M TH.Exp))
|
2580 | 2581 | repComp (MkC ss) = rep2 compEName [ss]
|
... | ... | @@ -2663,8 +2664,10 @@ repClause :: Core [(M TH.Pat)] -> Core (M TH.Body) -> Core [(M TH.Dec)] -> MetaM |
2663 | 2664 | repClause (MkC ps) (MkC bod) (MkC ds) = rep2 clauseName [ps, bod, ds]
|
2664 | 2665 | |
2665 | 2666 | -------------- Interpolated strings -----------------------------
|
2666 | -repInterString :: Core [M TH.InterStringPart] -> MetaM (Core (M TH.Exp))
|
|
2667 | -repInterString (MkC parts) = rep2 interStringEName [parts]
|
|
2667 | +repInterString :: Maybe ModuleName -> Core [M TH.InterStringPart] -> MetaM (Core (M TH.Exp))
|
|
2668 | +repInterString mQualMod (MkC parts) = do
|
|
2669 | + MkC mCoreModName <- repMaybeModName mQualMod
|
|
2670 | + rep2 interStringEName [mCoreModName, parts]
|
|
2668 | 2671 | |
2669 | 2672 | repInterStringRaw :: Core String -> MetaM (Core (M TH.InterStringPart))
|
2670 | 2673 | repInterStringRaw (MkC s) = rep2 interStringRawName [s]
|
... | ... | @@ -477,11 +477,12 @@ addTickHsExpr e@(HsIPVar {}) = return e |
477 | 477 | addTickHsExpr e@(HsOverLit {}) = return e
|
478 | 478 | addTickHsExpr e@(HsOverLabel{}) = return e
|
479 | 479 | addTickHsExpr e@(HsLit {}) = return e
|
480 | -addTickHsExpr (HsInterString x ty parts) = do
|
|
480 | +addTickHsExpr (HsInterString x mQualMod ty parts) = do
|
|
481 | + -- TODO: should we add ticks for qualified literals?
|
|
481 | 482 | parts' <- forM parts $ \case
|
482 | 483 | part@(HsInterStringRaw {}) -> return part
|
483 | 484 | HsInterStringExpr x e -> HsInterStringExpr x <$> addTickLHsExpr e
|
484 | - return $ HsInterString x ty parts'
|
|
485 | + return $ HsInterString x mQualMod ty parts'
|
|
485 | 486 | addTickHsExpr e@(HsEmbTy {}) = return e
|
486 | 487 | addTickHsExpr e@(HsHole {}) = return e
|
487 | 488 | addTickHsExpr e@(HsQual {}) = return e
|
... | ... | @@ -1198,7 +1198,7 @@ instance HiePass p => ToHie (LocatedA (HsExpr (GhcPass p))) where |
1198 | 1198 | [ toHie (L mspan o)
|
1199 | 1199 | ]
|
1200 | 1200 | HsLit _ _ -> []
|
1201 | - HsInterString _ _ parts ->
|
|
1201 | + HsInterString _ _ _ parts ->
|
|
1202 | 1202 | [ toHie expr
|
1203 | 1203 | | HsInterStringExpr _ expr <- parts
|
1204 | 1204 | ]
|
... | ... | @@ -736,9 +736,9 @@ are the most common patterns, rewritten as regular expressions for clarity: |
736 | 736 | CHAR { L _ (ITchar _ _) }
|
737 | 737 | STRING { L _ (ITstring _ StringTypeSingle _) }
|
738 | 738 | STRING_MULTI { L _ (ITstring _ StringTypeMulti _) }
|
739 | - STRING_INTER_BEGIN { L _ (ITstringInterBegin StringTypeSingle) }
|
|
739 | + STRING_INTER_BEGIN { L _ (ITstringInterBegin _ StringTypeSingle) }
|
|
740 | 740 | STRING_INTER_END { L _ (ITstringInterEnd StringTypeSingle) }
|
741 | - STRING_INTER_MULTI_BEGIN { L _ (ITstringInterBegin StringTypeMulti) }
|
|
741 | + STRING_INTER_MULTI_BEGIN { L _ (ITstringInterBegin _ StringTypeMulti) }
|
|
742 | 742 | STRING_INTER_MULTI_END { L _ (ITstringInterEnd StringTypeMulti) }
|
743 | 743 | STRING_INTER_RAW { L _ (ITstringInterRaw _ _) }
|
744 | 744 | STRING_INTER_EXP_OPEN { L _ ITstringInterExpOpen }
|
... | ... | @@ -4365,7 +4365,7 @@ processStringInter :: |
4365 | 4365 | processStringInter strType tokBegin parts tokEnd = do
|
4366 | 4366 | parts' <- mapM mkInterStringPartPV $ processRawLexedStrings parts
|
4367 | 4367 | ams1 (L (comb2 tokBegin tokEnd) ()) $
|
4368 | - HsInterString noExtField strType parts'
|
|
4368 | + HsInterString noExtField mQualMod strType parts'
|
|
4369 | 4369 | where
|
4370 | 4370 | processRawLexedStrings ::
|
4371 | 4371 | [Either (SourceText, RawLexedString) ECP] ->
|
... | ... | @@ -4379,6 +4379,10 @@ processStringInter strType tokBegin parts tokEnd = do |
4379 | 4379 | Left (src, s) -> pure $ HsInterStringRaw src (fsLit s)
|
4380 | 4380 | Right (ECP e) -> HsInterStringExpr noExtField <$> runPV e
|
4381 | 4381 | |
4382 | + mQualMod =
|
|
4383 | + let L _ (ITstringInterBegin mMod _) = tokBegin
|
|
4384 | + in mkModuleNameFS <$> mMod
|
|
4385 | + |
|
4382 | 4386 | -- Utilities for combining source spans
|
4383 | 4387 | comb2 :: (HasLoc a, HasLoc b) => a -> b -> SrcSpan
|
4384 | 4388 | comb2 !a !b = combineHasLocs a b
|
... | ... | @@ -632,6 +632,7 @@ $unigraphic / { isSmartQuote } { smart_quote_error } |
632 | 632 | -- See Note [Parsing interpolated strings] and Note [Lexing interpolated strings]
|
633 | 633 | <0,string_inter> {
|
634 | 634 | s \" / { ifExtension StringInterpolationBit } { string_inter_begin }
|
635 | + @qual \" / { ifExtension QualifiedLiteralsBit } { string_inter_begin }
|
|
635 | 636 | -- TODO(bchinn): interpolated multiline strings
|
636 | 637 | }
|
637 | 638 | |
... | ... | @@ -930,7 +931,9 @@ data Token |
930 | 931 | | ITstring SourceText StringType FastString -- Note [Literal source text] in "GHC.Types.SourceText"
|
931 | 932 | |
932 | 933 | -- See Note [Parsing interpolated strings]
|
933 | - | ITstringInterBegin StringType
|
|
934 | + | ITstringInterBegin
|
|
935 | + (Maybe FastString) -- Module name, if using QualifiedLiterals
|
|
936 | + StringType -- Single-line or multiline interpolated string?
|
|
934 | 937 | | ITstringInterRaw SourceText RawLexedString -- Note [Literal source text] in "GHC.Types.SourceText"
|
935 | 938 | | ITstringInterExpOpen
|
936 | 939 | | ITstringInterExpClose
|
... | ... | @@ -2196,9 +2199,14 @@ tok_string span buf len _buf2 = do |
2196 | 2199 | endsInHash = currentChar (offsetBytes (len - 1) buf) == '#'
|
2197 | 2200 | |
2198 | 2201 | string_inter_begin :: Action
|
2199 | -string_inter_begin span _ _ _ = do
|
|
2202 | +string_inter_begin span buf len _ = do
|
|
2200 | 2203 | pushLexState string_inter_content
|
2201 | - pure $ L span (ITstringInterBegin StringTypeSingle)
|
|
2204 | + let mQualMod
|
|
2205 | + | len == 2 = Nothing
|
|
2206 | + | otherwise =
|
|
2207 | + let (qualMod, _) = splitQualName buf len False
|
|
2208 | + in Just qualMod
|
|
2209 | + pure $ L span (ITstringInterBegin mQualMod StringTypeSingle)
|
|
2202 | 2210 | |
2203 | 2211 | string_inter_content_action :: Action
|
2204 | 2212 | string_inter_content_action span_init buf_init _ _ = go $ AI (psSpanStart span_init) buf_init
|
... | ... | @@ -2835,6 +2843,7 @@ data ExtBits |
2835 | 2843 | | RequiredTypeArgumentsBit
|
2836 | 2844 | | MultilineStringsBit
|
2837 | 2845 | | StringInterpolationBit
|
2846 | + | QualifiedLiteralsBit
|
|
2838 | 2847 | | LevelImportsBit
|
2839 | 2848 | |
2840 | 2849 | -- Flags that are updated once parsing starts
|
... | ... | @@ -2920,6 +2929,7 @@ mkParserOpts extensionFlags diag_opts |
2920 | 2929 | .|. RequiredTypeArgumentsBit `xoptBit` LangExt.RequiredTypeArguments
|
2921 | 2930 | .|. MultilineStringsBit `xoptBit` LangExt.MultilineStrings
|
2922 | 2931 | .|. StringInterpolationBit `xoptBit` LangExt.StringInterpolation
|
2932 | + .|. QualifiedLiteralsBit `xoptBit` LangExt.QualifiedLiterals
|
|
2923 | 2933 | .|. LevelImportsBit `xoptBit` LangExt.ExplicitLevelImports
|
2924 | 2934 | optBits =
|
2925 | 2935 | HaddockBit `setBitIf` isHaddock
|
... | ... | @@ -339,19 +339,19 @@ Interpolated strings are parsed in the following manner: |
339 | 339 | |
340 | 340 | and outputs the following tokens:
|
341 | 341 | |
342 | - [ ITstringInterBegin src StringTypeSingle
|
|
342 | + [ ITstringInterBegin Nothing StringTypeSingle
|
|
343 | 343 | , ITstringInterRaw src "Hello "
|
344 | - , ITstringInterExpOpen src
|
|
344 | + , ITstringInterExpOpen
|
|
345 | 345 | , ITqvarid ("Text.toUpper", "name")
|
346 | 346 | , ITvarid "name"
|
347 | - , ITstringInterExpClose src
|
|
347 | + , ITstringInterExpClose
|
|
348 | 348 | , ITstringInterRaw src "!"
|
349 | - , ITstringInterEnd src StringTypeSingle
|
|
349 | + , ITstringInterEnd StringTypeSingle
|
|
350 | 350 | ]
|
351 | 351 | |
352 | 352 | 2. The parser will then parse the tokens into the following HsExpr:
|
353 | 353 | |
354 | - HsInterString ext
|
|
354 | + HsInterString ext Nothing StringTypeSingle
|
|
355 | 355 | [ HsInterStringRaw ext "Hello "
|
356 | 356 | , HsInterStringExp ext $
|
357 | 357 | HsApp ext
|
... | ... | @@ -385,9 +385,9 @@ rnExpr (HsLit x lit) |
385 | 385 | rnExpr (HsOverLit x lit)
|
386 | 386 | = rnOverLit x lit
|
387 | 387 | |
388 | -rnExpr (HsInterString _ strType parts) = do
|
|
388 | +rnExpr (HsInterString _ mQualMod strType parts) = do
|
|
389 | 389 | (parts', fvs1) <- unzip <$> mapM rnInterStringPart parts
|
390 | - (expr, fvs2) <- rewriteInterString strType parts'
|
|
390 | + (expr, fvs2) <- rewriteInterString mQualMod strType parts'
|
|
391 | 391 | pure (expr, plusFVs fvs1 `plusFV` fvs2)
|
392 | 392 | where
|
393 | 393 | rnInterStringPart = \case
|
1 | 1 | {-# LANGUAGE LambdaCase #-}
|
2 | +{-# LANGUAGE MultiWayIf #-}
|
|
3 | +{-# LANGUAGE RecordWildCards #-}
|
|
2 | 4 | |
3 | 5 | module GHC.Rename.String (
|
4 | 6 | rewriteInterString,
|
... | ... | @@ -7,24 +9,24 @@ module GHC.Rename.String ( |
7 | 9 | import GHC.Prelude
|
8 | 10 | |
9 | 11 | import GHC.Builtin.Names (
|
10 | - fromBuilderName,
|
|
11 | 12 | interpolateName,
|
12 | - mappendName,
|
|
13 | - memptyName,
|
|
14 | - toBuilderName,
|
|
13 | + mconcatName,
|
|
15 | 14 | )
|
16 | 15 | import GHC.Builtin.Types (stringTyConName)
|
17 | 16 | import GHC.Data.FastString (fsLit, unpackFS)
|
18 | 17 | import GHC.Hs
|
19 | 18 | import qualified GHC.LanguageExtensions as LangExt
|
19 | +import GHC.Rename.Env (lookupOccRn)
|
|
20 | 20 | import GHC.Rename.Pat (rnOverLit)
|
21 | +import GHC.Tc.Errors.Types (WhatLooking (WL_None))
|
|
21 | 22 | import GHC.Tc.Utils.Monad
|
23 | +import GHC.Types.Name (Name)
|
|
24 | +import GHC.Types.Name.Occurrence (mkVarOcc)
|
|
25 | +import GHC.Types.Name.Reader (mkRdrQual)
|
|
22 | 26 | import GHC.Types.Name.Set (FreeVars, emptyFVs, plusFVs)
|
23 | 27 | import GHC.Types.SourceText (SourceText (..))
|
24 | 28 | import GHC.Types.SrcLoc (unLoc)
|
25 | 29 | |
26 | -import qualified Data.List.NonEmpty as NE
|
|
27 | - |
|
28 | 30 | {- Note [Desugaring interpolated strings]
|
29 | 31 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
30 | 32 | |
... | ... | @@ -34,7 +36,7 @@ Cross-references: |
34 | 36 | |
35 | 37 | Interpolated strings are represented with the following HsExpr tree:
|
36 | 38 | |
37 | - HsInterString ext
|
|
39 | + HsInterString ext mQualMod strType
|
|
38 | 40 | [ HsInterStringRaw ext "Hello "
|
39 | 41 | , HsInterStringExp ext $
|
40 | 42 | HsApp ext
|
... | ... | @@ -45,12 +47,19 @@ Interpolated strings are represented with the following HsExpr tree: |
45 | 47 | |
46 | 48 | We'll expand this during the renamer phase into the equivalent of:
|
47 | 49 | |
48 | - import GHC.Internal.Data.String.Interpolate
|
|
50 | + mconcat
|
|
51 | + [ fromString "Hello "
|
|
52 | + , interpolate (Text.toUpper name)
|
|
53 | + , fromString "!"
|
|
54 | + ]
|
|
49 | 55 | |
50 | - fromBuilder $
|
|
51 | - toBuilder "Hello "
|
|
52 | - <> interpolate (Text.toUpper name)
|
|
53 | - <> toBuilder "!"
|
|
56 | +If using QualifiedLiterals (mQualMod is Just), expand to:
|
|
57 | + |
|
58 | + ModName.fromParts
|
|
59 | + [ ModName.fromString "Hello "
|
|
60 | + , ModName.interpolate (Text.toUpper name)
|
|
61 | + , ModName.fromString "!"
|
|
62 | + ]
|
|
54 | 63 | |
55 | 64 | We're doing this in the renamer phase so that the expanded expression
|
56 | 65 | can be typechecked as usual, without any additional work.
|
... | ... | @@ -61,42 +70,77 @@ can be typechecked as usual, without any additional work. |
61 | 70 | -- necessary.
|
62 | 71 | --
|
63 | 72 | -- TODO(bchinn): allow -XRebindableSyntax -- lookupSyntaxName
|
64 | -rewriteInterString :: HsStringType -> [HsInterStringPart GhcRn] -> RnM (HsExpr GhcRn, FreeVars)
|
|
65 | -rewriteInterString strType parts = do
|
|
73 | +rewriteInterString ::
|
|
74 | + Maybe ModuleName
|
|
75 | + -> HsStringType
|
|
76 | + -> [HsInterStringPart GhcRn]
|
|
77 | + -> RnM (HsExpr GhcRn, FreeVars)
|
|
78 | +rewriteInterString mQualMod strType parts = do
|
|
66 | 79 | overloaded <- xoptM LangExt.OverloadedStrings
|
67 | - (parts', fvs) <- unzip <$> mapM (rewritePart overloaded) parts
|
|
80 | + mQualNames <- traverse lookupQualifiedLiteralStringsNames mQualMod
|
|
81 | + rewriteInterStringImpl overloaded mQualNames strType parts
|
|
82 | + |
|
83 | +rewriteInterStringImpl ::
|
|
84 | + Bool
|
|
85 | + -> Maybe QualifiedLiteralStringsNames
|
|
86 | + -> HsStringType
|
|
87 | + -> [HsInterStringPart GhcRn]
|
|
88 | + -> RnM (HsExpr GhcRn, FreeVars)
|
|
89 | +rewriteInterStringImpl overloaded mQualNames strType parts = do
|
|
90 | + (parts', fvs) <- unzip <$> mapM rewritePart parts
|
|
68 | 91 | let expr =
|
69 | - (if overloaded then id else addSig) . nlHsApp (nlHsVar fromBuilderName) $
|
|
70 | - maybe (nlHsVar memptyName) (foldr1 appendParts) (NE.nonEmpty parts')
|
|
92 | + addSig
|
|
93 | + . (nlHsApp $ nlHsVar $ maybe mconcatName qualFromParts mQualNames)
|
|
94 | + $ noLocA (ExplicitList noExtField parts')
|
|
71 | 95 | pure (unLoc expr, plusFVs fvs)
|
72 | 96 | where
|
73 | - appendParts l r = nlHsApps mappendName [l, r]
|
|
74 | - rewritePart overloaded = \case
|
|
75 | - HsInterStringRaw _ s -> do
|
|
76 | - (lit, fvs) <- mkStringLit overloaded s
|
|
77 | - pure (nlHsApps toBuilderName [lit], fvs)
|
|
78 | - HsInterStringExpr _ e ->
|
|
79 | - pure (nlHsApps interpolateName [e], emptyFVs)
|
|
80 | - |
|
81 | - -- Add ":: String" to the given expression
|
|
82 | - addSig e =
|
|
83 | - noLocA . ExprWithTySig noExtField e $
|
|
84 | - HsWC
|
|
85 | - { hswc_ext = []
|
|
86 | - , hswc_body =
|
|
87 | - noLocA
|
|
88 | - HsSig
|
|
89 | - { sig_ext = noExtField
|
|
90 | - , sig_bndrs = HsOuterImplicit []
|
|
91 | - , sig_body = nlHsTyVar NotPromoted stringTyConName
|
|
92 | - }
|
|
93 | - }
|
|
94 | - |
|
95 | - mkStringLit overloaded s = do
|
|
97 | + rewritePart = \case
|
|
98 | + HsInterStringRaw _ s -> mkStringLit s
|
|
99 | + HsInterStringExpr _ e -> do
|
|
100 | + let interpolateName' = maybe interpolateName qualInterpolate mQualNames
|
|
101 | + pure (nlHsApp (nlHsVar interpolateName') e, emptyFVs)
|
|
102 | + |
|
103 | + addSig e
|
|
104 | + | Just _ <- mQualNames = e
|
|
105 | + | overloaded = e
|
|
106 | + | otherwise =
|
|
107 | + -- explicitly add ":: String" if not overloaded
|
|
108 | + noLocA . ExprWithTySig noExtField e $
|
|
109 | + HsWC
|
|
110 | + { hswc_ext = []
|
|
111 | + , hswc_body =
|
|
112 | + noLocA
|
|
113 | + HsSig
|
|
114 | + { sig_ext = noExtField
|
|
115 | + , sig_bndrs = HsOuterImplicit []
|
|
116 | + , sig_body = nlHsTyVar NotPromoted stringTyConName
|
|
117 | + }
|
|
118 | + }
|
|
119 | + |
|
120 | + mkStringLit s = do
|
|
96 | 121 | let src = SourceText $ fsLit $ "\"" ++ unpackFS s ++ "\""
|
97 | - if overloaded
|
|
98 | - then do
|
|
99 | - (expr, fvs) <- rnOverLit noExtField $ OverLit noExtField (HsIsString src s)
|
|
100 | - pure (noLocA expr, fvs)
|
|
101 | - else
|
|
102 | - pure (nlHsLit $ HsString src strType s, emptyFVs) |
|
122 | + let lit = nlHsLit $ HsString src strType s
|
|
123 | + if
|
|
124 | + | Just qualNames <- mQualNames -> do
|
|
125 | + pure (nlHsApp (nlHsVar $ qualFromString qualNames) lit, emptyFVs)
|
|
126 | + | overloaded -> do
|
|
127 | + (expr, fvs) <- rnOverLit noExtField $ OverLit noExtField (HsIsString src s)
|
|
128 | + pure (noLocA expr, fvs)
|
|
129 | + | otherwise -> do
|
|
130 | + pure (lit, emptyFVs)
|
|
131 | + |
|
132 | +data QualifiedLiteralStringsNames = QualifiedLiteralStringsNames
|
|
133 | + { qualFromString :: Name
|
|
134 | + , qualInterpolate :: Name
|
|
135 | + , qualFromParts :: Name
|
|
136 | + }
|
|
137 | + |
|
138 | +lookupQualifiedLiteralStringsNames ::
|
|
139 | + ModuleName -> RnM QualifiedLiteralStringsNames
|
|
140 | +lookupQualifiedLiteralStringsNames modName = do
|
|
141 | + qualFromString <- lookup "fromString"
|
|
142 | + qualInterpolate <- lookup "interpolate"
|
|
143 | + qualFromParts <- lookup "fromParts"
|
|
144 | + pure QualifiedLiteralStringsNames{..}
|
|
145 | + where
|
|
146 | + lookup = lookupOccRn WL_None . mkRdrQual modName . mkVarOcc |
... | ... | @@ -727,7 +727,7 @@ exprCtOrigin (ExplicitList {}) = ListOrigin |
727 | 727 | exprCtOrigin (HsIPVar _ ip) = IPOccOrigin ip
|
728 | 728 | exprCtOrigin (HsOverLit _ lit) = LiteralOrigin lit
|
729 | 729 | exprCtOrigin (HsLit {}) = Shouldn'tHappenOrigin "concrete literal"
|
730 | -exprCtOrigin (HsInterString _ _ _) = InterStringOrigin
|
|
730 | +exprCtOrigin (HsInterString _ _ _ _) = InterStringOrigin
|
|
731 | 731 | exprCtOrigin (HsLam _ _ ms) = matchesCtOrigin ms
|
732 | 732 | exprCtOrigin (HsApp _ e1 _) = lexprCtOrigin e1
|
733 | 733 | exprCtOrigin (HsAppType _ e1 _) = lexprCtOrigin e1
|
... | ... | @@ -955,7 +955,7 @@ zonkExpr (HsOverLit x lit) |
955 | 955 | = do { lit' <- zonkOverLit lit
|
956 | 956 | ; return (HsOverLit x lit') }
|
957 | 957 | |
958 | -zonkExpr (HsInterString x _ _) = dataConCantHappen x
|
|
958 | +zonkExpr (HsInterString x _ _ _) = dataConCantHappen x
|
|
959 | 959 | |
960 | 960 | zonkExpr (HsLam x lam_variant matches)
|
961 | 961 | = do new_matches <- zonkMatchGroup zonkLExpr matches
|
... | ... | @@ -1226,11 +1226,12 @@ cvtl e = wrapLA (cvt e) |
1226 | 1226 | ; let tele = setTelescopeBndrsNameSpace varName $
|
1227 | 1227 | mkHsForAllVisTele noAnn tvs'
|
1228 | 1228 | ; return $ HsForAll noExtField tele body' }
|
1229 | - cvt (InterStringE parts) = do
|
|
1229 | + cvt (InterStringE mQualMod parts) = do
|
|
1230 | + let mQualMod' = mk_mod <$> mQualMod
|
|
1230 | 1231 | parts' <- forM parts $ \case
|
1231 | 1232 | InterStringRaw s -> pure $ HsInterStringRaw (SourceText $ fsLit s) (fsLit s)
|
1232 | 1233 | InterStringExp e -> HsInterStringExpr noExtField <$> cvtl e
|
1233 | - return $ HsInterString noExtField HsStringTypeSingle parts'
|
|
1234 | + return $ HsInterString noExtField mQualMod' HsStringTypeSingle parts'
|
|
1234 | 1235 | |
1235 | 1236 | {- | #16895 Ensure an infix expression's operator is a variable/constructor.
|
1236 | 1237 | Consider this example:
|
... | ... | @@ -349,6 +349,7 @@ data HsExpr p |
349 | 349 | | -- | See Note [Parsing interpolated strings]
|
350 | 350 | HsInterString
|
351 | 351 | (XInterString p)
|
352 | + (Maybe ModuleName) -- ^ Module, if using QualifiedLiterals
|
|
352 | 353 | HsStringType
|
353 | 354 | [HsInterStringPart p]
|
354 | 355 |
... | ... | @@ -250,9 +250,13 @@ pprExp i (ForallE tvars body) = |
250 | 250 | pprExp i (ConstrainedE ctx body) =
|
251 | 251 | parensIf (i >= funPrec) $ sep [pprCtxWith pprExp ctx, pprExp qualPrec body]
|
252 | 252 | |
253 | -pprExp _ (InterStringE parts) =
|
|
254 | - text "s\""<> hcat (map pprInterStringPart parts) <> text "\""
|
|
253 | +pprExp _ (InterStringE mQualMod parts) =
|
|
254 | + prefix <> char '"' <> hcat (map pprInterStringPart parts) <> char '"'
|
|
255 | 255 | where
|
256 | + prefix =
|
|
257 | + case mQualMod of
|
|
258 | + Nothing -> char 's'
|
|
259 | + Just qualMod -> text (modString qualMod) <> char '.'
|
|
256 | 260 | pprInterStringPart = \case
|
257 | 261 | InterStringRaw s -> text s
|
258 | 262 | InterStringExp e -> text "${" <> pprExp noPrec e <> text "}"
|
... | ... | @@ -15,11 +15,7 @@ See the proposal for motivation and explanations: |
15 | 15 | https://github.com/ghc-proposals/ghc-proposals/blob/master/proposals/0570-string-interpolation.rst
|
16 | 16 | -}
|
17 | 17 | module Data.String.Interpolate.Experimental (
|
18 | - Buildable (..),
|
|
19 | 18 | Interpolate (..),
|
20 | - |
|
21 | - -- * Built-in builders
|
|
22 | - StringBuilder (..),
|
|
23 | 19 | ) where
|
24 | 20 | |
25 | 21 | import GHC.Internal.Data.String.Interpolate |
... | ... | @@ -20,62 +20,66 @@ |
20 | 20 | -----------------------------------------------------------------------------
|
21 | 21 | |
22 | 22 | module GHC.Internal.Data.String.Interpolate (
|
23 | - Buildable (..),
|
|
24 | 23 | Interpolate (..),
|
25 | - |
|
26 | - -- * Built-in builders
|
|
27 | - StringBuilder (..),
|
|
28 | 24 | ) where
|
29 | 25 | |
30 | 26 | import GHC.Internal.Base
|
31 | -import GHC.Internal.Data.Monoid (Endo (..))
|
|
32 | -import GHC.Internal.Show (Show, shows)
|
|
33 | - |
|
34 | --- | @Buildable s@ allows @s@ to be built from an interpolated string.
|
|
35 | ---
|
|
36 | --- Laws:
|
|
37 | --- * @fromBuilder . toBuilder === id@
|
|
38 | --- * @toBuilder . fromBuilder === id@
|
|
39 | -class Monoid (Builder s) => Buildable s where
|
|
40 | - type Builder s = r | r -> s
|
|
41 | - toBuilder :: s -> Builder s
|
|
42 | - fromBuilder :: Builder s -> s
|
|
27 | +import GHC.Internal.Data.Either (Either (..))
|
|
28 | +import GHC.Internal.Data.List (intercalate)
|
|
29 | +import GHC.Internal.Show (show)
|
|
43 | 30 | |
44 | 31 | -- | @Interpolate a s@ allows a value of type @a@ to be interpolated
|
45 | 32 | -- into a string interpolation of type @s@.
|
46 | ---
|
|
47 | --- Laws:
|
|
48 | --- * @interpolate \@s \@s = toBuilder@
|
|
49 | --- * @interpolate \@(Builder s) \@s = id@
|
|
50 | -class Buildable s => Interpolate a s where
|
|
51 | - interpolate :: a -> Builder s
|
|
52 | - |
|
53 | -newtype StringBuilder = StringBuilder (Endo String)
|
|
54 | - deriving newtype (Semigroup, Monoid)
|
|
55 | - |
|
56 | -instance Buildable String where
|
|
57 | - type Builder String = StringBuilder
|
|
58 | - toBuilder = toStringBuilder
|
|
59 | - fromBuilder = fromStringBuilder
|
|
60 | - |
|
61 | -{-# RULES
|
|
62 | -"fromStringBuilder/toStringBuilder" forall s. fromStringBuilder (toStringBuilder s) = s
|
|
63 | -"toStringBuilder/fromStringBuilder" forall s. toStringBuilder (fromStringBuilder s) = s
|
|
64 | - #-}
|
|
65 | - |
|
66 | -toStringBuilder :: String -> StringBuilder
|
|
67 | -toStringBuilder s = StringBuilder (Endo (s ++))
|
|
68 | -{-# NOINLINE [2] toStringBuilder #-}
|
|
69 | - |
|
70 | -fromStringBuilder :: StringBuilder -> String
|
|
71 | -fromStringBuilder (StringBuilder (Endo f)) = f ""
|
|
72 | -{-# NOINLINE [2] fromStringBuilder #-}
|
|
33 | +class Interpolate a s where
|
|
34 | + interpolate :: a -> s
|
|
73 | 35 | |
74 | 36 | instance Interpolate String String where
|
75 | - interpolate = toBuilder
|
|
76 | -instance Interpolate StringBuilder String where
|
|
77 | 37 | interpolate = id
|
78 | 38 | instance Interpolate Char String where
|
79 | 39 | interpolate = interpolate . (:[])
|
80 | -instance {-# OVERLAPPABLE #-} Show a => Interpolate a String where
|
|
81 | - interpolate = StringBuilder . Endo . shows |
|
40 | + |
|
41 | +instance Interpolate Int String where
|
|
42 | + interpolate = show
|
|
43 | +instance Interpolate Double String where
|
|
44 | + interpolate = show
|
|
45 | +instance Interpolate Bool String where
|
|
46 | + interpolate = show
|
|
47 | + |
|
48 | +instance {-# OVERLAPPABLE #-}
|
|
49 | + ( Interpolate a String
|
|
50 | + ) => Interpolate [a] String where
|
|
51 | + interpolate as = "[" ++ (intercalate ", " . map interpolate) as ++ "]"
|
|
52 | +instance
|
|
53 | + ( Interpolate a String
|
|
54 | + , Interpolate b String
|
|
55 | + ) => Interpolate (Either a b) String where
|
|
56 | + interpolate (Left a) = "Left " ++ interpolate a
|
|
57 | + interpolate (Right b) = "Right " ++ interpolate b
|
|
58 | + |
|
59 | +instance
|
|
60 | + ( Interpolate a String
|
|
61 | + , Interpolate b String
|
|
62 | + ) => Interpolate (a, b) String where
|
|
63 | + interpolate (a, b) =
|
|
64 | + mconcat
|
|
65 | + [ "("
|
|
66 | + , interpolate a
|
|
67 | + , ", "
|
|
68 | + , interpolate b
|
|
69 | + , ")"
|
|
70 | + ]
|
|
71 | +instance
|
|
72 | + ( Interpolate a String
|
|
73 | + , Interpolate b String
|
|
74 | + , Interpolate c String
|
|
75 | + ) => Interpolate (a, b, c) String where
|
|
76 | + interpolate (a, b, c) =
|
|
77 | + mconcat
|
|
78 | + [ "("
|
|
79 | + , interpolate a
|
|
80 | + , ", "
|
|
81 | + , interpolate b
|
|
82 | + , ", "
|
|
83 | + , interpolate c
|
|
84 | + , ")"
|
|
85 | + ] |
... | ... | @@ -166,6 +166,7 @@ data Extension |
166 | 166 | | ListTuplePuns
|
167 | 167 | | MultilineStrings
|
168 | 168 | | StringInterpolation
|
169 | + | QualifiedLiterals
|
|
169 | 170 | | ExplicitLevelImports
|
170 | 171 | | ImplicitStagePersistence
|
171 | 172 | deriving (Eq, Enum, Show, Generic, Bounded)
|
... | ... | @@ -450,8 +450,8 @@ forallVisE tvars body = ForallVisE <$> sequenceA tvars <*> body |
450 | 450 | constrainedE :: Quote m => [m Exp] -> m Exp -> m Exp
|
451 | 451 | constrainedE ctx body = ConstrainedE <$> sequenceA ctx <*> body
|
452 | 452 | |
453 | -interStringE :: Quote m => [m InterStringPart] -> m Exp
|
|
454 | -interStringE parts = InterStringE <$> sequenceA parts
|
|
453 | +interStringE :: Quote m => Maybe ModName -> [m InterStringPart] -> m Exp
|
|
454 | +interStringE mQualMod parts = InterStringE mQualMod <$> sequenceA parts
|
|
455 | 455 | |
456 | 456 | -------------------------------------------------------------------------------
|
457 | 457 | -- * Dec
|
... | ... | @@ -1903,7 +1903,9 @@ data Exp |
1903 | 1903 | | ForallE [TyVarBndr Specificity] Exp -- ^ @forall \<vars\>. \<expr\>@
|
1904 | 1904 | | ForallVisE [TyVarBndr ()] Exp -- ^ @forall \<vars\> -> \<expr\>@
|
1905 | 1905 | | ConstrainedE [Exp] Exp -- ^ @\<ctxt\> => \<expr\>@
|
1906 | - | InterStringE [InterStringPart] -- ^ @{ s"Name: ${personName}" }@
|
|
1906 | + | InterStringE -- ^ @{ s"Name: ${personName}" }@ or @{ Mod."A ${x}" }@
|
|
1907 | + (Maybe ModName)
|
|
1908 | + [InterStringPart]
|
|
1907 | 1909 | deriving( Show, Eq, Ord, Data, Generic )
|
1908 | 1910 | |
1909 | 1911 | -- | A (field name, expression) pair. See 'RecConE' and 'RecUpdE'.
|
... | ... | @@ -36,7 +36,7 @@ check title expected got |
36 | 36 | |
37 | 37 | -- See Note [Adding a language extension] in compiler/GHC/Driver/Session.hs.
|
38 | 38 | expectedGhcOnlyExtensions :: [String]
|
39 | -expectedGhcOnlyExtensions = [ "StringInterpolation" ]
|
|
39 | +expectedGhcOnlyExtensions = [ "StringInterpolation", "QualifiedLiterals" ]
|
|
40 | 40 | |
41 | 41 | expectedCabalOnlyExtensions :: [String]
|
42 | 42 | expectedCabalOnlyExtensions = ["Generics",
|
1 | -{-# LANGUAGE DerivingStrategies #-}
|
|
2 | -{-# LANGUAGE GeneralisedNewtypeDeriving #-}
|
|
3 | 1 | {-# LANGUAGE MultilineStrings #-}
|
4 | 2 | {-# LANGUAGE OverloadedStrings #-}
|
5 | 3 | {-# LANGUAGE RecordWildCards #-}
|
... | ... | @@ -77,17 +75,9 @@ data SqlValue |
77 | 75 | | SqlInt Int
|
78 | 76 | deriving (Show)
|
79 | 77 | |
80 | -newtype SqlQueryBuilder = SqlQueryBuilder (Endo SqlQuery)
|
|
81 | - deriving newtype (Semigroup, Monoid)
|
|
82 | - |
|
83 | -instance Buildable SqlQuery where
|
|
84 | - type Builder SqlQuery = SqlQueryBuilder
|
|
85 | - toBuilder q = SqlQueryBuilder (Endo (q <>))
|
|
86 | - fromBuilder (SqlQueryBuilder (Endo f)) = f mempty
|
|
87 | - |
|
88 | 78 | instance Interpolate SqlQuery SqlQuery where
|
89 | - interpolate = toBuilder
|
|
79 | + interpolate = id
|
|
90 | 80 | instance Interpolate String SqlQuery where
|
91 | - interpolate s = toBuilder SqlQuery{sqlText = "?", sqlValues = [SqlString s]}
|
|
81 | + interpolate s = SqlQuery{sqlText = "?", sqlValues = [SqlString s]}
|
|
92 | 82 | instance Interpolate Int SqlQuery where
|
93 | - interpolate x = toBuilder SqlQuery{sqlText = "?", sqlValues = [SqlInt x]} |
|
83 | + interpolate x = SqlQuery{sqlText = "?", sqlValues = [SqlInt x]} |
1 | +{-# LANGUAGE MultilineStrings #-}
|
|
2 | +{-# LANGUAGE QualifiedLiterals #-}
|
|
3 | +{-# LANGUAGE RecordWildCards #-}
|
|
4 | + |
|
5 | +import qualified StringInterpolationQualified_SQL as SQL
|
|
6 | + |
|
7 | +main :: IO ()
|
|
8 | +main = mapM_ runTest allTests
|
|
9 | + |
|
10 | +data TestCase =
|
|
11 | + forall a. Show a =>
|
|
12 | + TestCase
|
|
13 | + { label :: String
|
|
14 | + , expression :: a
|
|
15 | + }
|
|
16 | + |
|
17 | +runTest :: TestCase -> IO ()
|
|
18 | +runTest TestCase{..} = do
|
|
19 | + putStrLn $ "****************************************"
|
|
20 | + putStrLn $ "Input:"
|
|
21 | + putStr $ unlines . map (" " ++) . lines $ label
|
|
22 | + putStrLn $ "====>"
|
|
23 | + putStrLn $ " " ++ show expression
|
|
24 | + |
|
25 | +allTests :: [TestCase]
|
|
26 | +allTests =
|
|
27 | + [ TestCase -- custom interpolation implementations
|
|
28 | + { label =
|
|
29 | + """
|
|
30 | + let
|
|
31 | + name = "'Robert'; DROP TABLE Students;--"
|
|
32 | + age = 10 :: Int
|
|
33 | + in
|
|
34 | + SQL."SELECT * FROM tab WHERE name ILIKE ${name} and age = ${age}"
|
|
35 | + """
|
|
36 | + , expression =
|
|
37 | + let
|
|
38 | + name = "'Robert'; DROP TABLE Students;--"
|
|
39 | + age = 10 :: Int
|
|
40 | + in
|
|
41 | + SQL."SELECT * FROM tab WHERE name ILIKE ${name} and age = ${age}"
|
|
42 | + }
|
|
43 | + -- TODO(bchinn): qualified interpolated multiline string
|
|
44 | + ] |
1 | +****************************************
|
|
2 | +Input:
|
|
3 | + let
|
|
4 | + name = "'Robert'; DROP TABLE Students;--"
|
|
5 | + age = 10 :: Int
|
|
6 | + in
|
|
7 | + SQL."SELECT * FROM tab WHERE name ILIKE ${name} and age = ${age}"
|
|
8 | +====>
|
|
9 | + SqlQuery {sqlText = "SELECT * FROM tab WHERE name ILIKE ? and age = ?", sqlValues = [SqlString "'Robert'; DROP TABLE Students;--",SqlInt 10]} |
1 | +module StringInterpolationQualified_SQL where
|
|
2 | + |
|
3 | +import qualified Data.String
|
|
4 | + |
|
5 | +data SqlQuery = SqlQuery
|
|
6 | + { sqlText :: String
|
|
7 | + , sqlValues :: [SqlValue]
|
|
8 | + }
|
|
9 | + deriving (Show)
|
|
10 | + |
|
11 | +instance Data.String.IsString SqlQuery where
|
|
12 | + fromString s = SqlQuery{sqlText = s, sqlValues = []}
|
|
13 | +instance Semigroup SqlQuery where
|
|
14 | + q1 <> q2 =
|
|
15 | + SqlQuery
|
|
16 | + { sqlText = sqlText q1 <> sqlText q2
|
|
17 | + , sqlValues = sqlValues q1 <> sqlValues q2
|
|
18 | + }
|
|
19 | +instance Monoid SqlQuery where
|
|
20 | + mempty =
|
|
21 | + SqlQuery
|
|
22 | + { sqlText = ""
|
|
23 | + , sqlValues = []
|
|
24 | + }
|
|
25 | + |
|
26 | +data SqlValue
|
|
27 | + = SqlString String
|
|
28 | + | SqlInt Int
|
|
29 | + deriving (Show)
|
|
30 | + |
|
31 | +class ToSqlValue a where
|
|
32 | + toSqlValue :: a -> SqlValue
|
|
33 | +instance ToSqlValue String where
|
|
34 | + toSqlValue = SqlString
|
|
35 | +instance ToSqlValue Int where
|
|
36 | + toSqlValue = SqlInt
|
|
37 | + |
|
38 | +{----- QualifiedLiterals -----}
|
|
39 | + |
|
40 | +fromString :: String -> SqlQuery
|
|
41 | +fromString = Data.String.fromString
|
|
42 | + |
|
43 | +interpolate :: ToSqlValue a => a -> SqlQuery
|
|
44 | +interpolate v = SqlQuery{sqlText = "?", sqlValues = [toSqlValue v]}
|
|
45 | + |
|
46 | +fromParts :: [SqlQuery] -> SqlQuery
|
|
47 | +fromParts = mconcat |
... | ... | @@ -38,3 +38,4 @@ test('T25784', normal, compile_and_run, ['']) |
38 | 38 | # String interpolation
|
39 | 39 | test('StringInterpolation', normal, compile_and_run, [''])
|
40 | 40 | test('StringInterpolationOverloaded', normal, compile_and_run, [''])
|
41 | +test('StringInterpolationQualified', [extra_files(['StringInterpolationQualified_SQL.hs'])], multimod_compile_and_run, ['StringInterpolationQualified', '']) |