Skip to content

Commit

Permalink
[J2KT] Implement rendering of J2ObjC-compatible "J2ObjCCompat.h" head…
Browse files Browse the repository at this point in the history
…er file.

PiperOrigin-RevId: 502078848
  • Loading branch information
Googler authored and copybara-github committed Jan 14, 2023
1 parent 97cfb09 commit 0e13b9a
Show file tree
Hide file tree
Showing 6 changed files with 271 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,7 @@ kt_jvm_library(
"//transpiler/java/com/google/j2cl/transpiler/backend/common",
"//transpiler/java/com/google/j2cl/transpiler/backend/kotlin/ast",
"//transpiler/java/com/google/j2cl/transpiler/backend/kotlin/common",
"//transpiler/java/com/google/j2cl/transpiler/backend/kotlin/objc",
"//transpiler/java/com/google/j2cl/transpiler/backend/kotlin/source",
],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
/*
* Copyright 2023 Google 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
*
* http://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.google.j2cl.transpiler.backend.kotlin

import com.google.common.base.CaseFormat
import com.google.j2cl.transpiler.ast.CompilationUnit
import com.google.j2cl.transpiler.ast.DeclaredTypeDescriptor
import com.google.j2cl.transpiler.ast.FieldDescriptor
import com.google.j2cl.transpiler.ast.Method
import com.google.j2cl.transpiler.ast.MethodDescriptor
import com.google.j2cl.transpiler.ast.PrimitiveTypeDescriptor
import com.google.j2cl.transpiler.ast.PrimitiveTypes
import com.google.j2cl.transpiler.ast.Type
import com.google.j2cl.transpiler.ast.TypeDeclaration
import com.google.j2cl.transpiler.ast.TypeDescriptor
import com.google.j2cl.transpiler.ast.TypeDescriptors.isJavaLangObject
import com.google.j2cl.transpiler.ast.TypeDescriptors.isPrimitiveVoid
import com.google.j2cl.transpiler.ast.Variable
import com.google.j2cl.transpiler.backend.kotlin.common.buildList
import com.google.j2cl.transpiler.backend.kotlin.common.letIf
import com.google.j2cl.transpiler.backend.kotlin.objc.Rendering
import com.google.j2cl.transpiler.backend.kotlin.objc.bindSource
import com.google.j2cl.transpiler.backend.kotlin.objc.classNameRendering
import com.google.j2cl.transpiler.backend.kotlin.objc.classRendering
import com.google.j2cl.transpiler.backend.kotlin.objc.combineSources
import com.google.j2cl.transpiler.backend.kotlin.objc.comment
import com.google.j2cl.transpiler.backend.kotlin.objc.dependency
import com.google.j2cl.transpiler.backend.kotlin.objc.functionDeclaration
import com.google.j2cl.transpiler.backend.kotlin.objc.idRendering
import com.google.j2cl.transpiler.backend.kotlin.objc.localImport
import com.google.j2cl.transpiler.backend.kotlin.objc.nsCopyingRendering
import com.google.j2cl.transpiler.backend.kotlin.objc.nsEnumTypedef
import com.google.j2cl.transpiler.backend.kotlin.objc.nsInlineRendering
import com.google.j2cl.transpiler.backend.kotlin.objc.nsMutableArrayRendering
import com.google.j2cl.transpiler.backend.kotlin.objc.nsMutableDictionaryRendering
import com.google.j2cl.transpiler.backend.kotlin.objc.nsMutableSetRendering
import com.google.j2cl.transpiler.backend.kotlin.objc.nsNumberRendering
import com.google.j2cl.transpiler.backend.kotlin.objc.nsObjectRendering
import com.google.j2cl.transpiler.backend.kotlin.objc.nsStringRendering
import com.google.j2cl.transpiler.backend.kotlin.objc.nsUIntegerRendering
import com.google.j2cl.transpiler.backend.kotlin.objc.orEmpty
import com.google.j2cl.transpiler.backend.kotlin.objc.protocolNameRendering
import com.google.j2cl.transpiler.backend.kotlin.objc.rendering
import com.google.j2cl.transpiler.backend.kotlin.objc.renderingWith
import com.google.j2cl.transpiler.backend.kotlin.objc.returnStatement
import com.google.j2cl.transpiler.backend.kotlin.objc.sourceWithDependencies
import com.google.j2cl.transpiler.backend.kotlin.source.dotSeparated
import com.google.j2cl.transpiler.backend.kotlin.source.emptyLineSeparated
import com.google.j2cl.transpiler.backend.kotlin.source.ifNotEmpty
import com.google.j2cl.transpiler.backend.kotlin.source.inAngleBrackets
import com.google.j2cl.transpiler.backend.kotlin.source.join
import com.google.j2cl.transpiler.backend.kotlin.source.plusNewLine
import com.google.j2cl.transpiler.backend.kotlin.source.source
import com.google.j2cl.transpiler.backend.kotlin.source.spaceSeparated

internal val CompilationUnit.j2ObjCCompatHeaderSource
get() =
dependenciesAndDeclarationsSource.ifNotEmpty {
emptyLineSeparated(fileCommentSource, it).plusNewLine
}

private val CompilationUnit.fileCommentSource
get() = comment(source("Generated by J2KT from \"${packageRelativePath}\""))

private val CompilationUnit.dependenciesAndDeclarationsSource
get() = declarationsRendering.sourceWithDependencies

private val CompilationUnit.declarationsRendering
get() = declarationsRenderings.combineSources(::emptyLineSeparated)

private val CompilationUnit.declarationsRenderings
get() = types.flatMap { it.declarationsRenderings }

private val Type.declarationsRenderings
get() =
buildList<Rendering> {
if (visibility.isPrivate || declaration.isKtNative) {
return@buildList
}

if (isEnum) {
add(nsEnumTypedefRendering)
addAll(enumGetFunctionRenderings)
}

addAll(methods.map { it.functionRendering })
}

private val Type.nsEnumTypedefRendering
get() =
nsEnumTypedef(
name = declaration.objCEnumName,
type = nsUIntegerRendering,
values = enumFields.map { it.descriptor.objCEnumName }
)

private val Type.enumGetFunctionRenderings
get() = enumFields.map { it.descriptor.enumGetFunctionRendering }

private val FieldDescriptor.enumGetFunctionRendering
get() =
functionDeclaration(
modifiers = listOf(nsInlineRendering),
returnType = enclosingTypeDescriptor.objCRendering,
name = enumGetFunctionName,
parameters = listOf(),
statements = listOf(returnStatement(enumGetExpressionRendering))
)

private val FieldDescriptor.enumGetFunctionName
get() = enclosingTypeDescriptor.typeDeclaration.objCName(forMember = true) + "_get_" + name!!

private val FieldDescriptor.enumObjCName
get() = CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, name!!)

private val FieldDescriptor.enumGetExpressionRendering
get() =
enclosingTypeDescriptor.typeDeclaration.objCNameRendering.bindSource { enumClassSource ->
rendering(dotSeparated(enumClassSource, source(enumObjCName)))
}

private val Method.functionRendering
get() =
takeIf { it.isStatic && !it.isConstructor }
?.toObjCNames()
?.let { functionRendering(it) }
.orEmpty

private fun Method.functionRendering(objCNames: MethodObjCNames) =
functionDeclaration(
modifiers = listOf(nsInlineRendering),
returnType = descriptor.returnTypeDescriptor.objCRendering,
name = descriptor.functionName(objCNames),
parameters = parameters.map { it.rendering },
statements = statementRenderings
)

private fun MethodDescriptor.functionName(objCNames: MethodObjCNames) =
enclosingTypeDescriptor
.objCName(useId = true, forMember = true)
.plus("_")
.plus(objCNames.methodName ?: ktName)
.letIf(objCNames.parameterNames.isNotEmpty()) {
it.plus("_").plus(objCNames.parameterNames.map { it + "_" }.joinToString(""))
}

private val Method.statementRenderings
get() =
if (isPrimitiveVoid(descriptor.returnTypeDescriptor)) listOf()
else listOf(returnStatement(rendering(source("0"))))

private val Variable.rendering
get() =
typeDescriptor.objCRendering.bindSource { typeSource ->
rendering(spaceSeparated(typeSource, source(name.objCName)))
}

private val TypeDeclaration.objCNameRendering
get() = objectiveCNameRendering ?: mappedObjCNameRendering ?: defaultObjCNameRendering

private val TypeDeclaration.objectiveCNameRendering
get() =
objectiveCName?.let { if (isInterface) protocolNameRendering(it) else classNameRendering(it) }

private val TypeDeclaration.mappedObjCNameRendering
get() =
when (qualifiedBinaryName) {
"java.lang.Object" -> nsObjectRendering
"java.lang.String" -> nsStringRendering
"java.lang.Class" -> classRendering
"java.lang.Number" -> nsNumberRendering
"java.lang.Cloneable" -> nsCopyingRendering
"java.util.List" -> nsMutableArrayRendering
"java.util.Set" -> nsMutableSetRendering
"java.util.Map" -> nsMutableDictionaryRendering
else -> null
}

private val TypeDeclaration.defaultObjCNameRendering
get() =
defaultObjCName(forMember = false).let {
if (isInterface) protocolNameRendering(it) else classNameRendering(it)
}

private val TypeDescriptor.objCRendering
get() =
when (this) {
is PrimitiveTypeDescriptor -> primitiveObjCRendering
is DeclaredTypeDescriptor -> declaredObjCRendering
// TODO: Handle TypeVariable and Array
else -> idRendering
}

private val PrimitiveTypeDescriptor.primitiveObjCRendering
get() =
when (this) {
PrimitiveTypes.VOID -> rendering(source("void"))
else -> source("j$simpleSourceName") renderingWith dependency(j2ObjCTypesImport)
}

private val DeclaredTypeDescriptor.declaredObjCRendering
get() =
when {
isJavaLangObject(this) -> idRendering
isInterface ->
typeDeclaration.objCNameRendering.bindSource {
rendering(join(source("id"), inAngleBrackets(it)))
}
else ->
typeDeclaration.objCNameRendering.bindSource { rendering(spaceSeparated(it, source("*"))) }
}

private val j2ObjCTypesImport
get() = localImport("third_party/java_src/j2objc/jre_emul/Classes/J2ObjC_types.h")

internal val TypeDeclaration.objCEnumName
get() = "${objCName}_Enum"

internal val FieldDescriptor.objCEnumName
get() = "${enclosingTypeDescriptor.typeDeclaration.objCEnumName}_$name"
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,26 @@ class KotlinGeneratorStage(private val output: OutputUtils.Output, private val p
}

private fun generateOutputs(compilationUnit: CompilationUnit) {
val source = renderSource(compilationUnit)
generateKtOutputs(compilationUnit)
// TODO(b/263416948): Uncomment after cl/501829004 is submitted.
// generateObjCOutputs(compilationUnit)
}

private fun generateKtOutputs(compilationUnit: CompilationUnit) {
val source = ktSource(compilationUnit)
val path = compilationUnit.packageRelativePath.replace(".java", ".kt")
output.write(path, source)
}

private fun renderSource(compilationUnit: CompilationUnit): String {
private fun generateObjCOutputs(compilationUnit: CompilationUnit) {
val source = compilationUnit.j2ObjCCompatHeaderSource
if (!source.isEmpty) {
val path = compilationUnit.packageRelativePath.replace(".java", "+J2ObjCCompat.h")
output.write(path, source.toString())
}
}

private fun ktSource(compilationUnit: CompilationUnit): String {
val nameToIdentifierMap = compilationUnit.buildNameToIdentifierMap()

val environment =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ private val TypeDeclaration.mappedObjCName: String?
else -> null
}

private fun TypeDeclaration.defaultObjCName(forMember: Boolean): String =
internal fun TypeDeclaration.defaultObjCName(forMember: Boolean): String =
objCNamePrefix(forMember) + simpleObjCName

private fun TypeDeclaration.objCNamePrefix(forMember: Boolean) =
Expand Down Expand Up @@ -158,12 +158,12 @@ private fun String.objCPackagePrefix(forMember: Boolean): String =
private val String.titleCase
get() = StringUtils.capitalize(this)

private val String.objCName
internal val String.objCName
get() = replace('$', '_')

private const val idObjCName = "id"

private fun TypeDescriptor.objCName(useId: Boolean, forMember: Boolean): String =
internal fun TypeDescriptor.objCName(useId: Boolean, forMember: Boolean): String =
when (this) {
is PrimitiveTypeDescriptor -> primitiveObjCName
is ArrayTypeDescriptor -> arrayObjCName(forMember = forMember)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import com.google.j2cl.transpiler.backend.kotlin.source.Source
import com.google.j2cl.transpiler.backend.kotlin.source.commaSeparated
import com.google.j2cl.transpiler.backend.kotlin.source.emptyLineSeparated
import com.google.j2cl.transpiler.backend.kotlin.source.emptySource
import com.google.j2cl.transpiler.backend.kotlin.source.ifEmpty
import com.google.j2cl.transpiler.backend.kotlin.source.inCurlyBrackets
import com.google.j2cl.transpiler.backend.kotlin.source.inNewLines
import com.google.j2cl.transpiler.backend.kotlin.source.inRoundBrackets
Expand Down Expand Up @@ -62,32 +63,25 @@ fun Iterable<Rendering>.combineSources(fn: (Iterable<Source>) -> Source) = bindS
val Rendering?.orEmpty
get() = this ?: rendering(emptySource)

fun declarator(type: Rendering, name: String, isPointer: Boolean) =
type.bindSource { typeSource ->
source(name).let { nameSource ->
rendering(
if (isPointer) join(typeSource, nameSource) else spaceSeparated(typeSource, nameSource)
)
}
}

fun return_(expression: Rendering) =
expression.bindSource { expressionSource -> rendering(semicolonEnded(return_(expressionSource))) }

fun function(
fun functionDeclaration(
modifiers: List<Rendering>,
declarator: Rendering,
returnType: Rendering,
name: String,
parameters: List<Rendering>,
statements: List<Rendering>
) =
modifiers.bindSources { modifierSources ->
declarator.bindSource { declaratorSource ->
invocation(parameters).bindSource { invocationSource ->
returnType.bindSource { returnTypeSource ->
parameters.bindSources { parameterSources ->
block(statements).bindSource { blockSource ->
rendering(
spaceSeparated(
spaceSeparated(modifierSources),
join(declaratorSource, invocationSource),
returnTypeSource,
join(
source(name),
inRoundBrackets(commaSeparated(parameterSources).ifEmpty { source("void") })
),
blockSource
)
)
Expand All @@ -96,14 +90,8 @@ fun function(
}
}

private fun invocation(parameters: List<Rendering>) =
parameters.bindSources { sources ->
rendering(
inRoundBrackets(
sources.toList().let { if (it.isEmpty()) source("void") else commaSeparated(it) }
)
)
}

private fun block(statements: List<Rendering>) =
fun block(statements: List<Rendering>) =
statements.bindSources { sources -> rendering(inCurlyBrackets(inNewLines(sources))) }

fun returnStatement(expression: Rendering) =
expression.bindSource { source -> rendering(semicolonEnded(return_(source))) }
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ val emptySource
val Source?.orEmpty
get() = this ?: emptySource

fun Source.ifEmpty(fn: () -> Source) = if (isEmpty) fn() else this

fun Source.ifNotEmpty(fn: (Source) -> Source) = if (isEmpty) this else fn(this)

fun source(string: String) = Source(string.isEmpty()) { it.append(string) }
Expand Down

0 comments on commit 0e13b9a

Please sign in to comment.