Skip to content

Commit 0e13b9a

Browse files
Googlercopybara-github
authored andcommitted
[J2KT] Implement rendering of J2ObjC-compatible "J2ObjCCompat.h" header file.
PiperOrigin-RevId: 502078848
1 parent 97cfb09 commit 0e13b9a

6 files changed

Lines changed: 271 additions & 32 deletions

File tree

‎transpiler/java/com/google/j2cl/transpiler/backend/kotlin/BUILD‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,7 @@ kt_jvm_library(
1717
"//transpiler/java/com/google/j2cl/transpiler/backend/common",
1818
"//transpiler/java/com/google/j2cl/transpiler/backend/kotlin/ast",
1919
"//transpiler/java/com/google/j2cl/transpiler/backend/kotlin/common",
20+
"//transpiler/java/com/google/j2cl/transpiler/backend/kotlin/objc",
21+
"//transpiler/java/com/google/j2cl/transpiler/backend/kotlin/source",
2022
],
2123
)
Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
/*
2+
* Copyright 2023 Google Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
5+
* use this file except in compliance with the License. You may obtain a copy of
6+
* the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations under
14+
* the License.
15+
*/
16+
package com.google.j2cl.transpiler.backend.kotlin
17+
18+
import com.google.common.base.CaseFormat
19+
import com.google.j2cl.transpiler.ast.CompilationUnit
20+
import com.google.j2cl.transpiler.ast.DeclaredTypeDescriptor
21+
import com.google.j2cl.transpiler.ast.FieldDescriptor
22+
import com.google.j2cl.transpiler.ast.Method
23+
import com.google.j2cl.transpiler.ast.MethodDescriptor
24+
import com.google.j2cl.transpiler.ast.PrimitiveTypeDescriptor
25+
import com.google.j2cl.transpiler.ast.PrimitiveTypes
26+
import com.google.j2cl.transpiler.ast.Type
27+
import com.google.j2cl.transpiler.ast.TypeDeclaration
28+
import com.google.j2cl.transpiler.ast.TypeDescriptor
29+
import com.google.j2cl.transpiler.ast.TypeDescriptors.isJavaLangObject
30+
import com.google.j2cl.transpiler.ast.TypeDescriptors.isPrimitiveVoid
31+
import com.google.j2cl.transpiler.ast.Variable
32+
import com.google.j2cl.transpiler.backend.kotlin.common.buildList
33+
import com.google.j2cl.transpiler.backend.kotlin.common.letIf
34+
import com.google.j2cl.transpiler.backend.kotlin.objc.Rendering
35+
import com.google.j2cl.transpiler.backend.kotlin.objc.bindSource
36+
import com.google.j2cl.transpiler.backend.kotlin.objc.classNameRendering
37+
import com.google.j2cl.transpiler.backend.kotlin.objc.classRendering
38+
import com.google.j2cl.transpiler.backend.kotlin.objc.combineSources
39+
import com.google.j2cl.transpiler.backend.kotlin.objc.comment
40+
import com.google.j2cl.transpiler.backend.kotlin.objc.dependency
41+
import com.google.j2cl.transpiler.backend.kotlin.objc.functionDeclaration
42+
import com.google.j2cl.transpiler.backend.kotlin.objc.idRendering
43+
import com.google.j2cl.transpiler.backend.kotlin.objc.localImport
44+
import com.google.j2cl.transpiler.backend.kotlin.objc.nsCopyingRendering
45+
import com.google.j2cl.transpiler.backend.kotlin.objc.nsEnumTypedef
46+
import com.google.j2cl.transpiler.backend.kotlin.objc.nsInlineRendering
47+
import com.google.j2cl.transpiler.backend.kotlin.objc.nsMutableArrayRendering
48+
import com.google.j2cl.transpiler.backend.kotlin.objc.nsMutableDictionaryRendering
49+
import com.google.j2cl.transpiler.backend.kotlin.objc.nsMutableSetRendering
50+
import com.google.j2cl.transpiler.backend.kotlin.objc.nsNumberRendering
51+
import com.google.j2cl.transpiler.backend.kotlin.objc.nsObjectRendering
52+
import com.google.j2cl.transpiler.backend.kotlin.objc.nsStringRendering
53+
import com.google.j2cl.transpiler.backend.kotlin.objc.nsUIntegerRendering
54+
import com.google.j2cl.transpiler.backend.kotlin.objc.orEmpty
55+
import com.google.j2cl.transpiler.backend.kotlin.objc.protocolNameRendering
56+
import com.google.j2cl.transpiler.backend.kotlin.objc.rendering
57+
import com.google.j2cl.transpiler.backend.kotlin.objc.renderingWith
58+
import com.google.j2cl.transpiler.backend.kotlin.objc.returnStatement
59+
import com.google.j2cl.transpiler.backend.kotlin.objc.sourceWithDependencies
60+
import com.google.j2cl.transpiler.backend.kotlin.source.dotSeparated
61+
import com.google.j2cl.transpiler.backend.kotlin.source.emptyLineSeparated
62+
import com.google.j2cl.transpiler.backend.kotlin.source.ifNotEmpty
63+
import com.google.j2cl.transpiler.backend.kotlin.source.inAngleBrackets
64+
import com.google.j2cl.transpiler.backend.kotlin.source.join
65+
import com.google.j2cl.transpiler.backend.kotlin.source.plusNewLine
66+
import com.google.j2cl.transpiler.backend.kotlin.source.source
67+
import com.google.j2cl.transpiler.backend.kotlin.source.spaceSeparated
68+
69+
internal val CompilationUnit.j2ObjCCompatHeaderSource
70+
get() =
71+
dependenciesAndDeclarationsSource.ifNotEmpty {
72+
emptyLineSeparated(fileCommentSource, it).plusNewLine
73+
}
74+
75+
private val CompilationUnit.fileCommentSource
76+
get() = comment(source("Generated by J2KT from \"${packageRelativePath}\""))
77+
78+
private val CompilationUnit.dependenciesAndDeclarationsSource
79+
get() = declarationsRendering.sourceWithDependencies
80+
81+
private val CompilationUnit.declarationsRendering
82+
get() = declarationsRenderings.combineSources(::emptyLineSeparated)
83+
84+
private val CompilationUnit.declarationsRenderings
85+
get() = types.flatMap { it.declarationsRenderings }
86+
87+
private val Type.declarationsRenderings
88+
get() =
89+
buildList<Rendering> {
90+
if (visibility.isPrivate || declaration.isKtNative) {
91+
return@buildList
92+
}
93+
94+
if (isEnum) {
95+
add(nsEnumTypedefRendering)
96+
addAll(enumGetFunctionRenderings)
97+
}
98+
99+
addAll(methods.map { it.functionRendering })
100+
}
101+
102+
private val Type.nsEnumTypedefRendering
103+
get() =
104+
nsEnumTypedef(
105+
name = declaration.objCEnumName,
106+
type = nsUIntegerRendering,
107+
values = enumFields.map { it.descriptor.objCEnumName }
108+
)
109+
110+
private val Type.enumGetFunctionRenderings
111+
get() = enumFields.map { it.descriptor.enumGetFunctionRendering }
112+
113+
private val FieldDescriptor.enumGetFunctionRendering
114+
get() =
115+
functionDeclaration(
116+
modifiers = listOf(nsInlineRendering),
117+
returnType = enclosingTypeDescriptor.objCRendering,
118+
name = enumGetFunctionName,
119+
parameters = listOf(),
120+
statements = listOf(returnStatement(enumGetExpressionRendering))
121+
)
122+
123+
private val FieldDescriptor.enumGetFunctionName
124+
get() = enclosingTypeDescriptor.typeDeclaration.objCName(forMember = true) + "_get_" + name!!
125+
126+
private val FieldDescriptor.enumObjCName
127+
get() = CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, name!!)
128+
129+
private val FieldDescriptor.enumGetExpressionRendering
130+
get() =
131+
enclosingTypeDescriptor.typeDeclaration.objCNameRendering.bindSource { enumClassSource ->
132+
rendering(dotSeparated(enumClassSource, source(enumObjCName)))
133+
}
134+
135+
private val Method.functionRendering
136+
get() =
137+
takeIf { it.isStatic && !it.isConstructor }
138+
?.toObjCNames()
139+
?.let { functionRendering(it) }
140+
.orEmpty
141+
142+
private fun Method.functionRendering(objCNames: MethodObjCNames) =
143+
functionDeclaration(
144+
modifiers = listOf(nsInlineRendering),
145+
returnType = descriptor.returnTypeDescriptor.objCRendering,
146+
name = descriptor.functionName(objCNames),
147+
parameters = parameters.map { it.rendering },
148+
statements = statementRenderings
149+
)
150+
151+
private fun MethodDescriptor.functionName(objCNames: MethodObjCNames) =
152+
enclosingTypeDescriptor
153+
.objCName(useId = true, forMember = true)
154+
.plus("_")
155+
.plus(objCNames.methodName ?: ktName)
156+
.letIf(objCNames.parameterNames.isNotEmpty()) {
157+
it.plus("_").plus(objCNames.parameterNames.map { it + "_" }.joinToString(""))
158+
}
159+
160+
private val Method.statementRenderings
161+
get() =
162+
if (isPrimitiveVoid(descriptor.returnTypeDescriptor)) listOf()
163+
else listOf(returnStatement(rendering(source("0"))))
164+
165+
private val Variable.rendering
166+
get() =
167+
typeDescriptor.objCRendering.bindSource { typeSource ->
168+
rendering(spaceSeparated(typeSource, source(name.objCName)))
169+
}
170+
171+
private val TypeDeclaration.objCNameRendering
172+
get() = objectiveCNameRendering ?: mappedObjCNameRendering ?: defaultObjCNameRendering
173+
174+
private val TypeDeclaration.objectiveCNameRendering
175+
get() =
176+
objectiveCName?.let { if (isInterface) protocolNameRendering(it) else classNameRendering(it) }
177+
178+
private val TypeDeclaration.mappedObjCNameRendering
179+
get() =
180+
when (qualifiedBinaryName) {
181+
"java.lang.Object" -> nsObjectRendering
182+
"java.lang.String" -> nsStringRendering
183+
"java.lang.Class" -> classRendering
184+
"java.lang.Number" -> nsNumberRendering
185+
"java.lang.Cloneable" -> nsCopyingRendering
186+
"java.util.List" -> nsMutableArrayRendering
187+
"java.util.Set" -> nsMutableSetRendering
188+
"java.util.Map" -> nsMutableDictionaryRendering
189+
else -> null
190+
}
191+
192+
private val TypeDeclaration.defaultObjCNameRendering
193+
get() =
194+
defaultObjCName(forMember = false).let {
195+
if (isInterface) protocolNameRendering(it) else classNameRendering(it)
196+
}
197+
198+
private val TypeDescriptor.objCRendering
199+
get() =
200+
when (this) {
201+
is PrimitiveTypeDescriptor -> primitiveObjCRendering
202+
is DeclaredTypeDescriptor -> declaredObjCRendering
203+
// TODO: Handle TypeVariable and Array
204+
else -> idRendering
205+
}
206+
207+
private val PrimitiveTypeDescriptor.primitiveObjCRendering
208+
get() =
209+
when (this) {
210+
PrimitiveTypes.VOID -> rendering(source("void"))
211+
else -> source("j$simpleSourceName") renderingWith dependency(j2ObjCTypesImport)
212+
}
213+
214+
private val DeclaredTypeDescriptor.declaredObjCRendering
215+
get() =
216+
when {
217+
isJavaLangObject(this) -> idRendering
218+
isInterface ->
219+
typeDeclaration.objCNameRendering.bindSource {
220+
rendering(join(source("id"), inAngleBrackets(it)))
221+
}
222+
else ->
223+
typeDeclaration.objCNameRendering.bindSource { rendering(spaceSeparated(it, source("*"))) }
224+
}
225+
226+
private val j2ObjCTypesImport
227+
get() = localImport("third_party/java_src/j2objc/jre_emul/Classes/J2ObjC_types.h")
228+
229+
internal val TypeDeclaration.objCEnumName
230+
get() = "${objCName}_Enum"
231+
232+
internal val FieldDescriptor.objCEnumName
233+
get() = "${enclosingTypeDescriptor.typeDeclaration.objCEnumName}_$name"

