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

Support named LambdaTypeName parameters #270

Merged
merged 6 commits into from
Nov 8, 2017
Merged

Conversation

Egorand
Copy link
Collaborator

@Egorand Egorand commented Nov 3, 2017

Fixes #269

@Egorand Egorand requested a review from JakeWharton November 3, 2017 09:51

constructor(name: String, type: Type) : this(name, type.asTypeName())

fun toCodeBlock() = if (name != null) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

internal?

@JvmStatic fun get(
receiver: TypeName? = null,
parameters: List<TypeName> = emptyList(),
returnType: TypeName)
= LambdaTypeName(receiver, parameters, returnType)
namedParameters: List<Parameter> = emptyList(),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's weird to have two lists. I can't mix and match to get like x: Long, y, z: Long.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't wanna break existing API, and adding a new factory method with parameters: List<Parameter> wouldn't compile due to type erasure. If breaking is fine - I'd just get rid of parameters: List<TypeName>.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm we can use @JvmName to defeat the erasure 🙈

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yeah, let me try that. Wonder how that would work for JS, no erasure - no problem?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, I couldn't come up with a meaningful value for @JvmName, so I went the following way instead: the version with parameters and namedParameters stays, but is deprecated in favor of the version with parameters: List<Parameter>. We can remove the former when going 1.0. Does this make sense?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’m not too concerned with backwards-compatibility at this point.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree. So would a single factory method with parameters: List<ParameterSpec> be enough, given that we'll have versions with vararg parameters: ParameterSpec and vararg parameters: TypeName?

@Egorand Egorand force-pushed the egorand/named-lambda-params branch from 0b75f81 to d8dbc80 Compare November 3, 2017 19:06
Copy link
Collaborator

@swankjesse swankjesse left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’ve got some questions about Kotlin style!

@@ -55,18 +55,51 @@ class LambdaTypeName internal constructor(
}

companion object {
/** Returns a lambda type with `returnType` and parameters of listed in `parameters`. */
/*
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/** ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right

import java.lang.reflect.Type
import kotlin.reflect.KClass

data class Parameter(val name: String? = null, val type: TypeName) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This competes with an existing class, ParameterSpec. I’m anxious about having both types that mean almost the exact same thing. Would it be simpler to just use ParameterSpec in LambdaTypeName ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good point. We'll need only name and type, should we just ignore any other params set on the ParameterSpec?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, the problem is that name is mandatory for ParameterSpec, and optional for LambdaTypeName. Will switching from name: String to name: String? on ParameterSpec be justified for that purpose? Then we could have ParameterSpec.ofType() to create an unnamed parameter.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That sounds a bit weird.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about we permit an empty string as the name in those cases, and require a factory method to get instances like that:

  ParameterSpec string = ParameterSpec.unnamed(String::class)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll give it a try


constructor(name: String, type: Type) : this(name, type.asTypeName())

internal fun toCodeBlock() = if (name != null) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

off topic: would it be better to do this kind of expression without braces?

internal fun toCodeBlock() = if (name != null)
    CodeBlock.of("%L: %T", name, type) else
    CodeBlock.of("%T", type)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I think that'd be fine with the guidelines

.receiver(LambdaTypeName.get(
parameters = listOf(
Parameter("name", String::class),
Companion.ofType(Int::class),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

off topic: referencing Companion like this seems out of place. Idiomatic?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, there seems to be a problem with auto-complete and I didn't notice it, gonna fix.

@@ -17,7 +17,7 @@ package com.squareup.kotlinpoet

class LambdaTypeName internal constructor(
val receiver: TypeName? = null,
parameters: List<Parameter> = emptyList(),
parameters: List<ParameterSpec> = emptyList(),
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: should we have checks to verify that all ParameterSpecs here have only the name and the type and nothing else?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should check what's supported. I have a feeling annotations might work...

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really? Let me see

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unsupported [annotation on parameter in function type]

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Modifiers and default values are unsupported as well, hence just name and type

| name: String,
| Int,
| age: Long
|) -> Unit).whatever(): Unit = Unit
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Formatting here is weird as we currently wrap parameter lists with 2 or more elements. I'll open an issue to improve it.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah we need a smarter CodeWriter which potentially buffers elements so that they can be rewritten when a line break is required.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator

@JakeWharton JakeWharton left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good

@Egorand Egorand merged commit a1c22f5 into master Nov 8, 2017
@Egorand Egorand deleted the egorand/named-lambda-params branch November 8, 2017 05:34
Copy link
Collaborator

@swankjesse swankjesse left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. I like where this landed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants