wireform-proto-0.1.0.0: src/Proto/IDL/AST.hs
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
{- | Proto file abstract syntax tree.
AST types use the /trees that grow/ pattern: each node is
parameterised by a phase @p@ that determines the annotation
carried at each node via the 'XNode' type family.
Two phases are provided:
* 'Semantic' — no source location info; used by codegen, analysis,
and all existing consumer code via backward-compatible type aliases
(@MessageDef = MessageDef' Semantic@, etc.).
* 'Parsed' — carries 'Span' for byte-accurate source reconstruction
via 'Proto.ExactPrint'.
Supports proto2, proto3, and Editions (2023+) syntax with full coverage of
messages, enums, services, oneofs, maps, extensions, and custom options.
-}
module Proto.IDL.AST (
-- * Phase types and spans (re-exported from "Proto.IDL.AST.Span")
Parsed,
Semantic,
Span (..),
SrcSpan (..),
XNode,
noSpan,
mkSpan,
-- * Top-level
ProtoFile' (..),
ProtoFile,
Syntax (..),
Edition (..),
TopLevel' (..),
TopLevel,
ImportDef' (..),
ImportDef,
ImportModifier (..),
-- * Messages
MessageDef' (..),
MessageDef,
MessageElement' (..),
MessageElement,
FieldDef' (..),
FieldDef,
FieldLabel (..),
FieldType (..),
ScalarType (..),
MapField' (..),
MapField,
OneofDef' (..),
OneofDef,
OneofField' (..),
OneofField,
ReservedDef (..),
ReservedRange (..),
-- * Enums
EnumDef' (..),
EnumDef,
EnumValue' (..),
EnumValue,
-- * Services
ServiceDef' (..),
ServiceDef,
RpcDef' (..),
RpcDef,
StreamQualifier (..),
-- * Options and annotations
OptionDef' (..),
OptionDef,
OptionName (..),
OptionNamePart (..),
Constant (..),
-- * Comments
Comment (..),
-- * Extensions
ExtensionRange (..),
ExtensionRangeBound (..),
-- * Field numbers
FieldNumber (..),
-- * Edition features
FeatureSet (..),
FieldPresenceFeature (..),
EnumTypeFeature (..),
RepeatedFieldEncodingFeature (..),
Utf8ValidationFeature (..),
MessageEncodingFeature (..),
JsonFormatFeature (..),
defaultFeatureSet,
featuresForEdition,
applyFeatureOptions,
resolveFileFeatures,
resolveFieldFeatures,
applyEditionFieldPresence,
-- * Phase conversion
stripSpans,
stripTopLevel,
stripMessage,
stripEnum,
stripService,
stripField,
stripOption,
) where
import Control.DeepSeq (NFData)
import Data.Text (Text)
import GHC.Generics (Generic)
import Proto.IDL.AST.Span
-- -----------------------------------------------------------------------
-- Backward-compatible type aliases (Semantic phase)
-- -----------------------------------------------------------------------
-- | A proto file in the 'Semantic' phase (no source location info).
type ProtoFile = ProtoFile' Semantic
-- | A top-level declaration in the 'Semantic' phase.
type TopLevel = TopLevel' Semantic
-- | An import declaration in the 'Semantic' phase.
type ImportDef = ImportDef' Semantic
-- | A message definition in the 'Semantic' phase.
type MessageDef = MessageDef' Semantic
-- | A message body element in the 'Semantic' phase.
type MessageElement = MessageElement' Semantic
-- | A field definition in the 'Semantic' phase.
type FieldDef = FieldDef' Semantic
-- | A map field definition in the 'Semantic' phase.
type MapField = MapField' Semantic
-- | A oneof group definition in the 'Semantic' phase.
type OneofDef = OneofDef' Semantic
-- | A single field within a oneof group, in the 'Semantic' phase.
type OneofField = OneofField' Semantic
-- | An enum definition in the 'Semantic' phase.
type EnumDef = EnumDef' Semantic
-- | An enum value in the 'Semantic' phase.
type EnumValue = EnumValue' Semantic
-- | A service definition in the 'Semantic' phase.
type ServiceDef = ServiceDef' Semantic
-- | An RPC method definition in the 'Semantic' phase.
type RpcDef = RpcDef' Semantic
-- | An option definition in the 'Semantic' phase.
type OptionDef = OptionDef' Semantic
-- -----------------------------------------------------------------------
-- Top-level
-- -----------------------------------------------------------------------
-- | A complete .proto file.
data ProtoFile' p = ProtoFile
{ protoSyntax :: !Syntax
-- ^ The syntax or edition declaration.
, protoPackage :: !(Maybe Text)
-- ^ The package name, if declared.
, protoImports :: ![ImportDef' p]
-- ^ Import declarations.
, protoOptions :: ![OptionDef' p]
-- ^ File-level options.
, protoTopLevels :: ![TopLevel' p]
-- ^ Top-level definitions (messages, enums, services, etc.).
, protoSource :: !(Maybe Text)
-- ^ Original source text (metadata, not used in equality).
}
deriving stock (Generic)
deriving stock instance Show (XNode p) => Show (ProtoFile' p)
instance NFData (XNode p) => NFData (ProtoFile' p)
-- | Equality ignores 'protoSource' (it's metadata, not semantics).
instance Eq (XNode p) => Eq (ProtoFile' p) where
a == b =
protoSyntax a == protoSyntax b
&& protoPackage a == protoPackage b
&& protoImports a == protoImports b
&& protoOptions a == protoOptions b
&& protoTopLevels a == protoTopLevels b
-- | The syntax version or edition of a proto file.
data Syntax
= -- | Proto2 syntax.
Proto2
| -- | Proto3 syntax.
Proto3
| -- | Editions syntax with a specific edition identifier.
Editions !Edition
deriving stock (Show, Eq, Ord, Generic)
deriving anyclass (NFData)
-- | Protobuf edition identifier (e.g. "2023", "2024").
newtype Edition = Edition {editionName :: Text}
deriving stock (Show, Eq, Ord, Generic)
deriving anyclass (NFData)
-- | An import declaration in a proto file.
data ImportDef' p = ImportDef
{ importExt :: !(XNode p)
-- ^ Phase-specific annotation (e.g. source span).
, importModifier :: !(Maybe ImportModifier)
-- ^ Optional import modifier (public or weak).
, importPath :: !Text
-- ^ The imported file path.
}
deriving stock (Generic)
deriving stock instance Show (XNode p) => Show (ImportDef' p)
deriving stock instance Eq (XNode p) => Eq (ImportDef' p)
instance NFData (XNode p) => NFData (ImportDef' p)
-- | Modifier on an import declaration.
data ImportModifier
= -- | @import public@ — re-exports the imported definitions.
ImportPublic
| -- | @import weak@ — the import is optional.
ImportWeak
deriving stock (Show, Eq, Generic)
deriving anyclass (NFData)
-- | A comment in proto source.
data Comment
= -- | @// content@ (without the @//@ prefix)
LineComment !Text
| -- | @/* content */@ (without delimiters)
BlockComment !Text
deriving stock (Show, Eq, Generic)
deriving anyclass (NFData)
-- | A top-level declaration in a proto file.
data TopLevel' p
= -- | A message definition.
TLMessage !(MessageDef' p)
| -- | An enum definition.
TLEnum !(EnumDef' p)
| -- | A service definition.
TLService !(ServiceDef' p)
| -- | An @extend@ block with the extended type name and additional fields.
TLExtend !Text ![FieldDef' p]
| -- | A file-level option.
TLOption !(OptionDef' p)
| -- | Standalone comment block between definitions.
TLComment ![Comment]
deriving stock (Generic)
deriving stock instance Show (XNode p) => Show (TopLevel' p)
deriving stock instance Eq (XNode p) => Eq (TopLevel' p)
instance NFData (XNode p) => NFData (TopLevel' p)
-- -----------------------------------------------------------------------
-- Messages
-- -----------------------------------------------------------------------
-- | A protobuf message definition.
data MessageDef' p = MessageDef
{ msgExt :: !(XNode p)
-- ^ Phase-specific annotation (e.g. source span).
, msgDoc :: !(Maybe Text)
-- ^ Documentation comment attached to the message.
, msgName :: !Text
-- ^ The message name.
, msgElements :: ![MessageElement' p]
-- ^ The body elements (fields, nested types, options, etc.).
}
deriving stock (Generic)
deriving stock instance Show (XNode p) => Show (MessageDef' p)
deriving stock instance Eq (XNode p) => Eq (MessageDef' p)
instance NFData (XNode p) => NFData (MessageDef' p)
-- | An element inside a message body.
data MessageElement' p
= -- | A regular field.
MEField !(FieldDef' p)
| -- | A nested enum definition.
MEEnum !(EnumDef' p)
| -- | A nested message definition.
MEMessage !(MessageDef' p)
| -- | A oneof group.
MEOneof !(OneofDef' p)
| -- | A map field.
MEMapField !(MapField' p)
| -- | A reserved declaration.
MEReserved !ReservedDef
| -- | An extensions range declaration.
MEExtensions ![ExtensionRange]
| -- | A message-level option.
MEOption !(OptionDef' p)
| -- | Standalone comment inside a message body.
MEComment ![Comment]
deriving stock (Generic)
deriving stock instance Show (XNode p) => Show (MessageElement' p)
deriving stock instance Eq (XNode p) => Eq (MessageElement' p)
instance NFData (XNode p) => NFData (MessageElement' p)
-- | A field definition within a message.
data FieldDef' p = FieldDef
{ fieldExt :: !(XNode p)
-- ^ Phase-specific annotation (e.g. source span).
, fieldDoc :: !(Maybe Text)
-- ^ Documentation comment attached to the field.
, fieldLabel :: !(Maybe FieldLabel)
-- ^ The field label (optional, required, or repeated).
, fieldType :: !FieldType
-- ^ The field's type.
, fieldName :: !Text
-- ^ The field name.
, fieldNumber :: !FieldNumber
-- ^ The field number.
, fieldOptions :: ![OptionDef' p]
-- ^ Inline field options (e.g. @[packed = true]@).
}
deriving stock (Generic)
deriving stock instance Show (XNode p) => Show (FieldDef' p)
deriving stock instance Eq (XNode p) => Eq (FieldDef' p)
instance NFData (XNode p) => NFData (FieldDef' p)
-- | A protobuf field number (1-536870911).
newtype FieldNumber = FieldNumber
{ unFieldNumber :: Int
-- ^ Unwrap the field number.
}
deriving stock (Show, Eq, Ord, Generic)
deriving anyclass (NFData)
-- | The label on a proto field.
data FieldLabel
= -- | An optional field (proto2) or singular field (proto3).
Optional
| -- | A required field (proto2 only).
Required
| -- | A repeated (list) field.
Repeated
deriving stock (Show, Eq, Ord, Generic)
deriving anyclass (NFData)
-- | The type of a proto field.
data FieldType
= -- | A built-in scalar type.
FTScalar !ScalarType
| -- | A named message or enum type (may be fully qualified).
FTNamed !Text
deriving stock (Show, Eq, Generic)
deriving anyclass (NFData)
-- | Built-in protobuf scalar types.
data ScalarType
= SDouble
| SFloat
| SInt32
| SInt64
| SUInt32
| SUInt64
| SSInt32
| SSInt64
| SFixed32
| SFixed64
| SSFixed32
| SSFixed64
| SBool
| SString
| SBytes
deriving stock (Show, Eq, Ord, Enum, Bounded, Generic)
deriving anyclass (NFData)
-- | A map field definition (@map\<K, V\>@).
data MapField' p = MapField
{ mapExt :: !(XNode p)
-- ^ Phase-specific annotation (e.g. source span).
, mapDoc :: !(Maybe Text)
-- ^ Documentation comment attached to the map field.
, mapKeyType :: !ScalarType
-- ^ The map key type (must be a scalar).
, mapValueType :: !FieldType
-- ^ The map value type.
, mapFieldName :: !Text
-- ^ The field name.
, mapFieldNum :: !FieldNumber
-- ^ The field number.
, mapOptions :: ![OptionDef' p]
-- ^ Inline field options.
}
deriving stock (Generic)
deriving stock instance Show (XNode p) => Show (MapField' p)
deriving stock instance Eq (XNode p) => Eq (MapField' p)
instance NFData (XNode p) => NFData (MapField' p)
-- | A oneof group definition.
data OneofDef' p = OneofDef
{ oneofExt :: !(XNode p)
-- ^ Phase-specific annotation (e.g. source span).
, oneofDoc :: !(Maybe Text)
-- ^ Documentation comment attached to the oneof.
, oneofName :: !Text
-- ^ The oneof group name.
, oneofFields :: ![OneofField' p]
-- ^ The fields belonging to this oneof.
, oneofOptions :: ![OptionDef' p]
-- ^ Options declared inside the oneof block.
}
deriving stock (Generic)
deriving stock instance Show (XNode p) => Show (OneofDef' p)
deriving stock instance Eq (XNode p) => Eq (OneofDef' p)
instance NFData (XNode p) => NFData (OneofDef' p)
-- | A single field within a oneof group.
data OneofField' p = OneofField
{ oneofFieldExt :: !(XNode p)
-- ^ Phase-specific annotation (e.g. source span).
, oneofFieldDoc :: !(Maybe Text)
-- ^ Documentation comment attached to the field.
, oneofFieldType :: !FieldType
-- ^ The field's type.
, oneofFieldName :: !Text
-- ^ The field name.
, oneofFieldNumber :: !FieldNumber
-- ^ The field number.
, oneofFieldOptions :: ![OptionDef' p]
-- ^ Inline field options.
}
deriving stock (Generic)
deriving stock instance Show (XNode p) => Show (OneofField' p)
deriving stock instance Eq (XNode p) => Eq (OneofField' p)
instance NFData (XNode p) => NFData (OneofField' p)
-- | A @reserved@ declaration inside a message or enum.
data ReservedDef
= -- | Reserved field number ranges.
ReservedNumbers ![ReservedRange]
| -- | Reserved field names.
ReservedNames ![Text]
deriving stock (Show, Eq, Generic)
deriving anyclass (NFData)
-- | A range within a @reserved@ declaration.
data ReservedRange
= -- | A single reserved field number.
ReservedSingle !Int
| -- | An inclusive range of reserved field numbers.
ReservedRange !Int !Int
deriving stock (Show, Eq, Generic)
deriving anyclass (NFData)
-- -----------------------------------------------------------------------
-- Enums
-- -----------------------------------------------------------------------
-- | A protobuf enum definition.
data EnumDef' p = EnumDef
{ enumExt :: !(XNode p)
-- ^ Phase-specific annotation (e.g. source span).
, enumDoc :: !(Maybe Text)
-- ^ Documentation comment attached to the enum.
, enumName :: !Text
-- ^ The enum name.
, enumValues :: ![EnumValue' p]
-- ^ The enum value entries.
, enumOptions :: ![OptionDef' p]
-- ^ Enum-level options (e.g. @allow_alias@).
}
deriving stock (Generic)
deriving stock instance Show (XNode p) => Show (EnumDef' p)
deriving stock instance Eq (XNode p) => Eq (EnumDef' p)
instance NFData (XNode p) => NFData (EnumDef' p)
-- | A single value within an enum definition.
data EnumValue' p = EnumValue
{ evExt :: !(XNode p)
-- ^ Phase-specific annotation (e.g. source span).
, evDoc :: !(Maybe Text)
-- ^ Documentation comment attached to the value.
, evName :: !Text
-- ^ The enum value name.
, evNumber :: !Int
-- ^ The numeric value.
, evOptions :: ![OptionDef' p]
-- ^ Inline options on the enum value.
}
deriving stock (Generic)
deriving stock instance Show (XNode p) => Show (EnumValue' p)
deriving stock instance Eq (XNode p) => Eq (EnumValue' p)
instance NFData (XNode p) => NFData (EnumValue' p)
-- -----------------------------------------------------------------------
-- Services
-- -----------------------------------------------------------------------
-- | A protobuf service definition.
data ServiceDef' p = ServiceDef
{ svcExt :: !(XNode p)
-- ^ Phase-specific annotation (e.g. source span).
, svcDoc :: !(Maybe Text)
-- ^ Documentation comment attached to the service.
, svcName :: !Text
-- ^ The service name.
, svcRpcs :: ![RpcDef' p]
-- ^ The RPC method definitions.
, svcOptions :: ![OptionDef' p]
-- ^ Service-level options.
}
deriving stock (Generic)
deriving stock instance Show (XNode p) => Show (ServiceDef' p)
deriving stock instance Eq (XNode p) => Eq (ServiceDef' p)
instance NFData (XNode p) => NFData (ServiceDef' p)
-- | An RPC method definition within a service.
data RpcDef' p = RpcDef
{ rpcExt :: !(XNode p)
-- ^ Phase-specific annotation (e.g. source span).
, rpcDoc :: !(Maybe Text)
-- ^ Documentation comment attached to the RPC.
, rpcName :: !Text
-- ^ The method name.
, rpcInput :: !Text
-- ^ The request message type name.
, rpcInputStr :: !StreamQualifier
-- ^ Whether the request is streaming.
, rpcOutput :: !Text
-- ^ The response message type name.
, rpcOutputStr :: !StreamQualifier
-- ^ Whether the response is streaming.
, rpcOptions :: ![OptionDef' p]
-- ^ Method-level options.
}
deriving stock (Generic)
deriving stock instance Show (XNode p) => Show (RpcDef' p)
deriving stock instance Eq (XNode p) => Eq (RpcDef' p)
instance NFData (XNode p) => NFData (RpcDef' p)
-- | Whether an RPC input or output is streaming.
data StreamQualifier
= -- | Unary (non-streaming).
NoStream
| -- | Server-side or client-side streaming.
Streaming
deriving stock (Show, Eq, Generic)
deriving anyclass (NFData)
-- -----------------------------------------------------------------------
-- Options
-- -----------------------------------------------------------------------
-- | An option (including custom options with extension names).
data OptionDef' p = OptionDef
{ optExt :: !(XNode p)
-- ^ Phase-specific annotation (e.g. source span).
, optName :: !OptionName
-- ^ The option name (simple or extension).
, optValue :: !Constant
-- ^ The option value.
}
deriving stock (Generic)
deriving stock instance Show (XNode p) => Show (OptionDef' p)
deriving stock instance Eq (XNode p) => Eq (OptionDef' p)
instance NFData (XNode p) => NFData (OptionDef' p)
{- | An option name can be a simple identifier or a parenthesized extension name,
optionally followed by dotted sub-field access.
-}
newtype OptionName = OptionName
{ optNameParts :: [OptionNamePart]
}
deriving stock (Show, Eq, Generic)
deriving anyclass (NFData)
-- | A single component of an option name path.
data OptionNamePart
= -- | A simple option name (e.g. @deprecated@).
SimpleOption !Text
| -- | A parenthesized extension option name (e.g. @(my_option)@).
ExtensionOption !Text
deriving stock (Show, Eq, Generic)
deriving anyclass (NFData)
-- | A constant value used in option assignments and default values.
data Constant
= -- | An identifier constant (e.g. an enum value name).
CIdent !Text
| -- | An integer constant.
CInt !Integer
| -- | A floating-point constant.
CFloat !Double
| -- | A string constant.
CString !Text
| -- | A boolean constant.
CBool !Bool
| -- | An aggregate (braced key-value) constant.
CAggregate ![(Text, Constant)]
deriving stock (Show, Eq, Generic)
deriving anyclass (NFData)
-- -----------------------------------------------------------------------
-- Extensions
-- -----------------------------------------------------------------------
-- | A range of field numbers reserved for extensions.
data ExtensionRange = ExtensionRange
{ extStart :: !Int
-- ^ The start of the extension range (inclusive).
, extEnd :: !ExtensionRangeBound
-- ^ The end of the extension range.
}
deriving stock (Show, Eq, Generic)
deriving anyclass (NFData)
-- | The upper bound of an extension range.
data ExtensionRangeBound
= -- | A specific field number upper bound (inclusive).
ExtBoundNum !Int
| -- | The @max@ keyword, meaning the maximum valid field number.
ExtBoundMax
deriving stock (Show, Eq, Generic)
deriving anyclass (NFData)
-- -----------------------------------------------------------------------
-- Edition features
-- -----------------------------------------------------------------------
-- | The set of edition features controlling proto semantics.
data FeatureSet = FeatureSet
{ featureFieldPresence :: !FieldPresenceFeature
-- ^ How field presence is tracked.
, featureEnumType :: !EnumTypeFeature
-- ^ Whether enums are open or closed.
, featureRepeatedFieldEncoding :: !RepeatedFieldEncodingFeature
-- ^ Whether repeated scalars use packed encoding.
, featureUtf8Validation :: !Utf8ValidationFeature
-- ^ Whether string fields are validated as UTF-8.
, featureMessageEncoding :: !MessageEncodingFeature
-- ^ How submessages are encoded on the wire.
, featureJsonFormat :: !JsonFormatFeature
-- ^ JSON format handling for this edition.
}
deriving stock (Show, Eq, Ord, Generic)
deriving anyclass (NFData)
-- | Controls how field presence is tracked for singular fields.
data FieldPresenceFeature
= -- | Field tracks presence explicitly (has-bit or optional wrapper).
ExplicitPresence
| -- | Field uses implicit presence (zero value means absent).
ImplicitPresence
| -- | Legacy required semantics (proto2 compatibility).
LegacyRequired
deriving stock (Show, Eq, Ord, Generic)
deriving anyclass (NFData)
-- | Controls whether an enum is open or closed.
data EnumTypeFeature
= -- | Open enum: unknown values are preserved.
OpenEnum
| -- | Closed enum: unknown values are rejected.
ClosedEnum
deriving stock (Show, Eq, Ord, Generic)
deriving anyclass (NFData)
-- | Controls wire encoding of repeated scalar fields.
data RepeatedFieldEncodingFeature
= -- | Packed encoding (all values in a single length-delimited chunk).
PackedEncoding
| -- | Expanded encoding (one tag-value pair per element).
ExpandedEncoding
deriving stock (Show, Eq, Ord, Generic)
deriving anyclass (NFData)
-- | Controls UTF-8 validation of string fields.
data Utf8ValidationFeature
= -- | Verify that string fields contain valid UTF-8.
Utf8Verify
| -- | Skip UTF-8 validation.
Utf8None
deriving stock (Show, Eq, Ord, Generic)
deriving anyclass (NFData)
-- | Controls submessage wire encoding.
data MessageEncodingFeature
= -- | Standard length-prefixed encoding.
LengthPrefixedEncoding
| -- | Group-style delimited encoding.
DelimitedEncoding
deriving stock (Show, Eq, Ord, Generic)
deriving anyclass (NFData)
-- | Controls JSON format handling.
data JsonFormatFeature
= -- | Full canonical JSON support.
JsonAllow
| -- | Legacy best-effort JSON (for older proto2 compatibility).
JsonLegacyBestEffort
deriving stock (Show, Eq, Ord, Generic)
deriving anyclass (NFData)
-- | Default feature set (matches proto3 defaults, which is edition 2023 default).
defaultFeatureSet :: FeatureSet
defaultFeatureSet =
FeatureSet
{ featureFieldPresence = ExplicitPresence
, featureEnumType = OpenEnum
, featureRepeatedFieldEncoding = PackedEncoding
, featureUtf8Validation = Utf8Verify
, featureMessageEncoding = LengthPrefixedEncoding
, featureJsonFormat = JsonAllow
}
-- | Get the default feature set for a given edition.
featuresForEdition :: Edition -> FeatureSet
featuresForEdition (Edition "2023") = defaultFeatureSet
featuresForEdition (Edition "2024") =
defaultFeatureSet
{ featureFieldPresence = ExplicitPresence
}
featuresForEdition _ = defaultFeatureSet
{- | Apply @features.*@ option overrides from a list of 'OptionDef's onto
a base 'FeatureSet'.
Handles the options defined in @google/protobuf/descriptor.proto@
(@features.field_presence@, @features.enum_type@,
@features.repeated_field_encoding@, @features.utf8_validation@,
@features.message_encoding@, @features.json_format@).
Unknown feature names and unrecognised values are silently ignored so
that a schema using a future edition feature does not break parsing.
-}
applyFeatureOptions :: [OptionDef] -> FeatureSet -> FeatureSet
applyFeatureOptions opts base = foldr applyOne base opts
where
applyOne (OptionDef _ (OptionName parts) val) fs
| matchFeature "field_presence" parts =
case identOf val of
"IMPLICIT" -> fs { featureFieldPresence = ImplicitPresence }
"EXPLICIT" -> fs { featureFieldPresence = ExplicitPresence }
"LEGACY_REQUIRED" -> fs { featureFieldPresence = LegacyRequired }
_ -> fs
| matchFeature "enum_type" parts =
case identOf val of
"OPEN" -> fs { featureEnumType = OpenEnum }
"CLOSED" -> fs { featureEnumType = ClosedEnum }
_ -> fs
| matchFeature "repeated_field_encoding" parts =
case identOf val of
"PACKED" -> fs { featureRepeatedFieldEncoding = PackedEncoding }
"EXPANDED" -> fs { featureRepeatedFieldEncoding = ExpandedEncoding }
_ -> fs
| matchFeature "utf8_validation" parts =
case identOf val of
"VERIFY" -> fs { featureUtf8Validation = Utf8Verify }
"NONE" -> fs { featureUtf8Validation = Utf8None }
_ -> fs
| matchFeature "message_encoding" parts =
case identOf val of
"LENGTH_PREFIXED" -> fs { featureMessageEncoding = LengthPrefixedEncoding }
"DELIMITED" -> fs { featureMessageEncoding = DelimitedEncoding }
_ -> fs
| matchFeature "json_format" parts =
case identOf val of
"ALLOW" -> fs { featureJsonFormat = JsonAllow }
"LEGACY_BEST_EFFORT" -> fs { featureJsonFormat = JsonLegacyBestEffort }
_ -> fs
| otherwise = fs
-- Match when the option name is [SimpleOption "features", SimpleOption key].
matchFeature :: Text -> [OptionNamePart] -> Bool
matchFeature key [SimpleOption "features", SimpleOption k] = k == key
matchFeature _ _ = False
identOf :: Constant -> Text
identOf (CIdent t) = t
identOf _ = ""
-- | Resolve the effective 'FeatureSet' for a file given its syntax and
-- any file-level @option features.*@ overrides.
resolveFileFeatures :: Syntax -> [OptionDef] -> FeatureSet
resolveFileFeatures (Editions ed) opts = applyFeatureOptions opts (featuresForEdition ed)
resolveFileFeatures _ _ = defaultFeatureSet
-- | Resolve the effective 'FeatureSet' for a field, inheriting from the
-- parent 'FeatureSet' and applying any field-level @features.*@ overrides.
resolveFieldFeatures :: FeatureSet -> [OptionDef] -> FeatureSet
resolveFieldFeatures parent opts = applyFeatureOptions opts parent
{- | Post-parse normalization pass for editions schemas.
In editions, the @optional@\/@required@\/@repeated@ label keywords are
absent from the syntax; field cardinality is controlled by
@option features.field_presence@. This pass translates that feature back
into 'fieldLabel' so that downstream codegen and TH paths see the same
'FieldLabel' representation as they do for proto2\/proto3:
* @EXPLICIT@ — sets 'fieldLabel' to @Just Optional@ (has-bit tracking).
* @IMPLICIT@ — leaves 'fieldLabel' as @Nothing@ (proto3-style, zero = absent).
* @LEGACY_REQUIRED@ — sets 'fieldLabel' to @Just Required@.
No-op for proto2 and proto3 files.
-}
applyEditionFieldPresence :: ProtoFile -> ProtoFile
applyEditionFieldPresence pf = case protoSyntax pf of
Editions ed ->
let fileFs = resolveFileFeatures (Editions ed) (protoOptions pf)
in pf {protoTopLevels = fmap (applyTLPresence fileFs) (protoTopLevels pf)}
_ -> pf
where
applyTLPresence fs (TLMessage msg) = TLMessage (applyMsgPresence fs msg)
applyTLPresence _ tl = tl
applyMsgPresence fs msg =
msg {msgElements = fmap (applyElemPresence fs) (msgElements msg)}
applyElemPresence parentFs = \case
MEField fd ->
let fieldFs = resolveFieldFeatures parentFs (fieldOptions fd)
in MEField fd {fieldLabel = effectiveLabel (featureFieldPresence fieldFs) (fieldLabel fd)}
MEMessage inner ->
MEMessage (applyMsgPresence parentFs inner)
MEOneof od ->
-- Oneof fields do not participate in presence features (they are
-- always explicitly-tracked by the oneof index).
MEOneof od
other -> other
-- Translate a FieldPresenceFeature to the appropriate FieldLabel,
-- preserving existing Repeated labels and only adjusting singular fields.
effectiveLabel :: FieldPresenceFeature -> Maybe FieldLabel -> Maybe FieldLabel
effectiveLabel _ (Just Repeated) = Just Repeated
effectiveLabel ExplicitPresence _ = Just Optional
effectiveLabel ImplicitPresence _ = Nothing
effectiveLabel LegacyRequired _ = Just Required
-- -----------------------------------------------------------------------
-- Phase conversion
-- -----------------------------------------------------------------------
-- | Strip all span annotations, converting any phase to 'Semantic'.
stripSpans :: ProtoFile' p -> ProtoFile
stripSpans pf =
ProtoFile
{ protoSyntax = protoSyntax pf
, protoPackage = protoPackage pf
, protoImports = fmap stripImport (protoImports pf)
, protoOptions = fmap stripOption (protoOptions pf)
, protoTopLevels = fmap stripTopLevel (protoTopLevels pf)
, protoSource = protoSource pf
}
-- | Strip span annotations from a single top-level declaration.
stripTopLevel :: TopLevel' p -> TopLevel
stripTopLevel = \case
TLMessage m -> TLMessage (stripMessage m)
TLEnum e -> TLEnum (stripEnum e)
TLService s -> TLService (stripService s)
TLExtend n fs -> TLExtend n (fmap stripField fs)
TLOption o -> TLOption (stripOption o)
TLComment cs -> TLComment cs
stripImport :: ImportDef' p -> ImportDef
stripImport i = ImportDef {importExt = (), importModifier = importModifier i, importPath = importPath i}
-- | Strip span annotations from a message definition.
stripMessage :: MessageDef' p -> MessageDef
stripMessage m =
MessageDef
{ msgExt = ()
, msgDoc = msgDoc m
, msgName = msgName m
, msgElements = fmap stripMsgElem (msgElements m)
}
stripMsgElem :: MessageElement' p -> MessageElement
stripMsgElem = \case
MEField f -> MEField (stripField f)
MEEnum e -> MEEnum (stripEnum e)
MEMessage m -> MEMessage (stripMessage m)
MEOneof o -> MEOneof (stripOneof o)
MEMapField mf -> MEMapField (stripMapField mf)
MEReserved r -> MEReserved r
MEExtensions e -> MEExtensions e
MEOption o -> MEOption (stripOption o)
MEComment cs -> MEComment cs
-- | Strip span annotations from a field definition.
stripField :: FieldDef' p -> FieldDef
stripField f =
FieldDef
{ fieldExt = ()
, fieldDoc = fieldDoc f
, fieldLabel = fieldLabel f
, fieldType = fieldType f
, fieldName = fieldName f
, fieldNumber = fieldNumber f
, fieldOptions = fmap stripOption (fieldOptions f)
}
-- | Strip span annotations from an enum definition.
stripEnum :: EnumDef' p -> EnumDef
stripEnum e =
EnumDef
{ enumExt = ()
, enumDoc = enumDoc e
, enumName = enumName e
, enumValues = fmap stripEnumValue (enumValues e)
, enumOptions = fmap stripOption (enumOptions e)
}
stripEnumValue :: EnumValue' p -> EnumValue
stripEnumValue v =
EnumValue
{ evExt = ()
, evDoc = evDoc v
, evName = evName v
, evNumber = evNumber v
, evOptions = fmap stripOption (evOptions v)
}
-- | Strip span annotations from a service definition.
stripService :: ServiceDef' p -> ServiceDef
stripService s =
ServiceDef
{ svcExt = ()
, svcDoc = svcDoc s
, svcName = svcName s
, svcRpcs = fmap stripRpc (svcRpcs s)
, svcOptions = fmap stripOption (svcOptions s)
}
stripRpc :: RpcDef' p -> RpcDef
stripRpc r =
RpcDef
{ rpcExt = ()
, rpcDoc = rpcDoc r
, rpcName = rpcName r
, rpcInput = rpcInput r
, rpcInputStr = rpcInputStr r
, rpcOutput = rpcOutput r
, rpcOutputStr = rpcOutputStr r
, rpcOptions = fmap stripOption (rpcOptions r)
}
stripOneof :: OneofDef' p -> OneofDef
stripOneof o =
OneofDef
{ oneofExt = ()
, oneofDoc = oneofDoc o
, oneofName = oneofName o
, oneofFields = fmap stripOneofField (oneofFields o)
, oneofOptions = fmap stripOption (oneofOptions o)
}
stripOneofField :: OneofField' p -> OneofField
stripOneofField f =
OneofField
{ oneofFieldExt = ()
, oneofFieldDoc = oneofFieldDoc f
, oneofFieldType = oneofFieldType f
, oneofFieldName = oneofFieldName f
, oneofFieldNumber = oneofFieldNumber f
, oneofFieldOptions = fmap stripOption (oneofFieldOptions f)
}
stripMapField :: MapField' p -> MapField
stripMapField m =
MapField
{ mapExt = ()
, mapDoc = mapDoc m
, mapKeyType = mapKeyType m
, mapValueType = mapValueType m
, mapFieldName = mapFieldName m
, mapFieldNum = mapFieldNum m
, mapOptions = fmap stripOption (mapOptions m)
}
-- | Strip span annotations from an option definition.
stripOption :: OptionDef' p -> OptionDef
stripOption o = OptionDef {optExt = (), optName = optName o, optValue = optValue o}