diff --git a/plugins/kotlin-dataframe/src/org/jetbrains/kotlinx/dataframe/plugin/extensions/ExpressionAnalysisAdditionalChecker.kt b/plugins/kotlin-dataframe/src/org/jetbrains/kotlinx/dataframe/plugin/extensions/ExpressionAnalysisAdditionalChecker.kt index 2f6a9ac351..59533e4aee 100644 --- a/plugins/kotlin-dataframe/src/org/jetbrains/kotlinx/dataframe/plugin/extensions/ExpressionAnalysisAdditionalChecker.kt +++ b/plugins/kotlin-dataframe/src/org/jetbrains/kotlinx/dataframe/plugin/extensions/ExpressionAnalysisAdditionalChecker.kt @@ -9,6 +9,7 @@ import org.jetbrains.kotlin.diagnostics.DiagnosticReporter import org.jetbrains.kotlin.diagnostics.SourceElementPositioningStrategies import org.jetbrains.kotlin.diagnostics.error1 import org.jetbrains.kotlin.diagnostics.reportOn +import org.jetbrains.kotlin.diagnostics.warning1 import org.jetbrains.kotlin.fir.FirSession import org.jetbrains.kotlin.fir.analysis.checkers.MppCheckerKind import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext @@ -29,6 +30,7 @@ import org.jetbrains.kotlin.fir.types.coneType import org.jetbrains.kotlin.fir.types.isSubtypeOf import org.jetbrains.kotlin.fir.types.renderReadable import org.jetbrains.kotlin.fir.types.resolvedType +import org.jetbrains.kotlin.fir.types.toSymbol import org.jetbrains.kotlin.fir.types.type import org.jetbrains.kotlin.name.CallableId import org.jetbrains.kotlin.name.ClassId @@ -38,6 +40,7 @@ import org.jetbrains.kotlin.psi.KtElement import org.jetbrains.kotlinx.dataframe.plugin.impl.PluginDataFrameSchema import org.jetbrains.kotlinx.dataframe.plugin.impl.SimpleDataColumn import org.jetbrains.kotlinx.dataframe.plugin.impl.type +import org.jetbrains.kotlinx.dataframe.plugin.utils.Names class ExpressionAnalysisAdditionalChecker( session: FirSession, @@ -58,6 +61,7 @@ private class Checker( companion object { val ERROR by error1(SourceElementPositioningStrategies.DEFAULT) val CAST_ERROR by error1(SourceElementPositioningStrategies.CALL_ELEMENT_WITH_DOT) + val CAST_TARGET_WARNING by warning1(SourceElementPositioningStrategies.CALL_ELEMENT_WITH_DOT) val CAST_ID = CallableId(FqName.fromSegments(listOf("org", "jetbrains", "kotlinx", "dataframe", "api")), Name.identifier("cast")) val CHECK = ClassId(FqName("org.jetbrains.kotlinx.dataframe.annotations"), Name.identifier("Check")) } @@ -87,13 +91,18 @@ private class Checker( || !calleeReference.resolvedSymbol.hasAnnotation(CHECK, session)) { return } + val targetProjection = expression.typeArguments.getOrNull(0) as? FirTypeProjectionWithVariance ?: return + val targetType = targetProjection.typeRef.coneType as? ConeClassLikeType ?: return + val targetSymbol = targetType.toSymbol(session) + if (targetSymbol != null && !targetSymbol.hasAnnotation(Names.DATA_SCHEMA_CLASS_ID, session)) { + val text = "Annotate ${targetType.renderReadable()} with @DataSchema to use generated properties" + reporter.reportOn(expression.source, CAST_TARGET_WARNING, text, context) + } val coneType = expression.explicitReceiver?.resolvedType if (coneType != null) { val sourceType = coneType.fullyExpandedType(session).typeArguments.getOrNull(0)?.type as? ConeClassLikeType ?: return val source = pluginDataFrameSchema(sourceType) - val targetProjection = expression.typeArguments.getOrNull(0) as? FirTypeProjectionWithVariance ?: return - val targetType = targetProjection.typeRef.coneType as? ConeClassLikeType ?: return val target = pluginDataFrameSchema(targetType) val sourceColumns = source.flatten(includeFrames = true) val targetColumns = target.flatten(includeFrames = true) diff --git a/plugins/kotlin-dataframe/testData/diagnostics/targetOfCastIsNotDataSchema.fir.txt b/plugins/kotlin-dataframe/testData/diagnostics/targetOfCastIsNotDataSchema.fir.txt new file mode 100644 index 0000000000..2e26e6f8eb --- /dev/null +++ b/plugins/kotlin-dataframe/testData/diagnostics/targetOfCastIsNotDataSchema.fir.txt @@ -0,0 +1,11 @@ +FILE: targetOfCastIsNotDataSchema.kt + public abstract interface MySchema : R|kotlin/Any| { + public abstract val a: R|kotlin/String| + public get(): R|kotlin/String| + + } + public final fun box(): R|kotlin/String| { + lval df: R|{org/jetbrains/kotlinx/dataframe/AnyFrame=} org/jetbrains/kotlinx/dataframe/DataFrame<*>| = Q|org/jetbrains/kotlinx/dataframe/DataFrame|.R|org/jetbrains/kotlinx/dataframe/DataFrame.Companion.Empty| + R|/df|.R|org/jetbrains/kotlinx/dataframe/api/cast|() + ^box String(OK) + } diff --git a/plugins/kotlin-dataframe/testData/diagnostics/targetOfCastIsNotDataSchema.kt b/plugins/kotlin-dataframe/testData/diagnostics/targetOfCastIsNotDataSchema.kt new file mode 100644 index 0000000000..effed03feb --- /dev/null +++ b/plugins/kotlin-dataframe/testData/diagnostics/targetOfCastIsNotDataSchema.kt @@ -0,0 +1,13 @@ +import org.jetbrains.kotlinx.dataframe.* +import org.jetbrains.kotlinx.dataframe.annotations.* +import org.jetbrains.kotlinx.dataframe.api.* +import org.jetbrains.kotlinx.dataframe.io.* + +interface MySchema { + val a: String +} +fun box(): String { + val df = DataFrame.Empty + df.cast() + return "OK" +} diff --git a/plugins/kotlin-dataframe/tests-gen/org/jetbrains/kotlin/fir/dataframe/DataFrameDiagnosticTestGenerated.java b/plugins/kotlin-dataframe/tests-gen/org/jetbrains/kotlin/fir/dataframe/DataFrameDiagnosticTestGenerated.java index 55d7ff6fb8..dfc30eb01d 100644 --- a/plugins/kotlin-dataframe/tests-gen/org/jetbrains/kotlin/fir/dataframe/DataFrameDiagnosticTestGenerated.java +++ b/plugins/kotlin-dataframe/tests-gen/org/jetbrains/kotlin/fir/dataframe/DataFrameDiagnosticTestGenerated.java @@ -44,6 +44,12 @@ public void testStructuralCast() { runTest("testData/diagnostics/structuralCast.kt"); } + @Test + @TestMetadata("targetOfCastIsNotDataSchema.kt") + public void testTargetOfCastIsNotDataSchema() { + runTest("testData/diagnostics/targetOfCastIsNotDataSchema.kt"); + } + @Test @TestMetadata("toDataFrame_java.kt") public void testToDataFrame_java() {