Skip to content

Commit

Permalink
In the Kotlin DSL, reference builder methods with property syntax.
Browse files Browse the repository at this point in the history
Instead of `_builder.getFoo()` and `_builder.setFoo(value)`, the generated code now uses `_builder.foo` and `_builder.foo = value`. When compiling against the Java Proto API, this makes no difference, since the Kotlin compiler treats Java methods `getFoo`/`setFoo` as defining a Kotlin property `foo`. But if a compatible proto API is implemented in Kotlin then there is no such equivalence. Such an implementation would have to define either both forms or just the one that the DSL uses. For a Kotlin API it is more natural to use properties.

Similarly, the generated Java methods `getFoosList()`, `getFoosCount()`, `getFoosMap()`, and `getEnumValue()` are accessed via property names (`foosList`, `foosCount`, `foosMap`, `enumValue`).

PiperOrigin-RevId: 629220383
  • Loading branch information
eamonnmcmanus authored and copybara-github committed Apr 29, 2024
1 parent f2e1ad3 commit e9c9970
Show file tree
Hide file tree
Showing 16 changed files with 174 additions and 57 deletions.
28 changes: 19 additions & 9 deletions java/kotlin/src/test/kotlin/com/google/protobuf/Proto2Test.kt
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ class Proto2Test {
TestAllTypesKt.repeatedGroup { a = 1 },
TestAllTypesKt.repeatedGroup { a = 2 },
TestAllTypesKt.repeatedGroup { a = 3 },
TestAllTypesKt.repeatedGroup { a = 4 }
TestAllTypesKt.repeatedGroup { a = 4 },
)
)
repeatedGroup[0] = TestAllTypesKt.repeatedGroup { a = 5 }
Expand All @@ -235,7 +235,7 @@ class Proto2Test {
TestAllTypesKt.repeatedGroup { a = 5 },
TestAllTypesKt.repeatedGroup { a = 2 },
TestAllTypesKt.repeatedGroup { a = 3 },
TestAllTypesKt.repeatedGroup { a = 4 }
TestAllTypesKt.repeatedGroup { a = 4 },
)
)

Expand All @@ -249,7 +249,7 @@ class Proto2Test {
nestedMessage { bb = 1 },
nestedMessage { bb = 2 },
nestedMessage { bb = 3 },
nestedMessage { bb = 4 }
nestedMessage { bb = 4 },
)
)
repeatedNestedMessage[0] = nestedMessage { bb = 5 }
Expand All @@ -259,7 +259,7 @@ class Proto2Test {
nestedMessage { bb = 5 },
nestedMessage { bb = 2 },
nestedMessage { bb = 3 },
nestedMessage { bb = 4 }
nestedMessage { bb = 4 },
)
)

Expand Down Expand Up @@ -548,7 +548,7 @@ class Proto2Test {
repeatedGroupExtension { a = 1 },
repeatedGroupExtension { a = 2 },
repeatedGroupExtension { a = 3 },
repeatedGroupExtension { a = 4 }
repeatedGroupExtension { a = 4 },
)
)
this[UnittestProto.repeatedGroupExtension][0] = repeatedGroupExtension { a = 5 }
Expand All @@ -558,7 +558,7 @@ class Proto2Test {
repeatedGroupExtension { a = 5 },
repeatedGroupExtension { a = 2 },
repeatedGroupExtension { a = 3 },
repeatedGroupExtension { a = 4 }
repeatedGroupExtension { a = 4 },
)
)

Expand All @@ -575,7 +575,7 @@ class Proto2Test {
nestedMessage { bb = 1 },
nestedMessage { bb = 2 },
nestedMessage { bb = 3 },
nestedMessage { bb = 4 }
nestedMessage { bb = 4 },
)
)
this[UnittestProto.repeatedNestedMessageExtension][0] = nestedMessage { bb = 5 }
Expand All @@ -585,7 +585,7 @@ class Proto2Test {
nestedMessage { bb = 5 },
nestedMessage { bb = 2 },
nestedMessage { bb = 3 },
nestedMessage { bb = 4 }
nestedMessage { bb = 4 },
)
)

