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

Migrate NameAllocator from JVM to common #2024

Merged
merged 4 commits into from
Nov 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright (C) 2024 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.squareup.kotlinpoet

import kotlin.jvm.JvmInline

@JvmInline
internal value class CodePoint(val code: Int)

internal expect fun String.codePointAt(index: Int): CodePoint

internal expect fun CodePoint.isLowerCase(): Boolean
internal expect fun CodePoint.isUpperCase(): Boolean

internal expect fun CodePoint.isJavaIdentifierStart(): Boolean
internal expect fun CodePoint.isJavaIdentifierPart(): Boolean

internal expect fun CodePoint.charCount(): Int

internal expect fun StringBuilder.appendCodePoint(codePoint: CodePoint): StringBuilder
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
*/
package com.squareup.kotlinpoet

import java.util.UUID
import kotlin.jvm.JvmOverloads
import kotlin.random.Random
import kotlin.random.nextULong

/**
* Assigns Kotlin identifier names to avoid collisions, keywords, and invalid characters. To use,
Expand Down Expand Up @@ -118,7 +120,8 @@ public class NameAllocator private constructor(
*/
@JvmOverloads public fun newName(
suggestion: String,
tag: Any = UUID.randomUUID().toString(),
// TODO It's possible to use `kotlin.uuid.Uuid` when it's stable
tag: Any = Random.nextULong().toString(16).padStart(16, '0'),
): String {
var result = toJavaIdentifier(suggestion)
while (!allocatedNames.add(result)) {
Expand Down Expand Up @@ -154,18 +157,18 @@ private fun toJavaIdentifier(suggestion: String) = buildString {
while (i < suggestion.length) {
val codePoint = suggestion.codePointAt(i)
if (i == 0 &&
!Character.isJavaIdentifierStart(codePoint) &&
Character.isJavaIdentifierPart(codePoint)
!codePoint.isJavaIdentifierStart() &&
codePoint.isJavaIdentifierPart()
) {
append("_")
}

val validCodePoint: Int = if (Character.isJavaIdentifierPart(codePoint)) {
val validCodePoint: CodePoint = if (codePoint.isJavaIdentifierPart()) {
codePoint
} else {
'_'.code
CodePoint('_'.code)
}
appendCodePoint(validCodePoint)
i += Character.charCount(codePoint)
i += codePoint.charCount()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright (C) 2024 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.squareup.kotlinpoet

internal actual fun String.codePointAt(index: Int): CodePoint {
val code = jsCodePointAt(this, index)
return CodePoint(code)
}

internal actual fun CodePoint.isLowerCase(): Boolean {
// TODO Will there be a problem? Is there a better way?
val str = jsFromCodePoint(this.code)

if (str.length != 1) {
return false
}

return str.first().isLowerCase()
}

internal actual fun CodePoint.isUpperCase(): Boolean {
// TODO Will there be a problem? Is there a better way?
val str = jsFromCodePoint(this.code)

if (str.length != 1) {
return false
}

return str.first().isUpperCase()
}

@Suppress("unused")
private fun jsCodePointAt(str: String, index: Int): Int =
js("str.codePointAt(index)").unsafeCast<Int>()

@Suppress("unused")
private fun jsFromCodePoint(code: Int): String =
js("String.fromCodePoint(code)").toString()
Original file line number Diff line number Diff line change
Expand Up @@ -220,15 +220,15 @@ public class ClassName internal constructor(

// Add the package name, like "java.util.concurrent", or "" for no package.
var p = 0
while (p < classNameString.length && Character.isLowerCase(classNameString.codePointAt(p))) {
while (p < classNameString.length && classNameString.codePointAt(p).isLowerCase()) {
p = classNameString.indexOf('.', p) + 1
require(p != 0) { "couldn't make a guess for $classNameString" }
}
names += if (p != 0) classNameString.substring(0, p - 1) else ""

// Add the class names, like "Map" and "Entry".
for (part in classNameString.substring(p).split('.')) {
require(part.isNotEmpty() && Character.isUpperCase(part.codePointAt(0))) {
require(part.isNotEmpty() && part.codePointAt(0).isUpperCase()) {
"couldn't make a guess for $classNameString"
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright (C) 2024 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.squareup.kotlinpoet

import kotlin.text.codePointAt as codePointAtKt

internal actual fun String.codePointAt(index: Int): CodePoint =
CodePoint(codePointAtKt(index))

internal actual fun CodePoint.isLowerCase(): Boolean =
Character.isLowerCase(code)

internal actual fun CodePoint.isUpperCase(): Boolean =
Character.isUpperCase(code)

internal actual fun CodePoint.isJavaIdentifierStart(): Boolean =
Character.isJavaIdentifierStart(code)

internal actual fun CodePoint.isJavaIdentifierPart(): Boolean =
Character.isJavaIdentifierPart(code)
Comment on lines +29 to +33
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 sure we even need these anymore. It's retained from JavaPoet. We should probably write our own rules for the Kotlin language, as it is probably similar but not exactly the same.

Copy link
Collaborator

Choose a reason for hiding this comment

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

We can do this in a follow-up.


internal actual fun CodePoint.charCount(): Int {
return Character.charCount(code)
}

internal actual fun StringBuilder.appendCodePoint(codePoint: CodePoint): StringBuilder {
return appendCodePoint(codePoint.code)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright (C) 2024 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.squareup.kotlinpoet

/*
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
internal actual fun StringBuilder.appendCodePoint(codePoint: CodePoint): StringBuilder {
// Copied from StringBuilder.kt,
// TODO Is this correct?
val code = codePoint.code
if (code <= Char.MAX_VALUE.code) {
append(code.toChar())
} else {
append(Char.MIN_HIGH_SURROGATE + ((code - 0x10000) ushr 10))
append(Char.MIN_LOW_SURROGATE + (code and 0x3ff))
}
return this
}

internal actual fun CodePoint.isJavaIdentifierStart(): Boolean {
// TODO How check Java identifier start use code point?
if (charCount() == 1) {
return Char(code).isJavaIdentifierStart()
}

return true
}

internal actual fun CodePoint.isJavaIdentifierPart(): Boolean {
// TODO How check Java identifier part use code point?
if (charCount() == 1) {
return Char(code).isJavaIdentifierPart()
}

return true
}

internal actual fun CodePoint.charCount(): Int {
return if (code >= 0x10000) 2 else 1
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Copyright (C) 2024 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.squareup.kotlinpoet

internal actual fun String.codePointAt(index: Int): CodePoint {
val str = this
val code = jsCodePointAt(str, index)
return CodePoint(code)
}

internal actual fun CodePoint.isLowerCase(): Boolean {
// TODO Will there be a problem? Is there a better way?
val code = this.code
val str = jsFromCodePoint(code)

if (str.length != 1) {
return false
}

return str.first().isLowerCase()
}

internal actual fun CodePoint.isUpperCase(): Boolean {
// TODO Will there be a problem? Is there a better way?
val code = this.code
val str = jsFromCodePoint(code)

if (str.length != 1) {
return false
}

return str.first().isUpperCase()
}

@Suppress("UNUSED_PARAMETER")
private fun jsCodePointAt(str: String, index: Int): Int =
js("str.codePointAt(index)")
Copy link
Collaborator

Choose a reason for hiding this comment

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

Interesting that values returned by js() in wasmJs don't require casting same as in js, does WASM have its own flavour of js()?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, Kotlin/JS's js() seems to be different from Wasm's js() .


@Suppress("UNUSED_PARAMETER")
private fun jsFromCodePoint(code: Int): String =
js("String.fromCodePoint(code)")