-
Notifications
You must be signed in to change notification settings - Fork 37
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Set default values appropriately for type * Port most intermediate tpes * Copy in NameAllocator + improve raw types * Finish matching moshi API structure Resolves #169 Resolves #168 Resolves #167 Resolves #166 Resolves #174 Resolves #178 * Clean up some TODOs and descriptor API usages * Set non-constructor properties Resolves #179 * Note for later * Pass the standard compilation failure tests Doesn't cover proguard or anonymous objects * Support default values Resolves #171 * Finish porting moshi tests and address any remaining issues * More little cleanup * Fix copyrights * README, RELEASING, and snapshots * Fix generated arg in tests
- Loading branch information
Showing
42 changed files
with
5,391 additions
and
742 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
Moshi-IR | ||
======== | ||
|
||
A Kotlin IR implementation of Moshi code gen. | ||
|
||
The goal of this is to have functional parity with Moshi's native Kapt/KSP code gen but run as a fully embedded IR | ||
plugin. | ||
|
||
**Benefits** | ||
- Significantly faster build times | ||
- No reflection required at runtime to support default parameter values | ||
- Feature parity with Moshi's native code gen | ||
|
||
**Cons** | ||
- No support for Proguard file generation for now. You will need to add this manually to your rules if you use | ||
R8/Proguard. | ||
- One option is to use IR in debug builds and Kapt/KSP in release builds, the latter of which do still generate | ||
proguard rules. | ||
```proguard | ||
# Keep names for JsonClass-annotated classes | ||
-keepnames class @com.squareup.moshi.JsonClass ** | ||
# Keep generated adapter classes' constructors | ||
-keepclassmembers class *JsonAdapter { | ||
public <init>(...); | ||
} | ||
``` | ||
- Kotlin IR is not a stable API and may change in future Kotlin versions. While I'll try to publish quickly to adjust to | ||
these, you should be aware. If you have any issues, you can always fall back to Kapt/KSP. | ||
|
||
### Installation | ||
|
||
Simply apply the Gradle plugin in your project to use it. | ||
|
||
The Gradle plugin is published to Maven Central, so ensure you have `mavenCentral()` visible to your buildscript | ||
classpath. | ||
|
||
[](https://mvnrepository.com/artifact/dev.zacsweers.moshix/moshi-sealed-runtime) | ||
```gradle | ||
plugins { | ||
kotlin("jvm") | ||
id("dev.zacsweers.moshix") version "x.y.z" | ||
} | ||
``` | ||
|
||
Snapshots of the development version are available in [Sonatype's snapshots repository][snapshots]. | ||
|
||
License | ||
------- | ||
|
||
Copyright (C) 2021 Zac Sweers | ||
|
||
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. | ||
|
||
[snapshots]: https://oss.sonatype.org/content/repositories/snapshots/dev/zacsweers/moshix/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
63 changes: 63 additions & 0 deletions
63
...-ir/moshi-compiler-plugin/src/main/kotlin/dev/zacsweers/moshix/ir/compiler/AppliedType.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
/* | ||
* Copyright (C) 2021 Zac Sweers | ||
* | ||
* 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 dev.zacsweers.moshix.ir.compiler | ||
|
||
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext | ||
import org.jetbrains.kotlin.descriptors.ClassKind | ||
import org.jetbrains.kotlin.ir.declarations.IrClass | ||
import org.jetbrains.kotlin.ir.util.defaultType | ||
import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable | ||
import org.jetbrains.kotlin.ir.util.getAllSuperclasses | ||
|
||
/** | ||
* A concrete type like `List<String>` with enough information to know how to resolve its type | ||
* variables. | ||
*/ | ||
internal class AppliedType | ||
private constructor( | ||
val type: IrClass, | ||
) { | ||
|
||
/** | ||
* Returns all super classes of this, recursively. Only [IrClass] is used as we can't really use | ||
* other types. | ||
*/ | ||
fun superclasses( | ||
pluginContext: IrPluginContext, | ||
): LinkedHashSet<AppliedType> { | ||
val result: LinkedHashSet<AppliedType> = LinkedHashSet() | ||
result.add(this) | ||
for (supertype in type.getAllSuperclasses()) { | ||
if (supertype.kind != ClassKind.CLASS) continue | ||
// TODO do we need to check if it's j.l.Object? | ||
val irType = supertype.defaultType | ||
if (irType == pluginContext.irBuiltIns.anyType) { | ||
// Don't load properties for kotlin.Any/java.lang.Object. | ||
continue | ||
} | ||
result.add(AppliedType(supertype)) | ||
} | ||
return result | ||
} | ||
|
||
override fun toString() = type.fqNameWhenAvailable!!.asString() | ||
|
||
companion object { | ||
operator fun invoke(type: IrClass): AppliedType { | ||
return AppliedType(type) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
126 changes: 126 additions & 0 deletions
126
...-ir/moshi-compiler-plugin/src/main/kotlin/dev/zacsweers/moshix/ir/compiler/MoshiIrUtil.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
/* | ||
* Copyright (C) 2021 Zac Sweers | ||
* | ||
* 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 dev.zacsweers.moshix.ir.compiler | ||
|
||
import com.squareup.moshi.Json | ||
import dev.zacsweers.moshix.ir.compiler.api.DelegateKey | ||
import dev.zacsweers.moshix.ir.compiler.api.PropertyGenerator | ||
import dev.zacsweers.moshix.ir.compiler.api.TargetProperty | ||
import dev.zacsweers.moshix.ir.compiler.util.error | ||
import dev.zacsweers.moshix.ir.compiler.util.locationOf | ||
import org.jetbrains.kotlin.cli.common.messages.MessageCollector | ||
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities | ||
import org.jetbrains.kotlin.ir.declarations.IrAnnotationContainer | ||
import org.jetbrains.kotlin.ir.declarations.IrClass | ||
import org.jetbrains.kotlin.ir.declarations.IrProperty | ||
import org.jetbrains.kotlin.ir.expressions.IrConst | ||
import org.jetbrains.kotlin.ir.expressions.IrConstructorCall | ||
import org.jetbrains.kotlin.ir.expressions.IrGetEnumValue | ||
import org.jetbrains.kotlin.ir.types.classOrNull | ||
import org.jetbrains.kotlin.ir.util.file | ||
import org.jetbrains.kotlin.ir.util.getAnnotation | ||
import org.jetbrains.kotlin.ir.util.hasAnnotation | ||
import org.jetbrains.kotlin.name.FqName | ||
|
||
internal val JSON_ANNOTATION = FqName("com.squareup.moshi.Json") | ||
internal val JSON_QUALIFIER_ANNOTATION = FqName("com.squareup.moshi.JsonQualifier") | ||
|
||
internal fun IrAnnotationContainer?.jsonQualifiers(): Set<IrConstructorCall> { | ||
if (this == null) return emptySet() | ||
return annotations.filterTo(LinkedHashSet()) { | ||
it.type.classOrNull?.owner?.hasAnnotation(JSON_QUALIFIER_ANNOTATION) == true | ||
} | ||
} | ||
|
||
internal fun IrProperty.jsonNameFromAnywhere(): String? { | ||
return jsonName() ?: backingField?.jsonName() ?: getter?.jsonName() ?: setter?.jsonName() | ||
} | ||
|
||
internal fun IrProperty.jsonIgnoreFromAnywhere(): Boolean { | ||
return jsonIgnore() || | ||
backingField?.jsonIgnore() == true || | ||
getter?.jsonIgnore() == true || | ||
setter?.jsonIgnore() == true | ||
} | ||
|
||
internal fun IrAnnotationContainer.jsonName(): String? { | ||
@Suppress("UNCHECKED_CAST") | ||
return (getAnnotation(JSON_ANNOTATION)?.getValueArgument(0) as? IrConst<String>?)?.value | ||
?.takeUnless { it == Json.UNSET_NAME } | ||
} | ||
|
||
internal fun IrAnnotationContainer.jsonIgnore(): Boolean { | ||
@Suppress("UNCHECKED_CAST") | ||
return (getAnnotation(JSON_ANNOTATION)?.getValueArgument(1) as? IrConst<Boolean>?)?.value == true | ||
} | ||
|
||
private val TargetProperty.isSettable | ||
get() = property.isVar || parameter != null | ||
private val TargetProperty.isVisible: Boolean | ||
get() { | ||
return visibility == DescriptorVisibilities.INTERNAL || | ||
visibility == DescriptorVisibilities.PROTECTED || | ||
visibility == DescriptorVisibilities.PUBLIC | ||
} | ||
|
||
/** | ||
* Returns a generator for this property, or null if either there is an error and this property | ||
* cannot be used with code gen, or if no codegen is necessary for this property. | ||
*/ | ||
internal fun TargetProperty.generator( | ||
logger: MessageCollector, | ||
originalType: IrClass, | ||
): PropertyGenerator? { | ||
val location = { originalType.file.locationOf(originalType) } | ||
if (jsonIgnore) { | ||
if (!hasDefault) { | ||
logger.error(location) { "No default value for transient/ignored property $name" } | ||
return null | ||
} | ||
return PropertyGenerator(this, DelegateKey(type, emptyList()), true) | ||
} | ||
|
||
if (!isVisible) { | ||
logger.error(location) { "property $name is not visible" } | ||
return null | ||
} | ||
|
||
if (!isSettable) { | ||
return null // This property is not settable. Ignore it. | ||
} | ||
|
||
// Merge parameter and property annotations | ||
val qualifiers = parameter?.qualifiers.orEmpty() + property.jsonQualifiers() | ||
for (jsonQualifier in qualifiers) { | ||
val qualifierRawType = jsonQualifier.type.classOrNull!!.owner | ||
val retentionValue = | ||
qualifierRawType | ||
.getAnnotation(FqName("kotlin.annotation.Retention")) | ||
?.getValueArgument(0) as | ||
IrGetEnumValue? | ||
?: continue | ||
// TODO what about java qualifiers types? | ||
val retention = retentionValue.symbol.owner.name.identifier | ||
// Check Java types since that covers both Java and Kotlin annotations. | ||
if (retention != "RUNTIME") { | ||
logger.error({ originalType.file.locationOf(jsonQualifier) }) { | ||
"JsonQualifier @${qualifierRawType.name} must have RUNTIME retention" | ||
} | ||
} | ||
} | ||
|
||
return PropertyGenerator(this, DelegateKey(type, qualifiers.toList())) | ||
} |
Oops, something went wrong.