Expand Down Expand Up @@ -757,7 +757,7 @@ class Proto2Test {
1 to Proto2MapEnum.PROTO2_MAP_ENUM_FOO,
2 to Proto2MapEnum.PROTO2_MAP_ENUM_BAR,
3 to Proto2MapEnum.PROTO2_MAP_ENUM_BAZ,
4 to Proto2MapEnum.PROTO2_MAP_ENUM_FOO
4 to Proto2MapEnum.PROTO2_MAP_ENUM_FOO,
)
)
}
Expand Down Expand Up @@ -844,6 +844,11 @@ class Proto2Test {
cachedSize_ = "foo"
serializedSize_ = true
by = "foo"
dEPRECATEDFoo = "foo"
DEPRECATEDBar = "foo"
iD = "foo"
aBNotification = "foo"
notDEPRECATEDFoo = "foo"
}
)
.isEqualTo(
Expand All @@ -869,6 +874,11 @@ class Proto2Test {
.setCachedSize_("foo")
.setSerializedSize_(true)
.setBy("foo")
.setDEPRECATEDFoo("foo")
.setDEPRECATEDBar("foo")
.setID("foo")
.setABNotification("foo")
.setNotDEPRECATEDFoo("foo")
.build()
)

Expand Down
14 changes: 12 additions & 2 deletions java/kotlin/src/test/kotlin/com/google/protobuf/Proto3Test.kt
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ class Proto3Test {
nestedMessage { bb = 1 },
nestedMessage { bb = 2 },
nestedMessage { bb = 3 },
nestedMessage { bb = 4 }
nestedMessage { bb = 4 },
)
)
repeatedNestedMessage[0] = nestedMessage { bb = 5 }
Expand All @@ -91,7 +91,7 @@ class Proto3Test {
nestedMessage { bb = 5 },
nestedMessage { bb = 2 },
nestedMessage { bb = 3 },
nestedMessage { bb = 4 }
nestedMessage { bb = 4 },
)
)

Expand Down Expand Up @@ -200,6 +200,11 @@ class Proto3Test {
pairs["foo"] = 1
LeadingUnderscore = "foo"
option = 1
dEPRECATEDFoo = "foo"
iD = "foo"
aBNotification = "foo"
DEPRECATEDBar = "foo"
notDEPRECATEDFoo = "foo"
}
)
.isEqualTo(
Expand Down Expand Up @@ -237,6 +242,11 @@ class Proto3Test {
.putPairs("foo", 1)
.setLeadingUnderscore("foo")
.setOption(1)
.setDEPRECATEDFoo("foo")
.setID("foo")
.setABNotification("foo")
.setDEPRECATEDBar("foo")
.setNotDEPRECATEDFoo("foo")
.build()
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,15 @@ message EvilNamesProto2 {
optional string cached_size = 23;
optional bool serialized_size = 24;
optional string by = 25;

optional string DEPRECATED_foo = 26;
optional group DEPRECATED_NavigationImageRequested = 27 {
optional int32 DEPRECATED_FooBar = 28;
}
optional string __DEPRECATED_Bar = 29;
optional string ID = 30;
optional string a_b_notification = 31;
optional string not_DEPRECATED_foo = 32;
}

message List {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ message EvilNamesProto3 {
oneof _leading_underscore_oneof {
int32 option = 34;
}

optional string DEPRECATED_foo = 35;
optional string ID = 36;
optional string a_b_notification = 37;
optional string __DEPRECATED_Bar = 38;
optional string not_DEPRECATED_foo = 39;
}

message HardKeywordsAllTypesProto3 {
Expand Down
1 change: 1 addition & 0 deletions src/google/protobuf/compiler/java/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ cc_library(
"@com_google_absl//absl/container:flat_hash_map",
"@com_google_absl//absl/log:absl_check",
"@com_google_absl//absl/log:absl_log",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/strings:string_view",
],
)
Expand Down
56 changes: 56 additions & 0 deletions src/google/protobuf/compiler/java/field_common.cc
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
#include "google/protobuf/compiler/java/field_common.h"

#include <cstddef>
#include <string>

#include "absl/strings/str_cat.h"
#include "google/protobuf/compiler/java/helpers.h"
#include "google/protobuf/compiler/java/names.h"
#include "google/protobuf/descriptor.h"

namespace google {
namespace protobuf {
namespace compiler {
namespace java {

std::string GetKotlinPropertyName(const FieldDescriptor* descriptor);

void SetCommonFieldVariables(
const FieldDescriptor* descriptor, const FieldGeneratorInfo* info,
absl::flat_hash_map<absl::string_view, std::string>* variables) {
Expand All @@ -30,6 +35,11 @@ void SetCommonFieldVariables(
(*variables)["kt_name"] = IsForbiddenKotlin(info->name)
? absl::StrCat(info->name, "_")
: info->name;
auto kt_property_name = GetKotlinPropertyName(descriptor);
(*variables)["kt_property_name"] = kt_property_name;
(*variables)["kt_safe_name"] = IsForbiddenKotlin(kt_property_name)
? absl::StrCat("`", kt_property_name, "`")
: kt_property_name;
(*variables)["kt_capitalized_name"] =
IsForbiddenKotlin(info->name) ? absl::StrCat(info->capitalized_name, "_")
: info->capitalized_name;
Expand All @@ -51,6 +61,52 @@ void SetCommonFieldVariables(
}
}

// Locale-independent ASCII upper and lower case munging.
static bool IsUpper(char c) {
return static_cast<unsigned int>(c - 'A') <= 'Z' - 'A';
}

static char ToLower(char c) { return IsUpper(c) ? c - 'A' + 'a' : c; }

// Returns the name by which the generated Java getters and setters should be
// referenced from Kotlin as properties. In the simplest case, the original name
// is something like `foo_bar`, which gets translated into `getFooBar()` etc,
// and that in turn can be referenced from Kotlin as `fooBar`.
//
// The algorithm for translating proto names into Java getters and setters is
// straightforward. The first letter of each underscore-separated word gets
// uppercased and the underscores are deleted. There are no other changes, so in
// particular if the proto name has a string of capitals then those remain
// as-is.
//
// The algorithm that the Kotlin compiler uses to derive the property name is
// slightly more complicated. If the first character after `get` (etc) is a
// capital and the second isn't, then the property name is just that string with
// its first letter lowercased. So `getFoo` becomes `foo` and `getX` becomes
// `x`. But if there is more than one capital, then all but the last get
// lowercased. So `getHTMLPage` becomes `htmlPage`. If there are only capitals
// then they all get lowercased, so `getID` becomes `id`.
// TODO: move this to a Kotlin-specific location
std::string GetKotlinPropertyName(const FieldDescriptor* descriptor) {
// Find the first non-capital. If it is the second character, then we just
// need to lowercase the first one. Otherwise we need to lowercase everything
// up to but not including the last capital, except that if everything is
// capitals then everything must be lowercased.
std::string capitalized_name = CapitalizedFieldName(descriptor);
std::string kt_property_name = capitalized_name;
size_t first_non_capital;
for (first_non_capital = 0; first_non_capital < capitalized_name.length() &&
IsUpper(capitalized_name[first_non_capital]);
first_non_capital++) {
}
size_t stop = first_non_capital;
if (stop > 1 && stop < capitalized_name.length()) stop--;
for (size_t i = 0; i < stop; i++) {
kt_property_name[i] = ToLower(kt_property_name[i]);
}
return kt_property_name;
}

void SetCommonOneofVariables(
const FieldDescriptor* descriptor, const OneofGeneratorInfo* info,
absl::flat_hash_map<absl::string_view, std::string>* variables) {
Expand Down
10 changes: 5 additions & 5 deletions src/google/protobuf/compiler/java/immutable/enum_field.cc
Original file line number Diff line number Diff line change
Expand Up @@ -273,21 +273,21 @@ void ImmutableEnumFieldGenerator::GenerateKotlinDslMembers(
printer->Print(variables_,
"$kt_deprecation$public var $kt_name$: $kt_type$\n"
" @JvmName(\"${$get$kt_capitalized_name$$}$\")\n"
" get() = $kt_dsl_builder$.${$get$capitalized_name$$}$()\n"
" get() = $kt_dsl_builder$.${$$kt_safe_name$$}$\n"
" @JvmName(\"${$set$kt_capitalized_name$$}$\")\n"
" set(value) {\n"
" $kt_dsl_builder$.${$set$capitalized_name$$}$(value)\n"
" $kt_dsl_builder$.${$$kt_safe_name$$}$ = value\n"
" }\n");

if (SupportUnknownEnumValue(descriptor_)) {
printer->Print(
variables_,
"$kt_deprecation$public var $kt_name$Value: kotlin.Int\n"
" @JvmName(\"${$get$kt_capitalized_name$Value$}$\")\n"
" get() = $kt_dsl_builder$.${$get$capitalized_name$Value$}$()\n"
" get() = $kt_dsl_builder$.${$$kt_property_name$Value$}$\n"
" @JvmName(\"${$set$kt_capitalized_name$Value$}$\")\n"
" set(value) {\n"
" $kt_dsl_builder$.${$set$capitalized_name$Value$}$(value)\n"
" $kt_dsl_builder$.${$$kt_property_name$Value$}$ = value\n"
" }\n");
}

Expand Down Expand Up @@ -1100,7 +1100,7 @@ void RepeatedImmutableEnumFieldGenerator::GenerateKotlinDslMembers(
"<$kt_type$, ${$$kt_capitalized_name$Proxy$}$>\n"
" @kotlin.jvm.JvmSynthetic\n"
" get() = com.google.protobuf.kotlin.DslList(\n"
" $kt_dsl_builder$.${$get$capitalized_name$List$}$()\n"
" $kt_dsl_builder$.${$$kt_property_name$List$}$\n"
" )\n");

WriteFieldAccessorDocComment(printer, descriptor_, LIST_ADDER,
Expand Down
2 changes: 1 addition & 1 deletion src/google/protobuf/compiler/java/immutable/map_field.cc
Original file line number Diff line number Diff line change
Expand Up @@ -981,7 +981,7 @@ void ImmutableMapFieldGenerator::GenerateKotlinDslMembers(
" @kotlin.jvm.JvmSynthetic\n"
" @JvmName(\"get$kt_capitalized_name$Map\")\n"
" get() = com.google.protobuf.kotlin.DslMap(\n"
" $kt_dsl_builder$.${$get$capitalized_name$Map$}$()\n"
" $kt_dsl_builder$.${$$kt_property_name$Map$}$\n"
" )\n");

WriteFieldDocComment(printer, descriptor_, context_->options(),
Expand Down
6 changes: 3 additions & 3 deletions src/google/protobuf/compiler/java/immutable/message_field.cc
Original file line number Diff line number Diff line change
Expand Up @@ -378,10 +378,10 @@ void ImmutableMessageFieldGenerator::GenerateKotlinDslMembers(
printer->Print(variables_,
"$kt_deprecation$public var $kt_name$: $kt_type$\n"
" @JvmName(\"${$get$kt_capitalized_name$$}$\")\n"
" get() = $kt_dsl_builder$.${$get$capitalized_name$$}$()\n"
" get() = $kt_dsl_builder$.${$$kt_safe_name$$}$\n"
" @JvmName(\"${$set$kt_capitalized_name$$}$\")\n"
" set(value) {\n"
" $kt_dsl_builder$.${$set$capitalized_name$$}$(value)\n"
" $kt_dsl_builder$.${$$kt_safe_name$$}$ = value\n"
" }\n");

WriteFieldAccessorDocComment(printer, descriptor_, CLEARER,
Expand Down Expand Up @@ -1376,7 +1376,7 @@ void RepeatedImmutableMessageFieldGenerator::GenerateKotlinDslMembers(
"<$kt_type$, ${$$kt_capitalized_name$Proxy$}$>\n"
" @kotlin.jvm.JvmSynthetic\n"
" get() = com.google.protobuf.kotlin.DslList(\n"
" $kt_dsl_builder$.${$get$capitalized_name$List$}$()\n"
" $kt_dsl_builder$.${$$kt_property_name$List$}$\n"
" )\n");

WriteFieldAccessorDocComment(printer, descriptor_, LIST_ADDER,
Expand Down
31 changes: 22 additions & 9 deletions src/google/protobuf/compiler/java/immutable/primitive_field.cc
Original file line number Diff line number Diff line change
Expand Up @@ -291,14 +291,27 @@ void ImmutablePrimitiveFieldGenerator::GenerateKotlinDslMembers(
io::Printer* printer) const {
WriteFieldDocComment(printer, descriptor_, context_->options(),
/* kdoc */ true);
printer->Print(variables_,
"$kt_deprecation$public var $kt_name$: $kt_type$\n"
" @JvmName(\"${$get$kt_capitalized_name$$}$\")\n"
" get() = $kt_dsl_builder$.${$get$capitalized_name$$}$()\n"
" @JvmName(\"${$set$kt_capitalized_name$$}$\")\n"
" set(value) {\n"
" $kt_dsl_builder$.${$set$capitalized_name$$}$(value)\n"
" }\n");
if (descriptor_->name() == "is_initialized") {
printer->Print(
variables_,
"// TODO: remove this hack; we should access properties\n"
"$kt_deprecation$public var $kt_name$: $kt_type$\n"
" @JvmName(\"${$get$kt_capitalized_name$$}$\")\n"
" get() = $kt_dsl_builder$.${$get$kt_capitalized_name$$}$()\n"
" @JvmName(\"${$set$kt_capitalized_name$$}$\")\n"
" set(value) {\n"
" $kt_dsl_builder$.${$set$kt_capitalized_name$$}$(value)\n"
" }\n");
} else {
printer->Print(variables_,
"$kt_deprecation$public var $kt_name$: $kt_type$\n"
" @JvmName(\"${$get$kt_capitalized_name$$}$\")\n"
" get() = $kt_dsl_builder$.${$$kt_safe_name$$}$\n"
" @JvmName(\"${$set$kt_capitalized_name$$}$\")\n"
" set(value) {\n"
" $kt_dsl_builder$.${$$kt_safe_name$$}$ = value\n"
" }\n");
}

WriteFieldAccessorDocComment(printer, descriptor_, CLEARER,
context_->options(),
Expand Down Expand Up @@ -848,7 +861,7 @@ void RepeatedImmutablePrimitiveFieldGenerator::GenerateKotlinDslMembers(
"<$kt_type$, ${$$kt_capitalized_name$Proxy$}$>\n"
" @kotlin.jvm.JvmSynthetic\n"
" get() = com.google.protobuf.kotlin.DslList(\n"
" $kt_dsl_builder$.${$get$capitalized_name$List$}$()\n"
" $kt_dsl_builder$.${$$kt_property_name$List$}$\n"
" )\n");

WriteFieldAccessorDocComment(printer, descriptor_, LIST_ADDER,
Expand Down
Loading

0 comments on commit e9c9970

Please sign in to comment.