Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sort divergent elements deterministically #2846

Merged
merged 3 commits into from
Feb 10, 2023
Merged

Conversation

IgnatBeresnev
Copy link
Member

Fixes #2784

Comment on lines +637 to +644
compareBy<Documentable, String?>(nullsLast()) { it.dri.packageName }
.thenBy(nullsFirst()) { it.dri.classNames } // nullsFirst for top level to be first
.thenBy(
nullsLast(
compareBy<Callable> { it.params.size }
.thenBy { it.signature() }
)
) { it.dri.callable }
Copy link
Member Author

@IgnatBeresnev IgnatBeresnev Feb 6, 2023

Choose a reason for hiding this comment

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

There's definitely room for improvement in terms of sorting rules, but it's a start. To me it makes sense to display top level declarations to be on top as they have a wider scope, and to sort vararg-like overloads (like these) in a straircase-like manner - both are implemented.

Tell me if you can think of anything else right off the bat

@@ -187,4 +153,220 @@ class PageNodeMergerTest : BaseAbstractTest() {
}
}
}

@RepeatedTest(3)
fun `should deterministically render same name property extensions`() {
Copy link
Member Author

@IgnatBeresnev IgnatBeresnev Feb 6, 2023

Choose a reason for hiding this comment

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

I covered all the cases I could think of, but if you can think of a corner case that isn't covered or just a scenario you want to check - I'd be happy to add tests for it

* @see DefaultPageCreator.sortDivergentElementsDeterministically for usage
*/
private val divergentDocumentableComparator =
compareBy<Documentable, String?>(nullsLast()) { it.dri.packageName }
Copy link
Member

Choose a reason for hiding this comment

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

It can be confusing that extensions from different packages are placed in the same group. Extensions from different packages have different pages, each own one, and the group title is a link that leads to the page of the first one.

It doesn't block this PR, but a consideration for the further improvement.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, I also noticed it, but couldn't come up with how it can be fixed, other than not grouping such extensions under one block.

Hopefully, one day we will get rid of the left column altogether as planned, but in the meantime I can create an issue for fixing this, if you think it's significant enough.

Copy link
Member

Choose a reason for hiding this comment

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

I haven't seen such case of extensions from different packages in stdlib so far, but it can happen in kotlinx.coroutines once #2845 is implemented.

* @see DefaultPageCreator.sortDivergentElementsDeterministically for usage
*/
private val divergentDocumentableComparator =
compareBy<Documentable, String?>(nullsLast()) { it.dri.packageName }
Copy link
Member

Choose a reason for hiding this comment

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

Why is nullsLast for a package? should the "null" package be first?

Copy link
Member Author

@IgnatBeresnev IgnatBeresnev Feb 8, 2023

Choose a reason for hiding this comment

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

packageName is nullable, so it has to be either first or last :) By default comparison rules in this function, all nulls are last - the explicit use just locks it into place

should the "null" package be first?

If you have a reason for why it should, I don't mind

Copy link
Member

Choose a reason for hiding this comment

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

Null for package means the top level

val topLevel = DRI()

Copy link
Member

Choose a reason for hiding this comment

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

But I have checked we have an empty string for the root package

Copy link
Member

Choose a reason for hiding this comment

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

It seems like the last question here is about null but it's unclear whether nulls are possible at all.

The reasonable outcomes might be the following:

  • Just pick nullsFirst() or nullsLast(), file an issue and investigate whether nulls are possible as packageName
  • Figure out it here and fix accordingly.

Taking into account release pressure, my vote is for the first option

Copy link
Member Author

@IgnatBeresnev IgnatBeresnev Feb 9, 2023

Choose a reason for hiding this comment

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

it's unclear whether nulls are possible at all

Unless we're ready to change the API (i.e make packageName non nullable, mind 133 usages + it's very commonly used public API), I don't think it matters much - we'll still have to account for it one way or another. Unless you're proposing to add !! 🌚

So yeah, can be either first or last, I don't really care. I picked last for consistency with callable, which is also nullable and nullsLast(), but if there's a reason for why it should be first - I don't mind

Copy link
Member

@vmishenev vmishenev Feb 10, 2023

Choose a reason for hiding this comment

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

We have some stubs (as an empty string) for an unnamed package

packageName = classes.lastOrNull()?.qualifiedName?.substringBeforeLast('.', "") ?: "",

If we do not break it, the sort should work (I hope).

So the first option is reasonable for me to merge the PR sooner.

I picked last for consistency with callable

But it is inconsistent with .thenBy(nullsFirst()) { it.dri.classNames } so I dropped attention to this.

Copy link
Member Author

@IgnatBeresnev IgnatBeresnev Feb 10, 2023

Choose a reason for hiding this comment

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

But it is inconsistent with

Yes, that's why .thenBy(nullsFirst()) { it.dri.classNames } has a comment to explain the decision

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Non deterministic sorting of merged extensions/properties
4 participants