string-interpolate 0.3.3.0 → 0.3.4.0
raw patch · 7 files changed
+113/−16 lines, 7 filesdep ~QuickCheckdep ~haskell-src-extsdep ~quickcheck-instances
Dependency ranges changed: QuickCheck, haskell-src-exts, quickcheck-instances, template-haskell
Files
- CHANGELOG.md +5/−0
- README.md +14/−0
- src/lib/Data/String/Interpolate.hs +14/−10
- src/lib/Data/String/Interpolate/Conversion.hs +1/−1
- src/lib/Data/String/Interpolate/Conversion/Classes.hs +10/−1
- string-interpolate.cabal +3/−3
- test/spec.hs +66/−1
CHANGELOG.md view
@@ -2,6 +2,11 @@ ## Unreleased +## v0.3.4.0 (2024-07-09)+++ Fixed a performance bug causing exponential compilation times when using+ lots of interpolations (thanks mpickering!)+ ## v0.3.3.0 (2024-01-24) + Added support for GHC 9.8
README.md view
@@ -308,3 +308,17 @@ Enable either the `text-builder` or `bytestring-builder` Cabal flag, depending on your need, and you should see speedups constructing large strings, at the cost of slowing down smaller outputs.++## Release policy++As of July 2024, `string-interpolate` is essentially "done;" it does its job+reliably, and no further enhancements or feature additions are planned. Going+forward, the expectation is that any further releases are mostly for performance+improvements, bugfixes, or necessary security patches.++If major API rework or breaking changes become planned in the future,+`string-interpolate` will maintain strict backwards compatibility (i.e. no code+changes should be necessary in any existing code consumers) for a period no less+than 3 years. Note that this only applies to the exports of the+`Data.String.Interpolate` module; all other modules should be considered+internal.
src/lib/Data/String/Interpolate.hs view
@@ -61,7 +61,6 @@ import Data.Foldable ( traverse_ ) import Data.List ( intercalate )-import Data.Proxy import qualified Language.Haskell.Exts.Extension as Ext import Language.Haskell.Exts.Parser@@ -70,7 +69,7 @@ import Language.Haskell.TH import Language.Haskell.TH.Quote ( QuasiQuoter(..) ) -import Data.String.Interpolate.Conversion ( build, finalize, interpolate, ofString )+import Data.String.Interpolate.Conversion ( proxyWrapper, build, finalize, interpolate, ofString ) import Data.String.Interpolate.Lines ( IndentWarning(..), Mindent(..), handleIndents ) import Data.String.Interpolate.Parse@@ -116,16 +115,21 @@ -- Produce the final Template Haskell expression. Handles collapsing -- intermediate strings. outputToExp :: [OutputSegment] -> Q Exp-outputToExp segs = [|finalize Proxy $(go (collapseStrings segs))|]+outputToExp segs = finalExp where- go :: [OutputSegment] -> Q Exp- go = foldr- (\seg qexp -> [|build Proxy $(renderExp seg) $(qexp)|])- [|ofString Proxy ""|]+ finalExp = [| proxyWrapper $ \proxy -> $(mkFinalize [| proxy |]) |] - renderExp :: OutputSegment -> Q Exp- renderExp (OfString str) = [|ofString Proxy str|]- renderExp (Interpolate expr) = [|interpolate Proxy $(reifyExpression expr)|]+ mkFinalize :: Q Exp -> Q Exp+ mkFinalize proxy = [|finalize $proxy $(go proxy (collapseStrings segs))|]++ go :: Q Exp -> [OutputSegment] -> Q Exp+ go proxy = foldr+ (\seg qexp -> [|build $proxy $(renderExp proxy seg) $(qexp)|])+ [|ofString $proxy ""|]++ renderExp :: Q Exp -> OutputSegment -> Q Exp+ renderExp proxy (OfString str) = [|ofString $proxy str|]+ renderExp proxy (Interpolate expr) = [|interpolate $proxy $(reifyExpression expr)|] type Interpolator = ParseOutput -> Q Lines
src/lib/Data/String/Interpolate/Conversion.hs view
@@ -19,7 +19,7 @@ module Data.String.Interpolate.Conversion ( IsCustomSink, InterpSink(..), Interpolatable(..)- , bsToTextBuilder, lbsToTextBuilder, encodeCharUTF8+ , bsToTextBuilder, lbsToTextBuilder, encodeCharUTF8, proxyWrapper ) where
src/lib/Data/String/Interpolate/Conversion/Classes.hs view
@@ -2,10 +2,12 @@ {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-} module Data.String.Interpolate.Conversion.Classes ( B(..)- , IsCustomSink, InterpSink(..), Interpolatable(..)+ , IsCustomSink, InterpSink(..), Interpolatable(..), proxyWrapper ) where @@ -35,6 +37,13 @@ IsCustomSink LB.ByteString = 'True IsCustomSink LB.Builder = 'True IsCustomSink _ = 'False+++-- | Used to indicate to GHC that the value of all the proxy arguments is the same.+proxyWrapper :: forall final flag . (IsCustomSink final ~ flag) => (Proxy flag -> final) -> final+proxyWrapper k =+ let proxy = Proxy @(IsCustomSink final)+ in k proxy -- | Something that can be interpolated into. class IsCustomSink dst ~ flag => InterpSink (flag :: Bool) dst where
string-interpolate.cabal view
@@ -1,7 +1,7 @@ cabal-version: 1.18 name: string-interpolate-version: 0.3.3.0+version: 0.3.4.0 synopsis: Haskell string/text/bytestring interpolation that just works description: Unicode-aware string interpolation that handles all textual types. .@@ -91,7 +91,7 @@ build-depends: base ==4.* , string-interpolate- , QuickCheck <2.15+ , QuickCheck <2.16 , bytestring <0.13 , text <2.2 , template-haskell <2.22@@ -112,7 +112,7 @@ build-depends: base ==4.* , string-interpolate- , QuickCheck <2.15+ , QuickCheck <2.16 , bytestring <0.13 , text <2.2 , deepseq <1.6
test/spec.hs view
@@ -18,7 +18,7 @@ import Data.Char ( chr, isSpace ) import Data.Foldable ( foldMap ) import qualified Data.HashMap.Strict as HM-import Data.List ( sort )+import Data.List ( sort, intersperse ) import Data.Semigroup import qualified Data.Text as T import qualified Data.Text.Lazy as LT@@ -75,6 +75,71 @@ it "should parse TypeApplications" $ do let expected :: String = "2" [i|#{show @Int 2}|] `shouldBe` expected++ -- This test is primarily a regression test against a performance issue+ -- caused by GHC needing to unify all the proxies passed in the generated+ -- code.+ -- See <https://gitlab.com/williamyaoh/string-interpolate/-/merge_requests/72>+ -- and <https://gitlab.haskell.org/ghc/ghc/-/issues/24984>+ -- It would be preferable if we had something that would actually *fail*+ -- if compilation took too long, but compilation of the test suite being+ -- slow should be enough of a smoke signal.+ it "should work with many interpolations" $+ let x = ()+ (expected :: String) = concat $ intersperse " : " (replicate 780 (show x))+ in [iii| #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} :+ #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} :+ #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} :+ #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} :+ #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} :+ #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} :+ #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} :+ #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} :+ #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} :+ #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} :+ #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} :+ #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} :+ #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} :+ #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} :+ #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} :+ #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} :+ #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} :+ #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} :+ #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} :+ #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} :+ #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} :+ #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} :+ #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} :+ #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} :+ #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} :+ #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} :+ #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} :+ #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} :+ #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} :+ #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} :+ #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} :+ #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} :+ #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} :+ #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} :+ #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} :+ #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} :+ #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} :+ #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} :+ #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} :+ #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} :+ #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} :+ #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} :+ #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} :+ #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} :+ #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} :+ #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} :+ #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} :+ #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} :+ #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} :+ #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} :+ #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} :+ #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x} : #{x}+ |] `shouldBe` expected context "when using String as a parameter" $ do prop "just interpolating should be id" $