Skip to content

Commit 21deeb6

Browse files
added config option to allow unsafe mapping of platform types to non-nullable target (#273)
* added config option to allow unsafe mapping of Java platform types to non-nullable target * remove unused class * fix compile error * fix java classes not found in maven tests * applied suggested rename UseStrictJavaNullability -> UseStrictPlatformTypeNullabilityValidation * applied better comment suggestions * update changelog --------- Co-authored-by: Stefan Koppier <[email protected]>
1 parent 785085f commit 21deeb6

File tree

18 files changed

+299
-22
lines changed

18 files changed

+299
-22
lines changed

compiler-plugin/src/main/kotlin/tech/mappie/MappieCommandLineProcessor.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ class MappieCommandLineProcessor : CommandLineProcessor {
3131
description = "strictness of enum validation",
3232
required = false,
3333
),
34+
CliOption(
35+
optionName = OPTION_STRICTNESS_JAVA_NULLABILITY,
36+
valueDescription = "boolean",
37+
description = "strictness of java nullability validation",
38+
required = false,
39+
),
3440
CliOption(
3541
optionName = OPTION_STRICTNESS_VISIBILITY,
3642
valueDescription = "boolean",
@@ -56,6 +62,7 @@ class MappieCommandLineProcessor : CommandLineProcessor {
5662
OPTION_WARNINGS_AS_ERRORS -> configuration.put(ARGUMENT_WARNINGS_AS_ERRORS, value.toBooleanStrict())
5763
OPTION_USE_DEFAULT_ARGUMENTS -> configuration.put(ARGUMENT_USE_DEFAULT_ARGUMENTS, value.toBooleanStrict())
5864
OPTION_STRICTNESS_ENUMS -> configuration.put(ARGUMENT_STRICTNESS_ENUMS, value.toBooleanStrict())
65+
OPTION_STRICTNESS_JAVA_NULLABILITY -> configuration.put(ARGUMENT_STRICTNESS_JAVA_NULLABILITY, value.toBooleanStrict())
5966
OPTION_STRICTNESS_VISIBILITY -> configuration.put(ARGUMENT_STRICTNESS_VISIBILITY, value.toBooleanStrict())
6067
OPTION_REPORT_ENABLED -> configuration.put(ARGUMENT_REPORT_ENABLED, value.toBooleanStrict())
6168
OPTION_REPORT_DIR -> configuration.put(ARGUMENT_REPORT_DIR, value)
@@ -67,13 +74,15 @@ class MappieCommandLineProcessor : CommandLineProcessor {
6774
const val OPTION_WARNINGS_AS_ERRORS = "warnings-as-errors"
6875
const val OPTION_USE_DEFAULT_ARGUMENTS = "use-default-arguments"
6976
const val OPTION_STRICTNESS_ENUMS = "strict-enums"
77+
const val OPTION_STRICTNESS_JAVA_NULLABILITY = "strict-platform-type-nullability"
7078
const val OPTION_STRICTNESS_VISIBILITY = "strict-visibility"
7179
const val OPTION_REPORT_ENABLED = "report-enabled"
7280
const val OPTION_REPORT_DIR = "report-dir"
7381

7482
val ARGUMENT_WARNINGS_AS_ERRORS = CompilerConfigurationKey<Boolean>(OPTION_WARNINGS_AS_ERRORS)
7583
val ARGUMENT_USE_DEFAULT_ARGUMENTS = CompilerConfigurationKey<Boolean>(OPTION_USE_DEFAULT_ARGUMENTS)
7684
val ARGUMENT_STRICTNESS_ENUMS = CompilerConfigurationKey<Boolean>(OPTION_STRICTNESS_ENUMS)
85+
val ARGUMENT_STRICTNESS_JAVA_NULLABILITY = CompilerConfigurationKey<Boolean>(OPTION_STRICTNESS_JAVA_NULLABILITY)
7786
val ARGUMENT_STRICTNESS_VISIBILITY = CompilerConfigurationKey<Boolean>(OPTION_STRICTNESS_VISIBILITY)
7887
val ARGUMENT_REPORT_ENABLED = CompilerConfigurationKey<Boolean>(OPTION_REPORT_ENABLED)
7988
val ARGUMENT_REPORT_DIR = CompilerConfigurationKey<String>(OPTION_REPORT_DIR)

compiler-plugin/src/main/kotlin/tech/mappie/MappieCompilerPluginRegistrar.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import tech.mappie.MappieCommandLineProcessor.Companion.ARGUMENT_REPORT_DIR
1515
import tech.mappie.MappieCommandLineProcessor.Companion.ARGUMENT_REPORT_ENABLED
1616
import tech.mappie.MappieCommandLineProcessor.Companion.ARGUMENT_USE_DEFAULT_ARGUMENTS
1717
import tech.mappie.MappieCommandLineProcessor.Companion.ARGUMENT_WARNINGS_AS_ERRORS
18+
import tech.mappie.MappieCommandLineProcessor.Companion.ARGUMENT_STRICTNESS_JAVA_NULLABILITY
1819
import tech.mappie.compiler_plugin.BuildConfig
1920
import tech.mappie.config.MappieConfiguration
2021
import tech.mappie.config.MappieModule
@@ -38,6 +39,7 @@ class MappieCompilerPluginRegistrar : CompilerPluginRegistrar() {
3839
warningsAsErrors = configuration.get(ARGUMENT_WARNINGS_AS_ERRORS, false),
3940
useDefaultArguments = configuration.get(ARGUMENT_USE_DEFAULT_ARGUMENTS, true),
4041
strictEnums = configuration.get(ARGUMENT_STRICTNESS_ENUMS, true),
42+
strictplatformTypeNullability = configuration.get(ARGUMENT_STRICTNESS_JAVA_NULLABILITY, true),
4143
strictVisibility = configuration.get(ARGUMENT_STRICTNESS_VISIBILITY, false),
4244
reportEnabled = configuration.get(ARGUMENT_REPORT_ENABLED, false),
4345
reportDir = configuration.get(ARGUMENT_REPORT_DIR, ""),

compiler-plugin/src/main/kotlin/tech/mappie/config/MappieConfiguration.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ data class MappieConfiguration(
66
val warningsAsErrors: Boolean,
77
val useDefaultArguments: Boolean,
88
val strictEnums: Boolean,
9+
val strictplatformTypeNullability: Boolean,
910
val strictVisibility: Boolean,
1011
val reportEnabled: Boolean,
1112
val reportDir: String,
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package tech.mappie.config.options
2+
3+
import org.jetbrains.kotlin.ir.declarations.IrFunction
4+
import org.jetbrains.kotlin.ir.expressions.IrConstructorCall
5+
import org.jetbrains.kotlin.ir.types.classOrFail
6+
import org.jetbrains.kotlin.ir.util.getValueArgument
7+
import org.jetbrains.kotlin.ir.util.isTrueConst
8+
import org.jetbrains.kotlin.ir.util.parentAsClass
9+
import org.jetbrains.kotlin.name.Name
10+
import tech.mappie.MappieContext
11+
import tech.mappie.util.CLASS_ID_USE_STRICT_JAVA_NULLABILITY
12+
13+
fun MappieContext.useStrictPlatformTypeNullabilityValidationClassSymbol() =
14+
pluginContext.referenceClass(CLASS_ID_USE_STRICT_JAVA_NULLABILITY)
15+
16+
fun MappieContext.getUseStrictPlatformTypeNullabilityValidationAnnotation(origin: IrFunction): IrConstructorCall? =
17+
origin.parentAsClass.annotations.firstOrNull { it.type.classOrFail == useStrictPlatformTypeNullabilityValidationClassSymbol() }
18+
19+
fun MappieContext.useStrictPlatformTypeNullabilityValidation(origin: IrFunction): Boolean =
20+
getUseStrictPlatformTypeNullabilityValidationAnnotation(origin)
21+
?.let { it.getValueArgument(Name.identifier("value"))?.isTrueConst() ?: true }
22+
?: configuration.strictplatformTypeNullability

compiler-plugin/src/main/kotlin/tech/mappie/fir/MappieAdditionalCheckersExtension.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ class MappieAdditionalCheckersExtension(session: FirSession) : FirAdditionalChec
1313
override val expressionCheckers = object : ExpressionCheckers() {
1414
override val annotationCallCheckers = setOf(
1515
UseDefaultArgumentsAnnotationChecker(),
16-
UseStrictVisibilityAnnotationChecker(),
1716
UseStrictEnumsAnnotationChecker(),
17+
UseStrictVisibilityAnnotationChecker(),
1818
)
1919

2020
override val functionCallCheckers: Set<FirFunctionCallChecker> = setOf(

compiler-plugin/src/main/kotlin/tech/mappie/ir/analysis/problems/classes/UnsafePlatformTypeAssignmentProblems.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import org.jetbrains.kotlin.ir.types.removeAnnotations
44
import org.jetbrains.kotlin.ir.util.isNullable
55
import org.jetbrains.kotlin.ir.util.dumpKotlinLike
66
import org.jetbrains.kotlin.ir.util.fileEntry
7+
import tech.mappie.config.options.useStrictPlatformTypeNullabilityValidation
78
import tech.mappie.ir.resolving.ClassMappingRequest
89
import tech.mappie.ir.resolving.classes.sources.*
910
import tech.mappie.ir.resolving.classes.targets.ClassMappingTarget
@@ -20,7 +21,12 @@ class UnsafePlatformTypeAssignmentProblems(
2021
private val mappings: Map<ClassMappingTarget, ClassMappingSource>,
2122
) {
2223

23-
fun all(): List<Problem> = mappings.mapNotNull { validate(it.key, it.value) }
24+
fun all(): List<Problem> =
25+
if (context.useStrictPlatformTypeNullabilityValidation(context.function)) {
26+
mappings.mapNotNull { validate(it.key, it.value) }
27+
} else {
28+
emptyList()
29+
}
2430

2531
private fun validate(target: ClassMappingTarget, source: ClassMappingSource): Problem? {
2632
val sourceTypeString = source.type.removeAnnotations().dumpKotlinLike()

compiler-plugin/src/main/kotlin/tech/mappie/util/Identifiers.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,12 @@ val CLASS_ID_OBJECT_MAPPIE4 = ClassId(PACKAGE_TECH_MAPPIE_API, Name.identifier("
7676

7777
val CLASS_ID_OBJECT_MAPPIE5 = ClassId(PACKAGE_TECH_MAPPIE_API, Name.identifier("ObjectMappie5"))
7878

79-
val CLASS_ID_USE_STRICT_VISIBILITY = ClassId(PACKAGE_TECH_MAPPIE_API_CONFIG, Name.identifier("UseStrictVisibility"))
80-
8179
val CLASS_ID_USE_DEFAULT_ARGUMENTS = ClassId(PACKAGE_TECH_MAPPIE_API_CONFIG, Name.identifier("UseDefaultArguments"))
8280

8381
val CLASS_ID_USE_STRICT_ENUMS = ClassId(PACKAGE_TECH_MAPPIE_API_CONFIG, Name.identifier("UseStrictEnums"))
8482

83+
val CLASS_ID_USE_STRICT_JAVA_NULLABILITY = ClassId(PACKAGE_TECH_MAPPIE_API_CONFIG, Name.identifier("UseStrictPlatformTypeNullabilityValidation"))
84+
85+
val CLASS_ID_USE_STRICT_VISIBILITY = ClassId(PACKAGE_TECH_MAPPIE_API_CONFIG, Name.identifier("UseStrictVisibility"))
86+
8587
val CLASS_ID_RECORD = ClassId(FqName("java.lang"), Name.identifier("Record"))

gradle-plugin/src/main/kotlin/tech/mappie/MappieExtension.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@ abstract class MappieStrictnessExtension {
5353
*/
5454
abstract val enums: Property<Boolean>
5555

56+
/**
57+
* Report a warning if a nullable platform type is used to assign to a non-nullable target.
58+
*/
59+
abstract val platformTypeNullability: Property<Boolean>
60+
5661
/**
5762
* Whether to require called elements to be visible from the current scope.
5863
*/

gradle-plugin/src/main/kotlin/tech/mappie/MappieGradlePlugin.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ class MappieGradlePlugin : KotlinCompilerPluginSupportPlugin {
3030
extension.strictness.enums.orNull?.apply {
3131
add(SubpluginOption("strict-enums", this.toString()))
3232
}
33+
extension.strictness.platformTypeNullability.orNull?.apply {
34+
add(SubpluginOption("strict-platform-type-nullability", this.toString()))
35+
}
3336
extension.strictness.visibility.orNull?.apply {
3437
add(SubpluginOption("strict-visibility", this.toString()))
3538
}
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
package tech.mappie.testing.configuration.strictness
2+
3+
import tech.mappie.testing.TestBase
4+
import org.assertj.core.api.Assertions.assertThat
5+
import org.junit.jupiter.api.Test
6+
7+
class platformTypeNullabilityStrictnessTest : TestBase() {
8+
9+
@Test
10+
fun `java nullability warning is emitted when enabled`() {
11+
kotlin("build.gradle.kts",
12+
"""
13+
mappie {
14+
strictness {
15+
platformTypeNullability = true
16+
}
17+
}
18+
""".trimIndent()
19+
)
20+
21+
java("src/main/java/Input.java",
22+
"""
23+
public class Input {
24+
private String value;
25+
26+
public Input(String value) {
27+
this.value = value;
28+
}
29+
30+
public String getValue() {
31+
return value;
32+
}
33+
}
34+
""".trimIndent()
35+
)
36+
37+
kotlin("src/main/kotlin/Mapper.kt",
38+
"""
39+
import tech.mappie.api.ObjectMappie
40+
41+
data class Output(val value: String)
42+
43+
object Mapper : ObjectMappie<Input, Output>()
44+
""".trimIndent()
45+
)
46+
47+
val result = runner.withArguments("build").build()
48+
49+
assertThat(result.output.lines())
50+
.anyMatch { it.contains("is unsafe to assign") }
51+
}
52+
53+
@Test
54+
fun `java nullability warning is not emitted when disabled`() {
55+
kotlin("build.gradle.kts",
56+
"""
57+
mappie {
58+
strictness {
59+
platformTypeNullability = false
60+
}
61+
}
62+
""".trimIndent()
63+
)
64+
65+
java("src/main/java/Input.java",
66+
"""
67+
public class Input {
68+
private String value;
69+
70+
public Input(String value) {
71+
this.value = value;
72+
}
73+
74+
public String getValue() {
75+
return value;
76+
}
77+
}
78+
""".trimIndent()
79+
)
80+
81+
kotlin("src/main/kotlin/Mapper.kt",
82+
"""
83+
import tech.mappie.api.ObjectMappie
84+
85+
data class Output(val value: String)
86+
87+
object Mapper : ObjectMappie<Input, Output>()
88+
""".trimIndent()
89+
)
90+
91+
val result = runner.withArguments("build").build()
92+
93+
assertThat(result.output.lines())
94+
.noneMatch { it.contains("is unsafe to assign") }
95+
}
96+
97+
@Test
98+
fun `java nullability warning is not emitted when disabled locally`() {
99+
kotlin("build.gradle.kts",
100+
"""
101+
mappie {
102+
strictness {
103+
platformTypeNullability = true
104+
}
105+
}
106+
""".trimIndent()
107+
)
108+
109+
java("src/main/java/Input.java",
110+
"""
111+
public class Input {
112+
private String value;
113+
114+
public Input(String value) {
115+
this.value = value;
116+
}
117+
118+
public String getValue() {
119+
return value;
120+
}
121+
}
122+
""".trimIndent()
123+
)
124+
125+
kotlin("src/main/kotlin/Mapper.kt",
126+
"""
127+
import tech.mappie.api.ObjectMappie
128+
import tech.mappie.api.config.UseStrictPlatformTypeNullabilityValidation
129+
130+
data class Output(val value: String)
131+
132+
@UseStrictPlatformTypeNullabilityValidation(false)
133+
object Mapper : ObjectMappie<Input, Output>()
134+
""".trimIndent()
135+
)
136+
137+
val result = runner.withArguments("build").build()
138+
139+
assertThat(result.output.lines())
140+
.noneMatch { it.contains("is unsafe to assign") }
141+
}
142+
}

0 commit comments

Comments
 (0)