Skip to content

Commit e701f4f

Browse files
jhumpcopybara-github
authored andcommitted
protoc: support identifiers as reserved names in addition to string literals (only in editions) (#13471)
This addresses #13440. @mkruskal-google, I saw you are assigned the above issue. I hope it's okay that I took a stab at it. Closes #13471 COPYBARA_INTEGRATE_REVIEW=#13471 from jhump:jh/reserved-names-support-identifiers a23f18e PiperOrigin-RevId: 555610263
1 parent 43b1365 commit e701f4f

File tree

3 files changed

+186
-19
lines changed

3 files changed

+186
-19
lines changed

src/google/protobuf/compiler/parser.cc

+66-2
Original file line numberDiff line numberDiff line change
@@ -1796,10 +1796,27 @@ bool Parser::ParseReserved(DescriptorProto* message,
17961796
// Parse the declaration.
17971797
DO(Consume("reserved"));
17981798
if (LookingAtType(io::Tokenizer::TYPE_STRING)) {
1799+
if (syntax_identifier_ == "editions") {
1800+
RecordError(
1801+
"Reserved names must be identifiers in editions, not string "
1802+
"literals.");
1803+
return false;
1804+
}
17991805
LocationRecorder location(message_location,
18001806
DescriptorProto::kReservedNameFieldNumber);
18011807
location.StartAt(start_token);
18021808
return ParseReservedNames(message, location);
1809+
} else if (LookingAtType(io::Tokenizer::TYPE_IDENTIFIER)) {
1810+
if (syntax_identifier_ != "editions") {
1811+
RecordError(
1812+
"Reserved names must be string literals. (Only editions supports "
1813+
"identifiers.)");
1814+
return false;
1815+
}
1816+
LocationRecorder location(message_location,
1817+
DescriptorProto::kReservedNameFieldNumber);
1818+
location.StartAt(start_token);
1819+
return ParseReservedIdentifiers(message, location);
18031820
} else {
18041821
LocationRecorder location(message_location,
18051822
DescriptorProto::kReservedRangeFieldNumber);
@@ -1828,7 +1845,25 @@ bool Parser::ParseReservedNames(DescriptorProto* message,
18281845
const LocationRecorder& parent_location) {
18291846
do {
18301847
LocationRecorder location(parent_location, message->reserved_name_size());
1831-
DO(ParseReservedName(message->add_reserved_name(), "Expected field name."));
1848+
DO(ParseReservedName(message->add_reserved_name(),
1849+
"Expected field name string literal."));
1850+
} while (TryConsume(","));
1851+
DO(ConsumeEndOfDeclaration(";", &parent_location));
1852+
return true;
1853+
}
1854+
1855+
bool Parser::ParseReservedIdentifier(std::string* name,
1856+
absl::string_view error_message) {
1857+
DO(ConsumeIdentifier(name, error_message));
1858+
return true;
1859+
}
1860+
1861+
bool Parser::ParseReservedIdentifiers(DescriptorProto* message,
1862+
const LocationRecorder& parent_location) {
1863+
do {
1864+
LocationRecorder location(parent_location, message->reserved_name_size());
1865+
DO(ParseReservedIdentifier(message->add_reserved_name(),
1866+
"Expected field name identifier."));
18321867
} while (TryConsume(","));
18331868
DO(ConsumeEndOfDeclaration(";", &parent_location));
18341869
return true;
@@ -1889,10 +1924,27 @@ bool Parser::ParseReserved(EnumDescriptorProto* proto,
18891924
// Parse the declaration.
18901925
DO(Consume("reserved"));
18911926
if (LookingAtType(io::Tokenizer::TYPE_STRING)) {
1927+
if (syntax_identifier_ == "editions") {
1928+
RecordError(
1929+
"Reserved names must be identifiers in editions, not string "
1930+
"literals.");
1931+
return false;
1932+
}
18921933
LocationRecorder location(enum_location,
18931934
EnumDescriptorProto::kReservedNameFieldNumber);
18941935
location.StartAt(start_token);
18951936
return ParseReservedNames(proto, location);
1937+
} else if (LookingAtType(io::Tokenizer::TYPE_IDENTIFIER)) {
1938+
if (syntax_identifier_ != "editions") {
1939+
RecordError(
1940+
"Reserved names must be string literals. (Only editions supports "
1941+
"identifiers.)");
1942+
return false;
1943+
}
1944+
LocationRecorder location(enum_location,
1945+
EnumDescriptorProto::kReservedNameFieldNumber);
1946+
location.StartAt(start_token);
1947+
return ParseReservedIdentifiers(proto, location);
18961948
} else {
18971949
LocationRecorder location(enum_location,
18981950
EnumDescriptorProto::kReservedRangeFieldNumber);
@@ -1905,7 +1957,19 @@ bool Parser::ParseReservedNames(EnumDescriptorProto* proto,
19051957
const LocationRecorder& parent_location) {
19061958
do {
19071959
LocationRecorder location(parent_location, proto->reserved_name_size());
1908-
DO(ParseReservedName(proto->add_reserved_name(), "Expected enum value."));
1960+
DO(ParseReservedName(proto->add_reserved_name(),
1961+
"Expected enum value string literal."));
1962+
} while (TryConsume(","));
1963+
DO(ConsumeEndOfDeclaration(";", &parent_location));
1964+
return true;
1965+
}
1966+
1967+
bool Parser::ParseReservedIdentifiers(EnumDescriptorProto* proto,
1968+
const LocationRecorder& parent_location) {
1969+
do {
1970+
LocationRecorder location(parent_location, proto->reserved_name_size());
1971+
DO(ParseReservedIdentifier(proto->add_reserved_name(),
1972+
"Expected enum value identifier."));
19091973
} while (TryConsume(","));
19101974
DO(ConsumeEndOfDeclaration(";", &parent_location));
19111975
return true;

src/google/protobuf/compiler/parser.h

+6
Original file line numberDiff line numberDiff line change
@@ -403,12 +403,18 @@ class PROTOBUF_EXPORT Parser {
403403
bool ParseReservedNames(DescriptorProto* message,
404404
const LocationRecorder& parent_location);
405405
bool ParseReservedName(std::string* name, absl::string_view error_message);
406+
bool ParseReservedIdentifiers(DescriptorProto* message,
407+
const LocationRecorder& parent_location);
408+
bool ParseReservedIdentifier(std::string* name,
409+
absl::string_view error_message);
406410
bool ParseReservedNumbers(DescriptorProto* message,
407411
const LocationRecorder& parent_location);
408412
bool ParseReserved(EnumDescriptorProto* message,
409413
const LocationRecorder& message_location);
410414
bool ParseReservedNames(EnumDescriptorProto* message,
411415
const LocationRecorder& parent_location);
416+
bool ParseReservedIdentifiers(EnumDescriptorProto* message,
417+
const LocationRecorder& parent_location);
412418
bool ParseReservedNumbers(EnumDescriptorProto* message,
413419
const LocationRecorder& parent_location);
414420

src/google/protobuf/compiler/parser_unittest.cc

+114-17
Original file line numberDiff line numberDiff line change
@@ -871,6 +871,22 @@ TEST_F(ParseMessageTest, ReservedNames) {
871871
"}");
872872
}
873873

874+
TEST_F(ParseMessageTest, ReservedIdentifiers) {
875+
ExpectParsesTo(
876+
"edition = \"2023\";\n"
877+
"message TestMessage {\n"
878+
" reserved foo, bar;\n"
879+
"}\n",
880+
881+
"syntax: \"editions\" "
882+
"edition: \"2023\" "
883+
"message_type {"
884+
" name: \"TestMessage\""
885+
" reserved_name: \"foo\""
886+
" reserved_name: \"bar\""
887+
"}");
888+
}
889+
874890
TEST_F(ParseMessageTest, ExtensionRange) {
875891
ExpectParsesTo(
876892
"message TestMessage {\n"
@@ -1229,6 +1245,24 @@ TEST_F(ParseEnumTest, ReservedNames) {
12291245
"}");
12301246
}
12311247

1248+
TEST_F(ParseEnumTest, ReservedIdentifiers) {
1249+
ExpectParsesTo(
1250+
"edition = \"2023\";\n"
1251+
"enum TestEnum {\n"
1252+
" FOO = 0;\n"
1253+
" reserved foo, bar;\n"
1254+
"}\n",
1255+
1256+
"syntax: \"editions\" "
1257+
"edition: \"2023\" "
1258+
"enum_type {"
1259+
" name: \"TestEnum\""
1260+
" value { name:\"FOO\" number:0 }"
1261+
" reserved_name: \"foo\""
1262+
" reserved_name: \"bar\""
1263+
"}");
1264+
}
1265+
12321266
// ===================================================================
12331267

12341268
typedef ParserTest ParseServiceTest;
@@ -1502,6 +1536,44 @@ TEST_F(ParseErrorTest, DuplicateJsonName) {
15021536
"1:41: Already set option \"json_name\".\n");
15031537
}
15041538

1539+
TEST_F(ParseErrorTest, MsgReservedIdentifierOnlyInEditions) {
1540+
ExpectHasErrors(
1541+
"message TestMessage {\n"
1542+
" reserved foo, bar;\n"
1543+
"}\n",
1544+
"1:11: Reserved names must be string literals. (Only editions supports "
1545+
"identifiers.)\n");
1546+
}
1547+
TEST_F(ParseErrorTest, MsgReservedNameStringNotInEditions) {
1548+
ExpectHasErrors(
1549+
"edition = \"2023\";\n"
1550+
"message TestMessage {\n"
1551+
" reserved \"foo\", \"bar\";\n"
1552+
"}\n",
1553+
"2:11: Reserved names must be identifiers in editions, not string "
1554+
"literals.\n");
1555+
}
1556+
1557+
TEST_F(ParseErrorTest, EnumReservedIdentifierOnlyInEditions) {
1558+
ExpectHasErrors(
1559+
"enum TestEnum {\n"
1560+
" FOO = 0;\n"
1561+
" reserved foo, bar;\n"
1562+
"}\n",
1563+
"2:11: Reserved names must be string literals. (Only editions supports "
1564+
"identifiers.)\n");
1565+
}
1566+
TEST_F(ParseErrorTest, EnumReservedNameStringNotInEditions) {
1567+
ExpectHasErrors(
1568+
"edition = \"2023\";\n"
1569+
"enum TestEnum {\n"
1570+
" FOO = 0;\n"
1571+
" reserved \"foo\", \"bar\";\n"
1572+
"}\n",
1573+
"3:11: Reserved names must be identifiers in editions, not string "
1574+
"literals.\n");
1575+
}
1576+
15051577
TEST_F(ParseErrorTest, EnumValueOutOfRange) {
15061578
ExpectHasErrors(
15071579
"enum TestEnum {\n"
@@ -1701,13 +1773,16 @@ TEST_F(ParseErrorTest, EnumValueMissingNumber) {
17011773
"1:5: Missing numeric value for enum constant.\n");
17021774
}
17031775

1776+
// NB: with editions, this would be accepted and would reserve a value name of
1777+
// "max"
17041778
TEST_F(ParseErrorTest, EnumReservedStandaloneMaxNotAllowed) {
17051779
ExpectHasErrors(
17061780
"enum TestEnum {\n"
17071781
" FOO = 1;\n"
17081782
" reserved max;\n"
17091783
"}\n",
1710-
"2:11: Expected enum value or number range.\n");
1784+
"2:11: Reserved names must be string literals. (Only editions supports "
1785+
"identifiers.)\n");
17111786
}
17121787

17131788
TEST_F(ParseErrorTest, EnumReservedMixNameAndNumber) {
@@ -1718,6 +1793,15 @@ TEST_F(ParseErrorTest, EnumReservedMixNameAndNumber) {
17181793
"}\n",
17191794
"2:15: Expected enum number range.\n");
17201795
}
1796+
TEST_F(ParseErrorTest, EnumReservedMixNameAndNumberEditions) {
1797+
ExpectHasErrors(
1798+
"edition = \"2023\";\n"
1799+
"enum TestEnum {\n"
1800+
" FOO = 1;\n"
1801+
" reserved 10, foo;\n"
1802+
"}\n",
1803+
"3:15: Expected enum number range.\n");
1804+
}
17211805

17221806
TEST_F(ParseErrorTest, EnumReservedPositiveNumberOutOfRange) {
17231807
ExpectHasErrors(
@@ -1743,29 +1827,33 @@ TEST_F(ParseErrorTest, EnumReservedMissingQuotes) {
17431827
" FOO = 1;\n"
17441828
" reserved foo;\n"
17451829
"}\n",
1746-
"2:11: Expected enum value or number range.\n");
1830+
"2:11: Reserved names must be string literals. (Only editions supports "
1831+
"identifiers.)\n");
17471832
}
17481833

17491834
TEST_F(ParseErrorTest, EnumReservedInvalidIdentifier) {
17501835
ExpectHasWarnings(
1751-
R"(
1752-
enum TestEnum {
1753-
FOO = 1;
1754-
reserved "foo bar";
1755-
}
1756-
)",
1757-
"3:17: Reserved name \"foo bar\" is not a valid identifier.\n");
1836+
R"schema(
1837+
enum TestEnum {
1838+
FOO = 1;
1839+
reserved "foo bar";
1840+
}
1841+
)schema",
1842+
"3:19: Reserved name \"foo bar\" is not a valid identifier.\n");
17581843
}
17591844

17601845
// -------------------------------------------------------------------
17611846
// Reserved field number errors
17621847

1848+
// NB: with editions, this would be accepted and would reserve a field name of
1849+
// "max"
17631850
TEST_F(ParseErrorTest, ReservedStandaloneMaxNotAllowed) {
17641851
ExpectHasErrors(
17651852
"message Foo {\n"
17661853
" reserved max;\n"
17671854
"}\n",
1768-
"1:11: Expected field name or number range.\n");
1855+
"1:11: Reserved names must be string literals. (Only editions supports "
1856+
"identifiers.)\n");
17691857
}
17701858

17711859
TEST_F(ParseErrorTest, ReservedMixNameAndNumber) {
@@ -1775,23 +1863,32 @@ TEST_F(ParseErrorTest, ReservedMixNameAndNumber) {
17751863
"}\n",
17761864
"1:15: Expected field number range.\n");
17771865
}
1866+
TEST_F(ParseErrorTest, ReservedMixNameAndNumberEditions) {
1867+
ExpectHasErrors(
1868+
"edition = \"2023\";\n"
1869+
"message Foo {\n"
1870+
" reserved 10, foo;\n"
1871+
"}\n",
1872+
"2:15: Expected field number range.\n");
1873+
}
17781874

17791875
TEST_F(ParseErrorTest, ReservedMissingQuotes) {
17801876
ExpectHasErrors(
17811877
"message Foo {\n"
17821878
" reserved foo;\n"
17831879
"}\n",
1784-
"1:11: Expected field name or number range.\n");
1880+
"1:11: Reserved names must be string literals. (Only editions supports "
1881+
"identifiers.)\n");
17851882
}
17861883

17871884
TEST_F(ParseErrorTest, ReservedInvalidIdentifier) {
17881885
ExpectHasWarnings(
1789-
R"(
1790-
message Foo {
1791-
reserved "foo bar";
1792-
}
1793-
)",
1794-
"2:17: Reserved name \"foo bar\" is not a valid identifier.\n");
1886+
R"schema(
1887+
message Foo {
1888+
reserved "foo bar";
1889+
}
1890+
)schema",
1891+
"2:19: Reserved name \"foo bar\" is not a valid identifier.\n");
17951892
}
17961893

17971894
TEST_F(ParseErrorTest, ReservedNegativeNumber) {

0 commit comments

Comments
 (0)