Skip to content

Conversation

koperagen
Copy link
Collaborator

@koperagen koperagen commented Oct 3, 2025

Fixes #1465
Btw i think strict ktlint_standard_function-expression-body rule is unnecessary, functions with return and with expression body can be a preference and coexist. At least for me this rule ALWAYS fails and forces me to rewrite

@koperagen koperagen added this to the 1.0.0-Beta4 milestone Oct 3, 2025
@koperagen koperagen self-assigned this Oct 3, 2025
@koperagen koperagen added the bug Something isn't working label Oct 3, 2025
Copy link
Collaborator

@AndreiKingsley AndreiKingsley left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great, this logic is now really clear!
But please add a KDoc - or at least a comment for now, - to clearly define the behavior of toDataFrame; it's not actually trivial.

val childCol = df[Entry::child]
childCol.kind() shouldBe ColumnKind.Group
childCol.asColumnGroup().columnsCount() shouldBe 0
childCol.kind() shouldBe ColumnKind.Value
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, that was weird, didn't know!

@Jolanrensen Jolanrensen changed the title Todataframe fixes .toDataFrame fixes Oct 6, 2025
@Jolanrensen
Copy link
Collaborator

Btw i think strict ktlint_standard_function-expression-body rule is unnecessary, functions with return and with expression body can be a preference and coexist. At least for me this rule ALWAYS fails and forces me to rewrite

I have to disagree. Adding { return } for me feels like a waste of space, and mixing both expression functions and single-line-return functions in the same file feels messy.
You, for instance, wouldn't mix lambdas and normal function definitions in one place either, even though they do the same thing.

val myFunction: (String) -> Unit = { arg1 ->
    doSomething()
}

fun myFunction(arg1: String) {
    doSomething()
}

It increases cognitive load. I'd be fine if they were all single-line-returns too, but we're a JetBrains Kotlin library, so if we don't follow the guidelines, then who does? :P

But I understand it can be annoying if it fails the build :) For that I can recommend installing the KtLint plugin in the IDE and binding it to the reformatter, Ctrl+Alt+L. It highlights these cases and can fix them with one push of a button, or even upon file-save. Personally, I use it with pleasure, and as a result, the ktLintCheck almost never complains even though I often forget commas, write too long lines etc. And yes, sometimes it's a bit of a back-and-forth between me and the linter, but in the end the code looks clean, consistent, and readable.

@koperagen
Copy link
Collaborator Author

It increases cognitive load. I'd be fine if they were all single-line-returns too, but we're a JetBrains Kotlin library, so if we don't follow the guidelines, then who does? :P

I agree with the following

fun foo(): Int {     // bad
    return 1
}
fun foo() = 1        // good

However in Kotlin anything can be a single expression:

fun foo() = run { 
  // multiple statement go here!
}

Key point why i'd like that function to have a body: a lot of lines of code in the expression. We can keep this rule but split expressions into multiple statements, and technically linter won't complain
For trivial cases when there's only single line or otherwise not much code, always use it sure! Or if you want to rely on type inference, might as well abuse run { } - with Kotlin DataFrame compiler plugin for example.

properties {
preserve(DataFrame::class) // this@properties: TraversePropertiesDsl - always works
}
preserve(B::row) // this@toDataFrame: TraversePropertiesDsl - doesn't
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we deprecate this function here? since it doesn't always work

Copy link
Collaborator

@Jolanrensen Jolanrensen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good increase in robustness :)

ktlint_ignore_back_ticked_identifier = true
ktlint_standard_multiline-expression-wrapping = disabled
ktlint_standard_when-entry-bracing = disabled
ktlint_standard_function-expression-body = disabled
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Turning this off project-wide disables the quick-fix too. I use this quite often :( I'd rather recommend suppressing it on a class/file/function basis if you really need it for clarity. The KtLint plugin has a quick-fix option for suppressing.


private fun KType.classifierOrAny(): KClass<*> = classifier as? KClass<*> ?: Any::class

private fun KClass<*>.properties(): List<KCallable<*>> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd either rewrite it declaratively/functionally, like:

private fun KClass<*>.properties(): List<KCallable<*>> =
    memberProperties
        .filter { it.visibility == KVisibility.PUBLIC }
        // fall back to getter functions for pojo-like classes if no member properties were found
        .ifEmpty {
            memberFunctions
                .filter { it.visibility == KVisibility.PUBLIC && it.isGetterLike() }
        }

or imperatively:

private fun KClass<*>.properties(): List<KCallable<*>> {
    val publicMembers = memberProperties.filter { it.visibility == KVisibility.PUBLIC }
    return if (publicMembers.isEmpty()) {
        // fall back to getter functions for pojo-like classes if no member properties were found
        memberFunctions.filter { it.visibility == KVisibility.PUBLIC && it.isGetterLike() }
    } else {
        publicMembers
    }
}

(or a variant of either). In either case the linter won't complain while sticking to one code paradigm.

@Interpretable("toDataFrameDsl")
public inline fun <reified T> Iterable<T>.toDataFrame(noinline body: CreateDataFrameDsl<T>.() -> Unit): DataFrame<T> =
createDataFrameImpl(T::class, body)
createDataFrameImpl(typeOf<T>(), body)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice! We should really do a sweep of ::class usage in DF. typeOf<T>() is cheaper I believe

class TestItem(val name: String, val containingDeclaration: MyEmptyDeclaration, val test: Int)

@Test
fun `preserve empty interface consistency`() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does it work as well for interfaces, data classes, and objects?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No for data classes - they require at least one property. But in general anything T that has no member properties / getter like functions, here we rely on Kotlin reflection

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Fix all issues with toDataFrame in the library and compiler plugin
3 participants