Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

protoc: support identifiers as reserved names in addition to string literals (only in editions) #13471

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
56 changes: 54 additions & 2 deletions src/google/protobuf/compiler/parser.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1773,10 +1773,23 @@ bool Parser::ParseReserved(DescriptorProto* message,
// Parse the declaration.
DO(Consume("reserved"));
if (LookingAtType(io::Tokenizer::TYPE_STRING)) {
if (syntax_identifier_ == "editions") {
RecordError("Reserved names must be identifiers in editions, not string literals.");
return false;
}
LocationRecorder location(message_location,
DescriptorProto::kReservedNameFieldNumber);
location.StartAt(start_token);
return ParseReservedNames(message, location);
} else if (LookingAtType(io::Tokenizer::TYPE_IDENTIFIER)) {
if (syntax_identifier_ != "editions") {
RecordError("Reserved names must be string literals. (Only editions supports identifiers.)");
return false;
}
LocationRecorder location(message_location,
DescriptorProto::kReservedNameFieldNumber);
location.StartAt(start_token);
return ParseReservedIdentifiers(message, location);
} else {
LocationRecorder location(message_location,
DescriptorProto::kReservedRangeFieldNumber);
Expand Down Expand Up @@ -1805,7 +1818,23 @@ bool Parser::ParseReservedNames(DescriptorProto* message,
const LocationRecorder& parent_location) {
do {
LocationRecorder location(parent_location, message->reserved_name_size());
DO(ParseReservedName(message->add_reserved_name(), "Expected field name."));
DO(ParseReservedName(message->add_reserved_name(), "Expected field name string literal."));
} while (TryConsume(","));
DO(ConsumeEndOfDeclaration(";", &parent_location));
return true;
}

bool Parser::ParseReservedIdentifier(std::string* name,
absl::string_view error_message) {
DO(ConsumeIdentifier(name, error_message));
return true;
}

bool Parser::ParseReservedIdentifiers(DescriptorProto* message,
const LocationRecorder& parent_location) {
do {
LocationRecorder location(parent_location, message->reserved_name_size());
DO(ParseReservedIdentifier(message->add_reserved_name(), "Expected field name identifier."));
} while (TryConsume(","));
DO(ConsumeEndOfDeclaration(";", &parent_location));
return true;
Expand Down Expand Up @@ -1866,10 +1895,23 @@ bool Parser::ParseReserved(EnumDescriptorProto* proto,
// Parse the declaration.
DO(Consume("reserved"));
if (LookingAtType(io::Tokenizer::TYPE_STRING)) {
if (syntax_identifier_ == "editions") {
RecordError("Reserved names must be identifiers in editions, not string literals.");
return false;
}
LocationRecorder location(enum_location,
EnumDescriptorProto::kReservedNameFieldNumber);
location.StartAt(start_token);
return ParseReservedNames(proto, location);
} else if (LookingAtType(io::Tokenizer::TYPE_IDENTIFIER)) {
if (syntax_identifier_ != "editions") {
RecordError("Reserved names must be string literals. (Only editions supports identifiers.)");
return false;
}
LocationRecorder location(enum_location,
EnumDescriptorProto::kReservedNameFieldNumber);
location.StartAt(start_token);
return ParseReservedIdentifiers(proto, location);
} else {
LocationRecorder location(enum_location,
EnumDescriptorProto::kReservedRangeFieldNumber);
Expand All @@ -1882,7 +1924,17 @@ bool Parser::ParseReservedNames(EnumDescriptorProto* proto,
const LocationRecorder& parent_location) {
do {
LocationRecorder location(parent_location, proto->reserved_name_size());
DO(ParseReservedName(proto->add_reserved_name(), "Expected enum value."));
DO(ParseReservedName(proto->add_reserved_name(), "Expected enum value string literal."));
} while (TryConsume(","));
DO(ConsumeEndOfDeclaration(";", &parent_location));
return true;
}

bool Parser::ParseReservedIdentifiers(EnumDescriptorProto* proto,
const LocationRecorder& parent_location) {
do {
LocationRecorder location(parent_location, proto->reserved_name_size());
DO(ParseReservedIdentifier(proto->add_reserved_name(), "Expected enum value identifier."));
} while (TryConsume(","));
DO(ConsumeEndOfDeclaration(";", &parent_location));
return true;
Expand Down
5 changes: 5 additions & 0 deletions src/google/protobuf/compiler/parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -403,12 +403,17 @@ class PROTOBUF_EXPORT Parser {
bool ParseReservedNames(DescriptorProto* message,
const LocationRecorder& parent_location);
bool ParseReservedName(std::string* name, absl::string_view error_message);
bool ParseReservedIdentifiers(DescriptorProto* message,
const LocationRecorder& parent_location);
bool ParseReservedIdentifier(std::string* name, absl::string_view error_message);
bool ParseReservedNumbers(DescriptorProto* message,
const LocationRecorder& parent_location);
bool ParseReserved(EnumDescriptorProto* message,
const LocationRecorder& message_location);
bool ParseReservedNames(EnumDescriptorProto* message,
const LocationRecorder& parent_location);
bool ParseReservedIdentifiers(EnumDescriptorProto* message,
const LocationRecorder& parent_location);
bool ParseReservedNumbers(EnumDescriptorProto* message,
const LocationRecorder& parent_location);

Expand Down
95 changes: 91 additions & 4 deletions src/google/protobuf/compiler/parser_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -869,6 +869,22 @@ TEST_F(ParseMessageTest, ReservedNames) {
"}");
}

TEST_F(ParseMessageTest, ReservedIdentifiers) {
ExpectParsesTo(
"edition = \"2023\";\n"
"message TestMessage {\n"
" reserved foo, bar;\n"
mkruskal-google marked this conversation as resolved.
Show resolved Hide resolved
"}\n",

"syntax: \"editions\" "
"edition: \"2023\" "
"message_type {"
" name: \"TestMessage\""
" reserved_name: \"foo\""
" reserved_name: \"bar\""
"}");
}

TEST_F(ParseMessageTest, ExtensionRange) {
ExpectParsesTo(
"message TestMessage {\n"
Expand Down Expand Up @@ -1227,6 +1243,24 @@ TEST_F(ParseEnumTest, ReservedNames) {
"}");
}

TEST_F(ParseEnumTest, ReservedIdentifiers) {
ExpectParsesTo(
"edition = \"2023\";\n"
"enum TestEnum {\n"
" FOO = 0;\n"
" reserved foo, bar;\n"
"}\n",

"syntax: \"editions\" "
"edition: \"2023\" "
"enum_type {"
" name: \"TestEnum\""
" value { name:\"FOO\" number:0 }"
" reserved_name: \"foo\""
" reserved_name: \"bar\""
"}");
}

// ===================================================================

typedef ParserTest ParseServiceTest;
Expand Down Expand Up @@ -1500,6 +1534,40 @@ TEST_F(ParseErrorTest, DuplicateJsonName) {
"1:41: Already set option \"json_name\".\n");
}

TEST_F(ParseErrorTest, MsgReservedIdentifierOnlyInEditions) {
ExpectHasErrors(
"message TestMessage {\n"
" reserved foo, bar;\n"
"}\n",
"1:11: Reserved names must be string literals. (Only editions supports identifiers.)\n");
}
TEST_F(ParseErrorTest, MsgReservedNameStringNotInEditions) {
ExpectHasErrors(
"edition = \"2023\";\n"
"message TestMessage {\n"
" reserved \"foo\", \"bar\";\n"
"}\n",
"2:11: Reserved names must be identifiers in editions, not string literals.\n");
}

TEST_F(ParseErrorTest, EnumReservedIdentifierOnlyInEditions) {
ExpectHasErrors(
"enum TestEnum {\n"
" FOO = 0;\n"
" reserved foo, bar;\n"
"}\n",
"2:11: Reserved names must be string literals. (Only editions supports identifiers.)\n");
}
TEST_F(ParseErrorTest, EnumReservedNameStringNotInEditions) {
ExpectHasErrors(
"edition = \"2023\";\n"
"enum TestEnum {\n"
" FOO = 0;\n"
" reserved \"foo\", \"bar\";\n"
"}\n",
"3:11: Reserved names must be identifiers in editions, not string literals.\n");
}

TEST_F(ParseErrorTest, EnumValueOutOfRange) {
ExpectHasErrors(
"enum TestEnum {\n"
Expand Down Expand Up @@ -1699,13 +1767,14 @@ TEST_F(ParseErrorTest, EnumValueMissingNumber) {
"1:5: Missing numeric value for enum constant.\n");
}

// NB: with editions, this would be accepted and would reserve a value name of "max"
TEST_F(ParseErrorTest, EnumReservedStandaloneMaxNotAllowed) {
ExpectHasErrors(
"enum TestEnum {\n"
" FOO = 1;\n"
" reserved max;\n"
"}\n",
"2:11: Expected enum value or number range.\n");
"2:11: Reserved names must be string literals. (Only editions supports identifiers.)\n");
}

TEST_F(ParseErrorTest, EnumReservedMixNameAndNumber) {
Expand All @@ -1716,6 +1785,15 @@ TEST_F(ParseErrorTest, EnumReservedMixNameAndNumber) {
"}\n",
"2:15: Expected enum number range.\n");
}
TEST_F(ParseErrorTest, EnumReservedMixNameAndNumberEditions) {
ExpectHasErrors(
"edition = \"2023\";\n"
"enum TestEnum {\n"
" FOO = 1;\n"
" reserved 10, foo;\n"
"}\n",
"3:15: Expected enum number range.\n");
}

TEST_F(ParseErrorTest, EnumReservedPositiveNumberOutOfRange) {
ExpectHasErrors(
Expand All @@ -1741,7 +1819,7 @@ TEST_F(ParseErrorTest, EnumReservedMissingQuotes) {
" FOO = 1;\n"
" reserved foo;\n"
"}\n",
"2:11: Expected enum value or number range.\n");
"2:11: Reserved names must be string literals. (Only editions supports identifiers.)\n");
}

TEST_F(ParseErrorTest, EnumReservedInvalidIdentifier) {
Expand All @@ -1758,12 +1836,13 @@ TEST_F(ParseErrorTest, EnumReservedInvalidIdentifier) {
// -------------------------------------------------------------------
// Reserved field number errors

// NB: with editions, this would be accepted and would reserve a field name of "max"
TEST_F(ParseErrorTest, ReservedStandaloneMaxNotAllowed) {
ExpectHasErrors(
"message Foo {\n"
" reserved max;\n"
"}\n",
"1:11: Expected field name or number range.\n");
"1:11: Reserved names must be string literals. (Only editions supports identifiers.)\n");
}

TEST_F(ParseErrorTest, ReservedMixNameAndNumber) {
Expand All @@ -1773,13 +1852,21 @@ TEST_F(ParseErrorTest, ReservedMixNameAndNumber) {
"}\n",
"1:15: Expected field number range.\n");
}
TEST_F(ParseErrorTest, ReservedMixNameAndNumberEditions) {
ExpectHasErrors(
"edition = \"2023\";\n"
"message Foo {\n"
" reserved 10, foo;\n"
"}\n",
"2:15: Expected field number range.\n");
}

TEST_F(ParseErrorTest, ReservedMissingQuotes) {
ExpectHasErrors(
"message Foo {\n"
" reserved foo;\n"
"}\n",
"1:11: Expected field name or number range.\n");
"1:11: Reserved names must be string literals. (Only editions supports identifiers.)\n");
}

TEST_F(ParseErrorTest, ReservedInvalidIdentifier) {
Expand Down