Improve encoding/decoding speed (#1500)

... by not going through a `Term` intermediate

This gives a ~28% performance in decoding improvement, which means that
cache looks are not faster.

Here are the new decoding benchmarks before and after this change:

Before:

```
benchmarked Issue #108/Binary
time                 266.5 μs   (265.7 μs .. 267.4 μs)
                     1.000 R²   (1.000 R² .. 1.000 R²)
mean                 266.3 μs   (265.6 μs .. 267.1 μs)
std dev              2.418 μs   (1.891 μs .. 3.436 μs)

benchmarking Kubernetes/Binary ... took 36.94 s, total 56 iterations
benchmarked Kubernetes/Binary
time                 641.3 ms   (623.0 ms .. 655.4 ms)
                     0.999 R²   (0.997 R² .. 1.000 R²)
mean                 679.7 ms   (665.5 ms .. 702.6 ms)
std dev              29.48 ms   (14.15 ms .. 39.05 ms)
```

After:

```
benchmarked Issue #108/Binary
time                 282.2 μs   (279.6 μs .. 284.7 μs)
                     1.000 R²   (0.999 R² .. 1.000 R²)
mean                 281.9 μs   (280.7 μs .. 287.7 μs)
std dev              7.089 μs   (2.550 μs .. 15.44 μs)
variance introduced by outliers: 11% (moderately inflated)

benchmarking Kubernetes/Binary ... took 27.57 s, total 56 iterations
benchmarked Kubernetes/Binary
time                 499.1 ms   (488.1 ms .. 506.6 ms)
                     0.999 R²   (0.998 R² .. 1.000 R²)
mean                 498.9 ms   (494.4 ms .. 503.9 ms)
std dev              8.539 ms   (6.236 ms .. 12.56 ms)
```

There's a slight performance regression for the decoding microbenchmark, but
in practice my testing on real examples matches performance improvements seen
in the larger benchmark based on an example cache product from
`dhall-kubernetes`.

Note that is a breaking change because:

* There is no longer a `FromTerm` nor `ToTerm` class.  Now we use the
  `Serialise` class and `{encode,decode}Expression` now work on `ByteString`s
  instead of `Term`s

* I further narrowed the types of several encoding/decoding utilites to expect a
  `Void` for the first type parameter of `Expr`

* This is a regression with respect to stripping 55799 CBOR tags, mainly
  because properly handling the tags at every possible point in the syntax tree
  would considerably complicate the code
This commit is contained in:
Gabriel Gonzalez 2019-10-31 20:05:22 -07:00 committed by GitHub
parent 5ceb8d9d60
commit b843cae5d2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 1136 additions and 896 deletions

View File

@ -165,5 +165,5 @@ normalize (WellTyped expr) = Normal $ Dhall.normalize expr
-- Dhall's hash annotations (prefixed by "sha256:" and base-64 encoded). -- Dhall's hash annotations (prefixed by "sha256:" and base-64 encoded).
hashNormalToCode :: Normal -> Text hashNormalToCode :: Normal -> Text
hashNormalToCode (Normal expr) = hashNormalToCode (Normal expr) =
Dhall.hashExpressionToCode alphaNormal Dhall.hashExpressionToCode (Dhall.denote alphaNormal)
where alphaNormal = Dhall.alphaNormalize expr where alphaNormal = Dhall.alphaNormalize expr

Binary file not shown.

View File

@ -6,7 +6,8 @@ module Main where
import Control.Monad (forM) import Control.Monad (forM)
import Data.Map (Map, foldrWithKey, singleton, unions) import Data.Map (Map, foldrWithKey, singleton, unions)
import Data.Monoid ((<>)) import Data.Monoid ((<>))
import Gauge (defaultMain, bgroup, bench, whnf, nfIO) import Data.Void (Void)
import Gauge (defaultMain, bgroup, bench, nf, whnf, nfIO)
import System.Directory import System.Directory
@ -67,27 +68,25 @@ benchExprFromText name expr =
benchExprFromBytes benchExprFromBytes
:: String -> Data.ByteString.Lazy.ByteString -> Gauge.Benchmark :: String -> Data.ByteString.Lazy.ByteString -> Gauge.Benchmark
benchExprFromBytes name bytes = bench name (whnf f bytes) benchExprFromBytes name bytes = bench name (nf f bytes)
where where
f bytes = do f bytes = do
term <- case Codec.Serialise.deserialiseOrFail bytes of case Dhall.Binary.decodeExpression bytes of
Left _ -> Nothing Left exception -> error (show exception)
Right term -> return term Right expression -> expression :: Dhall.Expr Void Dhall.Import
case Dhall.Binary.decodeExpression term
:: Either Dhall.Binary.DecodingFailure (Dhall.Expr () Dhall.Import) of
Left _ -> Nothing
Right expression -> return expression
main :: IO () main :: IO ()
main = do main = do
prelude <- loadPreludeFiles prelude <- loadPreludeFiles
issue108Text <- TIO.readFile "benchmark/examples/issue108.dhall" issue108Text <- TIO.readFile "benchmark/examples/issue108.dhall"
issue108Bytes <- Data.ByteString.Lazy.readFile "benchmark/examples/issue108.dhall.bin" issue108Bytes <- Data.ByteString.Lazy.readFile "benchmark/examples/issue108.dhall.bin"
kubernetesExample <- Data.ByteString.Lazy.readFile "benchmark/examples/kubernetes.dhall.bin"
defaultMain defaultMain
[ bgroup "Issue #108" [ bgroup "Issue #108"
[ benchExprFromText "Text" issue108Text [ benchExprFromText "Text" issue108Text
, benchExprFromBytes "Binary" issue108Bytes , benchExprFromBytes "Binary" issue108Bytes
] ]
, benchExprFromBytes "Kubernetes/Binary" kubernetesExample
, benchExprFromText "Long variable names" (T.replicate 1000000 "x") , benchExprFromText "Long variable names" (T.replicate 1000000 "x")
, benchExprFromText "Large number of function arguments" (T.replicate 10000 "x ") , benchExprFromText "Large number of function arguments" (T.replicate 10000 "x ")
, benchExprFromText "Long double-quoted strings" ("\"" <> T.replicate 1000000 "x" <> "\"") , benchExprFromText "Long double-quoted strings" ("\"" <> T.replicate 1000000 "x" <> "\"")

File diff suppressed because it is too large Load Diff

View File

@ -26,7 +26,6 @@ import Data.Text (Text)
import Data.Text.Prettyprint.Doc (Doc, Pretty) import Data.Text.Prettyprint.Doc (Doc, Pretty)
import Data.Void (Void) import Data.Void (Void)
import Dhall.Syntax (Binding(..), Chunks (..), Const(..), DhallDouble(..), Expr(..), Var(..)) import Dhall.Syntax (Binding(..), Chunks (..), Const(..), DhallDouble(..), Expr(..), Var(..))
import Dhall.Binary (ToTerm)
import Dhall.Map (Map) import Dhall.Map (Map)
import Dhall.Set (Set) import Dhall.Set (Set)
import Dhall.Pretty.Internal (Ann) import Dhall.Pretty.Internal (Ann)
@ -156,7 +155,7 @@ rparen :: Diff
rparen = token Internal.rparen rparen = token Internal.rparen
-- | Render the difference between the normal form of two expressions -- | Render the difference between the normal form of two expressions
diffNormalized :: (Eq a, Pretty a, ToTerm a) => Expr s a -> Expr s a -> Diff diffNormalized :: (Eq a, Pretty a) => Expr s a -> Expr s a -> Diff
diffNormalized l0 r0 = Dhall.Diff.diff l1 r1 diffNormalized l0 r0 = Dhall.Diff.diff l1 r1
where where
l1 = Dhall.Normalize.alphaNormalize (Dhall.Normalize.normalize l0) l1 = Dhall.Normalize.alphaNormalize (Dhall.Normalize.normalize l0)

View File

@ -62,7 +62,7 @@ freezeImport directory import_ = do
Dhall.Core.alphaNormalize (Dhall.Core.normalize expression) Dhall.Core.alphaNormalize (Dhall.Core.normalize expression)
-- make sure the frozen import is present in the semantic cache -- make sure the frozen import is present in the semantic cache
Dhall.Import.writeExpressionToSemanticCache expression Dhall.Import.writeExpressionToSemanticCache (Dhall.Core.denote expression)
let expressionHash = Dhall.Import.hashExpression normalizedExpression let expressionHash = Dhall.Import.hashExpression normalizedExpression

View File

@ -138,7 +138,6 @@ module Dhall.Import (
) where ) where
import Control.Applicative (Alternative(..)) import Control.Applicative (Alternative(..))
import Codec.CBOR.Term (Term(..))
import Control.Exception (Exception, SomeException, IOException, toException) import Control.Exception (Exception, SomeException, IOException, toException)
import Control.Monad (when) import Control.Monad (when)
import Control.Monad.Catch (throwM, MonadCatch(catch), handle) import Control.Monad.Catch (throwM, MonadCatch(catch), handle)
@ -180,6 +179,8 @@ import Dhall.Import.Types
import Dhall.Parser (Parser(..), ParseError(..), Src(..), SourcedException(..)) import Dhall.Parser (Parser(..), ParseError(..), Src(..), SourcedException(..))
import Lens.Family.State.Strict (zoom) import Lens.Family.State.Strict (zoom)
import qualified Codec.CBOR.Encoding as Encoding
import qualified Codec.CBOR.Write as Write
import qualified Codec.Serialise import qualified Codec.Serialise
import qualified Control.Monad.Trans.Maybe as Maybe import qualified Control.Monad.Trans.Maybe as Maybe
import qualified Control.Monad.Trans.State.Strict as State import qualified Control.Monad.Trans.State.Strict as State
@ -504,6 +505,7 @@ loadImportWithSemanticCache
case mCached of case mCached of
Just bytesStrict -> do Just bytesStrict -> do
let actualHash = Dhall.Crypto.sha256Hash bytesStrict let actualHash = Dhall.Crypto.sha256Hash bytesStrict
if semanticHash == actualHash if semanticHash == actualHash
then return () then return ()
else do else do
@ -511,12 +513,10 @@ loadImportWithSemanticCache
throwMissingImport (Imported _stack (HashMismatch {expectedHash = semanticHash, ..})) throwMissingImport (Imported _stack (HashMismatch {expectedHash = semanticHash, ..}))
let bytesLazy = Data.ByteString.Lazy.fromStrict bytesStrict let bytesLazy = Data.ByteString.Lazy.fromStrict bytesStrict
term <- case Codec.Serialise.deserialiseOrFail bytesLazy of
Left err -> throwMissingImport (Imported _stack err) importSemantics <- case Dhall.Binary.decodeExpression bytesLazy of
Right t -> return t Left err -> throwMissingImport (Imported _stack err)
importSemantics <- case Dhall.Binary.decodeExpression term of Right e -> return e
Left err -> throwMissingImport (Imported _stack err)
Right sem -> return sem
return (ImportSemantics {..}) return (ImportSemantics {..})
@ -544,7 +544,7 @@ fetchFromSemanticCache expectedHash = Maybe.runMaybeT $ do
-- | Ensure that the given expression is present in the semantic cache. The -- | Ensure that the given expression is present in the semantic cache. The
-- given expression should be alpha-beta-normal. -- given expression should be alpha-beta-normal.
writeExpressionToSemanticCache :: Expr Src Void -> IO () writeExpressionToSemanticCache :: Expr Void Void -> IO ()
writeExpressionToSemanticCache expression = writeToSemanticCache hash bytes writeExpressionToSemanticCache expression = writeToSemanticCache hash bytes
where where
bytes = encodeExpression NoVersion expression bytes = encodeExpression NoVersion expression
@ -592,18 +592,14 @@ loadImportWithSemisemanticCache (Chained (Import (ImportHashed _ importType) Cod
-- Check the semi-semantic cache. See -- Check the semi-semantic cache. See
-- https://github.com/dhall-lang/dhall-haskell/issues/1098 for the reasoning -- https://github.com/dhall-lang/dhall-haskell/issues/1098 for the reasoning
-- behind semi-semantic caching. -- behind semi-semantic caching.
let semisemanticHash = computeSemisemanticHash resolvedExpr let semisemanticHash = computeSemisemanticHash (Dhall.Core.denote resolvedExpr)
mCached <- lift $ fetchFromSemisemanticCache semisemanticHash mCached <- lift $ fetchFromSemisemanticCache semisemanticHash
importSemantics <- case mCached of importSemantics <- case mCached of
Just bytesStrict -> do Just bytesStrict -> do
let bytesLazy = Data.ByteString.Lazy.fromStrict bytesStrict let bytesLazy = Data.ByteString.Lazy.fromStrict bytesStrict
term <- case Codec.Serialise.deserialiseOrFail bytesLazy of importSemantics <- case Dhall.Binary.decodeExpression bytesLazy of
Left err -> throwMissingImport (Imported _stack err)
Right t -> return t
importSemantics <- case Dhall.Binary.decodeExpression term of
Left err -> throwMissingImport (Imported _stack err) Left err -> throwMissingImport (Imported _stack err)
Right sem -> return sem Right sem -> return sem
@ -659,7 +655,7 @@ loadImportWithSemisemanticCache (Chained (Import (ImportHashed _ importType) Loc
-- AST (without normalising or type-checking it first). See -- AST (without normalising or type-checking it first). See
-- https://github.com/dhall-lang/dhall-haskell/issues/1098 for further -- https://github.com/dhall-lang/dhall-haskell/issues/1098 for further
-- discussion. -- discussion.
computeSemisemanticHash :: Expr Src Void -> Dhall.Crypto.SHA256Digest computeSemisemanticHash :: Expr Void Void -> Dhall.Crypto.SHA256Digest
computeSemisemanticHash resolvedExpr = hashExpression resolvedExpr computeSemisemanticHash resolvedExpr = hashExpression resolvedExpr
-- Fetch encoded normal form from "semi-semantic cache" -- Fetch encoded normal form from "semi-semantic cache"
@ -1012,7 +1008,7 @@ loadWith expr₀ = case expr₀ of
ImportSemantics {..} <- loadImport child ImportSemantics {..} <- loadImport child
zoom stack (State.put _stack) zoom stack (State.put _stack)
return importSemantics return (Dhall.Core.renote importSemantics)
ImportAlt a b -> loadWith a `catch` handler ImportAlt a b -> loadWith a `catch` handler
where where
@ -1109,33 +1105,30 @@ loadRelativeTo rootDirectory semanticCacheMode expression =
(emptyStatus rootDirectory) { _semanticCacheMode = semanticCacheMode } (emptyStatus rootDirectory) { _semanticCacheMode = semanticCacheMode }
encodeExpression encodeExpression
:: forall s :: StandardVersion
. StandardVersion
-- ^ `NoVersion` means to encode without the version tag -- ^ `NoVersion` means to encode without the version tag
-> Expr s Void -> Expr Void Void
-> Data.ByteString.ByteString -> Data.ByteString.ByteString
encodeExpression _standardVersion expression = bytesStrict encodeExpression _standardVersion expression = bytesStrict
where where
intermediateExpression :: Expr s Import intermediateExpression :: Expr Void Import
intermediateExpression = fmap absurd expression intermediateExpression = fmap absurd expression
term :: Term encoding =
term = Dhall.Binary.encodeExpression intermediateExpression
taggedTerm :: Term
taggedTerm =
case _standardVersion of case _standardVersion of
NoVersion -> term NoVersion ->
s -> TList [ TString v, term ] Codec.Serialise.encode intermediateExpression
s ->
Encoding.encodeListLen 2
<> Encoding.encodeString v
<> Codec.Serialise.encode intermediateExpression
where where
v = Dhall.Binary.renderStandardVersion s v = Dhall.Binary.renderStandardVersion s
bytesLazy = Codec.Serialise.serialise taggedTerm bytesStrict = Write.toStrictByteString encoding
bytesStrict = Data.ByteString.Lazy.toStrict bytesLazy
-- | Hash a fully resolved expression -- | Hash a fully resolved expression
hashExpression :: Expr s Void -> Dhall.Crypto.SHA256Digest hashExpression :: Expr Void Void -> Dhall.Crypto.SHA256Digest
hashExpression expression = hashExpression expression =
Dhall.Crypto.sha256Hash (encodeExpression NoVersion expression) Dhall.Crypto.sha256Hash (encodeExpression NoVersion expression)
@ -1145,7 +1138,7 @@ hashExpression expression =
In other words, the output of this function can be pasted into Dhall In other words, the output of this function can be pasted into Dhall
source code to add an integrity check to an import source code to add an integrity check to an import
-} -}
hashExpressionToCode :: Expr s Void -> Text hashExpressionToCode :: Expr Void Void -> Text
hashExpressionToCode expr = hashExpressionToCode expr =
"sha256:" <> Text.pack (show (hashExpression expr)) "sha256:" <> Text.pack (show (hashExpression expr))

View File

@ -49,7 +49,7 @@ instance Pretty Chained where
-- | An import that has been fully interpeted -- | An import that has been fully interpeted
data ImportSemantics = ImportSemantics data ImportSemantics = ImportSemantics
{ importSemantics :: Expr Src Void { importSemantics :: Expr Void Void
-- ^ The fully resolved import, typechecked and beta-normal. -- ^ The fully resolved import, typechecked and beta-normal.
} }

View File

@ -44,7 +44,6 @@ import Text.Dot ((.->.))
import qualified Codec.CBOR.JSON import qualified Codec.CBOR.JSON
import qualified Codec.CBOR.Read import qualified Codec.CBOR.Read
import qualified Codec.CBOR.Write import qualified Codec.CBOR.Write
import qualified Codec.Serialise
import qualified Control.Exception import qualified Control.Exception
import qualified Control.Monad.Trans.State.Strict as State import qualified Control.Monad.Trans.State.Strict as State
import qualified Data.Aeson import qualified Data.Aeson
@ -673,9 +672,7 @@ command (Options {..}) = do
Encode {..} -> do Encode {..} -> do
expression <- getExpression file expression <- getExpression file
let term = Dhall.Binary.encodeExpression expression let bytes = Dhall.Binary.encodeExpression (Dhall.Core.denote expression)
let bytes = Codec.Serialise.serialise term
if json if json
then do then do
@ -696,7 +693,7 @@ command (Options {..}) = do
InputFile f -> Data.ByteString.Lazy.readFile f InputFile f -> Data.ByteString.Lazy.readFile f
StandardInput -> Data.ByteString.Lazy.getContents StandardInput -> Data.ByteString.Lazy.getContents
term <- do expression <- do
if json if json
then do then do
value <- case Data.Aeson.eitherDecode' bytes of value <- case Data.Aeson.eitherDecode' bytes of
@ -705,15 +702,14 @@ command (Options {..}) = do
let encoding = Codec.CBOR.JSON.encodeValue value let encoding = Codec.CBOR.JSON.encodeValue value
let cborBytes = Codec.CBOR.Write.toLazyByteString encoding let cborgBytes = Codec.CBOR.Write.toLazyByteString encoding
Dhall.Core.throws (Codec.Serialise.deserialiseOrFail cborBytes)
Dhall.Core.throws (Dhall.Binary.decodeExpression cborgBytes)
else do else do
Dhall.Core.throws (Codec.Serialise.deserialiseOrFail bytes) Dhall.Core.throws (Dhall.Binary.decodeExpression bytes)
expression <- Dhall.Core.throws (Dhall.Binary.decodeExpression term)
:: IO (Expr Src Import)
let doc = Dhall.Pretty.prettyCharacterSet characterSet expression let doc = Dhall.Pretty.prettyCharacterSet characterSet (Dhall.Core.renote expression :: Expr Src Import)
renderDoc System.IO.stdout doc renderDoc System.IO.stdout doc

View File

@ -211,7 +211,7 @@ newtype DhallDouble = DhallDouble { getDhallDouble :: Double }
-- --
-- This instance is also consistent with with the binary encoding of Dhall @Double@s: -- This instance is also consistent with with the binary encoding of Dhall @Double@s:
-- --
-- >>> toBytes n = Codec.Serialise.serialise (Dhall.Binary.encode (n :: DhallDouble)) -- >>> toBytes n = Dhall.Binary.encodeExpression (DoubleLit n :: Expr Void Import)
-- --
-- prop> \a b -> (a == b) == (toBytes a == toBytes b) -- prop> \a b -> (a == b) == (toBytes a == toBytes b)
instance Eq DhallDouble where instance Eq DhallDouble where

View File

@ -37,7 +37,6 @@ import Data.Text (Text)
import Data.Text.Prettyprint.Doc (Doc, Pretty(..)) import Data.Text.Prettyprint.Doc (Doc, Pretty(..))
import Data.Typeable (Typeable) import Data.Typeable (Typeable)
import Data.Void (Void, absurd) import Data.Void (Void, absurd)
import Dhall.Binary (ToTerm(..))
import Dhall.Context (Context) import Dhall.Context (Context)
import Dhall.Syntax (Binding(..), Const(..), Chunks(..), Expr(..), Var(..)) import Dhall.Syntax (Binding(..), Const(..), Chunks(..), Expr(..), Var(..))
import Dhall.Eval import Dhall.Eval
@ -1330,13 +1329,13 @@ data TypeMessage s a
| CantMultiply (Expr s a) (Expr s a) | CantMultiply (Expr s a) (Expr s a)
deriving (Show) deriving (Show)
shortTypeMessage :: (Eq a, Pretty a, ToTerm a) => TypeMessage s a -> Doc Ann shortTypeMessage :: (Eq a, Pretty a) => TypeMessage s a -> Doc Ann
shortTypeMessage msg = shortTypeMessage msg =
"\ESC[1;31mError\ESC[0m: " <> short <> "\n" "\ESC[1;31mError\ESC[0m: " <> short <> "\n"
where where
ErrorMessages {..} = prettyTypeMessage msg ErrorMessages {..} = prettyTypeMessage msg
longTypeMessage :: (Eq a, Pretty a, ToTerm a) => TypeMessage s a -> Doc Ann longTypeMessage :: (Eq a, Pretty a) => TypeMessage s a -> Doc Ann
longTypeMessage msg = longTypeMessage msg =
"\ESC[1;31mError\ESC[0m: " <> short <> "\n" "\ESC[1;31mError\ESC[0m: " <> short <> "\n"
<> "\n" <> "\n"
@ -1357,8 +1356,7 @@ _NOT = "\ESC[1mnot\ESC[0m"
insert :: Pretty a => a -> Doc Ann insert :: Pretty a => a -> Doc Ann
insert = Dhall.Util.insert insert = Dhall.Util.insert
prettyTypeMessage prettyTypeMessage :: (Eq a, Pretty a) => TypeMessage s a -> ErrorMessages
:: (Eq a, Pretty a, ToTerm a) => TypeMessage s a -> ErrorMessages
prettyTypeMessage (UnboundVariable x) = ErrorMessages {..} prettyTypeMessage (UnboundVariable x) = ErrorMessages {..}
-- We do not need to print variable name here. For the discussion see: -- We do not need to print variable name here. For the discussion see:
-- https://github.com/dhall-lang/dhall-haskell/pull/116 -- https://github.com/dhall-lang/dhall-haskell/pull/116
@ -4252,12 +4250,12 @@ data TypeError s a = TypeError
, typeMessage :: TypeMessage s a , typeMessage :: TypeMessage s a
} }
instance (Eq a, Pretty s, Pretty a, ToTerm a) => Show (TypeError s a) where instance (Eq a, Pretty s, Pretty a) => Show (TypeError s a) where
show = Pretty.renderString . Pretty.layoutPretty layoutOpts . Pretty.pretty show = Pretty.renderString . Pretty.layoutPretty layoutOpts . Pretty.pretty
instance (Eq a, Pretty s, Pretty a, ToTerm a, Typeable s, Typeable a) => Exception (TypeError s a) instance (Eq a, Pretty s, Pretty a, Typeable s, Typeable a) => Exception (TypeError s a)
instance (Eq a, Pretty s, Pretty a, ToTerm a) => Pretty (TypeError s a) where instance (Eq a, Pretty s, Pretty a) => Pretty (TypeError s a) where
pretty (TypeError _ expr msg) pretty (TypeError _ expr msg)
= Pretty.unAnnotate = Pretty.unAnnotate
( "\n" ( "\n"
@ -4425,12 +4423,12 @@ messageExpressions f m = case m of
newtype DetailedTypeError s a = DetailedTypeError (TypeError s a) newtype DetailedTypeError s a = DetailedTypeError (TypeError s a)
deriving (Typeable) deriving (Typeable)
instance (Eq a, Pretty s, Pretty a, ToTerm a) => Show (DetailedTypeError s a) where instance (Eq a, Pretty s, Pretty a) => Show (DetailedTypeError s a) where
show = Pretty.renderString . Pretty.layoutPretty layoutOpts . Pretty.pretty show = Pretty.renderString . Pretty.layoutPretty layoutOpts . Pretty.pretty
instance (Eq a, Pretty s, Pretty a, ToTerm a, Typeable s, Typeable a) => Exception (DetailedTypeError s a) instance (Eq a, Pretty s, Pretty a, Typeable s, Typeable a) => Exception (DetailedTypeError s a)
instance (Eq a, Pretty s, Pretty a, ToTerm a) => Pretty (DetailedTypeError s a) where instance (Eq a, Pretty s, Pretty a) => Pretty (DetailedTypeError s a) where
pretty (DetailedTypeError (TypeError ctx expr msg)) pretty (DetailedTypeError (TypeError ctx expr msg))
= Pretty.unAnnotate = Pretty.unAnnotate
( "\n" ( "\n"

View File

@ -10,9 +10,6 @@ import Prelude hiding (FilePath)
import Test.Tasty (TestTree) import Test.Tasty (TestTree)
import Turtle (FilePath, (</>)) import Turtle (FilePath, (</>))
import qualified Codec.CBOR.Read as CBOR
import qualified Codec.CBOR.Term as CBOR
import qualified Codec.Serialise as Serialise
import qualified Control.Monad as Monad import qualified Control.Monad as Monad
import qualified Data.Bifunctor as Bifunctor import qualified Data.Bifunctor as Bifunctor
import qualified Data.ByteString as ByteString import qualified Data.ByteString as ByteString
@ -26,6 +23,7 @@ import qualified Dhall.Parser as Parser
import qualified Dhall.Test.Util as Test.Util import qualified Dhall.Test.Util as Test.Util
import qualified Test.Tasty as Tasty import qualified Test.Tasty as Tasty
import qualified Test.Tasty.HUnit as Tasty.HUnit import qualified Test.Tasty.HUnit as Tasty.HUnit
import qualified Text.Printf as Printf
import qualified Turtle import qualified Turtle
parseDirectory :: FilePath parseDirectory :: FilePath
@ -135,16 +133,16 @@ shouldParse path = do
expression <- Core.throws (Parser.exprFromText mempty text) expression <- Core.throws (Parser.exprFromText mempty text)
let term = Binary.encodeExpression expression let bytes = Binary.encodeExpression (Core.denote expression)
let bytes = Serialise.serialise term let render =
concatMap (Printf.printf "%02x ")
. ByteString.Lazy.unpack
Monad.unless (encoded == bytes) $ do Monad.unless (encoded == bytes) $ do
("", expected) <- Core.throws (CBOR.deserialiseFromBytes CBOR.decodeTerm encoded)
let message = "The expected CBOR representation doesn't match the actual one\n" let message = "The expected CBOR representation doesn't match the actual one\n"
++ "expected: " ++ show expected ++ "\n but got: " ++ show term ++ "expected: " ++ render encoded ++ "\n but got: " ++ render bytes
++ "\n expr: " ++ show expression ++ "\n expr: " ++ show (Core.denote expression :: Expr Void Import)
Tasty.HUnit.assertFailure message Tasty.HUnit.assertFailure message
@ -202,16 +200,29 @@ shouldNotParse path = do
shouldDecode :: Text -> TestTree shouldDecode :: Text -> TestTree
shouldDecode pathText = do shouldDecode pathText = do
let expectedFailures = [] let expectedFailures =
[ {- Note that this test actually successfully decodes the value, but
mistakenly decodes the value to `_` instead of `x`. This is
because the 55799 tag causes normal decoding to fail, so it falls
back to treating the `"x"` as a version tag instead of a label.
Either way, fixing 55799 decoding would cause this test to pass
again.
-}
binaryDecodeDirectory </> "success/unit/SelfDescribeCBORX2"
, binaryDecodeDirectory </> "success/unit/SelfDescribeCBORX3"
]
let pathString = Text.unpack pathText let pathString = Text.unpack pathText
Test.Util.testCase pathText expectedFailures (do Test.Util.testCase pathText expectedFailures (do
bytes <- ByteString.Lazy.readFile (pathString <> "A.dhallb") bytes <- ByteString.Lazy.readFile (pathString <> "A.dhallb")
term <- Core.throws (Serialise.deserialiseOrFail bytes) decodedExpression <- case Binary.decodeExpression bytes of
Left exception ->
decodedExpression <- Core.throws (Binary.decodeExpression term) Tasty.HUnit.assertFailure (show exception)
Right decodedExpression ->
return decodedExpression
text <- Text.IO.readFile (pathString <> "B.dhall") text <- Text.IO.readFile (pathString <> "B.dhall")
@ -223,7 +234,7 @@ shouldDecode pathText = do
let message = let message =
"The decoded expression didn't match the parsed expression" "The decoded expression didn't match the parsed expression"
Tasty.HUnit.assertEqual message decodedExpression strippedExpression ) Tasty.HUnit.assertEqual message strippedExpression decodedExpression )
shouldNotDecode :: Text -> TestTree shouldNotDecode :: Text -> TestTree
shouldNotDecode pathText = do shouldNotDecode pathText = do
@ -234,8 +245,6 @@ shouldNotDecode pathText = do
Test.Util.testCase pathText expectedFailures (do Test.Util.testCase pathText expectedFailures (do
bytes <- ByteString.Lazy.readFile (pathString <> ".dhallb") bytes <- ByteString.Lazy.readFile (pathString <> ".dhallb")
term <- Core.throws (Serialise.deserialiseOrFail bytes) case Binary.decodeExpression bytes :: Either Binary.DecodingFailure (Expr Void Import) of
case Binary.decodeExpression term :: Either Binary.DecodingFailure (Expr Void Import) of
Left _ -> return () Left _ -> return ()
Right _ -> Tasty.HUnit.assertFailure "Unexpected successful decode" ) Right _ -> Tasty.HUnit.assertFailure "Unexpected successful decode" )

View File

@ -10,10 +10,10 @@
module Dhall.Test.QuickCheck where module Dhall.Test.QuickCheck where
import Codec.Serialise (DeserialiseFailure(..))
import Data.Either (isRight) import Data.Either (isRight)
import Data.Either.Validation (Validation(..)) import Data.Either.Validation (Validation(..))
import Data.Monoid ((<>)) import Data.Monoid ((<>))
import Data.Void (Void)
import Dhall (ToDhall(..), FromDhall(..), auto, extract, inject, embed, Vector) import Dhall (ToDhall(..), FromDhall(..), auto, extract, inject, embed, Vector)
import Dhall.Map (Map) import Dhall.Map (Map)
import Dhall.Core import Dhall.Core
@ -53,8 +53,6 @@ import Test.Tasty.QuickCheck (QuickCheckTests(..))
import Text.Megaparsec (SourcePos(..), Pos) import Text.Megaparsec (SourcePos(..), Pos)
import qualified Control.Spoon import qualified Control.Spoon
import qualified Codec.Serialise
import qualified Data.Coerce
import qualified Data.List import qualified Data.List
import qualified Data.Sequence import qualified Data.Sequence
import qualified Data.SpecialValues import qualified Data.SpecialValues
@ -78,13 +76,6 @@ import qualified Test.Tasty
import qualified Test.Tasty.QuickCheck import qualified Test.Tasty.QuickCheck
import qualified Text.Megaparsec as Megaparsec import qualified Text.Megaparsec as Megaparsec
newtype DeserialiseFailureWithEq = D DeserialiseFailure
deriving (Show)
instance Eq DeserialiseFailureWithEq where
D (DeserialiseFailure aL bL) == D (DeserialiseFailure aR bR) =
aL == aR && bL == bR
instance (Arbitrary a, Ord a) => Arbitrary (Set a) where instance (Arbitrary a, Ord a) => Arbitrary (Set a) where
arbitrary = Dhall.Set.fromList <$> arbitrary arbitrary = Dhall.Set.fromList <$> arbitrary
shrink = map Dhall.Set.fromList . shrink . Dhall.Set.toList shrink = map Dhall.Set.fromList . shrink . Dhall.Set.toList
@ -412,21 +403,11 @@ instance Arbitrary Var where
binaryRoundtrip :: Expr () Import -> Property binaryRoundtrip :: Expr () Import -> Property
binaryRoundtrip expression = binaryRoundtrip expression =
wrap Dhall.Binary.decodeExpression (Dhall.Binary.encodeExpression denotedExpression)
(fmap === Right denotedExpression
Dhall.Binary.decodeExpression
(Codec.Serialise.deserialiseOrFail
(Codec.Serialise.serialise
(Dhall.Binary.encodeExpression expression)
)
)
)
=== wrap (Right (Right (Dhall.Core.denote expression :: Expr () Import)))
where where
wrap denotedExpression :: Expr Void Import
:: Either DeserialiseFailure a denotedExpression = Dhall.Core.denote expression
-> Either DeserialiseFailureWithEq a
wrap = Data.Coerce.coerce
everythingWellTypedNormalizes :: Expr () () -> Property everythingWellTypedNormalizes :: Expr () () -> Property
everythingWellTypedNormalizes expression = everythingWellTypedNormalizes expression =