‎transpiler/java/com/google/j2cl/transpiler/backend/kotlin/KotlinGeneratorStage.kt‎

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,26 @@ class KotlinGeneratorStage(private val output: OutputUtils.Output, private val p
3737
}
3838

3939
private fun generateOutputs(compilationUnit: CompilationUnit) {
40-
val source = renderSource(compilationUnit)
40+
generateKtOutputs(compilationUnit)
41+
// TODO(b/263416948): Uncomment after cl/501829004 is submitted.
42+
// generateObjCOutputs(compilationUnit)
43+
}
44+
45+
private fun generateKtOutputs(compilationUnit: CompilationUnit) {
46+
val source = ktSource(compilationUnit)
4147
val path = compilationUnit.packageRelativePath.replace(".java", ".kt")
4248
output.write(path, source)
4349
}
4450

45-
private fun renderSource(compilationUnit: CompilationUnit): String {
51+
private fun generateObjCOutputs(compilationUnit: CompilationUnit) {
52+
val source = compilationUnit.j2ObjCCompatHeaderSource
53+
if (!source.isEmpty) {
54+
val path = compilationUnit.packageRelativePath.replace(".java", "+J2ObjCCompat.h")
55+
output.write(path, source.toString())
56+
}
57+
}
58+
59+
private fun ktSource(compilationUnit: CompilationUnit): String {
4660
val nameToIdentifierMap = compilationUnit.buildNameToIdentifierMap()
4761

4862
val environment =

‎transpiler/java/com/google/j2cl/transpiler/backend/kotlin/ObjCNameRenderer.kt‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ private val TypeDeclaration.mappedObjCName: String?
130130
else -> null
131131
}
132132

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

136136
private fun TypeDeclaration.objCNamePrefix(forMember: Boolean) =
@@ -158,12 +158,12 @@ private fun String.objCPackagePrefix(forMember: Boolean): String =
158158
private val String.titleCase
159159
get() = StringUtils.capitalize(this)
160160

161-
private val String.objCName
161+
internal val String.objCName
162162
get() = replace('$', '_')
163163

164164
private const val idObjCName = "id"
165165

166-
private fun TypeDescriptor.objCName(useId: Boolean, forMember: Boolean): String =
166+
internal fun TypeDescriptor.objCName(useId: Boolean, forMember: Boolean): String =
167167
when (this) {
168168
is PrimitiveTypeDescriptor -> primitiveObjCName
169169
is ArrayTypeDescriptor -> arrayObjCName(forMember = forMember)

‎transpiler/java/com/google/j2cl/transpiler/backend/kotlin/objc/Rendering.kt‎

Lines changed: 15 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import com.google.j2cl.transpiler.backend.kotlin.source.Source
1919
import com.google.j2cl.transpiler.backend.kotlin.source.commaSeparated
2020
import com.google.j2cl.transpiler.backend.kotlin.source.emptyLineSeparated
2121
import com.google.j2cl.transpiler.backend.kotlin.source.emptySource
22+
import com.google.j2cl.transpiler.backend.kotlin.source.ifEmpty
2223
import com.google.j2cl.transpiler.backend.kotlin.source.inCurlyBrackets
2324
import com.google.j2cl.transpiler.backend.kotlin.source.inNewLines
2425
import com.google.j2cl.transpiler.backend.kotlin.source.inRoundBrackets
@@ -62,32 +63,25 @@ fun Iterable<Rendering>.combineSources(fn: (Iterable<Source>) -> Source) = bindS
6263
val Rendering?.orEmpty
6364
get() = this ?: rendering(emptySource)
6465

65-
fun declarator(type: Rendering, name: String, isPointer: Boolean) =
66-
type.bindSource { typeSource ->
67-
source(name).let { nameSource ->
68-
rendering(
69-
if (isPointer) join(typeSource, nameSource) else spaceSeparated(typeSource, nameSource)
70-
)
71-
}
72-
}
73-
74-
fun return_(expression: Rendering) =
75-
expression.bindSource { expressionSource -> rendering(semicolonEnded(return_(expressionSource))) }
76-
77-
fun function(
66+
fun functionDeclaration(
7867
modifiers: List<Rendering>,
79-
declarator: Rendering,
68+
returnType: Rendering,
69+
name: String,
8070
parameters: List<Rendering>,
8171
statements: List<Rendering>
8272
) =
8373
modifiers.bindSources { modifierSources ->
84-
declarator.bindSource { declaratorSource ->
85-
invocation(parameters).bindSource { invocationSource ->
74+
returnType.bindSource { returnTypeSource ->
75+
parameters.bindSources { parameterSources ->
8676
block(statements).bindSource { blockSource ->
8777
rendering(
8878
spaceSeparated(
8979
spaceSeparated(modifierSources),
90-
join(declaratorSource, invocationSource),
80+
returnTypeSource,
81+
join(
82+
source(name),
83+
inRoundBrackets(commaSeparated(parameterSources).ifEmpty { source("void") })
84+
),
9185
blockSource
9286
)
9387
)
@@ -96,14 +90,8 @@ fun function(
9690
}
9791
}
9892

99-
private fun invocation(parameters: List<Rendering>) =
100-
parameters.bindSources { sources ->
101-
rendering(
102-
inRoundBrackets(
103-
sources.toList().let { if (it.isEmpty()) source("void") else commaSeparated(it) }
104-
)
105-
)
106-
}
107-
108-
private fun block(statements: List<Rendering>) =
93+
fun block(statements: List<Rendering>) =
10994
statements.bindSources { sources -> rendering(inCurlyBrackets(inNewLines(sources))) }
95+
96+
fun returnStatement(expression: Rendering) =
97+
expression.bindSource { source -> rendering(semicolonEnded(return_(source))) }

‎transpiler/java/com/google/j2cl/transpiler/backend/kotlin/source/Source.kt‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ val emptySource
3030
val Source?.orEmpty
3131
get() = this ?: emptySource
3232

33+
fun Source.ifEmpty(fn: () -> Source) = if (isEmpty) fn() else this
34+
3335
fun Source.ifNotEmpty(fn: (Source) -> Source) = if (isEmpty) this else fn(this)
3436

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

0 commit comments

Comments
 (0)