commit
a077f68b1c
249 changed files with 9463 additions and 5376 deletions
File diff suppressed because it is too large
Load Diff
@ -1,20 +1,40 @@ |
|||||||
Recommended.Proto3.TextFormatInput.StringLiteralLongUnicodeEscapeSurrogateFirstOnlyBytes |
Recommended.Proto3.TextFormatInput.StringLiteralLongUnicodeEscapeSurrogateFirstOnlyBytes |
||||||
|
Recommended.Editions_Proto3.TextFormatInput.StringLiteralLongUnicodeEscapeSurrogateFirstOnlyBytes |
||||||
Recommended.Proto3.TextFormatInput.StringLiteralLongUnicodeEscapeSurrogateFirstOnlyString |
Recommended.Proto3.TextFormatInput.StringLiteralLongUnicodeEscapeSurrogateFirstOnlyString |
||||||
|
Recommended.Editions_Proto3.TextFormatInput.StringLiteralLongUnicodeEscapeSurrogateFirstOnlyString |
||||||
Recommended.Proto3.TextFormatInput.StringLiteralLongUnicodeEscapeSurrogatePairBytes |
Recommended.Proto3.TextFormatInput.StringLiteralLongUnicodeEscapeSurrogatePairBytes |
||||||
|
Recommended.Editions_Proto3.TextFormatInput.StringLiteralLongUnicodeEscapeSurrogatePairBytes |
||||||
Recommended.Proto3.TextFormatInput.StringLiteralLongUnicodeEscapeSurrogatePairString |
Recommended.Proto3.TextFormatInput.StringLiteralLongUnicodeEscapeSurrogatePairString |
||||||
|
Recommended.Editions_Proto3.TextFormatInput.StringLiteralLongUnicodeEscapeSurrogatePairString |
||||||
Recommended.Proto3.TextFormatInput.StringLiteralLongUnicodeEscapeSurrogateSecondOnlyBytes |
Recommended.Proto3.TextFormatInput.StringLiteralLongUnicodeEscapeSurrogateSecondOnlyBytes |
||||||
|
Recommended.Editions_Proto3.TextFormatInput.StringLiteralLongUnicodeEscapeSurrogateSecondOnlyBytes |
||||||
Recommended.Proto3.TextFormatInput.StringLiteralLongUnicodeEscapeSurrogateSecondOnlyString |
Recommended.Proto3.TextFormatInput.StringLiteralLongUnicodeEscapeSurrogateSecondOnlyString |
||||||
|
Recommended.Editions_Proto3.TextFormatInput.StringLiteralLongUnicodeEscapeSurrogateSecondOnlyString |
||||||
Recommended.Proto3.TextFormatInput.StringLiteralShortUnicodeEscapeSurrogateFirstOnlyBytes |
Recommended.Proto3.TextFormatInput.StringLiteralShortUnicodeEscapeSurrogateFirstOnlyBytes |
||||||
|
Recommended.Editions_Proto3.TextFormatInput.StringLiteralShortUnicodeEscapeSurrogateFirstOnlyBytes |
||||||
Recommended.Proto3.TextFormatInput.StringLiteralShortUnicodeEscapeSurrogateFirstOnlyString |
Recommended.Proto3.TextFormatInput.StringLiteralShortUnicodeEscapeSurrogateFirstOnlyString |
||||||
|
Recommended.Editions_Proto3.TextFormatInput.StringLiteralShortUnicodeEscapeSurrogateFirstOnlyString |
||||||
Recommended.Proto3.TextFormatInput.StringLiteralShortUnicodeEscapeSurrogatePairBytes |
Recommended.Proto3.TextFormatInput.StringLiteralShortUnicodeEscapeSurrogatePairBytes |
||||||
|
Recommended.Editions_Proto3.TextFormatInput.StringLiteralShortUnicodeEscapeSurrogatePairBytes |
||||||
Recommended.Proto3.TextFormatInput.StringLiteralShortUnicodeEscapeSurrogatePairString |
Recommended.Proto3.TextFormatInput.StringLiteralShortUnicodeEscapeSurrogatePairString |
||||||
|
Recommended.Editions_Proto3.TextFormatInput.StringLiteralShortUnicodeEscapeSurrogatePairString |
||||||
Recommended.Proto3.TextFormatInput.StringLiteralShortUnicodeEscapeSurrogateSecondOnlyBytes |
Recommended.Proto3.TextFormatInput.StringLiteralShortUnicodeEscapeSurrogateSecondOnlyBytes |
||||||
|
Recommended.Editions_Proto3.TextFormatInput.StringLiteralShortUnicodeEscapeSurrogateSecondOnlyBytes |
||||||
Recommended.Proto3.TextFormatInput.StringLiteralShortUnicodeEscapeSurrogateSecondOnlyString |
Recommended.Proto3.TextFormatInput.StringLiteralShortUnicodeEscapeSurrogateSecondOnlyString |
||||||
|
Recommended.Editions_Proto3.TextFormatInput.StringLiteralShortUnicodeEscapeSurrogateSecondOnlyString |
||||||
Recommended.Proto3.TextFormatInput.StringLiteralUnicodeEscapeSurrogatePairLongShortBytes |
Recommended.Proto3.TextFormatInput.StringLiteralUnicodeEscapeSurrogatePairLongShortBytes |
||||||
|
Recommended.Editions_Proto3.TextFormatInput.StringLiteralUnicodeEscapeSurrogatePairLongShortBytes |
||||||
Recommended.Proto3.TextFormatInput.StringLiteralUnicodeEscapeSurrogatePairLongShortString |
Recommended.Proto3.TextFormatInput.StringLiteralUnicodeEscapeSurrogatePairLongShortString |
||||||
|
Recommended.Editions_Proto3.TextFormatInput.StringLiteralUnicodeEscapeSurrogatePairLongShortString |
||||||
Recommended.Proto3.TextFormatInput.StringLiteralUnicodeEscapeSurrogatePairShortLongBytes |
Recommended.Proto3.TextFormatInput.StringLiteralUnicodeEscapeSurrogatePairShortLongBytes |
||||||
|
Recommended.Editions_Proto3.TextFormatInput.StringLiteralUnicodeEscapeSurrogatePairShortLongBytes |
||||||
Recommended.Proto3.TextFormatInput.StringLiteralUnicodeEscapeSurrogatePairShortLongString |
Recommended.Proto3.TextFormatInput.StringLiteralUnicodeEscapeSurrogatePairShortLongString |
||||||
|
Recommended.Editions_Proto3.TextFormatInput.StringLiteralUnicodeEscapeSurrogatePairShortLongString |
||||||
Required.Proto3.TextFormatInput.StringFieldBadUTF8Hex |
Required.Proto3.TextFormatInput.StringFieldBadUTF8Hex |
||||||
|
Required.Editions_Proto3.TextFormatInput.StringFieldBadUTF8Hex |
||||||
Required.Proto3.TextFormatInput.StringFieldBadUTF8Octal |
Required.Proto3.TextFormatInput.StringFieldBadUTF8Octal |
||||||
|
Required.Editions_Proto3.TextFormatInput.StringFieldBadUTF8Octal |
||||||
Required.Proto3.TextFormatInput.StringLiteralLongUnicodeEscapeTooLargeBytes |
Required.Proto3.TextFormatInput.StringLiteralLongUnicodeEscapeTooLargeBytes |
||||||
|
Required.Editions_Proto3.TextFormatInput.StringLiteralLongUnicodeEscapeTooLargeBytes |
||||||
Required.Proto3.TextFormatInput.StringLiteralLongUnicodeEscapeTooLargeString |
Required.Proto3.TextFormatInput.StringLiteralLongUnicodeEscapeTooLargeString |
||||||
|
Required.Editions_Proto3.TextFormatInput.StringLiteralLongUnicodeEscapeTooLargeString |
||||||
|
@ -0,0 +1,150 @@ |
|||||||
|
# Editions: Feature Extension Layout |
||||||
|
|
||||||
|
**Author:** [@mkruskal-google](https://github.com/mkruskal-google), |
||||||
|
[@zhangskz](https://github.com/zhangskz) |
||||||
|
|
||||||
|
**Approved:** 2023-08-23 |
||||||
|
|
||||||
|
## Background |
||||||
|
|
||||||
|
"[What are Protobuf Editions](what-are-protobuf-editions.md)" lays out a plan |
||||||
|
for allowing for more targeted features not owned by the protobuf team. It uses |
||||||
|
extensions of the global features proto to implement this. One thing that was |
||||||
|
left a bit ambiguous was *who* should own these extensions. Language, code |
||||||
|
generator, and runtime implementations are all similar but not identical |
||||||
|
distinctions. |
||||||
|
|
||||||
|
"Editions Zero Feature: utf8_validation" (not available externally, though a |
||||||
|
later version, |
||||||
|
"[Editions Zero: utf8_validation Without Problematic Options](editions-zero-utf8_validation.md)" |
||||||
|
is) is a recent plan to add a new set of generator features for utf8 validation. |
||||||
|
While the sole feature we had originally created (`legacy_closed_enum` in Java |
||||||
|
and C++) didn't have any ambiguity here, this one did. Specifically in Python, |
||||||
|
the current behaviors across proto2/proto3 are distinct for all 3 |
||||||
|
implementations: pure python, Python/C++, Python/upb. |
||||||
|
|
||||||
|
## Overview |
||||||
|
|
||||||
|
In meetings, we've discussed various alternatives, captured below. The original |
||||||
|
plan was to make feature extensions runtime implementation-specific (e.g. C++, |
||||||
|
Java, Python, upb). There are some notable complications that came up though: |
||||||
|
|
||||||
|
1. **Polyglot** - it's not clear how upb or C++ runtimes should behave in |
||||||
|
multi-language situations. Which feature sets do they consider for runtime |
||||||
|
behaviors? *Note: this is already a serious issue today, where all proto2 |
||||||
|
strings and many proto3 strings are completely unsafe across languages.* |
||||||
|
|
||||||
|
2. **Shared Implementations** - Runtimes like upb and C++ are used as backing |
||||||
|
implementations of multiple other languages (e.g. Python, Rust, Ruby, PHP). |
||||||
|
If we have a single set of `upb` or `cpp` features, migrating to those |
||||||
|
shared implementations would be more difficult (since there's no independent |
||||||
|
switches per-language). *Note: this is already the situation we're in today, |
||||||
|
where switching the runtime implementation can cause subtle and dangerous |
||||||
|
behavior changes.* |
||||||
|
|
||||||
|
Given that we only have two behaviors, and one of them is unambiguous, it seems |
||||||
|
reasonable to punt on this decision until we have more information. We may |
||||||
|
encounter more edge cases that require feature extensions (and give us more |
||||||
|
information) during the rollout of edition zero. We also have a lot of freedom |
||||||
|
to re-model features in later editions, so keeping the initial implementation as |
||||||
|
simple as possible seems best (i.e. Alternative 2). |
||||||
|
|
||||||
|
## Alternatives |
||||||
|
|
||||||
|
### Alternative 1: Runtime Implementation Features |
||||||
|
|
||||||
|
Features would be per-runtime implementation as originally described in |
||||||
|
"Editions Zero Feature: utf8_validation." For example, Protobuf Python users |
||||||
|
would set different features depending on the backing implementation (e.g. |
||||||
|
`features.(pb.cpp).<feature>`, `features.(pb.upb).<feature>`). |
||||||
|
|
||||||
|
#### Pros |
||||||
|
|
||||||
|
* Most consistent with range of behaviors expressible pre-Editions |
||||||
|
|
||||||
|
#### Cons |
||||||
|
|
||||||
|
* Implementation may / should not be obvious to users. |
||||||
|
* Lack of levers specifically for language / implementation combos. For |
||||||
|
example, there is no way to set Python-C++ behavior independently of C++ |
||||||
|
behavior which may make migration harder from other Python implementations. |
||||||
|
|
||||||
|
### Alternative 2: Generator Features |
||||||
|
|
||||||
|
Features would be per-generator only (i.e. each protoc plugin would own one set |
||||||
|
of features). This was the second decision we made in later discussions, and |
||||||
|
while very similar to the above alternative, it's more inline with our goal of |
||||||
|
making features primarily for codegen. |
||||||
|
|
||||||
|
For example, all Python implementations would share the same set of features |
||||||
|
(e.g. `features.(pb.python).<feature>`). However, certain features could be |
||||||
|
targeted to specific implementations (e.g. |
||||||
|
`features.(pb.python).upb_utf8_validation` would only be used by Python/upb). |
||||||
|
|
||||||
|
#### Pros |
||||||
|
|
||||||
|
* Allows independent controls of shared implementations in different target |
||||||
|
languages (e.g. Python's upb feature won't affect PHP). |
||||||
|
|
||||||
|
#### Cons |
||||||
|
|
||||||
|
* Possible complexity in upb to understand which language's features to |
||||||
|
respect. UPB is not currently aware of what language it is being used for. |
||||||
|
* Limits in-process sharing across languages with shared implementations (e.g. |
||||||
|
Python upb, PHP upb) in the case of conflicting behaviors. |
||||||
|
* Additional checks may be needed. |
||||||
|
|
||||||
|
### Alternative 3: Migrate to bytes |
||||||
|
|
||||||
|
Since this whole discussion revolves around the utf8 validation feature, one |
||||||
|
option would be to just remove it from edition zero. Instead of adding a new |
||||||
|
toggle for UTF8 behavior, we could simply migrate everyone who doesn't enforce |
||||||
|
utf8 today to `bytes`. This would likely need another new *codegen* feature for |
||||||
|
generating byte getters/setters as strings, but that wouldn't have any of the |
||||||
|
ambiguity we're seeing today. |
||||||
|
|
||||||
|
Unfortunately, this doesn't seem feasible because of all the different behaviors |
||||||
|
laid out in "Editions Zero Feature: utf8_validation." UTF8 validation isn't |
||||||
|
really a binary on/off decision, and it can vary widely between languages. There |
||||||
|
are many cases where UTF8 is validated in **some** languages but not others, and |
||||||
|
there's also the C++ "hint" behavior that logs errors but allows invalid UTF8. |
||||||
|
|
||||||
|
**Note:** This could still be partially done in a follow-up LSC by targeting |
||||||
|
specific combinations of the new feature that disable validation in all relevant |
||||||
|
languages. |
||||||
|
|
||||||
|
#### Pros |
||||||
|
|
||||||
|
* Punts on the issue, we wouldn't need any upb features and C++ features would |
||||||
|
all be code-gen only |
||||||
|
* Simplifies the situation, avoids adding a very complicated feature in |
||||||
|
edition zero |
||||||
|
|
||||||
|
#### Cons |
||||||
|
|
||||||
|
* Not really possible given the current complexity |
||||||
|
* There are O(10M) proto2 string fields that would be blindly changed to bytes |
||||||
|
|
||||||
|
### Alternative 4: Nested Features |
||||||
|
|
||||||
|
Another option is to allow for shared feature set messages. For example, upb |
||||||
|
would define a feature message, but *not* make it an extension of the global |
||||||
|
`FeatureSet`. Instead, languages with upb implementations would have a field of |
||||||
|
this type to allow for finer-grained controls. C++ would both extend the global |
||||||
|
`FeatureSet` and also be allowed as a field in other languages. |
||||||
|
|
||||||
|
For example, python utf8 validation could be specified as: |
||||||
|
|
||||||
|
We could have checks during feature validation that enforce that impossible |
||||||
|
combinations aren't specified. For example, with our current implementation |
||||||
|
`features.(pb.python).cpp` should always be identical to `features.(pb.cpp)`, |
||||||
|
since we don't have any mechanism for distinguishing them. |
||||||
|
|
||||||
|
#### Pros |
||||||
|
|
||||||
|
* Much more explicit than options 1 and 2 |
||||||
|
|
||||||
|
#### Cons |
||||||
|
|
||||||
|
* Maybe too explicit? Proto owners would be forced to duplicate a lot of |
||||||
|
features |
@ -0,0 +1,144 @@ |
|||||||
|
# Legacy Syntax Editions |
||||||
|
|
||||||
|
**Author:** [@mkruskal-google](https://github.com/mkruskal-google) |
||||||
|
|
||||||
|
**Approved:** 2023-09-08 |
||||||
|
|
||||||
|
Should proto2/proto3 be treated as editions? |
||||||
|
|
||||||
|
## Background |
||||||
|
|
||||||
|
[Edition Zero Features](edition-zero-features.md) lays out our plan for edition |
||||||
|
2023, which will unify proto2 and proto3. Since early in the design process, |
||||||
|
we've discussed the possibility of making proto2 and proto3 "special" editions, |
||||||
|
but never laid out what exactly it would look like or determined if it was |
||||||
|
necessary. |
||||||
|
|
||||||
|
We recently redesigned editions to be represented as enums |
||||||
|
([Edition Naming](edition-naming.md)), and also how edition defaults are |
||||||
|
propagated to generators and runtimes |
||||||
|
([Editions: Life of a FeatureSet](editions-life-of-a-featureset.md)). With these |
||||||
|
changes, there could be an opportunity to special-case proto2 and proto3 in a |
||||||
|
beneficial way. |
||||||
|
|
||||||
|
## Problem Description |
||||||
|
|
||||||
|
While the original plan was to keep editions and syntax orthogonal, that naively |
||||||
|
means we'd be supporting two very different codebases. This has some serious |
||||||
|
maintenance costs though, especially when it comes to test coverage. We could |
||||||
|
expect to have sub-optimal test coverage of editions initially, which would |
||||||
|
gradually become poor coverage of syntax later. Since we need to support both |
||||||
|
syntax and editions long-term, this isn't ideal. |
||||||
|
|
||||||
|
In the implementation of editions in C++, we decided to unify a lot of the |
||||||
|
infrastructure to avoid this issue. We define global feature sets for proto2 and |
||||||
|
proto3, and try to use those internally instead of checking syntax directly. By |
||||||
|
pushing the syntax/editions branch earlier in the stack, it gives us a lot of |
||||||
|
indirect test coverage for editions much earlier. |
||||||
|
|
||||||
|
A separate issue is how Prototiller will support the conversion of syntax to |
||||||
|
edition 2023. For features it knows about, we can hardcode defaults into the |
||||||
|
transforms. However, third party feature owners will have no way of signaling |
||||||
|
what the old proto2/proto3 behavior was, so Prototiller won't be able to provide |
||||||
|
any transformations by default. They'd need to provide custom Prototiller |
||||||
|
transforms hardcoding all of their features. |
||||||
|
|
||||||
|
## Recommended Solution |
||||||
|
|
||||||
|
We recommend adding two new special editions to our current set: |
||||||
|
|
||||||
|
``` |
||||||
|
enum Edition { |
||||||
|
EDITION_UNKNOWN = 0; |
||||||
|
EDITION_PROTO2 = 998; |
||||||
|
EDITION_PROTO3 = 999; |
||||||
|
EDITION_2023 = 1000; |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
These will be treated the same as any other edition, except in our parser which |
||||||
|
will reject `edition = "proto2"` and `edition = "proto3"` in proto files. The |
||||||
|
real benefit here is that this allows features to specify what their |
||||||
|
proto2/proto3 defaults are, making it easier for Prototiller to handle |
||||||
|
migration. It also allows generators and runtimes to unify their internals more |
||||||
|
completely, treating proto2/proto3 files exactly the same as editions. |
||||||
|
|
||||||
|
### Serialized Descriptors |
||||||
|
|
||||||
|
As we now know, there are a lot of serialized `descriptor.proto` descriptor sets |
||||||
|
out there that need to continue working for O(months). In order to avoid |
||||||
|
blocking edition zero for that long, we may need fallbacks in protoc for the |
||||||
|
case where feature resolution *fails*. If the file is proto2/proto3, failure |
||||||
|
should result in a fallback to the existing hardcoded defaults. We can remove |
||||||
|
these later once we're willing to break stale `descriptor.proto` snapshots that |
||||||
|
predate the changes in this doc. |
||||||
|
|
||||||
|
### Bootstrapping |
||||||
|
|
||||||
|
In order to get feature resolution running in proto2 and proto3, we need to be |
||||||
|
able to support bootstrapped protos. For these builds, we can't use any |
||||||
|
reflection without deadlocking, which means feature defaults can't be compiled |
||||||
|
during runtime. We would have had to solve this problem anyway when it came time |
||||||
|
to migrate these protos to editions, but this proposal forces our hand early. |
||||||
|
Luckily, "Editions: Life of a FeatureSet" already set us up for this scenario, |
||||||
|
and we have Blaze rules for embedding these defaults into code. For C++ |
||||||
|
specifically, this will need to be checked in alongside the other bootstrapped |
||||||
|
protos. Other languages will be able to do this more dynamically via genrules. |
||||||
|
|
||||||
|
### Feature Inference |
||||||
|
|
||||||
|
While we can calculate defaults using the same logic as in editions, actually |
||||||
|
inferring "features" from proto2/proto3 needs some custom code. For example: |
||||||
|
|
||||||
|
* The `required` keyword sets `LEGACY_REQUIRED` feature |
||||||
|
* The `optional` keyword in proto3 sets `EXPLICIT` presence |
||||||
|
* The `group` keyword implies `DELIMITED` encoding |
||||||
|
* The `enforce_utf8` options flips between `PACKED` and `EXPANDED` encoding |
||||||
|
|
||||||
|
This logic needs to be written in code, and will need to be duplicated in every |
||||||
|
language we support. Any language-specific feature transformations will also |
||||||
|
need to be included in that language. To make this as portable as possible, we |
||||||
|
will define functions like: |
||||||
|
|
||||||
|
Each type of descriptor will have its own set of transformations that should be |
||||||
|
applied to its features for legacy editions. |
||||||
|
|
||||||
|
#### Pros |
||||||
|
|
||||||
|
* Makes it clearer that proto2/proto3 are "like" editions |
||||||
|
|
||||||
|
* Gives Prototiller a little more information in the transformation from |
||||||
|
proto2/proto3 to editions (not necessarily 2023) |
||||||
|
|
||||||
|
* Allows proto2/proto3 defaults to be specified in a single location |
||||||
|
|
||||||
|
* Makes unification of syntax/edition code easier to implement in runtimes |
||||||
|
|
||||||
|
* Allows cross-language proto2/proto3 testing with the conformance framework |
||||||
|
mentioned in "Editions: Life of a FeatureSet" |
||||||
|
|
||||||
|
#### Cons |
||||||
|
|
||||||
|
* Adds special-case legacy editions, which may be somewhat confusing |
||||||
|
|
||||||
|
* We will need to port feature inference logic across all languages. This is |
||||||
|
arguably cheaper than maintaining branched proto2/proto3 code in all |
||||||
|
languages though |
||||||
|
|
||||||
|
## Considered Alternatives |
||||||
|
|
||||||
|
### Do Nothing |
||||||
|
|
||||||
|
If we do nothing, there will be no built-in unification of syntax and editions. |
||||||
|
Runtimes could choose any point to split the logic. |
||||||
|
|
||||||
|
#### Pros |
||||||
|
|
||||||
|
* Requires no changes to editions code |
||||||
|
|
||||||
|
#### Cons |
||||||
|
|
||||||
|
* Likely results in lower test coverage |
||||||
|
* May hide issues until we start rolling out edition 2023 |
||||||
|
* Prototiller would have to hard-code proto2/proto3 defaults of features it |
||||||
|
knows, and couldn't even try to migrate runtimes it doesn't |
@ -0,0 +1,139 @@ |
|||||||
|
# Minimum Required Edition |
||||||
|
|
||||||
|
**Author:** [@mcy](https://github.com/mcy) |
||||||
|
|
||||||
|
**Approved:** 2022-11-15 |
||||||
|
|
||||||
|
A versioning mechanism for descriptors that ensures old runtimes do not load |
||||||
|
descriptors that are "too new." |
||||||
|
|
||||||
|
## Background |
||||||
|
|
||||||
|
Suppose we decide to add a novel definition like |
||||||
|
|
||||||
|
``` |
||||||
|
const int32 MY_CONSTANT = 42; |
||||||
|
``` |
||||||
|
|
||||||
|
to the Protobuf language. This would entail a descriptor change to track the |
||||||
|
values of constants, but they would not be loaded properly by older runtimes. |
||||||
|
This document describes an addition to `descriptor.proto` that prevents this |
||||||
|
version mismatch issue. |
||||||
|
|
||||||
|
[Protobuf Editions](what-are-protobuf-editions.md) intends to add the concept of |
||||||
|
an edition to Protobuf, which will be an approximately-annually incrementing |
||||||
|
value. Because of their annual nature, and because runtimes need to be updated |
||||||
|
to handle new features they implement regardless, we can use them as a poison |
||||||
|
pill for old runtimes that try to load descriptors that are "too new." |
||||||
|
|
||||||
|
## Overview |
||||||
|
|
||||||
|
We propose adding a new field to `FileDescriptorProto`: |
||||||
|
|
||||||
|
``` |
||||||
|
optional string minimum_required_edition = ...; |
||||||
|
``` |
||||||
|
|
||||||
|
This field would exist alongside the `edition` field, and would have the |
||||||
|
following semantics: |
||||||
|
|
||||||
|
Every Protobuf runtime implementation must specify the newest edition whose |
||||||
|
constructs it can handle (at a particular rev of that implementation). If that |
||||||
|
edition is less than `minimum_required_edition`, loading the descriptor must |
||||||
|
fail. |
||||||
|
|
||||||
|
"Less than" is defined per the edition total order given in |
||||||
|
[Life of an Edition](life-of-an-edition.md). To restate it, it is the following |
||||||
|
algorithm: |
||||||
|
|
||||||
|
``` |
||||||
|
def edition_less_than(a, b): |
||||||
|
parts_a = a.split(".") |
||||||
|
parts_b = b.split(".") |
||||||
|
for i in range(0, min(len(parts_a), len(parts_b))): |
||||||
|
if int(parts_a[i]) < int(parts_b[i]): return True |
||||||
|
return len(a) < len(b) |
||||||
|
``` |
||||||
|
|
||||||
|
`protoc` should keep track of which constructions require which minimum edition. |
||||||
|
For example, if constants are introduced in edition 2025, but they are not |
||||||
|
present in a file, `protoc` should not require that runtimes understand |
||||||
|
constants by picking a lower edition, like 2023 (assuming no other construct |
||||||
|
requires a higher edition). |
||||||
|
|
||||||
|
In particular, the following changes should keep the minimum edition constant, |
||||||
|
with all other things unchanged: |
||||||
|
|
||||||
|
* An upgrade of the proto compiler. |
||||||
|
* Upgrading the specified edition of a file via Prototiller. |
||||||
|
|
||||||
|
### Bootstrapping Concerns |
||||||
|
|
||||||
|
"Epochs for `descriptor.proto`" (not available externally) describes a potential |
||||||
|
issue with bootstrapping. It is not the case here: minimum edition is only |
||||||
|
incremented once a particular file uses a new feature. Since `descriptor.proto` |
||||||
|
and other schemas used by `protoc` and the backends would not use new features |
||||||
|
immediately, introducing a new feature does not immediately stop the compiler |
||||||
|
from being able to compile itself. |
||||||
|
|
||||||
|
### Concerns for Schema Producers |
||||||
|
|
||||||
|
Schema producers should consider changes to their schemas that increase the |
||||||
|
minimum required edition to be breaking changes, since it will stop compiled |
||||||
|
descriptors from being loaded at runtime. |
||||||
|
|
||||||
|
## Recommendation |
||||||
|
|
||||||
|
We recommend adding the aforementioned minimum required edition field, along |
||||||
|
with the semantics it entails. This logic should be implemented entirely in the |
||||||
|
protoc frontend. |
||||||
|
|
||||||
|
## Alternatives |
||||||
|
|
||||||
|
### Use a Non-Editions Version Number |
||||||
|
|
||||||
|
Rather than using the editions value, use some other version number. This number |
||||||
|
would be incremented rarely (3-5 year horizon). This is the approach proposed |
||||||
|
in "Epochs for `descriptor.proto`." |
||||||
|
|
||||||
|
#### Pros |
||||||
|
|
||||||
|
* Does not re-use the editions value for a semantically-different meaning; the |
||||||
|
edition remains being "just" a key into a table of features defaults. |
||||||
|
|
||||||
|
#### Cons |
||||||
|
|
||||||
|
* Introduces another version number to Protobuf that increments at its own |
||||||
|
cadence. |
||||||
|
* Could potentially be confused with the edition value, even though they serve |
||||||
|
distinct purposes. |
||||||
|
|
||||||
|
### Minimum Required Edition Should Not Be Minimal |
||||||
|
|
||||||
|
The proto compiler should not guarantee that the minimum required edition is as |
||||||
|
small as it could possibly be. |
||||||
|
|
||||||
|
#### Pros |
||||||
|
|
||||||
|
* Reduces implementation burden. |
||||||
|
|
||||||
|
#### Cons |
||||||
|
|
||||||
|
* This introduces situations where an upgrade of the proto compiler, or an |
||||||
|
innocuous change to a schema, can lead the the minimum required edition |
||||||
|
being incremented. This is a problem for schema producers. |
||||||
|
|
||||||
|
### Do Nothing |
||||||
|
|
||||||
|
#### Pros |
||||||
|
|
||||||
|
* Reduces churn in runtimes, since they do not need to implement new handling |
||||||
|
for new *editions* (as contrasted to just *features)* regularly. |
||||||
|
* Avoids a situation where old software cannot load new descriptors at |
||||||
|
runtime. |
||||||
|
|
||||||
|
#### Cons |
||||||
|
|
||||||
|
* Commits us to never changing the descriptor wire format in |
||||||
|
backwards-incompatible ways, which has far-reaching effects on evolution. |
||||||
|
These consequences are discussed in "Epochs for `descriptor.proto`." |
@ -0,0 +1,597 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
require_once('test_base.php'); |
||||||
|
require_once('test_util.php'); |
||||||
|
|
||||||
|
use Google\Protobuf\Internal\RepeatedField; |
||||||
|
use Google\Protobuf\Internal\GPBType; |
||||||
|
use Foo\EmptyAnySerialization; |
||||||
|
use Foo\TestInt32Value; |
||||||
|
use Foo\TestStringValue; |
||||||
|
use Foo\TestAny; |
||||||
|
use Foo\TestEnum; |
||||||
|
use Foo\TestLargeFieldNumber; |
||||||
|
use Foo\TestMessage; |
||||||
|
use Foo\TestMessage\Sub; |
||||||
|
use Foo\TestPackedMessage; |
||||||
|
use Foo\TestRandomFieldOrder; |
||||||
|
use Foo\TestUnpackedMessage; |
||||||
|
use Google\Protobuf\Any; |
||||||
|
use Google\Protobuf\DoubleValue; |
||||||
|
use Google\Protobuf\FieldMask; |
||||||
|
use Google\Protobuf\FloatValue; |
||||||
|
use Google\Protobuf\Int32Value; |
||||||
|
use Google\Protobuf\UInt32Value; |
||||||
|
use Google\Protobuf\Int64Value; |
||||||
|
use Google\Protobuf\UInt64Value; |
||||||
|
use Google\Protobuf\BoolValue; |
||||||
|
use Google\Protobuf\StringValue; |
||||||
|
use Google\Protobuf\BytesValue; |
||||||
|
use Google\Protobuf\Value; |
||||||
|
use Google\Protobuf\ListValue; |
||||||
|
use Google\Protobuf\Struct; |
||||||
|
use Google\Protobuf\GPBEmpty; |
||||||
|
|
||||||
|
class DebugInfoTest extends TestBase |
||||||
|
{ |
||||||
|
public function setUp(): void |
||||||
|
{ |
||||||
|
if (extension_loaded('protobuf')) { |
||||||
|
$this->markTestSkipped('__debugInfo is not supported for the protobuf extension'); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public function testVarDumpOutput() |
||||||
|
{ |
||||||
|
$m = new DoubleValue(); |
||||||
|
$m->setValue(1.5); |
||||||
|
var_dump($m); |
||||||
|
$regex = <<<EOL |
||||||
|
object(Google\Protobuf\DoubleValue)#%s (1) { |
||||||
|
["value"]=> |
||||||
|
float(1.5) |
||||||
|
} |
||||||
|
EOL; |
||||||
|
$this->expectOutputRegex('/' . sprintf(preg_quote($regex), '\d+') . '/'); |
||||||
|
} |
||||||
|
|
||||||
|
public function testTopLevelDoubleValue() |
||||||
|
{ |
||||||
|
$m = new DoubleValue(); |
||||||
|
$m->setValue(1.5); |
||||||
|
$this->assertSame(['value' => 1.5], $m->__debugInfo()); |
||||||
|
} |
||||||
|
|
||||||
|
public function testTopLevelFloatValue() |
||||||
|
{ |
||||||
|
$m = new FloatValue(); |
||||||
|
$m->setValue(1.5); |
||||||
|
$this->assertSame(['value' => 1.5], $m->__debugInfo()); |
||||||
|
} |
||||||
|
|
||||||
|
public function testTopLevelInt32Value() |
||||||
|
{ |
||||||
|
$m = new Int32Value(); |
||||||
|
$m->setValue(1); |
||||||
|
$this->assertSame(['value' => 1], $m->__debugInfo()); |
||||||
|
} |
||||||
|
|
||||||
|
public function testTopLevelUInt32Value() |
||||||
|
{ |
||||||
|
$m = new UInt32Value(); |
||||||
|
$m->setValue(1); |
||||||
|
$this->assertSame(['value' => 1], $m->__debugInfo()); |
||||||
|
} |
||||||
|
|
||||||
|
public function testTopLevelInt64Value() |
||||||
|
{ |
||||||
|
$m = new Int64Value(); |
||||||
|
$m->setValue(1); |
||||||
|
$this->assertSame(['value' => '1'], $m->__debugInfo()); |
||||||
|
} |
||||||
|
|
||||||
|
public function testTopLevelUInt64Value() |
||||||
|
{ |
||||||
|
$m = new UInt64Value(); |
||||||
|
$m->setValue(1); |
||||||
|
$this->assertSame(['value' => '1'], $m->__debugInfo()); |
||||||
|
} |
||||||
|
|
||||||
|
public function testTopLevelStringValue() |
||||||
|
{ |
||||||
|
$m = new StringValue(); |
||||||
|
$m->setValue("a"); |
||||||
|
$this->assertSame(['value' => 'a'], $m->__debugInfo()); |
||||||
|
} |
||||||
|
|
||||||
|
public function testTopLevelBoolValue() |
||||||
|
{ |
||||||
|
$m = new BoolValue(); |
||||||
|
$m->setValue(true); |
||||||
|
$this->assertSame(['value' => true], $m->__debugInfo()); |
||||||
|
} |
||||||
|
|
||||||
|
public function testTopLevelBytesValue() |
||||||
|
{ |
||||||
|
$m = new BytesValue(); |
||||||
|
$m->setValue("a"); |
||||||
|
$this->assertSame(['value' => 'YQ=='], $m->__debugInfo()); |
||||||
|
} |
||||||
|
|
||||||
|
public function testTopLevelLongBytesValue() |
||||||
|
{ |
||||||
|
$m = new BytesValue(); |
||||||
|
$data = $this->generateRandomString(12007); |
||||||
|
$m->setValue($data); |
||||||
|
$expected = base64_encode($data); |
||||||
|
$this->assertSame(['value' => $expected], $m->__debugInfo()); |
||||||
|
} |
||||||
|
|
||||||
|
public function testJsonEncodeNullSubMessage() |
||||||
|
{ |
||||||
|
$from = new TestMessage(); |
||||||
|
$from->setOptionalMessage(null); |
||||||
|
$data = $from->__debugInfo(); |
||||||
|
$this->assertEquals([], $data); |
||||||
|
} |
||||||
|
|
||||||
|
public function testDuration() |
||||||
|
{ |
||||||
|
$m = new Google\Protobuf\Duration(); |
||||||
|
$m->setSeconds(1234); |
||||||
|
$m->setNanos(999999999); |
||||||
|
$this->assertEquals([ |
||||||
|
'seconds' => 1234, |
||||||
|
'nanos' => 999999999, |
||||||
|
], $m->__debugInfo()); |
||||||
|
} |
||||||
|
|
||||||
|
public function testTimestamp() |
||||||
|
{ |
||||||
|
$m = new Google\Protobuf\Timestamp(); |
||||||
|
$m->setSeconds(946684800); |
||||||
|
$m->setNanos(123456789); |
||||||
|
$this->assertEquals([ |
||||||
|
'seconds' => 946684800, |
||||||
|
'nanos' => 123456789, |
||||||
|
], $m->__debugInfo()); |
||||||
|
} |
||||||
|
|
||||||
|
public function testTopLevelValue() |
||||||
|
{ |
||||||
|
$m = new Value(); |
||||||
|
$m->setStringValue("a"); |
||||||
|
$this->assertSame(['stringValue' => 'a'], $m->__debugInfo()); |
||||||
|
|
||||||
|
$m = new Value(); |
||||||
|
$m->setNumberValue(1.5); |
||||||
|
$this->assertSame(['numberValue' => 1.5], $m->__debugInfo()); |
||||||
|
|
||||||
|
$m = new Value(); |
||||||
|
$m->setBoolValue(true); |
||||||
|
$this->assertSame(['boolValue' => true], $m->__debugInfo()); |
||||||
|
|
||||||
|
$m = new Value(); |
||||||
|
$m->setNullValue(\Google\Protobuf\NullValue::NULL_VALUE); |
||||||
|
$this->assertSame(['nullValue' => 0], $m->__debugInfo()); |
||||||
|
|
||||||
|
$m = new Value(); |
||||||
|
$struct = new Struct(); |
||||||
|
$map = $struct->getFields(); |
||||||
|
$sub = new Value(); |
||||||
|
$sub->setNumberValue(1.5); |
||||||
|
$map["a"] = $sub; |
||||||
|
$m->setStructValue($struct); |
||||||
|
$this->assertSame(['structValue' => ['a' => 1.5]], $m->__debugInfo()); |
||||||
|
|
||||||
|
$m = new Value(); |
||||||
|
$list = new ListValue(); |
||||||
|
$arr = $list->getValues(); |
||||||
|
$sub = new Value(); |
||||||
|
$sub->setNumberValue(1.5); |
||||||
|
$arr[] = $sub; |
||||||
|
$m->setListValue($list); |
||||||
|
$this->assertSame(['listValue' => [1.5]], $m->__debugInfo()); |
||||||
|
} |
||||||
|
|
||||||
|
public function testTopLevelListValue() |
||||||
|
{ |
||||||
|
$m = new ListValue(); |
||||||
|
$arr = $m->getValues(); |
||||||
|
$sub = new Value(); |
||||||
|
$sub->setNumberValue(1.5); |
||||||
|
$arr[] = $sub; |
||||||
|
$this->assertSame([1.5], $m->__debugInfo()); |
||||||
|
} |
||||||
|
|
||||||
|
public function testEmptyListValue() |
||||||
|
{ |
||||||
|
$m = new Struct(); |
||||||
|
$m->setFields(['test' => (new Value())->setListValue(new ListValue())]); |
||||||
|
$this->assertSame(['test' => []], $m->__debugInfo()); |
||||||
|
} |
||||||
|
|
||||||
|
public function testTopLevelStruct() |
||||||
|
{ |
||||||
|
$m = new Struct(); |
||||||
|
$map = $m->getFields(); |
||||||
|
$sub = new Value(); |
||||||
|
$sub->setNumberValue(1.5); |
||||||
|
$map["a"] = $sub; |
||||||
|
$this->assertSame(['a' => 1.5], $m->__debugInfo()); |
||||||
|
} |
||||||
|
|
||||||
|
public function testEmptyStruct() |
||||||
|
{ |
||||||
|
$m = new Struct(); |
||||||
|
$m->setFields(['test' => (new Value())->setStructValue(new Struct())]); |
||||||
|
$this->assertSame(['test' => []], $m->__debugInfo()); |
||||||
|
} |
||||||
|
|
||||||
|
public function testEmptyValue() |
||||||
|
{ |
||||||
|
$m = new Value(); |
||||||
|
$this->assertSame([], $m->__debugInfo()); |
||||||
|
} |
||||||
|
|
||||||
|
public function testTopLevelAny() |
||||||
|
{ |
||||||
|
// Test a normal message. |
||||||
|
$packed = new TestMessage(); |
||||||
|
$packed->setOptionalInt32(123); |
||||||
|
$packed->setOptionalString("abc"); |
||||||
|
|
||||||
|
$m = new Any(); |
||||||
|
$m->pack($packed); |
||||||
|
$expected = [ |
||||||
|
'@type' => 'type.googleapis.com/foo.TestMessage', |
||||||
|
'optionalInt32' => 123, |
||||||
|
'optionalString' => 'abc', |
||||||
|
]; |
||||||
|
$result = $m->__debugInfo(); |
||||||
|
$this->assertSame($expected, $result); |
||||||
|
|
||||||
|
// Test a well known message. |
||||||
|
$packed = new Int32Value(); |
||||||
|
$packed->setValue(123); |
||||||
|
|
||||||
|
$m = new Any(); |
||||||
|
$m->pack($packed); |
||||||
|
$this->assertSame([ |
||||||
|
'@type' => 'type.googleapis.com/google.protobuf.Int32Value', |
||||||
|
'value' => 123 |
||||||
|
], $m->__debugInfo()); |
||||||
|
|
||||||
|
// Test an Any message. |
||||||
|
$outer = new Any(); |
||||||
|
$outer->pack($m); |
||||||
|
$this->assertSame([ |
||||||
|
'@type' => 'type.googleapis.com/google.protobuf.Any', |
||||||
|
'value' => [ |
||||||
|
'@type' => 'type.googleapis.com/google.protobuf.Int32Value', |
||||||
|
'value' => 123 |
||||||
|
], |
||||||
|
], $outer->__debugInfo()); |
||||||
|
|
||||||
|
// Test a Timestamp message. |
||||||
|
$packed = new Google\Protobuf\Timestamp(); |
||||||
|
$packed->setSeconds(946684800); |
||||||
|
$packed->setNanos(123456789); |
||||||
|
$m = new Any(); |
||||||
|
$m->pack($packed); |
||||||
|
$this->assertSame([ |
||||||
|
'@type' => 'type.googleapis.com/google.protobuf.Timestamp', |
||||||
|
'value' => '2000-01-01T00:00:00.123456789Z', |
||||||
|
], $m->__debugInfo()); |
||||||
|
} |
||||||
|
|
||||||
|
public function testAnyWithDefaultWrapperMessagePacked() |
||||||
|
{ |
||||||
|
$any = new Any(); |
||||||
|
$any->pack(new TestInt32Value([ |
||||||
|
'field' => new Int32Value(['value' => 0]), |
||||||
|
])); |
||||||
|
$this->assertSame( |
||||||
|
['@type' => 'type.googleapis.com/foo.TestInt32Value', 'field' => 0], |
||||||
|
$any->__debugInfo() |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
public function testTopLevelFieldMask() |
||||||
|
{ |
||||||
|
$m = new FieldMask(); |
||||||
|
$m->setPaths(["foo.bar_baz", "qux"]); |
||||||
|
$this->assertSame(['paths' => ['foo.bar_baz', 'qux']], $m->__debugInfo()); |
||||||
|
} |
||||||
|
|
||||||
|
public function testEmptyAnySerialization() |
||||||
|
{ |
||||||
|
$m = new EmptyAnySerialization(); |
||||||
|
|
||||||
|
$any = new Any(); |
||||||
|
$any->pack($m); |
||||||
|
|
||||||
|
$data = $any->__debugInfo(); |
||||||
|
$this->assertEquals(['@type' => 'type.googleapis.com/foo.EmptyAnySerialization'], $data); |
||||||
|
} |
||||||
|
|
||||||
|
public function testRepeatedStringValue() |
||||||
|
{ |
||||||
|
$m = new TestStringValue(); |
||||||
|
$r = [new StringValue(['value' => 'a'])]; |
||||||
|
$m->setRepeatedField($r); |
||||||
|
$this->assertSame(['repeatedField' => ['a']], $m->__debugInfo()); |
||||||
|
} |
||||||
|
|
||||||
|
public function testMapStringValue() |
||||||
|
{ |
||||||
|
$m = new TestStringValue(); |
||||||
|
$m->mergeFromJsonString("{\"map_field\":{\"1\": \"a\"}}"); |
||||||
|
$this->assertSame(['mapField' => [1 => 'a']], $m->__debugInfo()); |
||||||
|
} |
||||||
|
|
||||||
|
public function testNestedAny() |
||||||
|
{ |
||||||
|
// Make sure packed message has been created at least once. |
||||||
|
$m = new TestAny(); |
||||||
|
$m->mergeFromJsonString( |
||||||
|
"{\"any\":{\"optionalInt32\": 1, " . |
||||||
|
"\"@type\":\"type.googleapis.com/foo.TestMessage\", " . |
||||||
|
"\"optionalInt64\": 2}}"); |
||||||
|
$this->assertSame([ |
||||||
|
'any' => [ |
||||||
|
'@type' => 'type.googleapis.com/foo.TestMessage', |
||||||
|
'optionalInt32' => 1, |
||||||
|
'optionalInt64' => '2', |
||||||
|
] |
||||||
|
], $m->__debugInfo()); |
||||||
|
} |
||||||
|
|
||||||
|
public function testEnum() |
||||||
|
{ |
||||||
|
$m = new TestMessage(); |
||||||
|
$m->setOneofEnum(TestEnum::ZERO); |
||||||
|
$this->assertSame(['oneofEnum' => 'ZERO'], $m->__debugInfo()); |
||||||
|
} |
||||||
|
|
||||||
|
public function testTopLevelRepeatedField() |
||||||
|
{ |
||||||
|
$r1 = new RepeatedField(GPBType::class); |
||||||
|
$r1[] = 'a'; |
||||||
|
$this->assertSame(['a'], $r1->__debugInfo()); |
||||||
|
|
||||||
|
$r2 = new RepeatedField(TestMessage::class); |
||||||
|
$r2[] = new TestMessage(['optional_int32' => -42]); |
||||||
|
$r2[] = new TestMessage(['optional_int64' => 43]); |
||||||
|
$this->assertSame([ |
||||||
|
['optionalInt32' => -42], |
||||||
|
['optionalInt64' => '43'], |
||||||
|
], $r2->__debugInfo()); |
||||||
|
|
||||||
|
$r3 = new RepeatedField(RepeatedField::class); |
||||||
|
$r3[] = $r1; |
||||||
|
$r3[] = $r2; |
||||||
|
|
||||||
|
$this->assertSame([ |
||||||
|
['a'], |
||||||
|
[ |
||||||
|
['optionalInt32' => -42], |
||||||
|
['optionalInt64' => '43'], |
||||||
|
], |
||||||
|
], $r3->__debugInfo()); |
||||||
|
} |
||||||
|
|
||||||
|
public function testEverything() |
||||||
|
{ |
||||||
|
$m = new TestMessage([ |
||||||
|
'optional_int32' => -42, |
||||||
|
'optional_int64' => -43, |
||||||
|
'optional_uint32' => 42, |
||||||
|
'optional_uint64' => 43, |
||||||
|
'optional_sint32' => -44, |
||||||
|
'optional_sint64' => -45, |
||||||
|
'optional_fixed32' => 46, |
||||||
|
'optional_fixed64' => 47, |
||||||
|
'optional_sfixed32' => -46, |
||||||
|
'optional_sfixed64' => -47, |
||||||
|
'optional_float' => 1.5, |
||||||
|
'optional_double' => 1.6, |
||||||
|
'optional_bool' => true, |
||||||
|
'optional_string' => 'a', |
||||||
|
'optional_bytes' => 'bbbb', |
||||||
|
'optional_enum' => TestEnum::ONE, |
||||||
|
'optional_message' => new Sub([ |
||||||
|
'a' => 33 |
||||||
|
]), |
||||||
|
'repeated_int32' => [-42, -52], |
||||||
|
'repeated_int64' => [-43, -53], |
||||||
|
'repeated_uint32' => [42, 52], |
||||||
|
'repeated_uint64' => [43, 53], |
||||||
|
'repeated_sint32' => [-44, -54], |
||||||
|
'repeated_sint64' => [-45, -55], |
||||||
|
'repeated_fixed32' => [46, 56], |
||||||
|
'repeated_fixed64' => [47, 57], |
||||||
|
'repeated_sfixed32' => [-46, -56], |
||||||
|
'repeated_sfixed64' => [-47, -57], |
||||||
|
'repeated_float' => [1.5, 2.5], |
||||||
|
'repeated_double' => [1.6, 2.6], |
||||||
|
'repeated_bool' => [true, false], |
||||||
|
'repeated_string' => ['a', 'c'], |
||||||
|
'repeated_bytes' => ['bbbb', 'dddd'], |
||||||
|
'repeated_enum' => [TestEnum::ZERO, TestEnum::ONE], |
||||||
|
'repeated_message' => [new Sub(['a' => 34]), |
||||||
|
new Sub(['a' => 35])], |
||||||
|
'map_int32_int32' => [-62 => -62], |
||||||
|
'map_int64_int64' => [-63 => -63], |
||||||
|
'map_uint32_uint32' => [62 => 62], |
||||||
|
'map_uint64_uint64' => [63 => 63], |
||||||
|
'map_sint32_sint32' => [-64 => -64], |
||||||
|
'map_sint64_sint64' => [-65 => -65], |
||||||
|
'map_fixed32_fixed32' => [66 => 66], |
||||||
|
'map_fixed64_fixed64' => [67 => 67], |
||||||
|
'map_sfixed32_sfixed32' => [-68 => -68], |
||||||
|
'map_sfixed64_sfixed64' => [-69 => -69], |
||||||
|
'map_int32_float' => [1 => 3.5], |
||||||
|
'map_int32_double' => [1 => 3.6], |
||||||
|
'map_bool_bool' => [true => true], |
||||||
|
'map_string_string' => ['e' => 'e'], |
||||||
|
'map_int32_bytes' => [1 => 'ffff'], |
||||||
|
'map_int32_enum' => [1 => TestEnum::ONE], |
||||||
|
'map_int32_message' => [1 => new Sub(['a' => 36])], |
||||||
|
]); |
||||||
|
|
||||||
|
$this->assertSame([ |
||||||
|
'optionalInt32' => -42, |
||||||
|
'optionalInt64' => '-43', |
||||||
|
'optionalUint32' => 42, |
||||||
|
'optionalUint64' => '43', |
||||||
|
'optionalSint32' => -44, |
||||||
|
'optionalSint64' => '-45', |
||||||
|
'optionalFixed32' => 46, |
||||||
|
'optionalFixed64' => '47', |
||||||
|
'optionalSfixed32' => -46, |
||||||
|
'optionalSfixed64' => '-47', |
||||||
|
'optionalFloat' => 1.5, |
||||||
|
'optionalDouble' => 1.6, |
||||||
|
'optionalBool' => true, |
||||||
|
'optionalString' => 'a', |
||||||
|
'optionalBytes' => 'YmJiYg==', |
||||||
|
'optionalEnum' => 'ONE', |
||||||
|
'optionalMessage' => [ |
||||||
|
'a' => 33, |
||||||
|
], |
||||||
|
'repeatedInt32' => [ |
||||||
|
-42, |
||||||
|
-52, |
||||||
|
], |
||||||
|
'repeatedInt64' => [ |
||||||
|
'-43', |
||||||
|
'-53', |
||||||
|
], |
||||||
|
'repeatedUint32' => [ |
||||||
|
42, |
||||||
|
52, |
||||||
|
], |
||||||
|
'repeatedUint64' => [ |
||||||
|
'43', |
||||||
|
'53', |
||||||
|
], |
||||||
|
'repeatedSint32' => [ |
||||||
|
-44, |
||||||
|
-54, |
||||||
|
], |
||||||
|
'repeatedSint64' => [ |
||||||
|
'-45', |
||||||
|
'-55', |
||||||
|
], |
||||||
|
'repeatedFixed32' => [ |
||||||
|
46, |
||||||
|
56, |
||||||
|
], |
||||||
|
'repeatedFixed64' => [ |
||||||
|
'47', |
||||||
|
'57', |
||||||
|
], |
||||||
|
'repeatedSfixed32' => [ |
||||||
|
-46, |
||||||
|
-56, |
||||||
|
], |
||||||
|
'repeatedSfixed64' => [ |
||||||
|
'-47', |
||||||
|
'-57', |
||||||
|
], |
||||||
|
'repeatedFloat' => [ |
||||||
|
1.5, |
||||||
|
2.5, |
||||||
|
], |
||||||
|
'repeatedDouble' => [ |
||||||
|
1.6, |
||||||
|
2.6, |
||||||
|
], |
||||||
|
'repeatedBool' => [ |
||||||
|
true, |
||||||
|
false, |
||||||
|
], |
||||||
|
'repeatedString' => [ |
||||||
|
'a', |
||||||
|
'c', |
||||||
|
], |
||||||
|
'repeatedBytes' => [ |
||||||
|
'YmJiYg==', |
||||||
|
'ZGRkZA==', |
||||||
|
], |
||||||
|
'repeatedEnum' => [ |
||||||
|
'ZERO', |
||||||
|
'ONE', |
||||||
|
], |
||||||
|
'repeatedMessage' => [ |
||||||
|
[ |
||||||
|
'a' => 34, |
||||||
|
], |
||||||
|
[ |
||||||
|
'a' => 35, |
||||||
|
], |
||||||
|
], |
||||||
|
'mapInt32Int32' => [ |
||||||
|
-62 => -62, |
||||||
|
], |
||||||
|
'mapInt64Int64' => [ |
||||||
|
-63 => '-63', |
||||||
|
], |
||||||
|
'mapUint32Uint32' => [ |
||||||
|
62 => 62, |
||||||
|
], |
||||||
|
'mapUint64Uint64' => [ |
||||||
|
63 => '63', |
||||||
|
], |
||||||
|
'mapSint32Sint32' => [ |
||||||
|
-64 => -64, |
||||||
|
], |
||||||
|
'mapSint64Sint64' => [ |
||||||
|
-65 => '-65', |
||||||
|
], |
||||||
|
'mapFixed32Fixed32' => [ |
||||||
|
66 => 66, |
||||||
|
], |
||||||
|
'mapFixed64Fixed64' => [ |
||||||
|
67 => '67', |
||||||
|
], |
||||||
|
'mapSfixed32Sfixed32' => [ |
||||||
|
-68 => -68, |
||||||
|
], |
||||||
|
'mapSfixed64Sfixed64' => [ |
||||||
|
-69 => '-69', |
||||||
|
], |
||||||
|
'mapInt32Float' => [ |
||||||
|
1 => 3.5, |
||||||
|
], |
||||||
|
'mapInt32Double' => [ |
||||||
|
1 => 3.6, |
||||||
|
], |
||||||
|
'mapBoolBool' => [ |
||||||
|
'true' => true, |
||||||
|
], |
||||||
|
'mapStringString' => [ |
||||||
|
'e' => 'e', |
||||||
|
], |
||||||
|
'mapInt32Bytes' => [ |
||||||
|
1 => 'ZmZmZg==', |
||||||
|
], |
||||||
|
'mapInt32Enum' => [ |
||||||
|
1 => 'ONE', |
||||||
|
], |
||||||
|
'mapInt32Message' => [ |
||||||
|
1 => ['a' => 36], |
||||||
|
], |
||||||
|
], $m->__debugInfo()); |
||||||
|
} |
||||||
|
|
||||||
|
private function generateRandomString($length = 10) |
||||||
|
{ |
||||||
|
$randomString = str_repeat("+", $length); |
||||||
|
for ($i = 0; $i < $length; $i++) { |
||||||
|
$randomString[$i] = chr(rand(0, 255)); |
||||||
|
} |
||||||
|
return $randomString; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,39 @@ |
|||||||
|
#include "google/protobuf/repeated_field.h" |
||||||
|
|
||||||
|
extern "C" { |
||||||
|
|
||||||
|
#define expose_repeated_field_methods(ty, rust_ty) \ |
||||||
|
google::protobuf::RepeatedField<ty>* __pb_rust_RepeatedField_##rust_ty##_new() { \
|
||||||
|
return new google::protobuf::RepeatedField<ty>(); \
|
||||||
|
} \
|
||||||
|
void __pb_rust_RepeatedField_##rust_ty##_add(google::protobuf::RepeatedField<ty>* r, \
|
||||||
|
ty val) { \
|
||||||
|
r->Add(val); \
|
||||||
|
} \
|
||||||
|
size_t __pb_rust_RepeatedField_##rust_ty##_size( \
|
||||||
|
google::protobuf::RepeatedField<ty>* r) { \
|
||||||
|
return r->size(); \
|
||||||
|
} \
|
||||||
|
ty __pb_rust_RepeatedField_##rust_ty##_get(google::protobuf::RepeatedField<ty>* r, \
|
||||||
|
size_t index) { \
|
||||||
|
return r->Get(index); \
|
||||||
|
} \
|
||||||
|
void __pb_rust_RepeatedField_##rust_ty##_set(google::protobuf::RepeatedField<ty>* r, \
|
||||||
|
size_t index, ty val) { \
|
||||||
|
return r->Set(index, val); \
|
||||||
|
} \
|
||||||
|
void __pb_rust_RepeatedField_##rust_ty##_copy_from( \
|
||||||
|
google::protobuf::RepeatedField<ty> const& src, google::protobuf::RepeatedField<ty>& dst) { \
|
||||||
|
dst.CopyFrom(src); \
|
||||||
|
} |
||||||
|
|
||||||
|
expose_repeated_field_methods(int32_t, i32); |
||||||
|
expose_repeated_field_methods(uint32_t, u32); |
||||||
|
expose_repeated_field_methods(float, f32); |
||||||
|
expose_repeated_field_methods(double, f64); |
||||||
|
expose_repeated_field_methods(bool, bool); |
||||||
|
expose_repeated_field_methods(uint64_t, u64); |
||||||
|
expose_repeated_field_methods(int64_t, i64); |
||||||
|
|
||||||
|
#undef expose_repeated_field_methods |
||||||
|
} |
@ -0,0 +1,233 @@ |
|||||||
|
// Protocol Buffers - Google's data interchange format
|
||||||
|
// Copyright 2023 Google LLC. All rights reserved.
|
||||||
|
//
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file or at
|
||||||
|
// https://developers.google.com/open-source/licenses/bsd
|
||||||
|
|
||||||
|
/// Repeated scalar fields are implemented around the runtime-specific
|
||||||
|
/// `RepeatedField` struct. `RepeatedField` stores an opaque pointer to the
|
||||||
|
/// runtime-specific representation of a repeated scalar (`upb_Array*` on upb,
|
||||||
|
/// and `RepeatedField<T>*` on cpp).
|
||||||
|
use std::marker::PhantomData; |
||||||
|
|
||||||
|
use crate::{ |
||||||
|
Mut, MutProxy, Proxied, SettableValue, View, ViewProxy, |
||||||
|
__internal::{Private, RawRepeatedField}, |
||||||
|
__runtime::{RepeatedField, RepeatedFieldInner}, |
||||||
|
primitive::PrimitiveMut, |
||||||
|
vtable::ProxiedWithRawVTable, |
||||||
|
}; |
||||||
|
|
||||||
|
#[derive(Clone, Copy)] |
||||||
|
pub struct RepeatedFieldRef<'a> { |
||||||
|
pub repeated_field: RawRepeatedField, |
||||||
|
pub _phantom: PhantomData<&'a mut ()>, |
||||||
|
} |
||||||
|
|
||||||
|
unsafe impl<'a> Send for RepeatedFieldRef<'a> {} |
||||||
|
unsafe impl<'a> Sync for RepeatedFieldRef<'a> {} |
||||||
|
|
||||||
|
#[derive(Clone, Copy)] |
||||||
|
#[repr(transparent)] |
||||||
|
pub struct RepeatedView<'a, T: ?Sized> { |
||||||
|
inner: RepeatedField<'a, T>, |
||||||
|
} |
||||||
|
|
||||||
|
unsafe impl<'a, T: ProxiedWithRawVTable> Sync for RepeatedView<'a, T> {} |
||||||
|
unsafe impl<'a, T: ProxiedWithRawVTable> Send for RepeatedView<'a, T> {} |
||||||
|
|
||||||
|
impl<'msg, T: ?Sized> RepeatedView<'msg, T> { |
||||||
|
pub fn from_inner(_private: Private, inner: RepeatedFieldInner<'msg>) -> Self { |
||||||
|
Self { inner: RepeatedField::<'msg>::from_inner(_private, inner) } |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
pub struct RepeatedFieldIter<'a, T> { |
||||||
|
inner: RepeatedField<'a, T>, |
||||||
|
current_index: usize, |
||||||
|
} |
||||||
|
|
||||||
|
impl<'a, T> std::fmt::Debug for RepeatedView<'a, T> { |
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
||||||
|
f.debug_tuple("RepeatedView").finish() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[repr(transparent)] |
||||||
|
#[derive(Debug)] |
||||||
|
pub struct RepeatedMut<'a, T: ?Sized> { |
||||||
|
inner: RepeatedField<'a, T>, |
||||||
|
} |
||||||
|
|
||||||
|
unsafe impl<'a, T: ProxiedWithRawVTable> Sync for RepeatedMut<'a, T> {} |
||||||
|
|
||||||
|
impl<'msg, T: ?Sized> RepeatedMut<'msg, T> { |
||||||
|
pub fn from_inner(_private: Private, inner: RepeatedFieldInner<'msg>) -> Self { |
||||||
|
Self { inner: RepeatedField::from_inner(_private, inner) } |
||||||
|
} |
||||||
|
pub fn as_mut(&self) -> RepeatedMut<'_, T> { |
||||||
|
Self { inner: self.inner } |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl<'a, T> std::ops::Deref for RepeatedMut<'a, T> { |
||||||
|
type Target = RepeatedView<'a, T>; |
||||||
|
fn deref(&self) -> &Self::Target { |
||||||
|
// SAFETY:
|
||||||
|
// - `Repeated{View,Mut}<'a, T>` are both `#[repr(transparent)]` over
|
||||||
|
// `RepeatedField<'a, T>`.
|
||||||
|
// - `RepeatedField` is a type alias for `NonNull`.
|
||||||
|
unsafe { &*(self as *const Self as *const RepeatedView<'a, T>) } |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
pub struct RepeatedFieldIterMut<'a, T> { |
||||||
|
inner: RepeatedMut<'a, T>, |
||||||
|
current_index: usize, |
||||||
|
} |
||||||
|
|
||||||
|
pub struct Repeated<T>(PhantomData<T>); |
||||||
|
|
||||||
|
macro_rules! impl_repeated_primitives { |
||||||
|
($($t:ty),*) => { |
||||||
|
$( |
||||||
|
impl Proxied for Repeated<$t> { |
||||||
|
type View<'a> = RepeatedView<'a, $t>; |
||||||
|
type Mut<'a> = RepeatedMut<'a, $t>; |
||||||
|
} |
||||||
|
|
||||||
|
impl<'a> ViewProxy<'a> for RepeatedView<'a, $t> { |
||||||
|
type Proxied = Repeated<$t>; |
||||||
|
|
||||||
|
fn as_view(&self) -> View<'_, Self::Proxied> { |
||||||
|
*self |
||||||
|
} |
||||||
|
|
||||||
|
fn into_view<'shorter>(self) -> View<'shorter, Self::Proxied> |
||||||
|
where 'a: 'shorter, |
||||||
|
{ |
||||||
|
RepeatedView { inner: self.inner } |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl<'a> ViewProxy<'a> for RepeatedMut<'a, $t> { |
||||||
|
type Proxied = Repeated<$t>; |
||||||
|
|
||||||
|
fn as_view(&self) -> View<'_, Self::Proxied> { |
||||||
|
**self |
||||||
|
} |
||||||
|
|
||||||
|
fn into_view<'shorter>(self) -> View<'shorter, Self::Proxied> |
||||||
|
where 'a: 'shorter, |
||||||
|
{ |
||||||
|
*self.into_mut::<'shorter>() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl<'a> MutProxy<'a> for RepeatedMut<'a, $t> { |
||||||
|
fn as_mut(&mut self) -> Mut<'_, Self::Proxied> { |
||||||
|
RepeatedMut { inner: self.inner } |
||||||
|
} |
||||||
|
|
||||||
|
fn into_mut<'shorter>(self) -> Mut<'shorter, Self::Proxied> |
||||||
|
where 'a: 'shorter, |
||||||
|
{ |
||||||
|
RepeatedMut { inner: self.inner } |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl <'a> SettableValue<Repeated<$t>> for RepeatedView<'a, $t> { |
||||||
|
fn set_on(self, _private: Private, mut mutator: Mut<'_, Repeated<$t>>) { |
||||||
|
mutator.copy_from(self); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl<'a> RepeatedView<'a, $t> { |
||||||
|
pub fn len(&self) -> usize { |
||||||
|
self.inner.len() |
||||||
|
} |
||||||
|
pub fn is_empty(&self) -> bool { |
||||||
|
self.len() == 0 |
||||||
|
} |
||||||
|
pub fn get(&self, index: usize) -> Option<$t> { |
||||||
|
self.inner.get(index) |
||||||
|
} |
||||||
|
pub fn iter(&self) -> RepeatedFieldIter<'_, $t> { |
||||||
|
(*self).into_iter() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl<'a> RepeatedMut<'a, $t> { |
||||||
|
pub fn push(&mut self, val: $t) { |
||||||
|
self.inner.push(val) |
||||||
|
} |
||||||
|
pub fn set(&mut self, index: usize, val: $t) { |
||||||
|
self.inner.set(index, val) |
||||||
|
} |
||||||
|
pub fn get_mut(&mut self, index: usize) -> Option<Mut<'_, $t>> { |
||||||
|
if index >= self.len() { |
||||||
|
return None; |
||||||
|
} |
||||||
|
Some(PrimitiveMut::Repeated(self.as_mut(), index)) |
||||||
|
} |
||||||
|
pub fn iter(&self) -> RepeatedFieldIter<'_, $t> { |
||||||
|
self.as_view().into_iter() |
||||||
|
} |
||||||
|
pub fn iter_mut(&mut self) -> RepeatedFieldIterMut<'_, $t> { |
||||||
|
self.as_mut().into_iter() |
||||||
|
} |
||||||
|
pub fn copy_from(&mut self, src: RepeatedView<'_, $t>) { |
||||||
|
self.inner.copy_from(&src.inner); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl<'a> std::iter::Iterator for RepeatedFieldIter<'a, $t> { |
||||||
|
type Item = $t; |
||||||
|
fn next(&mut self) -> Option<Self::Item> { |
||||||
|
let val = self.inner.get(self.current_index); |
||||||
|
if val.is_some() { |
||||||
|
self.current_index += 1; |
||||||
|
} |
||||||
|
val |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl<'a> std::iter::IntoIterator for RepeatedView<'a, $t> { |
||||||
|
type Item = $t; |
||||||
|
type IntoIter = RepeatedFieldIter<'a, $t>; |
||||||
|
fn into_iter(self) -> Self::IntoIter { |
||||||
|
RepeatedFieldIter { inner: self.inner, current_index: 0 } |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl <'a> std::iter::Iterator for RepeatedFieldIterMut<'a, $t> { |
||||||
|
type Item = Mut<'a, $t>; |
||||||
|
fn next(&mut self) -> Option<Self::Item> { |
||||||
|
if self.current_index >= self.inner.len() { |
||||||
|
return None; |
||||||
|
} |
||||||
|
let elem = PrimitiveMut::Repeated( |
||||||
|
// While this appears to allow mutable aliasing
|
||||||
|
// (multiple `Self::Item`s can co-exist), each `Item`
|
||||||
|
// only references a specific unique index.
|
||||||
|
RepeatedMut{ inner: self.inner.inner }, |
||||||
|
self.current_index, |
||||||
|
); |
||||||
|
self.current_index += 1; |
||||||
|
Some(elem) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl<'a> std::iter::IntoIterator for RepeatedMut<'a, $t> { |
||||||
|
type Item = Mut<'a, $t>; |
||||||
|
type IntoIter = RepeatedFieldIterMut<'a, $t>; |
||||||
|
fn into_iter(self) -> Self::IntoIter { |
||||||
|
RepeatedFieldIterMut { inner: self, current_index: 0 } |
||||||
|
} |
||||||
|
} |
||||||
|
)* |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl_repeated_primitives!(i32, u32, bool, f32, f64, i64, u64); |
@ -1,29 +0,0 @@ |
|||||||
// Protocol Buffers - Google's data interchange format
|
|
||||||
// Copyright 2008 Google Inc. All rights reserved.
|
|
||||||
//
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file or at
|
|
||||||
// https://developers.google.com/open-source/licenses/bsd
|
|
||||||
|
|
||||||
#include "google/protobuf/compiler/allowlists/allowlists.h" |
|
||||||
|
|
||||||
#include "absl/strings/string_view.h" |
|
||||||
#include "google/protobuf/compiler/allowlists/allowlist.h" |
|
||||||
|
|
||||||
namespace google { |
|
||||||
namespace protobuf { |
|
||||||
namespace compiler { |
|
||||||
|
|
||||||
// NOTE: Allowlists in this file are not accepting new entries unless otherwise
|
|
||||||
// specified.
|
|
||||||
|
|
||||||
static constexpr auto kWeakImports = internal::MakeAllowlist({ |
|
||||||
// Intentionally left blank.
|
|
||||||
}); |
|
||||||
|
|
||||||
bool IsWeakImportFile(absl::string_view file) { |
|
||||||
return kWeakImports.Allows(file); |
|
||||||
} |
|
||||||
} // namespace compiler
|
|
||||||
} // namespace protobuf
|
|
||||||
} // namespace google
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue