-
Notifications
You must be signed in to change notification settings - Fork 578
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
Generate Kotlin-esque build function for using closures in addition to Builders #2674
Conversation
@@ -548,6 +549,7 @@ class KotlinGenerator private constructor( | |||
|
|||
addDefaultFields(type, companionBuilder, nameAllocator) | |||
addAdapter(type, companionBuilder) | |||
if (buildersOnly || javaInterOp) addBuildClosure(type, companionBuilder) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added support for javaInterop
since we get Builders there too - but I don't know exactly what the implications would be if Java code tried to use this (not a Java expert). Looking for feedback here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I probably wouldn't include it for buildersOnly
since this technically isn't a classic Java builder pattern, but would for javaInterop
if this build closure is going to be default generated for all Kotlin targets.
It probably shouldn't be generated if the target is Java given I don't think Java supports Kotlin-style closures like this.
Is it possible to not have to call Beforeval person = com.protos.Person {
it.id = "123"
it.name = "Alice Bob"
} Afterval person = com.protos.Person {
id = "123"
name = "Alice Bob"
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please also add a test somewhere that uses this function and compares it to a regular built value to confirm it works.
Like
@Test fun works() {
val a = Foo.build {
name = "hey"
age = 21
}
val b = Foo.Builder()
.name("hey")
.age(21)
.build()
assertThat(a).isEqualTo(b)
}
addStatement("return builder.build()") | ||
} | ||
val buildFn = FunSpec.builder("build") | ||
.addParameter("buildFn", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generally we avoid abbreviations in names. I think "builder" or "body" would work here.
wire-kotlin-generator/src/main/java/com/squareup/wire/kotlin/KotlinGenerator.kt
Outdated
Show resolved
Hide resolved
wire-kotlin-generator/src/main/java/com/squareup/wire/kotlin/KotlinGenerator.kt
Outdated
Show resolved
Hide resolved
It is, but it introduces a variable shadowing issue:
Using The workaround is to rename the outer |
Actually, hold that thought. I had tested this previously but looks like maybe I had done something wrong. If we pass the builder as the receiver, looks like maybe we can use
Are folks more into that? Seems cleaner for the happy case with a fairly straightforward workaround when name conflicts arise (which shouldn't actually be all that often since Wire generates snake_case properties and folks typically use camelCase variable names) |
ae795fc
to
3f91236
Compare
Changed to use Builder as receiver in this commit, I think that's probably the right path here. Usage: // .proto
message MyMessage {
optional int32 property = 1;
} // Wire generated
companion object {
...
public fun build(body: Builder.() -> Unit): MyMessage {
val builder = Builder()
builder.body()
return builder.build()
}
} // .kt
val message = MyMessage.build {
property = 5
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. Just two small tweaks I didn't notice first time around.
wire-kotlin-generator/src/main/java/com/squareup/wire/kotlin/KotlinGenerator.kt
Show resolved
Hide resolved
wire-kotlin-generator/src/main/java/com/squareup/wire/kotlin/KotlinGenerator.kt
Outdated
Show resolved
Hide resolved
f6c9499
to
c406c41
Compare
c406c41
to
1ba2b30
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
@@ -1968,6 +1971,23 @@ class KotlinGenerator private constructor( | |||
} | |||
} | |||
|
|||
private fun addBuildClosure(type: MessageType, companionBuilder: TypeSpec.Builder, builderClassName: ClassName) { | |||
val buildFn = FunSpec.builder("build") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you rename to buildFunction
or whatever explicit name this could be
.generateKotlin("SomeMessage", buildersOnly = false, javaInterop = false) | ||
assertThat(code).doesNotContain("inline fun build(body: Builder.() -> Unit): SomeMessage") | ||
val buildersOnlyCode = KotlinWithProfilesGenerator(schema) | ||
.generateKotlin("SomeMessage", buildersOnly = true) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.generateKotlin("SomeMessage", buildersOnly = true) | |
.generateKotlin("SomeMessage", buildersOnly = true, javaInterop = false) |
.generateKotlin("SomeMessage", buildersOnly = true) | ||
assertThat(buildersOnlyCode).contains("inline fun build(body: Builder.() -> Unit): SomeMessage") | ||
val javaInteropCode = KotlinWithProfilesGenerator(schema) | ||
.generateKotlin("SomeMessage", javaInterop = true) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.generateKotlin("SomeMessage", javaInterop = true) | |
.generateKotlin("SomeMessage", buildersOnly = false, javaInterop = true) |
@@ -1968,6 +1971,23 @@ class KotlinGenerator private constructor( | |||
} | |||
} | |||
|
|||
private fun addBuildClosure(type: MessageType, companionBuilder: TypeSpec.Builder, builderClassName: ClassName) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
private fun addBuildClosure(type: MessageType, companionBuilder: TypeSpec.Builder, builderClassName: ClassName) { | |
/** | |
* Adds a closure into the [companionBuilder] allowing the creation of an instance via the Kotlin | |
* DSL. | |
* | |
* Example | |
* ``` | |
* companion object { | |
* public inline fun build(body: Builder.() -> Unit): AllTypes = Builder().apply(body).build() | |
* } | |
* ``` | |
*/ | |
private fun addBuildClosure(type: MessageType, companionBuilder: TypeSpec.Builder, builderClassName: ClassName) { |
I think it'd be good to hide it from Java callers using |
@oldergod Sorry missed your comments! Followup incoming. |
#2589
This adds a static
build
method on Messages to allow for a more Kotlin-esque style of building up proto objects, rather than forcing everyone into Builders now that constructors are generally deemed unsafe.Usage: