wireform-proto 0.2.0.0 → 0.3.0.0
raw patch · 29 files changed
+2507/−87 lines, 29 filesdep ~aesondep ~basedep ~bytestring
Dependency ranges changed: aeson, base, bytestring, containers, deepseq, hashable, reflection, text, vector, wireform-core
Files
- CHANGELOG.md +22/−0
- README.md +12/−1
- src/Proto/CodeGen.hs +12/−6
- src/Proto/Internal/FieldPresence.hs +65/−0
- src/Proto/Internal/JSON.hs +7/−25
- src/Proto/Internal/JSON/Number.hs +37/−0
- src/Proto/Internal/Wire/Encode.hs +20/−11
- src/Proto/TH.hs +5/−3
- src/Proto/TH/Metadata.hs +10/−32
- test-conformance/failure_list_proto3.txt +19/−0
- test-conformance/protos/conformance.proto +87/−0
- test-conformance/protos/test_messages_proto2.proto +233/−0
- test-conformance/protos/test_messages_proto3.proto +253/−0
- test-integration/Main.hs +9/−1
- test-integration/Test/RingSink.hs +146/−0
- test-regression/Generate.hs +28/−0
- test-regression/Generated/Edition2023.hs +522/−0
- test-regression/Generated/Edition2024.hs +287/−0
- test-regression/Generated/Integermaps.hs +392/−0
- test-regression/Main.hs +151/−0
- test-regression/THFixtures.hs +14/−0
- test-regression/proto/edition2023.proto +18/−0
- test-regression/proto/edition2024.proto +11/−0
- test-regression/proto/integerMaps.proto +15/−0
- test-regression/proto/json_numbers.proto +7/−0
- test/data/derive_regression.proto +23/−0
- test/data/oneof_regression.proto +30/−0
- test/data/topenum_regression.proto +30/−0
- wireform-proto.cabal +42/−8
CHANGELOG.md view
@@ -1,6 +1,28 @@ # Changelog for wireform-proto +## 0.3.0.0 -- 2026-11-06++* Breaking generated-code change: Editions singular scalar and enum fields+ use `Maybe` for explicit presence, including the default in Edition 2023+ and later. Both standalone codegen and `loadProto` preserve explicitly+ encoded default values. File/field `EXPLICIT` and `IMPLICIT` overrides,+ including aggregate option syntax, are resolved consistently. Unsupported+ `LEGACY_REQUIRED` presence now produces a generation error.+* Accept quoted ProtoJSON numbers with `e`/`E` and signed exponents in+ generated scalar/optional/oneof float and double decoders and the runtime+ numeric helpers. Share the numeric grammar with the integer helpers;+ retain full input consumption, canonical special-value spellings and+ target-type overflow checks.+* Fix standalone integer-map encoders by applying composed value encoders+ as complete functions. Encode and size `sint32`/`sint64` map values with+ ZigZag, matching their decoders and the TH path.+* Stream large string fields across bounded ring chunks without a+ contiguous reservation larger than the ring; retain the direct-copy+ fast path and avoid intermediate pinned bytestrings.+* Add runtime regressions using real standalone-generated and TH-generated+ messages, with a reproducible fixture generator and drift check.+ ## 0.2.0.0 -- 2026-07-16 Breaking release from 0.1.0.0.
README.md view
@@ -389,7 +389,7 @@ | :-------- | -------------: | ---------: | ----: | | Small | 41.6 ns | 81.6 ns | 1.96x | | Medium | 111 ns | 212 ns | 1.90x |-| Nested | 80.10 ns | 151 ns | 1.87x |+| Nested | 81.0 ns | 151 ns | 1.87x | | Repeated | 1422 ns | 2293 ns | 1.61x | <sub>Last run 2026-06-27 11:56:42 UTC. ghc-9.8.4 on darwin-aarch64, criterion 1.6.5.</sub>@@ -404,6 +404,17 @@ --- ## Also included++### Editions field presence++Standalone generation and `loadProto` retain explicit presence for Editions+singular scalar and enum fields using `Maybe`. An explicitly encoded zero+round-trips as `Just 0`; an absent field remains `Nothing`. File and field+`features.field_presence` settings support `EXPLICIT` and `IMPLICIT`, using+both dotted and aggregate syntax. `LEGACY_REQUIRED` is rejected during+code generation because required-field validation is not implemented.+This is a source-level change from 0.2 for generated Editions records;+proto2 and proto3 carriers keep their existing shapes. ### Proto3 canonical JSON
src/Proto/CodeGen.hs view
@@ -98,6 +98,7 @@ import Proto.IDL.Options.Custom (emptyCustomOptionRegistry, extractExtensionOptions, registerCustomOption) import Proto.IDL.Parser.Resolver (ResolvedProto (..)) import Proto.Internal.CodeGen.Combinators (braceBlock, instanceHead, tshow, txt)+import Proto.Internal.FieldPresence (resolveFieldPresence) -- | Emit a Haddock doc comment block from a proto doc comment.@@ -480,8 +481,9 @@ -- | Generate a complete Haskell module as a 'Doc' from a proto file. generateModule :: GenerateOpts -> TypeRegistry -> FilePath -> ProtoFile -> Doc ann-generateModule opts reg filePath pf =- let thisMod = moduleNameForProto opts filePath pf+generateModule opts reg filePath sourceFile =+ let pf = either (error . T.unpack) id (resolveFieldPresence sourceFile)+ thisMod = moduleNameForProto opts filePath pf localTypes = collectLocalTypes (fromMaybe "" (protoPackage pf)) [] (protoTopLevels pf) ctx = GenCtx@@ -513,7 +515,7 @@ , mempty , genImports importedModules , mempty- , genFileDescriptorBinding filePath pf+ , genFileDescriptorBinding filePath sourceFile , mempty , vsep body ]@@ -1117,13 +1119,15 @@ SString -> txt "encodeFieldString " <> pretty (T.pack (show fn)) SBytes -> txt "encodeFieldBytes " <> pretty (T.pack (show fn)) SBool -> txt "encodeFieldBool " <> pretty (T.pack (show fn))+ SSInt32 -> txt "encodeFieldSVarint32 " <> pretty (T.pack (show fn))+ SSInt64 -> txt "encodeFieldSVarint64 " <> pretty (T.pack (show fn)) SDouble -> txt "encodeFieldDouble " <> pretty (T.pack (show fn)) SFloat -> txt "encodeFieldFloat " <> pretty (T.pack (show fn)) SFixed32 -> txt "encodeFieldFixed32 " <> pretty (T.pack (show fn)) SFixed64 -> txt "encodeFieldFixed64 " <> pretty (T.pack (show fn))- SSFixed32 -> txt "encodeFieldFixed32 " <> pretty (T.pack (show fn)) <> txt " . fromIntegral"- SSFixed64 -> txt "encodeFieldFixed64 " <> pretty (T.pack (show fn)) <> txt " . fromIntegral"- _ -> txt "encodeFieldVarint " <> pretty (T.pack (show fn)) <> txt " . fromIntegral"+ SSFixed32 -> txt "(encodeFieldFixed32 " <> pretty (T.pack (show fn)) <> txt " . fromIntegral)"+ SSFixed64 -> txt "(encodeFieldFixed64 " <> pretty (T.pack (show fn)) <> txt " . fromIntegral)"+ _ -> txt "(encodeFieldVarint " <> pretty (T.pack (show fn)) <> txt " . fromIntegral)" genBuildExprOneof :: GenCtx -> [Text] -> Text -> Text -> OneofDef -> Doc ann@@ -1355,6 +1359,8 @@ mapValSizeExpr :: GenCtx -> FieldType -> Doc ann mapValSizeExpr ctx = \case+ FTScalar SSInt32 -> txt "fieldSVarint32Size 2 v"+ FTScalar SSInt64 -> txt "fieldSVarint64Size 2 v" FTScalar SString -> txt "fieldTextSize 2 v" FTScalar SBytes -> txt "fieldBytesSize 2 v" FTScalar SBool -> txt "fieldBoolSize 2"
+ src/Proto/Internal/FieldPresence.hs view
@@ -0,0 +1,65 @@+{- | Resolve Editions field presence before either generator chooses field+carriers. The public parser retains the original source-level labels.+-}+module Proto.Internal.FieldPresence (resolveFieldPresence) where++import Data.List (find)+import Data.Text (Text)+import Proto.IDL.AST+import Proto.IDL.Annotations (lookupSimpleOption)+++resolveFieldPresence :: ProtoFile -> Either Text ProtoFile+resolveFieldPresence pf = case protoSyntax pf of+ Editions edition -> do+ filePresence <- presenceOption (featureFieldPresence (featuresForEdition edition)) (protoOptions pf)+ topLevels <- traverse (topLevel filePresence) (protoTopLevels pf)+ pure pf {protoTopLevels = topLevels}+ _ -> Right pf+ where+ topLevel presence = \case+ TLMessage msg -> TLMessage <$> message presence msg+ TLExtend name fields -> TLExtend name <$> traverse (field presence) fields+ other -> Right other+ message presence msg = do+ elements <- traverse (element presence) (msgElements msg)+ pure msg {msgElements = elements}+ element presence = \case+ MEField fd -> MEField <$> field presence fd+ MEMessage msg -> MEMessage <$> message presence msg+ MEExtend name fields -> MEExtend name <$> traverse (field presence) fields+ other -> Right other+ field presence fd = case fieldLabel fd of+ Just Repeated -> Right fd+ _ -> do+ resolved <- presenceOption presence (fieldOptions fd)+ pure+ fd+ { fieldLabel = case resolved of+ ExplicitPresence -> Just Optional+ ImplicitPresence -> Nothing+ LegacyRequired -> Just Required+ }+++-- Field presence is scoped to files and fields, not enclosing messages.+-- Accept both dotted and aggregate feature-option spellings.+presenceOption :: FieldPresenceFeature -> [OptionDef] -> Either Text FieldPresenceFeature+presenceOption fallback opts = case setting of+ Nothing -> Right fallback+ Just (CIdent "EXPLICIT") -> Right ExplicitPresence+ Just (CIdent "IMPLICIT") -> Right ImplicitPresence+ Just (CIdent "LEGACY_REQUIRED") ->+ Left "features.field_presence = LEGACY_REQUIRED is not supported by the code generators"+ Just _ -> Left "features.field_presence must be EXPLICIT or IMPLICIT"+ where+ setting = case find isPresence opts of+ Just option -> Just (optValue option)+ Nothing -> case lookupSimpleOption "features" opts of+ Just (CAggregate values) -> lookup "field_presence" values+ _ -> Nothing++ isPresence option = case optNameParts (optName option) of+ [SimpleOption "features", SimpleOption "field_presence"] -> True+ [SimpleOption "features.field_presence"] -> True+ _ -> False
src/Proto/Internal/JSON.hs view
@@ -89,12 +89,13 @@ import Data.Int (Int64) import Data.Map.Strict (Map) import Data.Map.Strict qualified as Map-import Data.Scientific (Scientific, fromFloatDigits, toBoundedInteger, toRealFloat)+import Data.Scientific (Scientific, fromFloatDigits, toBoundedInteger) import Data.Text (Text) import Data.Text qualified as T import Data.Text.Encoding qualified as TE import Data.Text.Lazy qualified as TL import Data.Word (Word64)+import Proto.Internal.JSON.Number qualified as Number -- | Build a JSON object from key-value pairs (for generated code).@@ -178,24 +179,9 @@ protoWord64FromJSON _ = fail "Expected uint64 string or number" -{- | Parse a 'Scientific' from a textual JSON value. Used for-the 64-bit-int code path (proto3 spec encodes them as strings-on output, accepts both shapes on input).--@reads@ is unfortunate but @Data.Text.Read@ doesn't export a-'Scientific' parser, and pulling in @attoparsec@ here just-for one helper isn't worth it.--}+-- | Parse a quoted ProtoJSON number, including exponent notation. sciFromText :: Text -> Aeson.Parser Scientific-sciFromText t- | hasLeadingSpace t = fail ("Invalid numeric string (leading whitespace): " <> show t)- | otherwise = case reads (T.unpack t) :: [(Scientific, String)] of- [(s, "")] -> pure s- _ -> fail ("Invalid numeric string: " <> show t)- where- hasLeadingSpace s = case T.uncons s of- Just (c, _) -> c == ' ' || c == '\t' || c == '\n' || c == '\r'- Nothing -> True+sciFromText = Number.numericString {- | Coerce a 'Scientific' to a bounded integral type, failing@@ -231,16 +217,12 @@ -- | Parse a 'Double' from JSON, accepting NaN\/Infinity string sentinels. protoDoubleFromJSON :: Aeson.Value -> Aeson.Parser Double-protoDoubleFromJSON (Aeson.Number n) = pure (toRealFloat n)-protoDoubleFromJSON (Aeson.String "NaN") = pure (0 / 0)-protoDoubleFromJSON (Aeson.String "Infinity") = pure (1 / 0)-protoDoubleFromJSON (Aeson.String "-Infinity") = pure (negate (1 / 0))-protoDoubleFromJSON _ = fail "Expected number or special float string"+protoDoubleFromJSON = Number.floating --- | Parse a 'Float' from JSON, accepting NaN\/Infinity string sentinels.+-- | Parse a 'Float' with ProtoJSON sentinel, exponent and overflow handling. protoFloatFromJSON :: Aeson.Value -> Aeson.Parser Float-protoFloatFromJSON v = realToFrac <$> protoDoubleFromJSON v+protoFloatFromJSON = Number.floating -- Proto3 canonical JSON: bytes as base64.
+ src/Proto/Internal/JSON/Number.hs view
@@ -0,0 +1,37 @@+-- | Shared ProtoJSON numeric parsing for runtime helpers and generated codecs.+module Proto.Internal.JSON.Number (numericString, floating) where++import Data.Aeson qualified as Aeson+import Data.Aeson.Types qualified as Aeson+import Data.Char (isSpace)+import Data.Scientific (Scientific, toRealFloat)+import Data.Text (Text)+import Data.Text qualified as T+import Data.Text.Encoding qualified as TE+++{- | Use JSON's full numeric grammar, including signed e/E exponents, with+complete consumption. Whitespace inside a quoted number is not permitted.+-}+numericString :: Text -> Aeson.Parser Scientific+numericString t+ | T.null t || T.any isSpace t = fail "Invalid numeric string (empty or whitespace)"+ | otherwise = case Aeson.eitherDecodeStrict' (TE.encodeUtf8 t) of+ Right (Aeson.Number n) -> pure n+ _ -> fail "Invalid numeric string"+++floating :: forall a. RealFloat a => Aeson.Value -> Aeson.Parser a+floating = \case+ Aeson.Number n -> finite n+ Aeson.String "NaN" -> pure (0 / 0)+ Aeson.String "Infinity" -> pure (1 / 0)+ Aeson.String "-Infinity" -> pure (negate (1 / 0))+ Aeson.String t -> numericString t >>= finite+ _ -> fail "Expected JSON number or numeric string"+ where+ finite n =+ let value = toRealFloat n :: a+ in if isInfinite value+ then fail "float/double overflow"+ else pure value
src/Proto/Internal/Wire/Encode.hs view
@@ -64,7 +64,7 @@ import Data.Text.Foreign qualified as TF import Data.Text.Internal qualified as TI import Data.Word (Word32, Word64)-import GHC.Exts (ByteArray#, Int (..), Int#, copyByteArrayToAddr#, plusAddr#)+import GHC.Exts (ByteArray#, Int (..), Int#, copyByteArrayToAddr#, isTrue#, minusAddr#, plusAddr#, (+#), (-#), (<=#)) import Proto.Internal.Wire (WireType, fieldTag) import Wireform.Builder (Builder) import Wireform.Builder qualified as B@@ -203,19 +203,28 @@ This avoids the intermediate pinned 'BS.ByteString' allocation that @'B.byteStringCopy' . 'TE.encodeUtf8'@ would force. We use-fast-builder's 'ensureBytes' to guarantee buffer space, then-construct a raw 'Builder' that 'copyByteArrayToAddr#' 's the slice-from the text's unpinned backing store into the builder's pinned-buffer. Net cost is one buffer write, no heap allocation.+a raw 'Builder' to copy each available chunk from the text's+unpinned backing store. The fitting fast path performs one copy with+no heap allocation; the overflow path advances the sink one chunk+at a time, including rings smaller than the complete text payload. -} byteArraySliceBuilder :: ByteArray# -> Int# -> Int# -> Builder byteArraySliceBuilder arr# off# len# =- FBI.ensureBytes (I# len#)- <> FBI.Builder- ( \_ (# cur#, end#, s# #) ->- case copyByteArrayToAddr# arr# off# cur# len# s# of- s'# -> (# plusAddr# cur# len#, end#, s'# #)- )+ FBI.Builder $ \sink (# cur#, end#, s# #) ->+ let available# = minusAddr# end# cur#+ in if isTrue# (len# <=# available#)+ then case copyByteArrayToAddr# arr# off# cur# len# s# of+ s'# -> (# plusAddr# cur# len#, end#, s'# #)+ else+ -- A ring cannot grant a contiguous reservation larger than itself.+ -- Fill the current chunk, request one byte to advance/flush the+ -- sink, and continue without an intermediate pinned ByteString.+ case copyByteArrayToAddr# arr# off# cur# available# s# of+ s'# ->+ FBI.unBuilder+ (FBI.ensureBytes 1 <> byteArraySliceBuilder arr# (off# +# available#) (len# -# available#))+ sink+ (# end#, end#, s'# #) {-# INLINE byteArraySliceBuilder #-}
src/Proto/TH.hs view
@@ -126,8 +126,6 @@ import GHC.Generics (Generic) import Language.Haskell.TH import Language.Haskell.TH.Syntax (addDependentFile, addModFinalizer)-import System.Directory (doesFileExist)-import System.FilePath ((</>)) import Proto.CodeGen ( FieldNaming (..), escapeReserved,@@ -156,10 +154,13 @@ import Proto.IDL.Options.Custom (emptyCustomOptionRegistry, extractExtensionOptions, registerCustomOption) import Proto.IDL.Parser (parseProtoFile, renderParseError) import Proto.Internal.Derive qualified as PDI+import Proto.Internal.FieldPresence (resolveFieldPresence) import Proto.Internal.JSON.Extension qualified as PJExt import Proto.Repr import Proto.Schema qualified as PS import Proto.TH.Metadata qualified as PTM+import System.Directory (doesFileExist)+import System.FilePath ((</>)) import Wireform.Derive.Modifier (MapKeyScalar (..)) @@ -360,7 +361,8 @@ on name clashes. -} protoFileToDeclsScoped :: FieldNaming -> RepConfig -> THHooks -> [TopLevel] -> ProtoFile -> Q [Dec]-protoFileToDeclsScoped naming cfg hooks importedTopLevels pf = do+protoFileToDeclsScoped naming cfg hooks importedTopLevels sourceFile = do+ pf <- either (fail . T.unpack) pure (resolveFieldPresence sourceFile) let scope = ScopeCtx { scSyntax = protoSyntax pf
src/Proto/TH/Metadata.hs view
@@ -102,6 +102,7 @@ import Proto.Internal.JSON qualified as PJ import Proto.Internal.JSON qualified as PJI import Proto.Internal.JSON.Extension qualified as PJExt+import Proto.Internal.JSON.Number qualified as Number import Proto.Internal.JSON.WellKnown qualified as WK import Proto.Schema qualified as PS @@ -415,11 +416,12 @@ ] --- | Read a record field expression: record-dot @msg.field@ when the--- record uses the unprefixed layout ('NoFieldSelectors' + shared field--- names), else the bare selector application @field msg@. The label for--- the record-dot form is the selector's base name — the field name in--- either layout.+{- | Read a record field expression: record-dot @msg.field@ when the+record uses the unprefixed layout ('NoFieldSelectors' + shared field+names), else the bare selector application @field msg@. The label for+the record-dot form is the selector's base name — the field name in+either layout.+-} dotRead :: Bool -> Name -> Name -> Exp dotRead recDot sel msgVar | recDot = GetFieldE (VarE msgVar) (nameBase sel)@@ -1368,20 +1370,7 @@ -} protoFloatFromJSONLenient :: forall a. (RealFloat a) => Aeson.Value -> AesonT.Parser a-protoFloatFromJSONLenient v = case v of- Aeson.Number n -> finite n- Aeson.String "NaN" -> pure (0 / 0)- Aeson.String "Infinity" -> pure (1 / 0)- Aeson.String "-Infinity" -> pure (negate (1 / 0))- Aeson.String s -> sciFromText32 s >>= finite- _ -> fail "Expected JSON Number or numeric String"- where- finite :: Sci.Scientific -> AesonT.Parser a- finite n =- let d = Sci.toRealFloat n :: a- in if isInfinite d- then fail ("float/double overflow: " <> show n)- else pure d+protoFloatFromJSONLenient = Number.floating {- | Parse @Maybe (Map Text v)@ where @v@ is a generated@@ -1636,20 +1625,9 @@ _ -> fail "Expected JSON Number or String for UInt32" -{- | Parse 'Scientific' from a JSON-quoted numeric string. We-can't reuse 'PJ.sciFromText' without adding a Proto.Internal.JSON-dependency edge, so duplicate the trivial implementation.--}+-- | Shared full-consumption ProtoJSON number parser. sciFromText32 :: Text -> AesonT.Parser Sci.Scientific-sciFromText32 t- | hasLeadingWs t = fail ("Invalid numeric string (leading whitespace): " <> show t)- | otherwise = case reads (T.unpack t) :: [(Sci.Scientific, String)] of- [(s, "")] -> pure s- _ -> fail ("Invalid numeric string: " <> show t)- where- hasLeadingWs s = case T.uncons s of- Just (c, _) -> c == ' ' || c == '\t' || c == '\n' || c == '\r'- Nothing -> True+sciFromText32 = Number.numericString {- | Bounded-integer narrowing for 32-bit fields. Mirrors
+ test-conformance/failure_list_proto3.txt view
@@ -0,0 +1,19 @@+# Expected-failure list for the protobuf-conformance suite.+# Covers BOTH the proto3 and the proto2 binary/json suites+# (despite the legacy filename) — the conformance runner+# applies a single failure list to whichever suite it's+# currently running.+#+# Status: 0 expected failures / 2675 successes / 0 skipped /+# 0 unexpected failures.+#+# Add an entry below when a test is intentionally not supported+# (open issues should be tracked separately). The runner emits+# every test under a unique '<level>.<edition>.<input>.<test+# name>.<output>' identifier — paste the full identifier on its+# own line to suppress it.+#+# Re-run procedure:+# cabal test wireform-proto:protobuf-conformance-test \+# 2>&1 | tee /tmp/conformance.log+# then mine /tmp/conformance.log for new failure names.
+ test-conformance/protos/conformance.proto view
@@ -0,0 +1,87 @@+// Vendored from https://github.com/protocolbuffers/protobuf/blob/main/conformance/conformance.proto+//+// This is the wire protocol the upstream conformance_test_runner+// uses to drive any test program. Each test sends one+// ConformanceRequest over stdin and expects one ConformanceResponse+// back over stdout. See the upstream README for the runner's+// command-line interface and the test categories it covers.+//+// We pull this in with loadProto in+// `Test.Conformance.RunnerInstances` so the runner binary's+// codecs come from the same machinery the rest of wireform-proto+// uses (and consequently a regression in the deriver shows up+// immediately as a conformance-suite mismatch).++syntax = "proto3";++package conformance;++enum WireFormat {+ UNSPECIFIED = 0;+ PROTOBUF = 1;+ JSON = 2;+ JSPB = 3;+ TEXT_FORMAT = 4;+}++enum TestCategory {+ UNSPECIFIED_TEST = 0;+ BINARY_TEST = 1;+ JSON_TEST = 2;+ JSON_IGNORE_UNKNOWN_PARSING_TEST = 3;+ JSPB_TEST = 4;+ TEXT_FORMAT_TEST = 5;+}++message TestStatus {+ string name = 1;+ string failure_message = 2;+ string matched_name = 3;+}++message FailureSet {+ repeated TestStatus test = 2;+}++message ConformanceRequest {+ // The upstream schema models payload as a oneof; we honour+ // that. The bridge's auto-detected oneof codec generates a+ // sum-of-tagged-singletons carrier (ConformanceRequest'Payload)+ // plus the wire round-trip in 'Proto.Derive.Internal'.+ oneof payload {+ bytes protobuf_payload = 1;+ string json_payload = 2;+ string jspb_payload = 7;+ string text_payload = 8;+ }++ WireFormat requested_output_format = 3;+ string message_type = 4;+ TestCategory test_category = 5;++ JspbEncodingConfig jspb_encoding_options = 6;+ bool print_unknown_fields = 9;+}++message ConformanceResponse {+ // The upstream schema is also a oneof. Routing our response+ // through the oneof carrier means the wire-format presence of+ // the chosen arm is preserved end-to-end, which the conformance+ // runner relies on (an empty 'protobuf_payload' must be+ // distinguishable from an absent one).+ oneof result {+ string parse_error = 1;+ string serialize_error = 6;+ string timeout_error = 9;+ string runtime_error = 2;+ bytes protobuf_payload = 3;+ string json_payload = 4;+ string skipped = 5;+ string jspb_payload = 7;+ string text_payload = 8;+ }+}++message JspbEncodingConfig {+ bool use_jspb_array_any_format = 1;+}
+ test-conformance/protos/test_messages_proto2.proto view
@@ -0,0 +1,233 @@+// Stripped vendor of upstream protobuf+// src/google/protobuf/test_messages_proto2.proto. We drop the+// proto2 features 'loadProto' doesn't yet support and that the+// conformance suite doesn't strictly require:+//+// * 'group' declarations and group-typed fields (legacy+// proto2-only wire format using START_GROUP / END_GROUP).+// Tests targeting them stay 'skipped'.+// * 'message_set_wire_format' (proto1-era extension layout).+//+// The remaining surface (singular optional/required scalars,+// repeated, packed/unpacked, maps, enums, oneofs, recursive+// messages, default field values, extensions declarations,+// and the FieldName1..18 stress-test fields) is the spec area+// the binary/json conformance suite exercises.++syntax = "proto2";++package protobuf_test_messages.proto2;++message TestAllTypesProto2 {+ message NestedMessage {+ optional int32 a = 1;+ optional TestAllTypesProto2 corecursive = 2;+ }++ enum NestedEnum {+ FOO = 0;+ BAR = 1;+ BAZ = 2;+ NEG = -1;+ }++ // Singular+ optional int32 optional_int32 = 1;+ optional int64 optional_int64 = 2;+ optional uint32 optional_uint32 = 3;+ optional uint64 optional_uint64 = 4;+ optional sint32 optional_sint32 = 5;+ optional sint64 optional_sint64 = 6;+ optional fixed32 optional_fixed32 = 7;+ optional fixed64 optional_fixed64 = 8;+ optional sfixed32 optional_sfixed32 = 9;+ optional sfixed64 optional_sfixed64 = 10;+ optional float optional_float = 11;+ optional double optional_double = 12;+ optional bool optional_bool = 13;+ optional string optional_string = 14;+ optional bytes optional_bytes = 15;++ optional NestedMessage optional_nested_message = 18;+ optional ForeignMessageProto2 optional_foreign_message = 19;++ optional NestedEnum optional_nested_enum = 21;+ optional ForeignEnumProto2 optional_foreign_enum = 22;++ optional TestAllTypesProto2 recursive_message = 27;++ // Repeated+ repeated int32 repeated_int32 = 31;+ repeated int64 repeated_int64 = 32;+ repeated uint32 repeated_uint32 = 33;+ repeated uint64 repeated_uint64 = 34;+ repeated sint32 repeated_sint32 = 35;+ repeated sint64 repeated_sint64 = 36;+ repeated fixed32 repeated_fixed32 = 37;+ repeated fixed64 repeated_fixed64 = 38;+ repeated sfixed32 repeated_sfixed32 = 39;+ repeated sfixed64 repeated_sfixed64 = 40;+ repeated float repeated_float = 41;+ repeated double repeated_double = 42;+ repeated bool repeated_bool = 43;+ repeated string repeated_string = 44;+ repeated bytes repeated_bytes = 45;++ repeated NestedMessage repeated_nested_message = 48;+ repeated ForeignMessageProto2 repeated_foreign_message = 49;++ repeated NestedEnum repeated_nested_enum = 51;+ repeated ForeignEnumProto2 repeated_foreign_enum = 52;++ // Packed+ repeated int32 packed_int32 = 75 [packed = true];+ repeated int64 packed_int64 = 76 [packed = true];+ repeated uint32 packed_uint32 = 77 [packed = true];+ repeated uint64 packed_uint64 = 78 [packed = true];+ repeated sint32 packed_sint32 = 79 [packed = true];+ repeated sint64 packed_sint64 = 80 [packed = true];+ repeated fixed32 packed_fixed32 = 81 [packed = true];+ repeated fixed64 packed_fixed64 = 82 [packed = true];+ repeated sfixed32 packed_sfixed32 = 83 [packed = true];+ repeated sfixed64 packed_sfixed64 = 84 [packed = true];+ repeated float packed_float = 85 [packed = true];+ repeated double packed_double = 86 [packed = true];+ repeated bool packed_bool = 87 [packed = true];+ repeated NestedEnum packed_nested_enum = 88 [packed = true];++ // Unpacked+ repeated int32 unpacked_int32 = 89 [packed = false];+ repeated int64 unpacked_int64 = 90 [packed = false];+ repeated uint32 unpacked_uint32 = 91 [packed = false];+ repeated uint64 unpacked_uint64 = 92 [packed = false];+ repeated sint32 unpacked_sint32 = 93 [packed = false];+ repeated sint64 unpacked_sint64 = 94 [packed = false];+ repeated fixed32 unpacked_fixed32 = 95 [packed = false];+ repeated fixed64 unpacked_fixed64 = 96 [packed = false];+ repeated sfixed32 unpacked_sfixed32 = 97 [packed = false];+ repeated sfixed64 unpacked_sfixed64 = 98 [packed = false];+ repeated float unpacked_float = 99 [packed = false];+ repeated double unpacked_double = 100 [packed = false];+ repeated bool unpacked_bool = 101 [packed = false];+ repeated NestedEnum unpacked_nested_enum = 102 [packed = false];++ // Map+ map<int32, int32> map_int32_int32 = 56;+ map<int64, int64> map_int64_int64 = 57;+ map<uint32, uint32> map_uint32_uint32 = 58;+ map<uint64, uint64> map_uint64_uint64 = 59;+ map<sint32, sint32> map_sint32_sint32 = 60;+ map<sint64, sint64> map_sint64_sint64 = 61;+ map<fixed32, fixed32> map_fixed32_fixed32 = 62;+ map<fixed64, fixed64> map_fixed64_fixed64 = 63;+ map<sfixed32, sfixed32> map_sfixed32_sfixed32 = 64;+ map<sfixed64, sfixed64> map_sfixed64_sfixed64 = 65;+ map<int32, float> map_int32_float = 66;+ map<int32, double> map_int32_double = 67;+ map<bool, bool> map_bool_bool = 68;+ map<string, string> map_string_string = 69;+ map<string, bytes> map_string_bytes = 70;+ map<string, NestedMessage> map_string_nested_message = 71;+ map<string, ForeignMessageProto2> map_string_foreign_message = 72;+ map<string, NestedEnum> map_string_nested_enum = 73;+ map<string, ForeignEnumProto2> map_string_foreign_enum = 74;++ oneof oneof_field {+ uint32 oneof_uint32 = 111;+ NestedMessage oneof_nested_message = 112;+ string oneof_string = 113;+ bytes oneof_bytes = 114;+ bool oneof_bool = 115;+ uint64 oneof_uint64 = 116;+ float oneof_float = 117;+ double oneof_double = 118;+ NestedEnum oneof_enum = 119;+ }++ // Extensions placeholder (parser supports the declaration;+ // the actual extension fields are in the `extend` blocks+ // below).+ extensions 120 to 200;++ // Default values+ optional int32 default_int32 = 241 [default = -123456789];+ optional int64 default_int64 = 242 [default = -9123456789123456789];+ optional uint32 default_uint32 = 243 [default = 2123456789];+ optional uint64 default_uint64 = 244 [default = 10123456789123456789];+ optional sint32 default_sint32 = 245 [default = -123456789];+ optional sint64 default_sint64 = 246 [default = -9123456789123456789];+ optional fixed32 default_fixed32 = 247 [default = 2123456789];+ optional fixed64 default_fixed64 = 248 [default = 10123456789123456789];+ optional sfixed32 default_sfixed32 = 249 [default = -123456789];+ optional sfixed64 default_sfixed64 = 250 [default = -9123456789123456789];+ optional float default_float = 251 [default = 9e9];+ optional double default_double = 252 [default = 7e22];+ optional bool default_bool = 253 [default = true];+ optional string default_string = 254 [default = "Rosebud"];+ optional bytes default_bytes = 255 [default = "joshua"];++ // FieldName1..18 stress test (proto3 spec for snake_case <->+ // lowerCamelCase JSON name conversion applies to proto2 too).+ optional int32 fieldname1 = 401;+ optional int32 field_name2 = 402;+ optional int32 _field_name3 = 403;+ optional int32 field__name4_ = 404;+ optional int32 field0name5 = 405;+ optional int32 field_0_name6 = 406;+ optional int32 fieldName7 = 407;+ optional int32 FieldName8 = 408;+ optional int32 field_Name9 = 409;+ optional int32 Field_Name10 = 410;+ optional int32 FIELD_NAME11 = 411;+ optional int32 FIELD_name12 = 412;+ optional int32 __field_name13 = 413;+ optional int32 __Field_name14 = 414;+ optional int32 field__name15 = 415;+ optional int32 field__Name16 = 416;+ optional int32 field_name17__ = 417;+ optional int32 Field_name18__ = 418;++ // Reserved for unknown fields test.+ reserved 1000 to 9999;+}++message ForeignMessageProto2 {+ optional int32 c = 1;+}++enum ForeignEnumProto2 {+ FOREIGN_FOO = 0;+ FOREIGN_BAR = 1;+ FOREIGN_BAZ = 2;+}++extend TestAllTypesProto2 {+ optional int32 extension_int32 = 120;+}++message UnknownToTestAllTypes {+ optional int32 optional_int32 = 1001;+ optional string optional_string = 1002;+ optional ForeignMessageProto2 nested_message = 1003;+ optional bool optional_bool = 1006;+ repeated int32 repeated_int32 = 1011;+}++message NullHypothesisProto2 {}++message EnumOnlyProto2 {+ enum Bool {+ kFalse = 0;+ kTrue = 1;+ }+}++message OneStringProto2 {+ optional string data = 1;+}++message ProtoWithKeywords {+ optional int32 inline = 1;+ optional string concept = 2;+ repeated string requires = 3;+}
+ test-conformance/protos/test_messages_proto3.proto view
@@ -0,0 +1,253 @@+// Vendored (subset) from+// https://github.com/protocolbuffers/protobuf/blob/main/src/google/protobuf/test_messages_proto3.proto+//+// We carry the full structural shape of TestAllTypesProto3+// including the Well-Known-Types arms (Timestamp, Duration, Any,+// Struct, Wrappers, FieldMask, Empty, Value, ListValue,+// NullValue). 'loadProto' doesn't follow proto @import@s, so we+// can't @import "google/protobuf/timestamp.proto"@ here; instead+// the bridge has a hard-coded WKT registry (see+// 'Proto.TH.lookupWkt') that resolves any @FTNamed+// "google.protobuf.X"@ reference to the corresponding pre-+// generated 'Proto.Google.Protobuf.X' type.+//+// To avoid duplicating the WKT type names in this file (the+// parser doesn't know they're WKTs), we cite them by their+// fully-qualified proto names. The bridge's WKT registry then+// maps each FQN to the right Haskell type.++syntax = "proto3";++package protobuf_test_messages.proto3;++message TestAllTypesProto3 {+ message NestedMessage {+ int32 a = 1;+ TestAllTypesProto3 corecursive = 2;+ }++ enum NestedEnum {+ FOO = 0;+ BAR = 1;+ BAZ = 2;+ NEG = -1;+ }++ // Mirrors upstream test_messages_proto3 — multiple proto-side+ // names share wire number 2 (allow_alias). The conformance+ // EnumFieldWithAlias{*} tests fire on this enum.+ enum AliasedEnum {+ option allow_alias = true;++ ALIAS_FOO = 0;+ ALIAS_BAR = 1;+ ALIAS_BAZ = 2;+ MOO = 2;+ moo = 2;+ bAz = 2;+ }++ // Singular scalars.+ int32 optional_int32 = 1;+ int64 optional_int64 = 2;+ uint32 optional_uint32 = 3;+ uint64 optional_uint64 = 4;+ sint32 optional_sint32 = 5;+ sint64 optional_sint64 = 6;+ fixed32 optional_fixed32 = 7;+ fixed64 optional_fixed64 = 8;+ sfixed32 optional_sfixed32 = 9;+ sfixed64 optional_sfixed64 = 10;+ float optional_float = 11;+ double optional_double = 12;+ bool optional_bool = 13;+ string optional_string = 14;+ bytes optional_bytes = 15;++ NestedMessage optional_nested_message = 18;+ ForeignMessage optional_foreign_message = 19;++ NestedEnum optional_nested_enum = 21;+ ForeignEnum optional_foreign_enum = 22;+ AliasedEnum optional_aliased_enum = 23;++ TestAllTypesProto3 recursive_message = 27;++ // Repeated scalars (default proto3 = packed).+ repeated int32 repeated_int32 = 31;+ repeated int64 repeated_int64 = 32;+ repeated uint32 repeated_uint32 = 33;+ repeated uint64 repeated_uint64 = 34;+ repeated sint32 repeated_sint32 = 35;+ repeated sint64 repeated_sint64 = 36;+ repeated fixed32 repeated_fixed32 = 37;+ repeated fixed64 repeated_fixed64 = 38;+ repeated sfixed32 repeated_sfixed32 = 39;+ repeated sfixed64 repeated_sfixed64 = 40;+ repeated float repeated_float = 41;+ repeated double repeated_double = 42;+ repeated bool repeated_bool = 43;+ repeated string repeated_string = 44;+ repeated bytes repeated_bytes = 45;++ repeated NestedMessage repeated_nested_message = 48;+ repeated ForeignMessage repeated_foreign_message = 49;++ repeated NestedEnum repeated_nested_enum = 51;+ repeated ForeignEnum repeated_foreign_enum = 52;++ // Packed (explicit) — same wire format as proto3 default+ // for packable scalars; exercised separately to make sure the+ // packed encoder path is hit when the user writes [packed = true].+ repeated int32 packed_int32 = 75 [packed = true];+ repeated int64 packed_int64 = 76 [packed = true];+ repeated uint32 packed_uint32 = 77 [packed = true];+ repeated uint64 packed_uint64 = 78 [packed = true];+ repeated sint32 packed_sint32 = 79 [packed = true];+ repeated sint64 packed_sint64 = 80 [packed = true];+ repeated fixed32 packed_fixed32 = 81 [packed = true];+ repeated fixed64 packed_fixed64 = 82 [packed = true];+ repeated sfixed32 packed_sfixed32 = 83 [packed = true];+ repeated sfixed64 packed_sfixed64 = 84 [packed = true];+ repeated float packed_float = 85 [packed = true];+ repeated double packed_double = 86 [packed = true];+ repeated bool packed_bool = 87 [packed = true];+ repeated NestedEnum packed_nested_enum = 88 [packed = true];++ // Unpacked (explicit).+ repeated int32 unpacked_int32 = 89 [packed = false];+ repeated int64 unpacked_int64 = 90 [packed = false];+ repeated uint32 unpacked_uint32 = 91 [packed = false];+ repeated uint64 unpacked_uint64 = 92 [packed = false];+ repeated sint32 unpacked_sint32 = 93 [packed = false];+ repeated sint64 unpacked_sint64 = 94 [packed = false];+ repeated fixed32 unpacked_fixed32 = 95 [packed = false];+ repeated fixed64 unpacked_fixed64 = 96 [packed = false];+ repeated sfixed32 unpacked_sfixed32 = 97 [packed = false];+ repeated sfixed64 unpacked_sfixed64 = 98 [packed = false];+ repeated float unpacked_float = 99 [packed = false];+ repeated double unpacked_double = 100 [packed = false];+ repeated bool unpacked_bool = 101 [packed = false];+ repeated NestedEnum unpacked_nested_enum = 102 [packed = false];++ // Maps. Submessage / enum-valued maps come through as proto+ // sub-messages whose value type is one of the FTNamed shapes;+ // loadProto currently treats those as opaque submessages on+ // the value side, which is enough for wire-format round-trip.+ map<int32, int32> map_int32_int32 = 56;+ map<int64, int64> map_int64_int64 = 57;+ map<uint32, uint32> map_uint32_uint32 = 58;+ map<uint64, uint64> map_uint64_uint64 = 59;+ map<sint32, sint32> map_sint32_sint32 = 60;+ map<sint64, sint64> map_sint64_sint64 = 61;+ map<fixed32, fixed32> map_fixed32_fixed32 = 62;+ map<fixed64, fixed64> map_fixed64_fixed64 = 63;+ map<sfixed32, sfixed32> map_sfixed32_sfixed32 = 64;+ map<sfixed64, sfixed64> map_sfixed64_sfixed64 = 65;+ map<int32, float> map_int32_float = 66;+ map<int32, double> map_int32_double = 67;+ map<bool, bool> map_bool_bool = 68;+ map<string, string> map_string_string = 69;+ map<string, bytes> map_string_bytes = 70;+ // Submessage / enum-valued maps. The wire form is a repeated+ // MapEntry message; the JSON form is a JSON object whose+ // values are recursively the inner message / enum's JSON+ // shape. Conformance MessageMapField.* exercises the message+ // case; IgnoreUnknownEnumStringValueInMap*, etc. exercise the+ // enum case.+ map<string, NestedMessage> map_string_nested_message = 71;+ map<string, NestedEnum> map_string_nested_enum = 73;++ // Oneof.+ oneof oneof_field {+ uint32 oneof_uint32 = 111;+ NestedMessage oneof_nested_message = 112;+ string oneof_string = 113;+ bytes oneof_bytes = 114;+ bool oneof_bool = 115;+ uint64 oneof_uint64 = 116;+ float oneof_float = 117;+ double oneof_double = 118;+ NestedEnum oneof_enum = 119;+ google.protobuf.NullValue oneof_null_value = 120;+ }++ // FieldName1..18 stress test: exercises proto3's spec for+ // converting between snake_case proto names and the JSON+ // canonical camelCase form. The bridge handles both keys (the+ // 'parseBindStmt' fallback in 'Proto.TH.Metadata') and the+ // proto3-canonical-JSON output writes the camelCase form.+ int32 fieldname1 = 401;+ int32 field_name2 = 402;+ int32 _field_name3 = 403;+ int32 field__name4_ = 404;+ int32 field0name5 = 405;+ int32 field_0_name6 = 406;+ int32 fieldName7 = 407;+ int32 FieldName8 = 408;+ int32 field_Name9 = 409;+ int32 Field_Name10 = 410;+ int32 FIELD_NAME11 = 411;+ int32 FIELD_name12 = 412;+ int32 __field_name13 = 413;+ int32 __Field_name14 = 414;+ int32 field__name15 = 415;+ int32 field__Name16 = 416;+ int32 field_name17__ = 417;+ int32 Field_name18__ = 418;++ // Well-known-types: routed through the WKT registry in+ // 'Proto.TH'. The proto AST sees these as FTNamed types; the+ // bridge resolves each FQN to the corresponding pre-generated+ // 'Proto.Google.Protobuf.X' Haskell type.+ google.protobuf.BoolValue optional_bool_wrapper = 201;+ google.protobuf.Int32Value optional_int32_wrapper = 202;+ google.protobuf.Int64Value optional_int64_wrapper = 203;+ google.protobuf.UInt32Value optional_uint32_wrapper = 204;+ google.protobuf.UInt64Value optional_uint64_wrapper = 205;+ google.protobuf.FloatValue optional_float_wrapper = 206;+ google.protobuf.DoubleValue optional_double_wrapper = 207;+ google.protobuf.StringValue optional_string_wrapper = 208;+ google.protobuf.BytesValue optional_bytes_wrapper = 209;++ repeated google.protobuf.BoolValue repeated_bool_wrapper = 211;+ repeated google.protobuf.Int32Value repeated_int32_wrapper = 212;+ repeated google.protobuf.Int64Value repeated_int64_wrapper = 213;+ repeated google.protobuf.UInt32Value repeated_uint32_wrapper = 214;+ repeated google.protobuf.UInt64Value repeated_uint64_wrapper = 215;+ repeated google.protobuf.FloatValue repeated_float_wrapper = 216;+ repeated google.protobuf.DoubleValue repeated_double_wrapper = 217;+ repeated google.protobuf.StringValue repeated_string_wrapper = 218;+ repeated google.protobuf.BytesValue repeated_bytes_wrapper = 219;++ google.protobuf.Duration optional_duration = 301;+ google.protobuf.Timestamp optional_timestamp = 302;+ google.protobuf.FieldMask optional_field_mask = 303;+ google.protobuf.Struct optional_struct = 304;+ google.protobuf.Any optional_any = 305;+ google.protobuf.Value optional_value = 306;+ google.protobuf.NullValue optional_null_value = 307;+ google.protobuf.Empty optional_empty = 308;++ repeated google.protobuf.Duration repeated_duration = 311;+ repeated google.protobuf.Timestamp repeated_timestamp = 312;+ repeated google.protobuf.FieldMask repeated_fieldmask = 313;+ repeated google.protobuf.Struct repeated_struct = 324;+ repeated google.protobuf.Any repeated_any = 315;+ repeated google.protobuf.Value repeated_value = 316;+ repeated google.protobuf.ListValue repeated_list_value = 317;+ repeated google.protobuf.Empty repeated_empty = 318;++ reserved 501 to 510;+ reserved 999999;+}++message ForeignMessage {+ int32 c = 1;+}++enum ForeignEnum {+ FOREIGN_FOO = 0;+ FOREIGN_BAR = 1;+ FOREIGN_BAZ = 2;+}
test-integration/Main.hs view
@@ -1,5 +1,6 @@ module Main (main) where +import Hedgehog.Internal.Config (resolveSkip) import Test.CodeGen (codeGenTests) import Test.Collect (collectTests) import Test.Compat (compatTests)@@ -10,6 +11,7 @@ import Test.Parser (parserTests) import Test.PrintInspect (printInspectTests) import Test.Resolver (resolverTests)+import Test.RingSink (ringSinkTests) import Test.Roundtrip (roundtripTests) import Test.Schema (schemaTests) import Test.StreamCodec (streamCodecTests)@@ -21,13 +23,19 @@ main :: IO ()-main =+main = do+ -- Force Hedgehog's "HEDGEHOG_SKIP" CAF before sydtest starts its workers.+ -- Every property forces it first; when many capabilities hit it at once,+ -- GHC 9.8.4's RTS can lose the wakeup of a thread blocked on it, leaving+ -- one property hung until the per-test timeout.+ _ <- resolveSkip Nothing sydTest $ describe "wireform-proto" $ sequence_ [ parserTests , wireTests+ , ringSinkTests , roundtripTests , codeGenTests , wellKnownTests
+ test-integration/Test/RingSink.hs view
@@ -0,0 +1,146 @@+{-# LANGUAGE BangPatterns #-}++{- | Proto encoders through the ring-direct send sink.++'putText' reserves a string field's whole UTF-8 length with one+'ensureBytes', so a field longer than the send ring is a request+no contiguous ring region can satisfy. These properties drive+such fields through 'sendBuilderDirect' and check the bytes match+the reference ('B.toStrictByteString') encoding.+-}+module Test.RingSink (ringSinkTests) where++import Data.Bits (shiftR, (.&.))+import Data.ByteString (ByteString)+import Data.ByteString qualified as BS+import Data.Char (chr)+import Data.IORef+import Data.Text (Text)+import Data.Text qualified as T+import Data.Word (Word64)+import Foreign.Ptr (castPtr, plusPtr)+import Hedgehog+import Hedgehog.Gen qualified as Gen+import Hedgehog.Range qualified as Range+import Proto.Encode (buildMessage, encodeFieldString)+import Proto.Google.Protobuf.Wrappers (StringValue (..))+import Test.Syd+import Test.Syd.Hedgehog ()+import Wireform.Builder qualified as B+import Wireform.Ring.Internal (ringBase, ringMask, ringSize, withMagicRing)+import Wireform.Transport.Send+++ringSinkTests :: Spec+ringSinkTests = describe "RingSink (sendBuilderDirect)" $ do+ it "string field at ring boundaries matches the reference encoding" $+ property $ do+ ringSz <- genRingSize+ n <- forAll (Gen.element (boundaryLens ringSz))+ corked <- forAll Gen.bool+ let txt = asciiText n+ sent <- evalIO (sendVia ringSz corked (encodeFieldString 1 txt))+ sent === B.toStrictByteString (encodeFieldString 1 txt)++ it "generated StringValue with mixed-width UTF-8 matches the reference encoding" $+ property $ do+ ringSz <- genRingSize+ n <-+ forAll $+ Gen.frequency+ [ (3, Gen.element (boundaryLens ringSz))+ , (1, Gen.int (Range.linear 0 (3 * ringSz)))+ ]+ seed <- forAll (Gen.word64 Range.constantBounded)+ corked <- forAll Gen.bool+ let txt = mixedText n seed+ let msg = StringValue {stringValueValue = txt, stringValueUnknownFields = []}+ sent <- evalIO (sendVia ringSz corked (buildMessage msg))+ sent === B.toStrictByteString (buildMessage msg)+++-- | A ring of 1x, 2x or 4x the host page size.+genRingSize :: PropertyT IO Int+genRingSize = do+ page <- evalIO (withMagicRing 1 (pure . ringSize))+ mult <- forAll (Gen.element [1, 2, 4])+ ringSz <- evalIO (withMagicRing (mult * page) (pure . ringSize))+ annotateShow ringSz+ pure ringSz+++-- | The ring size, its doubles and the sink's 32 KiB chunk, each ±1.+boundaryLens :: Int -> [Int]+boundaryLens ringSz =+ filter (>= 0) $+ concatMap (\b -> fmap (b +) [-1, 0, 1]) [ringSz, 2 * ringSz, 32768]+++{- | Text of exactly @n@ UTF-8 bytes mixing 1- to 4-byte code+points, drawn from @seed@. Built directly rather than via+'Gen.text': generating and shrink-tracking tens of thousands of+characters costs ~70 MB per case, while this costs one 'Text'.+-}+mixedText :: Int -> Word64 -> Text+mixedText n seed0 = T.unfoldr step (n, seed0)+ where+ step (!left, !s)+ | left <= 0 = Nothing+ | otherwise =+ let !s' = s * 6364136223846793005 + 1442695040888963407+ !r = fromIntegral (s' `shiftR` 33) :: Int+ !w = 1 + r `rem` min 4 left+ !c = case w of+ 1 -> chr (0x61 + r `rem` 26) -- a-z+ 2 -> chr (0xE0 + r `rem` 0x100) -- U+00E0..U+01DF+ 3 -> chr (0x4E00 + r `rem` 0x1000) -- CJK, no surrogates+ _ -> chr (0x1F600 + r `rem` 0x40) -- emoji+ in Just (c, (left - w, s'))+++-- | @n@ ASCII characters, so the UTF-8 length is exactly @n@.+asciiText :: Int -> Text+asciiText n = T.unfoldrN n (\k -> Just (chr (97 + k `rem` 26), k + 1)) (0 :: Int)+++{- | Run a builder through 'sendBuilderDirect' over an in-memory+ring whose consumer drains inline on every publish, and return+everything it drained.+-}+sendVia :: Int -> Bool -> B.Builder -> IO ByteString+sendVia requested corked b = withMagicRing requested $ \ring -> do+ let !base = ringBase ring+ !sz = ringSize ring+ !msk = ringMask ring+ chunksRef <- newIORef ([] :: [ByteString])+ headRef <- newIORef (0 :: Word64)+ tailRef <- newIORef (0 :: Word64)+ let drainTo !hd = readIORef tailRef >>= go+ where+ go !cur+ | cur >= hd = pure ()+ | otherwise = do+ let !off = fromIntegral cur .&. msk+ !want = min (fromIntegral (hd - cur)) (sz - off)+ chunk <- BS.packCStringLen (castPtr (base `plusPtr` off), want)+ modifyIORef' chunksRef (chunk :)+ let !next = cur + fromIntegral want+ writeIORef tailRef next+ go next+ t =+ SendTransport+ { sendRingBase = base+ , sendRingSize = sz+ , sendRingMask = msk+ , sendLoadTail = readIORef tailRef+ , sendLoadHead = readIORef headRef+ , sendPublishHead = \h -> writeIORef headRef h >> drainTo h+ , sendWaitSpace = \_ -> SendSpaceAvailable <$> readIORef tailRef+ , sendFlush = readIORef headRef >>= drainTo+ , sendShutdownWrite = pure ()+ , sendClose = pure ()+ }+ if corked+ then withSendCork t (`sendBuilderDirect` b)+ else sendBuilderDirect t b+ BS.concat . reverse <$> readIORef chunksRef
+ test-regression/Generate.hs view
@@ -0,0 +1,28 @@+{-# LANGUAGE ImportQualifiedPost #-}+{-# LANGUAGE OverloadedStrings #-}++-- Run from wireform-proto: cabal exec -- runghc test-regression/Generate.hs+module Main where++import Data.Map.Strict qualified as Map+import Data.Text qualified as T+import Data.Text.IO qualified as TIO+import Proto.CodeGen+import Proto.IDL.Parser+import Proto.IDL.Parser.Resolver (ResolvedProto (..))+import System.Directory (createDirectoryIfMissing)+import System.FilePath (takeDirectory, (</>))+++main :: IO ()+main = mapM_ generate ["edition2023", "edition2024", "integerMaps"]+ where+ generate name = do+ let file = name <> ".proto"+ source <- TIO.readFile ("test-regression/proto" </> file)+ pf <- either (fail . show) pure (parseProtoFile file source)+ let opts = defaultGenerateOpts {genModulePrefix = "Generated"}+ registry = buildTypeRegistry opts [(file, ResolvedProto pf file Map.empty)]+ output = "test-regression" </> T.unpack (T.replace "." "/" (moduleNameForProto opts file pf)) <> ".hs"+ createDirectoryIfMissing True (takeDirectory output)+ TIO.writeFile output (generateModuleText opts registry file pf)
+ test-regression/Generated/Edition2023.hs view
@@ -0,0 +1,522 @@+{-# LANGUAGE StrictData #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE OverloadedRecordDot #-}+{-# OPTIONS_GHC -Wno-unused-imports -Wno-unused-matches -Wno-unused-top-binds #-}+-- | Auto-generated protobuf types.+--+-- __THIS FILE IS AUTO-GENERATED BY wireform. DO NOT EDIT.__+--+-- Any manual changes will be overwritten the next time code+-- generation is run. To modify the types or instances, edit the+-- @.proto@ source file and re-run the code generator.+module Generated.Edition2023 where++import Data.ByteString (ByteString)+import qualified Data.ByteString as BS+import qualified Wireform.Builder as B+import Data.Int (Int32, Int64)+import Data.Text (Text)+import qualified Data.Text as T+import Data.Word (Word32, Word64)+import qualified Data.Map.Strict as Map+import qualified Data.Vector as V+import qualified Data.Vector.Unboxed as VU+import GHC.Generics (Generic)+import Control.DeepSeq (NFData(..))+import Data.Hashable (Hashable(..))+import Proto.Encode+import Proto.Decode+import qualified Data.Aeson as Aeson+import qualified Data.Aeson.Types as Aeson+import qualified Data.Aeson.Key as AesonKey+import qualified Data.Aeson.KeyMap as AesonKM+import Proto.Internal.JSON (jsonObject, (.=:), parseFieldMaybe, bytesFieldToJSON, parseBytesFieldMaybe, bytesMapFieldToJSON, parseBytesMapFieldMaybe, protoBytesToJSON)+import Data.Proxy (Proxy(..))+import Data.Reflection (Given(..), given)+import Proto.Internal.JSON.Extension qualified as PJExt+import Proto.Schema (ProtoMessage(..), SomeFieldDescriptor(..), FieldDescriptor(..), FieldTypeDescriptor(..), ScalarFieldType(..), FieldLabel'(..))+import Proto.Registry (IsMessage)+import Proto.Registry qualified+import qualified Proto.Extension+import Proto.Internal.Wire (Tag(..), WireType(..))+import Proto.Internal.Wire.Encode (putTag, putVarint, putFixed32, putFixed64,+ putFloat, putDouble, putText, putByteString, putLengthDelimited,+ putSVarint32, putSVarint64, putVarintSigned,+ varintSize, tagSize, fieldMessageSize,+ fieldVarintSize, fieldFixed32Size, fieldFixed64Size,+ fieldBoolSize, fieldFloatSize, fieldDoubleSize,+ fieldTextSize, fieldBytesSize,+ fieldSVarint32Size, fieldSVarint64Size,+ varintSize32, zigZag32, zigZag64)+import Proto.Internal.Encode.Archetype (archVarint, archSVarint32, archSVarint64,+ archFixed32, archFixed64, archFloat, archDouble, archBool,+ archString, archBytes, archSubmessage,+ archVarintSize, archStringSize, archBytesSize, archBoolSize,+ archFixed32Size, archFixed64Size, archSubmessageSize)++-- | Serialized FileDescriptorProto for this .proto file.+-- Decode with @Proto.Google.Protobuf.Descriptor.decodeMessage@.+fileDescriptorProtoBytes :: ByteString+fileDescriptorProtoBytes = "\x0a\x11\x65\x64\x69\x74\x69\x6f\x6e\x32\x30\x32\x33\x2e\x70\x72\x6f\x74\x6f\x22\xbe\x02\x0a\x08\x46\x69\x65\x6c\x64\x73\x32\x33\x12\x0d\x0a\x05\x63\x6f\x75\x6e\x74\x18\x01\x20\x01\x28\x05\x12\x0c\x0a\x04\x6e\x61\x6d\x65\x18\x02\x20\x01\x28\x09\x12\x0f\x0a\x07\x70\x61\x79\x6c\x6f\x61\x64\x18\x03\x20\x01\x28\x0c\x12\x0f\x0a\x07\x65\x6e\x61\x62\x6c\x65\x64\x18\x04\x20\x01\x28\x08\x12\x18\x0a\x06\x73\x74\x61\x74\x75\x73\x18\x05\x20\x01\x28\x0b\x32\x08\x53\x74\x61\x74\x75\x73\x32\x33\x12\x11\x0a\x09\x6d\x61\x67\x6e\x69\x74\x75\x64\x65\x18\x06\x20\x01\x28\x01\x12\x10\x0a\x08\x66\x72\x61\x63\x74\x69\x6f\x6e\x18\x07\x20\x01\x28\x02\x12\x16\x0a\x0e\x69\x6d\x70\x6c\x69\x63\x69\x74\x5f\x63\x6f\x75\x6e\x74\x18\x08\x20\x01\x28\x05\x12\x0e\x0a\x06\x76\x61\x6c\x75\x65\x73\x18\x09\x20\x03\x28\x05\x12\x14\x0a\x05\x69\x6e\x6e\x65\x72\x18\x0a\x20\x01\x28\x0b\x32\x05\x49\x6e\x6e\x65\x72\x12\x19\x0a\x05\x61\x74\x74\x72\x73\x18\x0b\x20\x03\x28\x0b\x32\x0a\x61\x74\x74\x72\x73\x45\x6e\x74\x72\x79\x12\x0e\x0a\x06\x70\x69\x63\x6b\x65\x64\x18\x0c\x20\x01\x28\x05\x12\x0d\x0a\x05\x6c\x61\x62\x65\x6c\x18\x0d\x20\x01\x28\x09\x12\x1a\x0a\x12\x61\x67\x67\x72\x65\x67\x61\x74\x65\x5f\x69\x6d\x70\x6c\x69\x63\x69\x74\x18\x0e\x20\x01\x28\x05\x1a\x16\x0a\x05\x49\x6e\x6e\x65\x72\x12\x0d\x0a\x05\x63\x6f\x75\x6e\x74\x18\x01\x20\x01\x28\x05\x42\x08\x0a\x06\x63\x68\x6f\x69\x63\x65\x2a\x36\x0a\x08\x53\x74\x61\x74\x75\x73\x32\x33\x12\x16\x0a\x14\x53\x54\x41\x54\x55\x53\x32\x33\x5f\x55\x4e\x53\x50\x45\x43\x49\x46\x49\x45\x44\x12\x12\x0a\x0e\x53\x54\x41\x54\x55\x53\x32\x33\x5f\x52\x45\x41\x44\x59\x10\x01\x62\x08\x65\x64\x69\x74\x69\x6f\x6e\x73\x72\x04\x32\x30\x32\x33"+++data Status23+ = Status23'Status23Unspecified+ | Status23'Status23Ready+ deriving stock (Show, Eq, Ord, Enum, Bounded, Generic)+ deriving anyclass NFData++toProtoEnumStatus23 :: Status23 -> Int+toProtoEnumStatus23 Status23'Status23Unspecified = 0+toProtoEnumStatus23 Status23'Status23Ready = 1++fromProtoEnumStatus23 :: Int -> Maybe Status23+fromProtoEnumStatus23 0 = Just Status23'Status23Unspecified+fromProtoEnumStatus23 1 = Just Status23'Status23Ready+fromProtoEnumStatus23 _ = Nothing++instance MessageEncode Status23 where+ buildMessage _ = mempty+instance MessageSize Status23 where+ messageSize _ = 0+instance MessageDecode Status23 where+ messageDecoder = pure (toEnum 0)++instance Aeson.ToJSON Status23 where+ toJSON Status23'Status23Unspecified = Aeson.String "STATUS23_UNSPECIFIED"+ toJSON Status23'Status23Ready = Aeson.String "STATUS23_READY"++instance Aeson.FromJSON Status23 where+ parseJSON = \case+ Aeson.String "STATUS23_UNSPECIFIED" -> pure Status23'Status23Unspecified+ Aeson.String "STATUS23_READY" -> pure Status23'Status23Ready+ Aeson.Number n -> pure (toEnum (round n))+ _ -> fail "Invalid enum value for Status23"++instance Hashable Status23 where+ hashWithSalt salt x = hashWithSalt salt (toProtoEnumStatus23 x)++data Fields23 = Fields23+ { fields23Count :: !(Maybe Int32)+ , fields23Name :: !(Maybe Text)+ , fields23Payload :: !(Maybe ByteString)+ , fields23Enabled :: !(Maybe Bool)+ , fields23Status :: !(Maybe Status23)+ , fields23Magnitude :: !(Maybe Double)+ , fields23Fraction :: !(Maybe Float)+ , fields23ImplicitCount :: {-# UNPACK #-} !Int32+ , fields23Values :: !(VU.Vector Int32)+ , fields23Inner :: !(Maybe Fields23'Inner)+ , fields23Attrs :: !(Map.Map Text Text)+ , fields23Choice :: !(Maybe Fields23'Choice)+ , fields23AggregateImplicit :: {-# UNPACK #-} !Int32+ , fields23UnknownFields :: ![UnknownField]+ }+ deriving stock (Show, Eq, Generic)+ deriving anyclass NFData++data Fields23'Inner = Fields23'Inner+ { fields23InnerCount :: !(Maybe Int32)+ , fields23InnerUnknownFields :: ![UnknownField]+ }+ deriving stock (Show, Eq, Generic)+ deriving anyclass NFData++defaultFields23'Inner :: Fields23'Inner+defaultFields23'Inner = Fields23'Inner+ { fields23InnerCount = Nothing+ , fields23InnerUnknownFields = []+ }++instance MessageEncode Fields23'Inner where+ buildMessage msg =+ (maybe mempty (\v -> archVarint 8 (fromIntegral v)) msg.fields23InnerCount)+ <> encodeUnknownFields msg.fields23InnerUnknownFields++instance MessageSize Fields23'Inner where+ messageSize msg =+ (maybe 0 (\v -> archVarintSize (fromIntegral v)) msg.fields23InnerCount)+ + unknownFieldsSize msg.fields23InnerUnknownFields++instance MessageDecode Fields23'Inner where+ {-# INLINE messageDecoder #-}+ messageDecoder = loop Nothing []+ where+ loop acc_0 acc_unknown_ = withTagM+ (pure (Fields23'Inner {fields23InnerCount = acc_0, fields23InnerUnknownFields = reverse acc_unknown_}))+ (\fn wt -> case fn of+ 1 -> do+ v <- (fromIntegral <$> decodeFieldVarint)+ loop (Just v) acc_unknown_+ _ -> do+ uf <- captureUnknownField fn (toEnum wt)+ loop acc_0 (uf : acc_unknown_))++instance ProtoMessage Fields23'Inner where+ protoMessageName _ = "Fields23.Inner"+ protoPackageName _ = ""+ protoDefaultValue = defaultFields23'Inner+ protoFileDescriptorBytes _ = fileDescriptorProtoBytes+ protoFieldDescriptors _ = Map.fromList+ [ (1, SomeField FieldDescriptor+ { fdName = "count"+ , fdNumber = 1+ , fdTypeDesc = ScalarType Int32Field+ , fdLabel = LabelOptional+ , fdGet = fields23InnerCount+ , fdSet = \v m -> m { fields23InnerCount = v }+ })+ ]++instance IsMessage Fields23'Inner++instance Aeson.ToJSON Fields23'Inner where+ toJSON msg = jsonObject+ [ "count" .=: msg.fields23InnerCount++ ]++instance Aeson.FromJSON Fields23'Inner where+ parseJSON = Aeson.withObject "Fields23'Inner" $ \obj -> do+ fld_fields23InnerCount <- parseFieldMaybe obj "count"+ pure defaultFields23'Inner+ { fields23InnerCount = maybe (fields23InnerCount defaultFields23'Inner) id fld_fields23InnerCount+ , fields23InnerUnknownFields = []+ }++instance Hashable Fields23'Inner where+ hashWithSalt salt msg = hashWithSalt (salt) msg.fields23InnerCount++instance Proto.Extension.HasExtensions Fields23'Inner where+ messageUnknownFields = fields23InnerUnknownFields+ setMessageUnknownFields !ufs msg = msg { fields23InnerUnknownFields = ufs }++instance Semigroup Fields23'Inner where+ a <> b = Fields23'Inner+ { fields23InnerCount = case b.fields23InnerCount of { Nothing -> a.fields23InnerCount; x -> x }+ , fields23InnerUnknownFields = a.fields23InnerUnknownFields <> b.fields23InnerUnknownFields+ }++instance Monoid Fields23'Inner where+ mempty = defaultFields23'Inner+data Fields23'Choice+ = Fields23'Choice'Picked {-# UNPACK #-} !Int32+ | Fields23'Choice'Label !Text+ deriving stock (Show, Eq, Generic)+ deriving anyclass NFData+instance Aeson.ToJSON Fields23'Choice where+ toJSON _ = Aeson.Null+instance Aeson.FromJSON Fields23'Choice where+ parseJSON _ = fail "Cannot parse oneof from JSON"+instance Hashable Fields23'Choice where+ hashWithSalt salt (Fields23'Choice'Picked v) = salt `hashWithSalt` (0 :: Int) `hashWithSalt` v+ hashWithSalt salt (Fields23'Choice'Label v) = salt `hashWithSalt` (1 :: Int) `hashWithSalt` v++defaultFields23 :: Fields23+defaultFields23 = Fields23+ { fields23Count = Nothing+ , fields23Name = Nothing+ , fields23Payload = Nothing+ , fields23Enabled = Nothing+ , fields23Status = Nothing+ , fields23Magnitude = Nothing+ , fields23Fraction = Nothing+ , fields23ImplicitCount = 0+ , fields23Values = VU.empty+ , fields23Inner = Nothing+ , fields23Attrs = Map.empty+ , fields23Choice = Nothing+ , fields23AggregateImplicit = 0+ , fields23UnknownFields = []+ }++instance MessageEncode Fields23 where+ buildMessage msg =+ (maybe mempty (\v -> archVarint 8 (fromIntegral v)) msg.fields23Count)+ <> (maybe mempty (\v -> archString 18 v) msg.fields23Name)+ <> (maybe mempty (\v -> archBytes 26 v) msg.fields23Payload)+ <> (maybe mempty (\v -> archBool 32 v) msg.fields23Enabled)+ <> (maybe mempty (\v -> archVarint 40 (fromIntegral (fromEnum v))) msg.fields23Status)+ <> (maybe mempty (\v -> archDouble 49 v) msg.fields23Magnitude)+ <> (maybe mempty (\v -> archFloat 61 v) msg.fields23Fraction)+ <> (if msg.fields23ImplicitCount == 0 then mempty else archVarint 64 (fromIntegral msg.fields23ImplicitCount))+ <> encodePackedInt32 9 msg.fields23Values+ <> (maybe mempty (\v -> let sz = messageSize v in archSubmessage 82 sz (buildMessage v)) msg.fields23Inner)+ <> Map.foldlWithKey' (\acc k v -> acc <> encodeMapField 11 (encodeFieldString 1 k) (encodeFieldString 2 v)) mempty msg.fields23Attrs+ <> (case msg.fields23Choice of+ Nothing -> mempty+ Just (Fields23'Choice'Picked v) -> archVarint 96 (fromIntegral v)+ Just (Fields23'Choice'Label v) -> archString 106 v)+ <> (if msg.fields23AggregateImplicit == 0 then mempty else archVarint 112 (fromIntegral msg.fields23AggregateImplicit))+ <> encodeUnknownFields msg.fields23UnknownFields++instance MessageSize Fields23 where+ messageSize msg =+ (maybe 0 (\v -> archVarintSize (fromIntegral v)) msg.fields23Count)+ + (maybe 0 (\v -> archStringSize v) msg.fields23Name)+ + (maybe 0 (\v -> archBytesSize v) msg.fields23Payload)+ + (maybe 0 (\v -> archBoolSize) msg.fields23Enabled)+ + (maybe 0 (\v -> archVarintSize (fromIntegral (fromEnum v))) msg.fields23Status)+ + (maybe 0 (\v -> archFixed64Size) msg.fields23Magnitude)+ + (maybe 0 (\v -> archFixed32Size) msg.fields23Fraction)+ + (if msg.fields23ImplicitCount == 0 then 0 else archVarintSize (fromIntegral msg.fields23ImplicitCount))+ + (let pl = VU.foldl' (\a v -> a + varintSize (fromIntegral v)) 0 msg.fields23Values in if pl == 0 then 0 else tagSize 9 + varintSize (fromIntegral pl) + pl)+ + (maybe 0 (\v -> archSubmessageSize (messageSize v)) msg.fields23Inner)+ + (Map.foldlWithKey' (\acc k v -> let entrySz = fieldTextSize 1 k + fieldTextSize 2 v in acc + tagSize 11 + varintSize (fromIntegral entrySz) + entrySz) 0 msg.fields23Attrs)+ + (case msg.fields23Choice of { Nothing -> 0; Just (Fields23'Choice'Picked v) -> archVarintSize (fromIntegral v)+ ; Just (Fields23'Choice'Label v) -> archStringSize v })+ + (if msg.fields23AggregateImplicit == 0 then 0 else archVarintSize (fromIntegral msg.fields23AggregateImplicit))+ + unknownFieldsSize msg.fields23UnknownFields++instance MessageDecode Fields23 where+ {-# INLINE messageDecoder #-}+ messageDecoder = loop Nothing Nothing Nothing Nothing Nothing Nothing Nothing 0 VU.empty Nothing Map.empty Nothing 0 []+ where+ loop acc_0 acc_1 acc_2 acc_3 acc_4 acc_5 acc_6 acc_7 acc_8 acc_9 acc_10 acc_11 acc_12 acc_unknown_ = withTagM+ (pure (Fields23 {fields23Count = acc_0, fields23Name = acc_1, fields23Payload = acc_2, fields23Enabled = acc_3, fields23Status = acc_4, fields23Magnitude = acc_5, fields23Fraction = acc_6, fields23ImplicitCount = acc_7, fields23Values = acc_8, fields23Inner = acc_9, fields23Attrs = acc_10, fields23Choice = acc_11, fields23AggregateImplicit = acc_12, fields23UnknownFields = reverse acc_unknown_}))+ (\fn wt -> case fn of+ 1 -> do+ v <- (fromIntegral <$> decodeFieldVarint)+ loop (Just v) acc_1 acc_2 acc_3 acc_4 acc_5 acc_6 acc_7 acc_8 acc_9 acc_10 acc_11 acc_12 acc_unknown_+ 2 -> do+ v <- decodeFieldString+ loop acc_0 (Just v) acc_2 acc_3 acc_4 acc_5 acc_6 acc_7 acc_8 acc_9 acc_10 acc_11 acc_12 acc_unknown_+ 3 -> do+ v <- decodeFieldBytes+ loop acc_0 acc_1 (Just v) acc_3 acc_4 acc_5 acc_6 acc_7 acc_8 acc_9 acc_10 acc_11 acc_12 acc_unknown_+ 4 -> do+ v <- decodeFieldBool+ loop acc_0 acc_1 acc_2 (Just v) acc_4 acc_5 acc_6 acc_7 acc_8 acc_9 acc_10 acc_11 acc_12 acc_unknown_+ 5 -> do+ v <- decodeFieldEnum+ loop acc_0 acc_1 acc_2 acc_3 (Just v) acc_5 acc_6 acc_7 acc_8 acc_9 acc_10 acc_11 acc_12 acc_unknown_+ 6 -> do+ v <- decodeFieldDouble+ loop acc_0 acc_1 acc_2 acc_3 acc_4 (Just v) acc_6 acc_7 acc_8 acc_9 acc_10 acc_11 acc_12 acc_unknown_+ 7 -> do+ v <- decodeFieldFloat+ loop acc_0 acc_1 acc_2 acc_3 acc_4 acc_5 (Just v) acc_7 acc_8 acc_9 acc_10 acc_11 acc_12 acc_unknown_+ 8 -> do+ v <- (fromIntegral <$> decodeFieldVarint)+ loop acc_0 acc_1 acc_2 acc_3 acc_4 acc_5 acc_6 v acc_8 acc_9 acc_10 acc_11 acc_12 acc_unknown_+ 9 -> case wt of+ 2 -> do+ vs <- (VU.map fromIntegral <$> decodePackedVarint)+ loop acc_0 acc_1 acc_2 acc_3 acc_4 acc_5 acc_6 acc_7 (acc_8 <> vs) acc_9 acc_10 acc_11 acc_12 acc_unknown_+ _ -> do+ v <- (fromIntegral <$> decodeFieldVarint)+ loop acc_0 acc_1 acc_2 acc_3 acc_4 acc_5 acc_6 acc_7 (acc_8 <> VU.singleton v) acc_9 acc_10 acc_11 acc_12 acc_unknown_+ 10 -> do+ v <- decodeFieldMessage+ loop acc_0 acc_1 acc_2 acc_3 acc_4 acc_5 acc_6 acc_7 acc_8 (Just v) acc_10 acc_11 acc_12 acc_unknown_+ 11 -> do+ bs' <- getLengthDelimited+ let decodeEntry = runDecoder (decodeMapEntry decodeFieldString decodeFieldString "" "") bs'+ case decodeEntry of+ Left _ -> loop acc_0 acc_1 acc_2 acc_3 acc_4 acc_5 acc_6 acc_7 acc_8 acc_9 acc_10 acc_11 acc_12 acc_unknown_+ Right (mk', mv') -> loop acc_0 acc_1 acc_2 acc_3 acc_4 acc_5 acc_6 acc_7 acc_8 acc_9 (Map.union acc_10 (Map.singleton mk' mv')) acc_11 acc_12 acc_unknown_+ 12 -> do+ v <- (fromIntegral <$> decodeFieldVarint)+ loop acc_0 acc_1 acc_2 acc_3 acc_4 acc_5 acc_6 acc_7 acc_8 acc_9 acc_10 (Just (Fields23'Choice'Picked v)) acc_12 acc_unknown_+ 13 -> do+ v <- decodeFieldString+ loop acc_0 acc_1 acc_2 acc_3 acc_4 acc_5 acc_6 acc_7 acc_8 acc_9 acc_10 (Just (Fields23'Choice'Label v)) acc_12 acc_unknown_+ 14 -> do+ v <- (fromIntegral <$> decodeFieldVarint)+ loop acc_0 acc_1 acc_2 acc_3 acc_4 acc_5 acc_6 acc_7 acc_8 acc_9 acc_10 acc_11 v acc_unknown_+ _ -> do+ uf <- captureUnknownField fn (toEnum wt)+ loop acc_0 acc_1 acc_2 acc_3 acc_4 acc_5 acc_6 acc_7 acc_8 acc_9 acc_10 acc_11 acc_12 (uf : acc_unknown_))++instance ProtoMessage Fields23 where+ protoMessageName _ = "Fields23"+ protoPackageName _ = ""+ protoDefaultValue = defaultFields23+ protoFileDescriptorBytes _ = fileDescriptorProtoBytes+ protoFieldDescriptors _ = Map.fromList+ [ (1, SomeField FieldDescriptor+ { fdName = "count"+ , fdNumber = 1+ , fdTypeDesc = ScalarType Int32Field+ , fdLabel = LabelOptional+ , fdGet = fields23Count+ , fdSet = \v m -> m { fields23Count = v }+ }), (2, SomeField FieldDescriptor+ { fdName = "name"+ , fdNumber = 2+ , fdTypeDesc = ScalarType StringField+ , fdLabel = LabelOptional+ , fdGet = fields23Name+ , fdSet = \v m -> m { fields23Name = v }+ })+ , (3, SomeField FieldDescriptor+ { fdName = "payload"+ , fdNumber = 3+ , fdTypeDesc = ScalarType BytesField+ , fdLabel = LabelOptional+ , fdGet = fields23Payload+ , fdSet = \v m -> m { fields23Payload = v }+ })+ , (4, SomeField FieldDescriptor+ { fdName = "enabled"+ , fdNumber = 4+ , fdTypeDesc = ScalarType BoolField+ , fdLabel = LabelOptional+ , fdGet = fields23Enabled+ , fdSet = \v m -> m { fields23Enabled = v }+ })+ , (5, SomeField FieldDescriptor+ { fdName = "status"+ , fdNumber = 5+ , fdTypeDesc = MessageType "Status23"+ , fdLabel = LabelOptional+ , fdGet = fields23Status+ , fdSet = \v m -> m { fields23Status = v }+ })+ , (6, SomeField FieldDescriptor+ { fdName = "magnitude"+ , fdNumber = 6+ , fdTypeDesc = ScalarType DoubleField+ , fdLabel = LabelOptional+ , fdGet = fields23Magnitude+ , fdSet = \v m -> m { fields23Magnitude = v }+ })+ , (7, SomeField FieldDescriptor+ { fdName = "fraction"+ , fdNumber = 7+ , fdTypeDesc = ScalarType FloatField+ , fdLabel = LabelOptional+ , fdGet = fields23Fraction+ , fdSet = \v m -> m { fields23Fraction = v }+ })+ , (8, SomeField FieldDescriptor+ { fdName = "implicit_count"+ , fdNumber = 8+ , fdTypeDesc = ScalarType Int32Field+ , fdLabel = LabelOptional+ , fdGet = fields23ImplicitCount+ , fdSet = \v m -> m { fields23ImplicitCount = v }+ })+ , (9, SomeField FieldDescriptor+ { fdName = "values"+ , fdNumber = 9+ , fdTypeDesc = ScalarType Int32Field+ , fdLabel = LabelRepeated+ , fdGet = fields23Values+ , fdSet = \v m -> m { fields23Values = v }+ })+ , (10, SomeField FieldDescriptor+ { fdName = "inner"+ , fdNumber = 10+ , fdTypeDesc = MessageType "Inner"+ , fdLabel = LabelOptional+ , fdGet = fields23Inner+ , fdSet = \v m -> m { fields23Inner = v }+ })+ , (11, SomeField FieldDescriptor+ { fdName = "attrs"+ , fdNumber = 11+ , fdTypeDesc = ScalarType BytesField+ , fdLabel = LabelRepeated+ , fdGet = fields23Attrs+ , fdSet = \v m -> m { fields23Attrs = v }+ })+ , (12, SomeField FieldDescriptor+ { fdName = "choice"+ , fdNumber = 12+ , fdTypeDesc = MessageType "choice"+ , fdLabel = LabelOptional+ , fdGet = fields23Choice+ , fdSet = \v m -> m { fields23Choice = v }+ })+ , (14, SomeField FieldDescriptor+ { fdName = "aggregate_implicit"+ , fdNumber = 14+ , fdTypeDesc = ScalarType Int32Field+ , fdLabel = LabelOptional+ , fdGet = fields23AggregateImplicit+ , fdSet = \v m -> m { fields23AggregateImplicit = v }+ })+ ]++instance IsMessage Fields23++instance Aeson.ToJSON Fields23 where+ toJSON msg = jsonObject+ [ "count" .=: msg.fields23Count+ , "name" .=: msg.fields23Name+ , "payload" .=: (fmap protoBytesToJSON msg.fields23Payload)+ , "enabled" .=: msg.fields23Enabled+ , "status" .=: msg.fields23Status+ , "magnitude" .=: msg.fields23Magnitude+ , "fraction" .=: msg.fields23Fraction+ , "implicitCount" .=: msg.fields23ImplicitCount+ , "values" .=: msg.fields23Values+ , "inner" .=: msg.fields23Inner+ , "attrs" .=: msg.fields23Attrs+ , "choice" .=: msg.fields23Choice+ , "aggregateImplicit" .=: msg.fields23AggregateImplicit+ ]++instance Aeson.FromJSON Fields23 where+ parseJSON = Aeson.withObject "Fields23" $ \obj -> do+ fld_fields23Count <- parseFieldMaybe obj "count"+ fld_fields23Name <- parseFieldMaybe obj "name"+ fld_fields23Payload <- parseBytesFieldMaybe obj "payload"+ fld_fields23Enabled <- parseFieldMaybe obj "enabled"+ fld_fields23Status <- parseFieldMaybe obj "status"+ fld_fields23Magnitude <- parseFieldMaybe obj "magnitude"+ fld_fields23Fraction <- parseFieldMaybe obj "fraction"+ fld_fields23ImplicitCount <- parseFieldMaybe obj "implicitCount"+ fld_fields23Values <- parseFieldMaybe obj "values"+ fld_fields23Inner <- parseFieldMaybe obj "inner"+ fld_fields23Attrs <- parseFieldMaybe obj "attrs"+ fld_fields23Choice <- parseFieldMaybe obj "choice"+ fld_fields23AggregateImplicit <- parseFieldMaybe obj "aggregateImplicit"+ pure defaultFields23+ { fields23Count = maybe (fields23Count defaultFields23) id fld_fields23Count+ , fields23Name = maybe (fields23Name defaultFields23) id fld_fields23Name+ , fields23Payload = fld_fields23Payload+ , fields23Enabled = maybe (fields23Enabled defaultFields23) id fld_fields23Enabled+ , fields23Status = maybe (fields23Status defaultFields23) id fld_fields23Status+ , fields23Magnitude = maybe (fields23Magnitude defaultFields23) id fld_fields23Magnitude+ , fields23Fraction = maybe (fields23Fraction defaultFields23) id fld_fields23Fraction+ , fields23ImplicitCount = maybe (fields23ImplicitCount defaultFields23) id fld_fields23ImplicitCount+ , fields23Values = maybe (fields23Values defaultFields23) id fld_fields23Values+ , fields23Inner = maybe (fields23Inner defaultFields23) id fld_fields23Inner+ , fields23Attrs = maybe (fields23Attrs defaultFields23) id fld_fields23Attrs+ , fields23Choice = maybe (fields23Choice defaultFields23) id fld_fields23Choice+ , fields23AggregateImplicit = maybe (fields23AggregateImplicit defaultFields23) id fld_fields23AggregateImplicit+ , fields23UnknownFields = []+ }++instance Hashable Fields23 where+ hashWithSalt salt msg = hashWithSalt (hashWithSalt (Map.foldlWithKey' (\s k v -> s `hashWithSalt` k `hashWithSalt` v) (hashWithSalt (VU.foldl' hashWithSalt (hashWithSalt (hashWithSalt (hashWithSalt (hashWithSalt (hashWithSalt (hashWithSalt (hashWithSalt (hashWithSalt (salt) msg.fields23Count) msg.fields23Name) msg.fields23Payload) msg.fields23Enabled) msg.fields23Status) msg.fields23Magnitude) msg.fields23Fraction) msg.fields23ImplicitCount) msg.fields23Values) msg.fields23Inner) msg.fields23Attrs) msg.fields23Choice) msg.fields23AggregateImplicit++instance Proto.Extension.HasExtensions Fields23 where+ messageUnknownFields = fields23UnknownFields+ setMessageUnknownFields !ufs msg = msg { fields23UnknownFields = ufs }++instance Semigroup Fields23 where+ a <> b = Fields23+ { fields23Count = case b.fields23Count of { Nothing -> a.fields23Count; x -> x }+ , fields23Name = case b.fields23Name of { Nothing -> a.fields23Name; x -> x }+ , fields23Payload = case b.fields23Payload of { Nothing -> a.fields23Payload; x -> x }+ , fields23Enabled = case b.fields23Enabled of { Nothing -> a.fields23Enabled; x -> x }+ , fields23Status = case b.fields23Status of { Nothing -> a.fields23Status; x -> x }+ , fields23Magnitude = case b.fields23Magnitude of { Nothing -> a.fields23Magnitude; x -> x }+ , fields23Fraction = case b.fields23Fraction of { Nothing -> a.fields23Fraction; x -> x }+ , fields23ImplicitCount = if b.fields23ImplicitCount == 0 then a.fields23ImplicitCount else b.fields23ImplicitCount+ , fields23Values = a.fields23Values <> b.fields23Values+ , fields23Inner = case b.fields23Inner of { Nothing -> a.fields23Inner; x -> x }+ , fields23Attrs = a.fields23Attrs <> b.fields23Attrs+ , fields23Choice = case b.fields23Choice of { Nothing -> a.fields23Choice; x -> x }+ , fields23AggregateImplicit = if b.fields23AggregateImplicit == 0 then a.fields23AggregateImplicit else b.fields23AggregateImplicit+ , fields23UnknownFields = a.fields23UnknownFields <> b.fields23UnknownFields+ }++instance Monoid Fields23 where+ mempty = defaultFields23
+ test-regression/Generated/Edition2024.hs view
@@ -0,0 +1,287 @@+{-# LANGUAGE StrictData #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE OverloadedRecordDot #-}+{-# OPTIONS_GHC -Wno-unused-imports -Wno-unused-matches -Wno-unused-top-binds #-}+-- | Auto-generated protobuf types.+--+-- __THIS FILE IS AUTO-GENERATED BY wireform. DO NOT EDIT.__+--+-- Any manual changes will be overwritten the next time code+-- generation is run. To modify the types or instances, edit the+-- @.proto@ source file and re-run the code generator.+module Generated.Edition2024 where++import Data.ByteString (ByteString)+import qualified Data.ByteString as BS+import qualified Wireform.Builder as B+import Data.Int (Int32, Int64)+import Data.Text (Text)+import qualified Data.Text as T+import Data.Word (Word32, Word64)+import qualified Data.Map.Strict as Map+import qualified Data.Vector as V+import qualified Data.Vector.Unboxed as VU+import GHC.Generics (Generic)+import Control.DeepSeq (NFData(..))+import Data.Hashable (Hashable(..))+import Proto.Encode+import Proto.Decode+import qualified Data.Aeson as Aeson+import qualified Data.Aeson.Types as Aeson+import qualified Data.Aeson.Key as AesonKey+import qualified Data.Aeson.KeyMap as AesonKM+import Proto.Internal.JSON (jsonObject, (.=:), parseFieldMaybe, bytesFieldToJSON, parseBytesFieldMaybe, bytesMapFieldToJSON, parseBytesMapFieldMaybe, protoBytesToJSON)+import Data.Proxy (Proxy(..))+import Data.Reflection (Given(..), given)+import Proto.Internal.JSON.Extension qualified as PJExt+import Proto.Schema (ProtoMessage(..), SomeFieldDescriptor(..), FieldDescriptor(..), FieldTypeDescriptor(..), ScalarFieldType(..), FieldLabel'(..))+import Proto.Registry (IsMessage)+import Proto.Registry qualified+import qualified Proto.Extension+import Proto.Internal.Wire (Tag(..), WireType(..))+import Proto.Internal.Wire.Encode (putTag, putVarint, putFixed32, putFixed64,+ putFloat, putDouble, putText, putByteString, putLengthDelimited,+ putSVarint32, putSVarint64, putVarintSigned,+ varintSize, tagSize, fieldMessageSize,+ fieldVarintSize, fieldFixed32Size, fieldFixed64Size,+ fieldBoolSize, fieldFloatSize, fieldDoubleSize,+ fieldTextSize, fieldBytesSize,+ fieldSVarint32Size, fieldSVarint64Size,+ varintSize32, zigZag32, zigZag64)+import Proto.Internal.Encode.Archetype (archVarint, archSVarint32, archSVarint64,+ archFixed32, archFixed64, archFloat, archDouble, archBool,+ archString, archBytes, archSubmessage,+ archVarintSize, archStringSize, archBytesSize, archBoolSize,+ archFixed32Size, archFixed64Size, archSubmessageSize)++-- | Serialized FileDescriptorProto for this .proto file.+-- Decode with @Proto.Google.Protobuf.Descriptor.decodeMessage@.+fileDescriptorProtoBytes :: ByteString+fileDescriptorProtoBytes = "\x0a\x11\x65\x64\x69\x74\x69\x6f\x6e\x32\x30\x32\x34\x2e\x70\x72\x6f\x74\x6f\x22\x7b\x0a\x08\x46\x69\x65\x6c\x64\x73\x32\x34\x12\x0d\x0a\x05\x63\x6f\x75\x6e\x74\x18\x01\x20\x01\x28\x05\x12\x16\x0a\x0e\x65\x78\x70\x6c\x69\x63\x69\x74\x5f\x63\x6f\x75\x6e\x74\x18\x02\x20\x01\x28\x05\x12\x14\x0a\x05\x69\x6e\x6e\x65\x72\x18\x03\x20\x01\x28\x0b\x32\x05\x49\x6e\x6e\x65\x72\x1a\x32\x0a\x05\x49\x6e\x6e\x65\x72\x12\x11\x0a\x09\x69\x6e\x68\x65\x72\x69\x74\x65\x64\x18\x01\x20\x01\x28\x05\x12\x16\x0a\x0e\x65\x78\x70\x6c\x69\x63\x69\x74\x5f\x76\x61\x6c\x75\x65\x18\x02\x20\x01\x28\x05\x62\x08\x65\x64\x69\x74\x69\x6f\x6e\x73\x72\x04\x32\x30\x32\x34"+++data Fields24 = Fields24+ { fields24Count :: {-# UNPACK #-} !Int32+ , fields24ExplicitCount :: !(Maybe Int32)+ , fields24Inner :: !(Maybe Fields24'Inner)+ , fields24UnknownFields :: ![UnknownField]+ }+ deriving stock (Show, Eq, Generic)+ deriving anyclass NFData++data Fields24'Inner = Fields24'Inner+ { fields24InnerInherited :: {-# UNPACK #-} !Int32+ , fields24InnerExplicitValue :: !(Maybe Int32)+ , fields24InnerUnknownFields :: ![UnknownField]+ }+ deriving stock (Show, Eq, Generic)+ deriving anyclass NFData++defaultFields24'Inner :: Fields24'Inner+defaultFields24'Inner = Fields24'Inner+ { fields24InnerInherited = 0+ , fields24InnerExplicitValue = Nothing+ , fields24InnerUnknownFields = []+ }++instance MessageEncode Fields24'Inner where+ buildMessage msg =+ (if msg.fields24InnerInherited == 0 then mempty else archVarint 8 (fromIntegral msg.fields24InnerInherited))+ <> (maybe mempty (\v -> archVarint 16 (fromIntegral v)) msg.fields24InnerExplicitValue)+ <> encodeUnknownFields msg.fields24InnerUnknownFields++instance MessageSize Fields24'Inner where+ messageSize msg =+ (if msg.fields24InnerInherited == 0 then 0 else archVarintSize (fromIntegral msg.fields24InnerInherited))+ + (maybe 0 (\v -> archVarintSize (fromIntegral v)) msg.fields24InnerExplicitValue)+ + unknownFieldsSize msg.fields24InnerUnknownFields++instance MessageDecode Fields24'Inner where+ {-# INLINE messageDecoder #-}+ messageDecoder = loop 0 Nothing []+ where+ loop acc_0 acc_1 acc_unknown_ = withTagM+ (pure (Fields24'Inner {fields24InnerInherited = acc_0, fields24InnerExplicitValue = acc_1, fields24InnerUnknownFields = reverse acc_unknown_}))+ (\fn wt -> case fn of+ 1 -> do+ v <- (fromIntegral <$> decodeFieldVarint)+ loop v acc_1 acc_unknown_+ 2 -> do+ v <- (fromIntegral <$> decodeFieldVarint)+ loop acc_0 (Just v) acc_unknown_+ _ -> do+ uf <- captureUnknownField fn (toEnum wt)+ loop acc_0 acc_1 (uf : acc_unknown_))++instance ProtoMessage Fields24'Inner where+ protoMessageName _ = "Fields24.Inner"+ protoPackageName _ = ""+ protoDefaultValue = defaultFields24'Inner+ protoFileDescriptorBytes _ = fileDescriptorProtoBytes+ protoFieldDescriptors _ = Map.fromList+ [ (1, SomeField FieldDescriptor+ { fdName = "inherited"+ , fdNumber = 1+ , fdTypeDesc = ScalarType Int32Field+ , fdLabel = LabelOptional+ , fdGet = fields24InnerInherited+ , fdSet = \v m -> m { fields24InnerInherited = v }+ }), (2, SomeField FieldDescriptor+ { fdName = "explicit_value"+ , fdNumber = 2+ , fdTypeDesc = ScalarType Int32Field+ , fdLabel = LabelOptional+ , fdGet = fields24InnerExplicitValue+ , fdSet = \v m -> m { fields24InnerExplicitValue = v }+ })+ ]++instance IsMessage Fields24'Inner++instance Aeson.ToJSON Fields24'Inner where+ toJSON msg = jsonObject+ [ "inherited" .=: msg.fields24InnerInherited+ , "explicitValue" .=: msg.fields24InnerExplicitValue+ ]++instance Aeson.FromJSON Fields24'Inner where+ parseJSON = Aeson.withObject "Fields24'Inner" $ \obj -> do+ fld_fields24InnerInherited <- parseFieldMaybe obj "inherited"+ fld_fields24InnerExplicitValue <- parseFieldMaybe obj "explicitValue"+ pure defaultFields24'Inner+ { fields24InnerInherited = maybe (fields24InnerInherited defaultFields24'Inner) id fld_fields24InnerInherited+ , fields24InnerExplicitValue = maybe (fields24InnerExplicitValue defaultFields24'Inner) id fld_fields24InnerExplicitValue+ , fields24InnerUnknownFields = []+ }++instance Hashable Fields24'Inner where+ hashWithSalt salt msg = hashWithSalt (hashWithSalt (salt) msg.fields24InnerInherited) msg.fields24InnerExplicitValue++instance Proto.Extension.HasExtensions Fields24'Inner where+ messageUnknownFields = fields24InnerUnknownFields+ setMessageUnknownFields !ufs msg = msg { fields24InnerUnknownFields = ufs }++instance Semigroup Fields24'Inner where+ a <> b = Fields24'Inner+ { fields24InnerInherited = if b.fields24InnerInherited == 0 then a.fields24InnerInherited else b.fields24InnerInherited+ , fields24InnerExplicitValue = case b.fields24InnerExplicitValue of { Nothing -> a.fields24InnerExplicitValue; x -> x }+ , fields24InnerUnknownFields = a.fields24InnerUnknownFields <> b.fields24InnerUnknownFields+ }++instance Monoid Fields24'Inner where+ mempty = defaultFields24'Inner++defaultFields24 :: Fields24+defaultFields24 = Fields24+ { fields24Count = 0+ , fields24ExplicitCount = Nothing+ , fields24Inner = Nothing+ , fields24UnknownFields = []+ }++instance MessageEncode Fields24 where+ buildMessage msg =+ (if msg.fields24Count == 0 then mempty else archVarint 8 (fromIntegral msg.fields24Count))+ <> (maybe mempty (\v -> archVarint 16 (fromIntegral v)) msg.fields24ExplicitCount)+ <> (maybe mempty (\v -> let sz = messageSize v in archSubmessage 26 sz (buildMessage v)) msg.fields24Inner)+ <> encodeUnknownFields msg.fields24UnknownFields++instance MessageSize Fields24 where+ messageSize msg =+ (if msg.fields24Count == 0 then 0 else archVarintSize (fromIntegral msg.fields24Count))+ + (maybe 0 (\v -> archVarintSize (fromIntegral v)) msg.fields24ExplicitCount)+ + (maybe 0 (\v -> archSubmessageSize (messageSize v)) msg.fields24Inner)+ + unknownFieldsSize msg.fields24UnknownFields++instance MessageDecode Fields24 where+ {-# INLINE messageDecoder #-}+ messageDecoder = loop 0 Nothing Nothing []+ where+ loop acc_0 acc_1 acc_2 acc_unknown_ = withTagM+ (pure (Fields24 {fields24Count = acc_0, fields24ExplicitCount = acc_1, fields24Inner = acc_2, fields24UnknownFields = reverse acc_unknown_}))+ (\fn wt -> case fn of+ 1 -> do+ v <- (fromIntegral <$> decodeFieldVarint)+ loop v acc_1 acc_2 acc_unknown_+ 2 -> do+ v <- (fromIntegral <$> decodeFieldVarint)+ loop acc_0 (Just v) acc_2 acc_unknown_+ 3 -> do+ v <- decodeFieldMessage+ loop acc_0 acc_1 (Just v) acc_unknown_+ _ -> do+ uf <- captureUnknownField fn (toEnum wt)+ loop acc_0 acc_1 acc_2 (uf : acc_unknown_))++instance ProtoMessage Fields24 where+ protoMessageName _ = "Fields24"+ protoPackageName _ = ""+ protoDefaultValue = defaultFields24+ protoFileDescriptorBytes _ = fileDescriptorProtoBytes+ protoFieldDescriptors _ = Map.fromList+ [ (1, SomeField FieldDescriptor+ { fdName = "count"+ , fdNumber = 1+ , fdTypeDesc = ScalarType Int32Field+ , fdLabel = LabelOptional+ , fdGet = fields24Count+ , fdSet = \v m -> m { fields24Count = v }+ }), (2, SomeField FieldDescriptor+ { fdName = "explicit_count"+ , fdNumber = 2+ , fdTypeDesc = ScalarType Int32Field+ , fdLabel = LabelOptional+ , fdGet = fields24ExplicitCount+ , fdSet = \v m -> m { fields24ExplicitCount = v }+ })+ , (3, SomeField FieldDescriptor+ { fdName = "inner"+ , fdNumber = 3+ , fdTypeDesc = MessageType "Inner"+ , fdLabel = LabelOptional+ , fdGet = fields24Inner+ , fdSet = \v m -> m { fields24Inner = v }+ })+ ]++instance IsMessage Fields24++instance Aeson.ToJSON Fields24 where+ toJSON msg = jsonObject+ [ "count" .=: msg.fields24Count+ , "explicitCount" .=: msg.fields24ExplicitCount+ , "inner" .=: msg.fields24Inner+ ]++instance Aeson.FromJSON Fields24 where+ parseJSON = Aeson.withObject "Fields24" $ \obj -> do+ fld_fields24Count <- parseFieldMaybe obj "count"+ fld_fields24ExplicitCount <- parseFieldMaybe obj "explicitCount"+ fld_fields24Inner <- parseFieldMaybe obj "inner"+ pure defaultFields24+ { fields24Count = maybe (fields24Count defaultFields24) id fld_fields24Count+ , fields24ExplicitCount = maybe (fields24ExplicitCount defaultFields24) id fld_fields24ExplicitCount+ , fields24Inner = maybe (fields24Inner defaultFields24) id fld_fields24Inner+ , fields24UnknownFields = []+ }++instance Hashable Fields24 where+ hashWithSalt salt msg = hashWithSalt (hashWithSalt (hashWithSalt (salt) msg.fields24Count) msg.fields24ExplicitCount) msg.fields24Inner++instance Proto.Extension.HasExtensions Fields24 where+ messageUnknownFields = fields24UnknownFields+ setMessageUnknownFields !ufs msg = msg { fields24UnknownFields = ufs }++instance Semigroup Fields24 where+ a <> b = Fields24+ { fields24Count = if b.fields24Count == 0 then a.fields24Count else b.fields24Count+ , fields24ExplicitCount = case b.fields24ExplicitCount of { Nothing -> a.fields24ExplicitCount; x -> x }+ , fields24Inner = case b.fields24Inner of { Nothing -> a.fields24Inner; x -> x }+ , fields24UnknownFields = a.fields24UnknownFields <> b.fields24UnknownFields+ }++instance Monoid Fields24 where+ mempty = defaultFields24
+ test-regression/Generated/Integermaps.hs view
@@ -0,0 +1,392 @@+{-# LANGUAGE StrictData #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE OverloadedRecordDot #-}+{-# OPTIONS_GHC -Wno-unused-imports -Wno-unused-matches -Wno-unused-top-binds #-}+-- | Auto-generated protobuf types.+--+-- __THIS FILE IS AUTO-GENERATED BY wireform. DO NOT EDIT.__+--+-- Any manual changes will be overwritten the next time code+-- generation is run. To modify the types or instances, edit the+-- @.proto@ source file and re-run the code generator.+module Generated.Integermaps where++import Data.ByteString (ByteString)+import qualified Data.ByteString as BS+import qualified Wireform.Builder as B+import Data.Int (Int32, Int64)+import Data.Text (Text)+import qualified Data.Text as T+import Data.Word (Word32, Word64)+import qualified Data.Map.Strict as Map+import qualified Data.Vector as V+import qualified Data.Vector.Unboxed as VU+import GHC.Generics (Generic)+import Control.DeepSeq (NFData(..))+import Data.Hashable (Hashable(..))+import Proto.Encode+import Proto.Decode+import qualified Data.Aeson as Aeson+import qualified Data.Aeson.Types as Aeson+import qualified Data.Aeson.Key as AesonKey+import qualified Data.Aeson.KeyMap as AesonKM+import Proto.Internal.JSON (jsonObject, (.=:), parseFieldMaybe, bytesFieldToJSON, parseBytesFieldMaybe, bytesMapFieldToJSON, parseBytesMapFieldMaybe, protoBytesToJSON)+import Data.Proxy (Proxy(..))+import Data.Reflection (Given(..), given)+import Proto.Internal.JSON.Extension qualified as PJExt+import Proto.Schema (ProtoMessage(..), SomeFieldDescriptor(..), FieldDescriptor(..), FieldTypeDescriptor(..), ScalarFieldType(..), FieldLabel'(..))+import Proto.Registry (IsMessage)+import Proto.Registry qualified+import qualified Proto.Extension+import Proto.Internal.Wire (Tag(..), WireType(..))+import Proto.Internal.Wire.Encode (putTag, putVarint, putFixed32, putFixed64,+ putFloat, putDouble, putText, putByteString, putLengthDelimited,+ putSVarint32, putSVarint64, putVarintSigned,+ varintSize, tagSize, fieldMessageSize,+ fieldVarintSize, fieldFixed32Size, fieldFixed64Size,+ fieldBoolSize, fieldFloatSize, fieldDoubleSize,+ fieldTextSize, fieldBytesSize,+ fieldSVarint32Size, fieldSVarint64Size,+ varintSize32, zigZag32, zigZag64)+import Proto.Internal.Encode.Archetype (archVarint, archSVarint32, archSVarint64,+ archFixed32, archFixed64, archFloat, archDouble, archBool,+ archString, archBytes, archSubmessage,+ archVarintSize, archStringSize, archBytesSize, archBoolSize,+ archFixed32Size, archFixed64Size, archSubmessageSize)++-- | Serialized FileDescriptorProto for this .proto file.+-- Decode with @Proto.Google.Protobuf.Descriptor.decodeMessage@.+fileDescriptorProtoBytes :: ByteString+fileDescriptorProtoBytes = "\x0a\x11\x69\x6e\x74\x65\x67\x65\x72\x4d\x61\x70\x73\x2e\x70\x72\x6f\x74\x6f\x22\x93\x04\x0a\x0b\x49\x6e\x74\x65\x67\x65\x72\x4d\x61\x70\x73\x12\x27\x0a\x0c\x69\x6e\x74\x33\x32\x5f\x76\x61\x6c\x75\x65\x73\x18\x01\x20\x03\x28\x0b\x32\x11\x69\x6e\x74\x33\x32\x5f\x76\x61\x6c\x75\x65\x73\x45\x6e\x74\x72\x79\x12\x27\x0a\x0c\x69\x6e\x74\x36\x34\x5f\x76\x61\x6c\x75\x65\x73\x18\x02\x20\x03\x28\x0b\x32\x11\x69\x6e\x74\x36\x34\x5f\x76\x61\x6c\x75\x65\x73\x45\x6e\x74\x72\x79\x12\x29\x0a\x0d\x75\x69\x6e\x74\x33\x32\x5f\x76\x61\x6c\x75\x65\x73\x18\x03\x20\x03\x28\x0b\x32\x12\x75\x69\x6e\x74\x33\x32\x5f\x76\x61\x6c\x75\x65\x73\x45\x6e\x74\x72\x79\x12\x29\x0a\x0d\x75\x69\x6e\x74\x36\x34\x5f\x76\x61\x6c\x75\x65\x73\x18\x04\x20\x03\x28\x0b\x32\x12\x75\x69\x6e\x74\x36\x34\x5f\x76\x61\x6c\x75\x65\x73\x45\x6e\x74\x72\x79\x12\x29\x0a\x0d\x73\x69\x6e\x74\x33\x32\x5f\x76\x61\x6c\x75\x65\x73\x18\x05\x20\x03\x28\x0b\x32\x12\x73\x69\x6e\x74\x33\x32\x5f\x76\x61\x6c\x75\x65\x73\x45\x6e\x74\x72\x79\x12\x29\x0a\x0d\x73\x69\x6e\x74\x36\x34\x5f\x76\x61\x6c\x75\x65\x73\x18\x06\x20\x03\x28\x0b\x32\x12\x73\x69\x6e\x74\x36\x34\x5f\x76\x61\x6c\x75\x65\x73\x45\x6e\x74\x72\x79\x12\x2d\x0a\x0f\x73\x66\x69\x78\x65\x64\x33\x32\x5f\x76\x61\x6c\x75\x65\x73\x18\x07\x20\x03\x28\x0b\x32\x14\x73\x66\x69\x78\x65\x64\x33\x32\x5f\x76\x61\x6c\x75\x65\x73\x45\x6e\x74\x72\x79\x12\x2d\x0a\x0f\x73\x66\x69\x78\x65\x64\x36\x34\x5f\x76\x61\x6c\x75\x65\x73\x18\x08\x20\x03\x28\x0b\x32\x14\x73\x66\x69\x78\x65\x64\x36\x34\x5f\x76\x61\x6c\x75\x65\x73\x45\x6e\x74\x72\x79\x12\x27\x0a\x0c\x69\x6e\x74\x65\x67\x65\x72\x5f\x6b\x65\x79\x73\x18\x09\x20\x03\x28\x0b\x32\x11\x69\x6e\x74\x65\x67\x65\x72\x5f\x6b\x65\x79\x73\x45\x6e\x74\x72\x79\x12\x2b\x0a\x0e\x66\x69\x78\x65\x64\x33\x32\x5f\x76\x61\x6c\x75\x65\x73\x18\x0a\x20\x03\x28\x0b\x32\x13\x66\x69\x78\x65\x64\x33\x32\x5f\x76\x61\x6c\x75\x65\x73\x45\x6e\x74\x72\x79\x12\x2b\x0a\x0e\x66\x69\x78\x65\x64\x36\x34\x5f\x76\x61\x6c\x75\x65\x73\x18\x0b\x20\x03\x28\x0b\x32\x13\x66\x69\x78\x65\x64\x36\x34\x5f\x76\x61\x6c\x75\x65\x73\x45\x6e\x74\x72\x79\x12\x25\x0a\x0b\x7a\x69\x67\x7a\x61\x67\x5f\x6b\x65\x79\x73\x18\x0c\x20\x03\x28\x0b\x32\x10\x7a\x69\x67\x7a\x61\x67\x5f\x6b\x65\x79\x73\x45\x6e\x74\x72\x79\x62\x06\x70\x72\x6f\x74\x6f\x33"+++data IntegerMaps = IntegerMaps+ { integerMapsInt32Values :: !(Map.Map Text Int32)+ , integerMapsInt64Values :: !(Map.Map Text Int64)+ , integerMapsUint32Values :: !(Map.Map Text Word32)+ , integerMapsUint64Values :: !(Map.Map Text Word64)+ , integerMapsSint32Values :: !(Map.Map Text Int32)+ , integerMapsSint64Values :: !(Map.Map Text Int64)+ , integerMapsSfixed32Values :: !(Map.Map Text Int32)+ , integerMapsSfixed64Values :: !(Map.Map Text Int64)+ , integerMapsIntegerKeys :: !(Map.Map Int32 Int32)+ , integerMapsFixed32Values :: !(Map.Map Text Word32)+ , integerMapsFixed64Values :: !(Map.Map Text Word64)+ , integerMapsZigzagKeys :: !(Map.Map Int32 Text)+ , integerMapsUnknownFields :: ![UnknownField]+ }+ deriving stock (Show, Eq, Generic)+ deriving anyclass NFData++defaultIntegerMaps :: IntegerMaps+defaultIntegerMaps = IntegerMaps+ { integerMapsInt32Values = Map.empty+ , integerMapsInt64Values = Map.empty+ , integerMapsUint32Values = Map.empty+ , integerMapsUint64Values = Map.empty+ , integerMapsSint32Values = Map.empty+ , integerMapsSint64Values = Map.empty+ , integerMapsSfixed32Values = Map.empty+ , integerMapsSfixed64Values = Map.empty+ , integerMapsIntegerKeys = Map.empty+ , integerMapsFixed32Values = Map.empty+ , integerMapsFixed64Values = Map.empty+ , integerMapsZigzagKeys = Map.empty+ , integerMapsUnknownFields = []+ }++instance MessageEncode IntegerMaps where+ buildMessage msg =+ Map.foldlWithKey' (\acc k v -> acc <> encodeMapField 1 (encodeFieldString 1 k) ((encodeFieldVarint 2 . fromIntegral) v)) mempty msg.integerMapsInt32Values+ <> Map.foldlWithKey' (\acc k v -> acc <> encodeMapField 2 (encodeFieldString 1 k) ((encodeFieldVarint 2 . fromIntegral) v)) mempty msg.integerMapsInt64Values+ <> Map.foldlWithKey' (\acc k v -> acc <> encodeMapField 3 (encodeFieldString 1 k) ((encodeFieldVarint 2 . fromIntegral) v)) mempty msg.integerMapsUint32Values+ <> Map.foldlWithKey' (\acc k v -> acc <> encodeMapField 4 (encodeFieldString 1 k) ((encodeFieldVarint 2 . fromIntegral) v)) mempty msg.integerMapsUint64Values+ <> Map.foldlWithKey' (\acc k v -> acc <> encodeMapField 5 (encodeFieldString 1 k) (encodeFieldSVarint32 2 v)) mempty msg.integerMapsSint32Values+ <> Map.foldlWithKey' (\acc k v -> acc <> encodeMapField 6 (encodeFieldString 1 k) (encodeFieldSVarint64 2 v)) mempty msg.integerMapsSint64Values+ <> Map.foldlWithKey' (\acc k v -> acc <> encodeMapField 7 (encodeFieldString 1 k) ((encodeFieldFixed32 2 . fromIntegral) v)) mempty msg.integerMapsSfixed32Values+ <> Map.foldlWithKey' (\acc k v -> acc <> encodeMapField 8 (encodeFieldString 1 k) ((encodeFieldFixed64 2 . fromIntegral) v)) mempty msg.integerMapsSfixed64Values+ <> Map.foldlWithKey' (\acc k v -> acc <> encodeMapField 9 ((\x -> encodeFieldVarint 1 (fromIntegral x)) k) ((encodeFieldVarint 2 . fromIntegral) v)) mempty msg.integerMapsIntegerKeys+ <> Map.foldlWithKey' (\acc k v -> acc <> encodeMapField 10 (encodeFieldString 1 k) (encodeFieldFixed32 2 v)) mempty msg.integerMapsFixed32Values+ <> Map.foldlWithKey' (\acc k v -> acc <> encodeMapField 11 (encodeFieldString 1 k) (encodeFieldFixed64 2 v)) mempty msg.integerMapsFixed64Values+ <> Map.foldlWithKey' (\acc k v -> acc <> encodeMapField 12 (encodeFieldSVarint32 1 k) (encodeFieldString 2 v)) mempty msg.integerMapsZigzagKeys+ <> encodeUnknownFields msg.integerMapsUnknownFields++instance MessageSize IntegerMaps where+ messageSize msg =+ (Map.foldlWithKey' (\acc k v -> let entrySz = fieldTextSize 1 k + fieldVarintSize 2 (fromIntegral v) in acc + tagSize 1 + varintSize (fromIntegral entrySz) + entrySz) 0 msg.integerMapsInt32Values)+ + (Map.foldlWithKey' (\acc k v -> let entrySz = fieldTextSize 1 k + fieldVarintSize 2 (fromIntegral v) in acc + tagSize 2 + varintSize (fromIntegral entrySz) + entrySz) 0 msg.integerMapsInt64Values)+ + (Map.foldlWithKey' (\acc k v -> let entrySz = fieldTextSize 1 k + fieldVarintSize 2 (fromIntegral v) in acc + tagSize 3 + varintSize (fromIntegral entrySz) + entrySz) 0 msg.integerMapsUint32Values)+ + (Map.foldlWithKey' (\acc k v -> let entrySz = fieldTextSize 1 k + fieldVarintSize 2 (fromIntegral v) in acc + tagSize 4 + varintSize (fromIntegral entrySz) + entrySz) 0 msg.integerMapsUint64Values)+ + (Map.foldlWithKey' (\acc k v -> let entrySz = fieldTextSize 1 k + fieldSVarint32Size 2 v in acc + tagSize 5 + varintSize (fromIntegral entrySz) + entrySz) 0 msg.integerMapsSint32Values)+ + (Map.foldlWithKey' (\acc k v -> let entrySz = fieldTextSize 1 k + fieldSVarint64Size 2 v in acc + tagSize 6 + varintSize (fromIntegral entrySz) + entrySz) 0 msg.integerMapsSint64Values)+ + (Map.foldlWithKey' (\acc k v -> let entrySz = fieldTextSize 1 k + fieldFixed32Size 2 in acc + tagSize 7 + varintSize (fromIntegral entrySz) + entrySz) 0 msg.integerMapsSfixed32Values)+ + (Map.foldlWithKey' (\acc k v -> let entrySz = fieldTextSize 1 k + fieldFixed64Size 2 in acc + tagSize 8 + varintSize (fromIntegral entrySz) + entrySz) 0 msg.integerMapsSfixed64Values)+ + (Map.foldlWithKey' (\acc k v -> let entrySz = fieldVarintSize 1 (fromIntegral k) + fieldVarintSize 2 (fromIntegral v) in acc + tagSize 9 + varintSize (fromIntegral entrySz) + entrySz) 0 msg.integerMapsIntegerKeys)+ + (Map.foldlWithKey' (\acc k v -> let entrySz = fieldTextSize 1 k + fieldFixed32Size 2 in acc + tagSize 10 + varintSize (fromIntegral entrySz) + entrySz) 0 msg.integerMapsFixed32Values)+ + (Map.foldlWithKey' (\acc k v -> let entrySz = fieldTextSize 1 k + fieldFixed64Size 2 in acc + tagSize 11 + varintSize (fromIntegral entrySz) + entrySz) 0 msg.integerMapsFixed64Values)+ + (Map.foldlWithKey' (\acc k v -> let entrySz = fieldSVarint32Size 1 k + fieldTextSize 2 v in acc + tagSize 12 + varintSize (fromIntegral entrySz) + entrySz) 0 msg.integerMapsZigzagKeys)+ + unknownFieldsSize msg.integerMapsUnknownFields++instance MessageDecode IntegerMaps where+ {-# INLINE messageDecoder #-}+ messageDecoder = loop Map.empty Map.empty Map.empty Map.empty Map.empty Map.empty Map.empty Map.empty Map.empty Map.empty Map.empty Map.empty []+ where+ loop acc_0 acc_1 acc_2 acc_3 acc_4 acc_5 acc_6 acc_7 acc_8 acc_9 acc_10 acc_11 acc_unknown_ = withTagM+ (pure (IntegerMaps {integerMapsInt32Values = acc_0, integerMapsInt64Values = acc_1, integerMapsUint32Values = acc_2, integerMapsUint64Values = acc_3, integerMapsSint32Values = acc_4, integerMapsSint64Values = acc_5, integerMapsSfixed32Values = acc_6, integerMapsSfixed64Values = acc_7, integerMapsIntegerKeys = acc_8, integerMapsFixed32Values = acc_9, integerMapsFixed64Values = acc_10, integerMapsZigzagKeys = acc_11, integerMapsUnknownFields = reverse acc_unknown_}))+ (\fn wt -> case fn of+ 1 -> do+ bs' <- getLengthDelimited+ let decodeEntry = runDecoder (decodeMapEntry decodeFieldString (fromIntegral <$> decodeFieldVarint) "" 0) bs'+ case decodeEntry of+ Left _ -> loop acc_0 acc_1 acc_2 acc_3 acc_4 acc_5 acc_6 acc_7 acc_8 acc_9 acc_10 acc_11 acc_unknown_+ Right (mk', mv') -> loop (Map.union acc_0 (Map.singleton mk' mv')) acc_1 acc_2 acc_3 acc_4 acc_5 acc_6 acc_7 acc_8 acc_9 acc_10 acc_11 acc_unknown_+ 2 -> do+ bs' <- getLengthDelimited+ let decodeEntry = runDecoder (decodeMapEntry decodeFieldString (fromIntegral <$> decodeFieldVarint) "" 0) bs'+ case decodeEntry of+ Left _ -> loop acc_0 acc_1 acc_2 acc_3 acc_4 acc_5 acc_6 acc_7 acc_8 acc_9 acc_10 acc_11 acc_unknown_+ Right (mk', mv') -> loop acc_0 (Map.union acc_1 (Map.singleton mk' mv')) acc_2 acc_3 acc_4 acc_5 acc_6 acc_7 acc_8 acc_9 acc_10 acc_11 acc_unknown_+ 3 -> do+ bs' <- getLengthDelimited+ let decodeEntry = runDecoder (decodeMapEntry decodeFieldString (fromIntegral <$> decodeFieldVarint) "" 0) bs'+ case decodeEntry of+ Left _ -> loop acc_0 acc_1 acc_2 acc_3 acc_4 acc_5 acc_6 acc_7 acc_8 acc_9 acc_10 acc_11 acc_unknown_+ Right (mk', mv') -> loop acc_0 acc_1 (Map.union acc_2 (Map.singleton mk' mv')) acc_3 acc_4 acc_5 acc_6 acc_7 acc_8 acc_9 acc_10 acc_11 acc_unknown_+ 4 -> do+ bs' <- getLengthDelimited+ let decodeEntry = runDecoder (decodeMapEntry decodeFieldString decodeFieldVarint "" 0) bs'+ case decodeEntry of+ Left _ -> loop acc_0 acc_1 acc_2 acc_3 acc_4 acc_5 acc_6 acc_7 acc_8 acc_9 acc_10 acc_11 acc_unknown_+ Right (mk', mv') -> loop acc_0 acc_1 acc_2 (Map.union acc_3 (Map.singleton mk' mv')) acc_4 acc_5 acc_6 acc_7 acc_8 acc_9 acc_10 acc_11 acc_unknown_+ 5 -> do+ bs' <- getLengthDelimited+ let decodeEntry = runDecoder (decodeMapEntry decodeFieldString decodeFieldSVarint32 "" 0) bs'+ case decodeEntry of+ Left _ -> loop acc_0 acc_1 acc_2 acc_3 acc_4 acc_5 acc_6 acc_7 acc_8 acc_9 acc_10 acc_11 acc_unknown_+ Right (mk', mv') -> loop acc_0 acc_1 acc_2 acc_3 (Map.union acc_4 (Map.singleton mk' mv')) acc_5 acc_6 acc_7 acc_8 acc_9 acc_10 acc_11 acc_unknown_+ 6 -> do+ bs' <- getLengthDelimited+ let decodeEntry = runDecoder (decodeMapEntry decodeFieldString decodeFieldSVarint64 "" 0) bs'+ case decodeEntry of+ Left _ -> loop acc_0 acc_1 acc_2 acc_3 acc_4 acc_5 acc_6 acc_7 acc_8 acc_9 acc_10 acc_11 acc_unknown_+ Right (mk', mv') -> loop acc_0 acc_1 acc_2 acc_3 acc_4 (Map.union acc_5 (Map.singleton mk' mv')) acc_6 acc_7 acc_8 acc_9 acc_10 acc_11 acc_unknown_+ 7 -> do+ bs' <- getLengthDelimited+ let decodeEntry = runDecoder (decodeMapEntry decodeFieldString (fromIntegral <$> decodeFieldFixed32) "" 0) bs'+ case decodeEntry of+ Left _ -> loop acc_0 acc_1 acc_2 acc_3 acc_4 acc_5 acc_6 acc_7 acc_8 acc_9 acc_10 acc_11 acc_unknown_+ Right (mk', mv') -> loop acc_0 acc_1 acc_2 acc_3 acc_4 acc_5 (Map.union acc_6 (Map.singleton mk' mv')) acc_7 acc_8 acc_9 acc_10 acc_11 acc_unknown_+ 8 -> do+ bs' <- getLengthDelimited+ let decodeEntry = runDecoder (decodeMapEntry decodeFieldString (fromIntegral <$> decodeFieldFixed64) "" 0) bs'+ case decodeEntry of+ Left _ -> loop acc_0 acc_1 acc_2 acc_3 acc_4 acc_5 acc_6 acc_7 acc_8 acc_9 acc_10 acc_11 acc_unknown_+ Right (mk', mv') -> loop acc_0 acc_1 acc_2 acc_3 acc_4 acc_5 acc_6 (Map.union acc_7 (Map.singleton mk' mv')) acc_8 acc_9 acc_10 acc_11 acc_unknown_+ 9 -> do+ bs' <- getLengthDelimited+ let decodeEntry = runDecoder (decodeMapEntry (fromIntegral <$> decodeFieldVarint) (fromIntegral <$> decodeFieldVarint) 0 0) bs'+ case decodeEntry of+ Left _ -> loop acc_0 acc_1 acc_2 acc_3 acc_4 acc_5 acc_6 acc_7 acc_8 acc_9 acc_10 acc_11 acc_unknown_+ Right (mk', mv') -> loop acc_0 acc_1 acc_2 acc_3 acc_4 acc_5 acc_6 acc_7 (Map.union acc_8 (Map.singleton mk' mv')) acc_9 acc_10 acc_11 acc_unknown_+ 10 -> do+ bs' <- getLengthDelimited+ let decodeEntry = runDecoder (decodeMapEntry decodeFieldString decodeFieldFixed32 "" 0) bs'+ case decodeEntry of+ Left _ -> loop acc_0 acc_1 acc_2 acc_3 acc_4 acc_5 acc_6 acc_7 acc_8 acc_9 acc_10 acc_11 acc_unknown_+ Right (mk', mv') -> loop acc_0 acc_1 acc_2 acc_3 acc_4 acc_5 acc_6 acc_7 acc_8 (Map.union acc_9 (Map.singleton mk' mv')) acc_10 acc_11 acc_unknown_+ 11 -> do+ bs' <- getLengthDelimited+ let decodeEntry = runDecoder (decodeMapEntry decodeFieldString decodeFieldFixed64 "" 0) bs'+ case decodeEntry of+ Left _ -> loop acc_0 acc_1 acc_2 acc_3 acc_4 acc_5 acc_6 acc_7 acc_8 acc_9 acc_10 acc_11 acc_unknown_+ Right (mk', mv') -> loop acc_0 acc_1 acc_2 acc_3 acc_4 acc_5 acc_6 acc_7 acc_8 acc_9 (Map.union acc_10 (Map.singleton mk' mv')) acc_11 acc_unknown_+ 12 -> do+ bs' <- getLengthDelimited+ let decodeEntry = runDecoder (decodeMapEntry decodeFieldSVarint32 decodeFieldString 0 "") bs'+ case decodeEntry of+ Left _ -> loop acc_0 acc_1 acc_2 acc_3 acc_4 acc_5 acc_6 acc_7 acc_8 acc_9 acc_10 acc_11 acc_unknown_+ Right (mk', mv') -> loop acc_0 acc_1 acc_2 acc_3 acc_4 acc_5 acc_6 acc_7 acc_8 acc_9 acc_10 (Map.union acc_11 (Map.singleton mk' mv')) acc_unknown_+ _ -> do+ uf <- captureUnknownField fn (toEnum wt)+ loop acc_0 acc_1 acc_2 acc_3 acc_4 acc_5 acc_6 acc_7 acc_8 acc_9 acc_10 acc_11 (uf : acc_unknown_))++instance ProtoMessage IntegerMaps where+ protoMessageName _ = "IntegerMaps"+ protoPackageName _ = ""+ protoDefaultValue = defaultIntegerMaps+ protoFileDescriptorBytes _ = fileDescriptorProtoBytes+ protoFieldDescriptors _ = Map.fromList+ [ (1, SomeField FieldDescriptor+ { fdName = "int32_values"+ , fdNumber = 1+ , fdTypeDesc = ScalarType BytesField+ , fdLabel = LabelRepeated+ , fdGet = integerMapsInt32Values+ , fdSet = \v m -> m { integerMapsInt32Values = v }+ }), (2, SomeField FieldDescriptor+ { fdName = "int64_values"+ , fdNumber = 2+ , fdTypeDesc = ScalarType BytesField+ , fdLabel = LabelRepeated+ , fdGet = integerMapsInt64Values+ , fdSet = \v m -> m { integerMapsInt64Values = v }+ })+ , (3, SomeField FieldDescriptor+ { fdName = "uint32_values"+ , fdNumber = 3+ , fdTypeDesc = ScalarType BytesField+ , fdLabel = LabelRepeated+ , fdGet = integerMapsUint32Values+ , fdSet = \v m -> m { integerMapsUint32Values = v }+ })+ , (4, SomeField FieldDescriptor+ { fdName = "uint64_values"+ , fdNumber = 4+ , fdTypeDesc = ScalarType BytesField+ , fdLabel = LabelRepeated+ , fdGet = integerMapsUint64Values+ , fdSet = \v m -> m { integerMapsUint64Values = v }+ })+ , (5, SomeField FieldDescriptor+ { fdName = "sint32_values"+ , fdNumber = 5+ , fdTypeDesc = ScalarType BytesField+ , fdLabel = LabelRepeated+ , fdGet = integerMapsSint32Values+ , fdSet = \v m -> m { integerMapsSint32Values = v }+ })+ , (6, SomeField FieldDescriptor+ { fdName = "sint64_values"+ , fdNumber = 6+ , fdTypeDesc = ScalarType BytesField+ , fdLabel = LabelRepeated+ , fdGet = integerMapsSint64Values+ , fdSet = \v m -> m { integerMapsSint64Values = v }+ })+ , (7, SomeField FieldDescriptor+ { fdName = "sfixed32_values"+ , fdNumber = 7+ , fdTypeDesc = ScalarType BytesField+ , fdLabel = LabelRepeated+ , fdGet = integerMapsSfixed32Values+ , fdSet = \v m -> m { integerMapsSfixed32Values = v }+ })+ , (8, SomeField FieldDescriptor+ { fdName = "sfixed64_values"+ , fdNumber = 8+ , fdTypeDesc = ScalarType BytesField+ , fdLabel = LabelRepeated+ , fdGet = integerMapsSfixed64Values+ , fdSet = \v m -> m { integerMapsSfixed64Values = v }+ })+ , (9, SomeField FieldDescriptor+ { fdName = "integer_keys"+ , fdNumber = 9+ , fdTypeDesc = ScalarType BytesField+ , fdLabel = LabelRepeated+ , fdGet = integerMapsIntegerKeys+ , fdSet = \v m -> m { integerMapsIntegerKeys = v }+ })+ , (10, SomeField FieldDescriptor+ { fdName = "fixed32_values"+ , fdNumber = 10+ , fdTypeDesc = ScalarType BytesField+ , fdLabel = LabelRepeated+ , fdGet = integerMapsFixed32Values+ , fdSet = \v m -> m { integerMapsFixed32Values = v }+ })+ , (11, SomeField FieldDescriptor+ { fdName = "fixed64_values"+ , fdNumber = 11+ , fdTypeDesc = ScalarType BytesField+ , fdLabel = LabelRepeated+ , fdGet = integerMapsFixed64Values+ , fdSet = \v m -> m { integerMapsFixed64Values = v }+ })+ , (12, SomeField FieldDescriptor+ { fdName = "zigzag_keys"+ , fdNumber = 12+ , fdTypeDesc = ScalarType BytesField+ , fdLabel = LabelRepeated+ , fdGet = integerMapsZigzagKeys+ , fdSet = \v m -> m { integerMapsZigzagKeys = v }+ })+ ]++instance IsMessage IntegerMaps++instance Aeson.ToJSON IntegerMaps where+ toJSON msg = jsonObject+ [ "int32Values" .=: msg.integerMapsInt32Values+ , "int64Values" .=: msg.integerMapsInt64Values+ , "uint32Values" .=: msg.integerMapsUint32Values+ , "uint64Values" .=: msg.integerMapsUint64Values+ , "sint32Values" .=: msg.integerMapsSint32Values+ , "sint64Values" .=: msg.integerMapsSint64Values+ , "sfixed32Values" .=: msg.integerMapsSfixed32Values+ , "sfixed64Values" .=: msg.integerMapsSfixed64Values+ , "integerKeys" .=: msg.integerMapsIntegerKeys+ , "fixed32Values" .=: msg.integerMapsFixed32Values+ , "fixed64Values" .=: msg.integerMapsFixed64Values+ , "zigzagKeys" .=: msg.integerMapsZigzagKeys+ ]++instance Aeson.FromJSON IntegerMaps where+ parseJSON = Aeson.withObject "IntegerMaps" $ \obj -> do+ fld_integerMapsInt32Values <- parseFieldMaybe obj "int32Values"+ fld_integerMapsInt64Values <- parseFieldMaybe obj "int64Values"+ fld_integerMapsUint32Values <- parseFieldMaybe obj "uint32Values"+ fld_integerMapsUint64Values <- parseFieldMaybe obj "uint64Values"+ fld_integerMapsSint32Values <- parseFieldMaybe obj "sint32Values"+ fld_integerMapsSint64Values <- parseFieldMaybe obj "sint64Values"+ fld_integerMapsSfixed32Values <- parseFieldMaybe obj "sfixed32Values"+ fld_integerMapsSfixed64Values <- parseFieldMaybe obj "sfixed64Values"+ fld_integerMapsIntegerKeys <- parseFieldMaybe obj "integerKeys"+ fld_integerMapsFixed32Values <- parseFieldMaybe obj "fixed32Values"+ fld_integerMapsFixed64Values <- parseFieldMaybe obj "fixed64Values"+ fld_integerMapsZigzagKeys <- parseFieldMaybe obj "zigzagKeys"+ pure defaultIntegerMaps+ { integerMapsInt32Values = maybe (integerMapsInt32Values defaultIntegerMaps) id fld_integerMapsInt32Values+ , integerMapsInt64Values = maybe (integerMapsInt64Values defaultIntegerMaps) id fld_integerMapsInt64Values+ , integerMapsUint32Values = maybe (integerMapsUint32Values defaultIntegerMaps) id fld_integerMapsUint32Values+ , integerMapsUint64Values = maybe (integerMapsUint64Values defaultIntegerMaps) id fld_integerMapsUint64Values+ , integerMapsSint32Values = maybe (integerMapsSint32Values defaultIntegerMaps) id fld_integerMapsSint32Values+ , integerMapsSint64Values = maybe (integerMapsSint64Values defaultIntegerMaps) id fld_integerMapsSint64Values+ , integerMapsSfixed32Values = maybe (integerMapsSfixed32Values defaultIntegerMaps) id fld_integerMapsSfixed32Values+ , integerMapsSfixed64Values = maybe (integerMapsSfixed64Values defaultIntegerMaps) id fld_integerMapsSfixed64Values+ , integerMapsIntegerKeys = maybe (integerMapsIntegerKeys defaultIntegerMaps) id fld_integerMapsIntegerKeys+ , integerMapsFixed32Values = maybe (integerMapsFixed32Values defaultIntegerMaps) id fld_integerMapsFixed32Values+ , integerMapsFixed64Values = maybe (integerMapsFixed64Values defaultIntegerMaps) id fld_integerMapsFixed64Values+ , integerMapsZigzagKeys = maybe (integerMapsZigzagKeys defaultIntegerMaps) id fld_integerMapsZigzagKeys+ , integerMapsUnknownFields = []+ }++instance Hashable IntegerMaps where+ hashWithSalt salt msg = Map.foldlWithKey' (\s k v -> s `hashWithSalt` k `hashWithSalt` v) (Map.foldlWithKey' (\s k v -> s `hashWithSalt` k `hashWithSalt` v) (Map.foldlWithKey' (\s k v -> s `hashWithSalt` k `hashWithSalt` v) (Map.foldlWithKey' (\s k v -> s `hashWithSalt` k `hashWithSalt` v) (Map.foldlWithKey' (\s k v -> s `hashWithSalt` k `hashWithSalt` v) (Map.foldlWithKey' (\s k v -> s `hashWithSalt` k `hashWithSalt` v) (Map.foldlWithKey' (\s k v -> s `hashWithSalt` k `hashWithSalt` v) (Map.foldlWithKey' (\s k v -> s `hashWithSalt` k `hashWithSalt` v) (Map.foldlWithKey' (\s k v -> s `hashWithSalt` k `hashWithSalt` v) (Map.foldlWithKey' (\s k v -> s `hashWithSalt` k `hashWithSalt` v) (Map.foldlWithKey' (\s k v -> s `hashWithSalt` k `hashWithSalt` v) (Map.foldlWithKey' (\s k v -> s `hashWithSalt` k `hashWithSalt` v) (salt) msg.integerMapsInt32Values) msg.integerMapsInt64Values) msg.integerMapsUint32Values) msg.integerMapsUint64Values) msg.integerMapsSint32Values) msg.integerMapsSint64Values) msg.integerMapsSfixed32Values) msg.integerMapsSfixed64Values) msg.integerMapsIntegerKeys) msg.integerMapsFixed32Values) msg.integerMapsFixed64Values) msg.integerMapsZigzagKeys++instance Proto.Extension.HasExtensions IntegerMaps where+ messageUnknownFields = integerMapsUnknownFields+ setMessageUnknownFields !ufs msg = msg { integerMapsUnknownFields = ufs }++instance Semigroup IntegerMaps where+ a <> b = IntegerMaps+ { integerMapsInt32Values = a.integerMapsInt32Values <> b.integerMapsInt32Values+ , integerMapsInt64Values = a.integerMapsInt64Values <> b.integerMapsInt64Values+ , integerMapsUint32Values = a.integerMapsUint32Values <> b.integerMapsUint32Values+ , integerMapsUint64Values = a.integerMapsUint64Values <> b.integerMapsUint64Values+ , integerMapsSint32Values = a.integerMapsSint32Values <> b.integerMapsSint32Values+ , integerMapsSint64Values = a.integerMapsSint64Values <> b.integerMapsSint64Values+ , integerMapsSfixed32Values = a.integerMapsSfixed32Values <> b.integerMapsSfixed32Values+ , integerMapsSfixed64Values = a.integerMapsSfixed64Values <> b.integerMapsSfixed64Values+ , integerMapsIntegerKeys = a.integerMapsIntegerKeys <> b.integerMapsIntegerKeys+ , integerMapsFixed32Values = a.integerMapsFixed32Values <> b.integerMapsFixed32Values+ , integerMapsFixed64Values = a.integerMapsFixed64Values <> b.integerMapsFixed64Values+ , integerMapsZigzagKeys = a.integerMapsZigzagKeys <> b.integerMapsZigzagKeys+ , integerMapsUnknownFields = a.integerMapsUnknownFields <> b.integerMapsUnknownFields+ }++instance Monoid IntegerMaps where+ mempty = defaultIntegerMaps
+ test-regression/Main.hs view
@@ -0,0 +1,151 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}++module Main (main) where++import Control.Exception (ErrorCall, evaluate)+import Control.Monad (forM_)+import Data.Aeson qualified as Aeson+import Data.Aeson.Types qualified as Aeson+import Data.ByteString qualified as BS+import Data.Either (isLeft)+import Data.Map.Strict qualified as Map+import Data.Text qualified as T+import Data.Text.IO qualified as TIO+import Generated.Edition2023 qualified as G23+import Generated.Edition2024 qualified as G24+import Generated.Integermaps qualified as GM+import Proto.CodeGen+import Proto.Decode+import Proto.Encode+import Proto.IDL.Parser+import Proto.IDL.Parser.Resolver (ResolvedProto (..))+import Proto.Internal.JSON qualified as JSON+import THFixtures qualified as TH+import Test.Syd+++main :: IO ()+main = sydTest $ do+ describe "Editions field presence" $ do+ it "standalone codegen preserves every explicitly present scalar default" $+ forM_ scalarDefaults $+ roundtrip @G23.Fields23+ it "loadProto preserves every explicitly present scalar default" $+ forM_ scalarDefaults $+ roundtrip @TH.Fields23+ it "absent and explicitly zero fields remain distinct in both generators" $ do+ fmap G23.fields23Count (decodeMessage BS.empty) `shouldBe` Right Nothing+ fmap G23.fields23Count (decodeMessage (BS.pack [8, 0])) `shouldBe` Right (Just 0)+ fmap TH.fields23Count (decodeMessage BS.empty) `shouldBe` Right Nothing+ fmap TH.fields23Count (decodeMessage (BS.pack [8, 0])) `shouldBe` Right (Just 0)+ it "file and field overrides, aggregate options and nested messages agree" $ do+ forM_ [BS.pack [16, 0], BS.pack [26, 2, 16, 0]] $ roundtrip @G24.Fields24+ forM_ [BS.pack [16, 0], BS.pack [26, 2, 16, 0]] $ roundtrip @TH.Fields24+ fmap G24.fields24Count (decodeMessage (BS.pack [8, 0])) `shouldBe` Right 0+ fmap TH.fields24Count (decodeMessage (BS.pack [8, 0])) `shouldBe` Right 0+ fmap encodeMessage (decodeMessage (BS.pack [8, 0]) :: Either DecodeError G24.Fields24) `shouldBe` Right BS.empty+ fmap encodeMessage (decodeMessage (BS.pack [8, 0]) :: Either DecodeError TH.Fields24) `shouldBe` Right BS.empty+ fmap encodeMessage (decodeMessage (BS.pack [64, 0, 112, 0]) :: Either DecodeError G23.Fields23) `shouldBe` Right BS.empty+ fmap encodeMessage (decodeMessage (BS.pack [64, 0, 112, 0]) :: Either DecodeError TH.Fields23) `shouldBe` Right BS.empty+ it "repeated, map, oneof and nested fields retain their wire shapes" $ do+ forM_ collectionValues $ roundtrip @G23.Fields23+ forM_ collectionValues $ roundtrip @TH.Fields23+ it "merge preserves an explicitly present zero over a nonzero value" $ do+ case (decodeMessage (BS.pack [8, 7]), decodeMessage (BS.pack [8, 0])) of+ (Right a, Right b) -> encodeMessage (a <> b :: G23.Fields23) `shouldBe` BS.pack [8, 0]+ _ -> expectationFailure "standalone fixture decode failed"+ case (decodeMessage (BS.pack [8, 7]), decodeMessage (BS.pack [8, 0])) of+ (Right a, Right b) -> encodeMessage (a <> b :: TH.Fields23) `shouldBe` BS.pack [8, 0]+ _ -> expectationFailure "TH fixture decode failed"+ it "does not silently accept unsupported LEGACY_REQUIRED presence" $ do+ pf <-+ either (fail . show) pure $+ parseProtoFile+ "unsupported.proto"+ "edition = \"2023\"; message M { int32 x = 1 [features.field_presence = LEGACY_REQUIRED]; }"+ evaluate (T.length (generateModuleText defaultGenerateOpts Map.empty "unsupported.proto" pf))+ `shouldThrow` (\(_ :: ErrorCall) -> True)+ it "committed standalone fixtures exactly match the generator" $+ forM_ ["edition2023", "edition2024", "integerMaps"] $ \name -> do+ let file = name <> ".proto"+ source <- TIO.readFile ("test-regression/proto/" <> file)+ pf <- either (fail . show) pure (parseProtoFile file source)+ let opts = defaultGenerateOpts {genModulePrefix = "Generated"}+ registry = buildTypeRegistry opts [(file, ResolvedProto pf file Map.empty)]+ actual <- TIO.readFile ("test-regression/" <> T.unpack (T.replace "." "/" (moduleNameForProto opts file pf)) <> ".hs")+ actual `shouldBe` generateModuleText opts registry file pf+ describe "Standalone integer maps" $ do+ it "integer value functions compile and agree with TH for limits and zero" $+ forM_ integerMapValues $ \message -> do+ let wire = encodeMessage message+ decodeMessage wire `shouldBe` Right message+ messageSize message `shouldBe` BS.length wire+ encodeMessageSized message `shouldBe` wire+ fmap encodeMessage (decodeMessage wire :: Either DecodeError TH.IntegerMaps) `shouldBe` Right wire+ it "sint32 and sint64 map values use canonical ZigZag and exact sizes" $ do+ let small = GM.defaultIntegerMaps {GM.integerMapsSint32Values = Map.singleton "a" (-1)}+ large = GM.defaultIntegerMaps {GM.integerMapsSint64Values = Map.singleton "a" (-1)}+ encodeMessage small `shouldBe` BS.pack [42, 5, 10, 1, 97, 16, 1]+ encodeMessage large `shouldBe` BS.pack [50, 5, 10, 1, 97, 16, 1]+ messageSize small `shouldBe` 7+ messageSize large `shouldBe` 7+ describe "ProtoJSON quoted numbers" $ do+ it "generated Float and Double accept e/E and signed exponents" $+ forM_ (["1e2", "1E+2", "10000e-2", "1.0e2"] :: [T.Text]) $ \number -> do+ let json = Aeson.object ["value" Aeson..= number, "small" Aeson..= number]+ fmap TH.jsonNumbersValue (Aeson.parseEither Aeson.parseJSON json) `shouldBe` Right 100+ fmap TH.jsonNumbersSmall (Aeson.parseEither Aeson.parseJSON json) `shouldBe` Right 100+ it "optional and oneof floating fields use the same number grammar" $ do+ let json = "{\"optionalValue\":\"1e2\",\"selected\":\"1E2\"}"+ case Aeson.eitherDecode json of+ Left err -> expectationFailure err+ Right result -> do+ TH.jsonNumbersOptionalValue result `shouldBe` Just 100+ case TH.jsonNumbersChoice result of+ Just (TH.JsonNumbers'Choice'Selected value) -> value `shouldBe` 100+ _ -> expectationFailure "quoted oneof number was dropped"+ it "runtime float/double and int64 helpers accept quoted exponents" $ do+ Aeson.parseEither JSON.protoDoubleFromJSON (Aeson.String "1e2") `shouldBe` Right 100+ Aeson.parseEither JSON.protoFloatFromJSON (Aeson.String "1E2") `shouldBe` Right 100+ Aeson.parseEither JSON.protoInt64FromJSON (Aeson.String "1e2") `shouldBe` Right 100+ Aeson.parseEither JSON.protoWord64FromJSON (Aeson.String "1E2") `shouldBe` Right 100+ it "rejects whitespace, trailing junk, noncanonical sentinels and overflow" $+ forM_ ["", " 1", "1 ", "1e", "1e2x", "nan", "NAN", "+nan", "-nan", " NaN", "NaN ", "inf", "INF", "+Infinity", " Infinity", "Infinity ", "-inf", "-INF", " -Infinity", "-Infinity ", "1e1000"] $ \number -> do+ isLeft (Aeson.parseEither JSON.protoDoubleFromJSON (Aeson.String number)) `shouldBe` True+ isLeft (Aeson.parseEither JSON.protoFloatFromJSON (Aeson.String number)) `shouldBe` True+ isLeft (Aeson.parseEither Aeson.parseJSON (Aeson.object ["value" Aeson..= number]) :: Either String TH.JsonNumbers) `shouldBe` True+ it "retains canonical NaN/Infinity strings" $ do+ fmap isNaN (Aeson.parseEither JSON.protoDoubleFromJSON (Aeson.String "NaN")) `shouldBe` Right True+ fmap isInfinite (Aeson.parseEither JSON.protoDoubleFromJSON (Aeson.String "Infinity")) `shouldBe` Right True+ Aeson.parseEither JSON.protoDoubleFromJSON (Aeson.String "-Infinity") `shouldBe` Right (negate (1 / 0))+++scalarDefaults :: [BS.ByteString]+scalarDefaults = fmap BS.pack [[8, 0], [18, 0], [26, 0], [32, 0], [40, 0], [49, 0, 0, 0, 0, 0, 0, 0, 0], [61, 0, 0, 0, 0]]+++collectionValues :: [BS.ByteString]+collectionValues = fmap BS.pack [[74, 2, 1, 2], [82, 2, 8, 0], [90, 6, 10, 1, 97, 18, 1, 98], [96, 0], [106, 0]]+++roundtrip :: forall a. (MessageDecode a, MessageEncode a) => BS.ByteString -> IO ()+roundtrip wire = fmap (encodeMessage @a) (decodeMessage wire) `shouldBe` Right wire+++integerMapValues :: [GM.IntegerMaps]+integerMapValues =+ [ GM.defaultIntegerMaps {GM.integerMapsInt32Values = Map.fromList [("a", minBound), ("b", 0), ("c", maxBound)]}+ , GM.defaultIntegerMaps {GM.integerMapsInt64Values = Map.fromList [("a", minBound), ("b", 0), ("c", maxBound)]}+ , GM.defaultIntegerMaps {GM.integerMapsUint32Values = Map.fromList [("a", 0), ("b", maxBound)]}+ , GM.defaultIntegerMaps {GM.integerMapsUint64Values = Map.fromList [("a", 0), ("b", maxBound)]}+ , GM.defaultIntegerMaps {GM.integerMapsSint32Values = Map.fromList [("a", minBound), ("b", 0), ("c", maxBound)]}+ , GM.defaultIntegerMaps {GM.integerMapsSint64Values = Map.fromList [("a", minBound), ("b", 0), ("c", maxBound)]}+ , GM.defaultIntegerMaps {GM.integerMapsSfixed32Values = Map.fromList [("a", minBound), ("b", 0), ("c", maxBound)]}+ , GM.defaultIntegerMaps {GM.integerMapsSfixed64Values = Map.fromList [("a", minBound), ("b", 0), ("c", maxBound)]}+ , GM.defaultIntegerMaps {GM.integerMapsFixed32Values = Map.fromList [("a", 0), ("b", maxBound)]}+ , GM.defaultIntegerMaps {GM.integerMapsFixed64Values = Map.fromList [("a", 0), ("b", maxBound)]}+ , GM.defaultIntegerMaps {GM.integerMapsIntegerKeys = Map.fromList [(minBound, minBound), (0, 0), (maxBound, maxBound)]}+ , GM.defaultIntegerMaps {GM.integerMapsZigzagKeys = Map.fromList [(minBound, "min"), (0, "zero"), (maxBound, "max")]}+ ]
+ test-regression/THFixtures.hs view
@@ -0,0 +1,14 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE TemplateHaskell #-}++module THFixtures where++import Proto.TH (loadProto)+++$(loadProto "test-regression/proto/edition2023.proto")+$(loadProto "test-regression/proto/edition2024.proto")+$(loadProto "test-regression/proto/json_numbers.proto")+++$(loadProto "test-regression/proto/integerMaps.proto")
+ test-regression/proto/edition2023.proto view
@@ -0,0 +1,18 @@+edition = "2023";+enum Status23 { STATUS23_UNSPECIFIED = 0; STATUS23_READY = 1; }+message Fields23 {+ int32 count = 1;+ string name = 2;+ bytes payload = 3;+ bool enabled = 4;+ Status23 status = 5;+ double magnitude = 6;+ float fraction = 7;+ int32 implicit_count = 8 [features.field_presence = IMPLICIT];+ repeated int32 values = 9;+ message Inner { int32 count = 1; }+ Inner inner = 10;+ map<string, string> attrs = 11;+ oneof choice { int32 picked = 12; string label = 13; }+ int32 aggregate_implicit = 14 [features = {field_presence: IMPLICIT}];+}
+ test-regression/proto/edition2024.proto view
@@ -0,0 +1,11 @@+edition = "2024";+option features.field_presence = IMPLICIT;+message Fields24 {+ int32 count = 1;+ int32 explicit_count = 2 [features.field_presence = EXPLICIT];+ message Inner {+ int32 inherited = 1;+ int32 explicit_value = 2 [features = {field_presence: EXPLICIT}];+ }+ Inner inner = 3;+}
+ test-regression/proto/integerMaps.proto view
@@ -0,0 +1,15 @@+syntax = "proto3";+message IntegerMaps {+ map<string, int32> int32_values = 1;+ map<string, int64> int64_values = 2;+ map<string, uint32> uint32_values = 3;+ map<string, uint64> uint64_values = 4;+ map<string, sint32> sint32_values = 5;+ map<string, sint64> sint64_values = 6;+ map<string, sfixed32> sfixed32_values = 7;+ map<string, sfixed64> sfixed64_values = 8;+ map<int32, int32> integer_keys = 9;+ map<string, fixed32> fixed32_values = 10;+ map<string, fixed64> fixed64_values = 11;+ map<sint32, string> zigzag_keys = 12;+}
+ test-regression/proto/json_numbers.proto view
@@ -0,0 +1,7 @@+syntax = "proto3";+message JsonNumbers {+ double value = 1;+ float small = 2;+ optional double optional_value = 3;+ oneof choice { double selected = 4; string label = 5; }+}
+ test/data/derive_regression.proto view
@@ -0,0 +1,23 @@+syntax = "proto3";++// Regression fixture for the Proto.Derive IDL bridge: parallel types+// here drive `loadProto` while `Test.Proto.Derive.RegressionInstances`+// emits matching types via `deriveProtoFromTranslated`. The test+// asserts byte-identical output for the same logical message.+//+// Field names are intentionally distinct between RegItem and+// RegInventory because `loadProto` derives Haskell record selectors+// from the proto field names, and Haskell rejects duplicate selector+// names in the same module without DuplicateRecordFields. The+// "reg_" prefix also prevents clashes with the rich-types fixtures+// (`Inventory`, etc.) imported by the same test module.++message RegItem {+ string regi_name = 1;+ int32 regi_count = 2;+}++message RegInventory {+ string reginv_name = 1;+ repeated RegItem reginv_items = 2;+}
+ test/data/oneof_regression.proto view
@@ -0,0 +1,30 @@+syntax = "proto3";++// End-to-end oneof regression for `loadProto`'s bridge rewire.+//+// Before the rewire `loadProto` would emit a record with a+// `Maybe Choice` field but never declare the `Choice` sum+// type, so any proto file containing a `oneof` failed to compile.+// This fixture exercises the new `mkOneofDataDecs` + bridge path:+// the splice generates `Envelope'Choice` (sum) alongside the+// `Envelope` record, and threads each variant through+// `Proto.Derive.Internal`'s FKOneof codec emitter.+//+// We also include a sibling scalar field so the per-message+// dispatch still works when the oneof isn't the only thing in+// the record.++message Envelope {+ string envelope_label = 1;++ oneof envelope_choice {+ string choice_url = 6;+ bytes choice_blob = 7;+ int32 choice_seed = 8;+ Inner choice_inner = 9;+ }+}++message Inner {+ int32 inner_id = 1;+}
+ test/data/topenum_regression.proto view
@@ -0,0 +1,30 @@+syntax = "proto3";++// Regression for the top-level enum loadProto fix.+//+// Before the fix, `loadProto` translated `FTNamed` references as+// `PFSubmessage` unconditionally, so a top-level enum field+// silently encoded as a length-delimited submessage instead of a+// varint — producing bytes that no other proto implementation+// could read. The fix routes named-type lookups through the+// file's ScopeCtx so enums are recognised as such and emitted as+// PFEnum (varint via fromEnum/toEnum).++enum Status {+ STATUS_UNSPECIFIED = 0;+ STATUS_ACTIVE = 1;+ STATUS_RETIRED = 2;+ STATUS_BANNED = 3;+}++message Account {+ string acct_name = 1;+ Status acct_status = 2; // top-level enum field+}++// Also cover packed scalars: proto3 default for packable scalars+// is packed, so this should auto-emit a single length-delimited+// block on the wire (not one record per element).+message PackedBag {+ repeated int32 bag_nums = 1;+}
wireform-proto.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.0 name: wireform-proto-version: 0.2.0.0+version: 0.3.0.0 synopsis: Protocol Buffers (proto2/proto3) with IDL parser, code generation, JSON mapping description: A high-performance Protocol Buffers implementation for Haskell.@@ -37,10 +37,16 @@ copyright: 2026 Ian Duncan category: Data, Codec, Network, Serialization build-type: Simple-tested-with: GHC == 9.6.4, GHC == 9.8.4+tested-with: GHC == 9.10.3, GHC == 9.12.4, GHC == 9.14.1 extra-doc-files: CHANGELOG.md README.md+extra-source-files:+ test/data/*.proto+ test-conformance/protos/*.proto+ test-conformance/failure_list_proto3.txt+ test-regression/proto/*.proto+ test-regression/Generate.hs data-dir: data data-files: proto/google/protobuf/*.proto @@ -159,6 +165,8 @@ Proto.Google.Protobuf.Wrappers.Util other-modules:+ Proto.Internal.FieldPresence+ Proto.Internal.JSON.Number -- Internal codegen sub-modules (used only by Proto.CodeGen) Proto.CodeGen.Decode Proto.CodeGen.Encode@@ -171,7 +179,7 @@ Proto.Internal.Either Proto.Internal.Maybe build-depends:- base >= 4.16 && < 5,+ base >= 4.20 && < 5, wireform-core == 0.2.*, wireform-derive == 0.2.*, bytestring >= 0.11 && < 0.13,@@ -188,7 +196,7 @@ prettyprinter >= 1.7 && < 1.8, base16-bytestring >= 1.0 && < 1.1, base64-bytestring >= 1.2 && < 1.3,- aeson >= 2.1 && < 2.3,+ aeson >= 2.1 && < 2.4, scientific >= 0.3 && < 0.4, hashable >= 1.3 && < 1.6, unordered-containers >= 0.2.16 && < 0.3,@@ -209,6 +217,7 @@ main-is: Main.hs other-modules: Test.Wire+ Test.RingSink Test.Roundtrip Test.Collect Test.StreamCodec@@ -275,7 +284,7 @@ -- is fine here. ghc-options: -O2 -rtsopts -threaded build-depends:- base >= 4.16 && < 5,+ base >= 4.20 && < 5, wireform-proto, aeson, bytestring,@@ -300,7 +309,7 @@ Test.Conformance.Schema Test.Conformance.Handler build-depends:- base >= 4.16 && < 5,+ base >= 4.20 && < 5, wireform-proto, aeson, bytestring,@@ -324,7 +333,7 @@ Test.Conformance.Schema Test.Conformance.Handler build-depends:- base >= 4.16 && < 5,+ base >= 4.20 && < 5, wireform-proto, aeson, bytestring,@@ -410,7 +419,7 @@ Proto.Bench Proto.Bench_Fields build-depends:- base >= 4.16 && < 5+ base >= 4.20 && < 5 , wireform-core , wireform-proto , bytestring@@ -422,3 +431,28 @@ , proto-lens-runtime >= 0.7 && < 0.8 , lens-family >= 2.1 && < 2.2 hs-source-dirs: bench/compare, bench/compare/gen++-- Real generated-record regressions for Editions presence and ProtoJSON numbers.+test-suite wireform-proto-regression-test+ import: defaults+ type: exitcode-stdio-1.0+ hs-source-dirs: test-regression+ main-is: Main.hs+ other-modules:+ THFixtures+ Generated.Edition2023+ Generated.Edition2024+ Generated.Integermaps+ build-depends:+ base,+ wireform-proto,+ wireform-core,+ aeson,+ bytestring,+ containers,+ deepseq,+ hashable,+ reflection,+ text,+ vector,+ sydtest