Skip to content

Fix compiler plugin losing generic upper-bound (T : Geo) after DataFrame transformations #905

Description

@koperagen

Given API with type parameter bound to something:

interface Geo {
  val geometry: Geometry
}
internal class GeoDataFrame<T : Geo>(val df: DataFrame<T>) {
    fun update(f: DataFrame<T>.() -> DataFrame<T>): DataFrame<T> = TODO()
}

Two things can go wrong in combination with compiler plugin

geo.update { remove { geometry } }
  1. type of DataFrame after remove is not subtype of Geo. So org.jetbrains.kotlinx.dataframe.plugin.extensions.FunctionCallTransformer#intercept must somehow figure out that this supertype is needed. Note that incercept happens before call completion
  2. even if type is preserved, there will be no longer such column. An additional checker should be there to report that column is missing
    related to Geo extensions for DataFrame #875

Problem

Compiler plugin-generated local types do not preserve generic upper-bound constraints.

Example:

interface Geo {
    val geometry: Int
}

internal class GeoDataFrame<T : Geo>(val df: DataFrame<T>) {
    fun update(f: DataFrame<T>.() -> DataFrame<T>): DataFrame<T> = TODO()
}

internal fun f(geo: GeoDataFrame<Geo>) {
    geo.update {
        add("b") { 123 }
    }
}

The compiler plugin infers a new local DataFrame type after add, but this type is no longer treated as satisfying the original T : Geo constraint.

As a result, the call fails with a type mismatch:

DataFrame<Add_96> is inferred, but DataFrame<Geo> is expected.

Expected

Compiler plugin-generated local types should preserve required upper-bound schema constraints when used inside generic APIs.

In this example, after adding a column, the resulting local type should still satisfy the Geo contract because the required geometry column remains present.

Acceptance criteria

  • Reproduce the issue with a generic T : Geo DataFrame wrapper
  • Compiler plugin preserves upper-bound schema constraints when generated local type still contains required columns
  • If an operation removes a required upper-bound column, the plugin reports a clear diagnostic
  • Add tests for:
    • adding a column while preserving upper-bound schema
    • removing a required column and reporting an error
  • Document the expected behavior for generic schema bounds if needed

Motivation

Generic wrappers around DataFrame<T> are a natural way to build domain-specific APIs.

If compiler plugin local types do not preserve upper-bound constraints, such wrappers become hard to use with common operations like add, remove, or update.

This is important for making compiler-plugin-based typed DataFrame workflows reliable before 1.0.

Metadata

Metadata

Assignees

Labels

Compiler pluginAnything related to the DataFrame Compiler PluginbugSomething isn't working

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions