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

fix: paginator generator nullability bug caused by documents #1155

Merged
merged 9 commits into from
Sep 27, 2024
5 changes: 5 additions & 0 deletions .changes/f71f083b-6e5f-4a3c-9069-464b1f9f6d36.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"id": "f71f083b-6e5f-4a3c-9069-464b1f9f6d36",
"type": "bugfix",
"description": "Fix paginator generator `List<Document>` nullability"
Copy link
Contributor

Choose a reason for hiding this comment

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

nit/clarification: This change does not only affect List<Document>, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It should only affect List<Document> types because they're the only type of list that can contain nullables without the sparse trait being set on the list. Looking at KotlinSymbolProvider, document is the only symbol that always returns asNullable()

}
Original file line number Diff line number Diff line change
Expand Up @@ -272,18 +272,21 @@ private fun getItemDescriptorOrNull(paginationInfo: PaginationInfo, ctx: Codegen
val itemMember = ctx.model.expectShape(itemMemberId)
val isSparse = itemMember.isSparse
val (collectionLiteral, targetMember) = when (itemMember) {
is MapShape ->
ctx.symbolProvider.toSymbol(itemMember)
.expectProperty(SymbolProperty.ENTRY_EXPRESSION) as String to itemMember
is CollectionShape ->
ctx.symbolProvider.toSymbol(ctx.model.expectShape(itemMember.member.target)).name to ctx.model.expectShape(
itemMember.member.target,
)
is MapShape -> {
val literal = ctx.symbolProvider.toSymbol(itemMember)
.expectProperty(SymbolProperty.ENTRY_EXPRESSION) as String + if (isSparse) "?" else ""
literal to itemMember
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Question: Don't we also need to check if the symbol is nullable alongside the map being sparse?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Contributor

Choose a reason for hiding this comment

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

Can you clarify a little bit? I'm confused why MapShape only checks isSparse while CollectionShape checks symbol.isNullable || isSparse

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, for MapShape the symbol provider provides the symbol via toSymbol and then we use the ENTRY_EXPRESSION system property from the symbol as the literal, and never look at the type that the map targets like in collections.

The ENTRY_EXPRESSION system property is set in the code I linked above. It uses val valueType which uses val valueSuffix to add the ? is the value is nullable.

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think that's what we want. Your new unit test testRenderPaginatorWithSparseDocumentMap shows the problem: It takes a sparse map shape with Document values and codegens a paginator that returns Flow<Map.Entry<String, Document?>?>.

Note that there are two nullability modifiers:

  • one on Document because Document is nullable, which is correct
  • one on Map.Entry<String, Document?> because the map is sparse, which is incorrect

A sparse map does not have missing entries, only missing values. I believe the logic needs to coalesce the Document nullability and the map sparseness into the same spot. In other words, I believe the testRenderPaginatorWithSparseDocumentMap test should be expecting a Flow<Map.Entry<String, Document?>>.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I noticed that too, we've been code-generating them like that so I wasn't 100% sure if it was wrong. The fix is in the PR now.

is CollectionShape -> {
val symbol = ctx.symbolProvider.toSymbol(ctx.model.expectShape(itemMember.member.target))
Copy link
Contributor

Choose a reason for hiding this comment

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

simplification/duplication: ctx.model.expectShape(itemMember.member.target) is used twice, refactor to local value

val literal = symbol.name + if (symbol.isNullable || isSparse) "?" else ""
literal to ctx.model.expectShape(itemMember.member.target)
}
else -> error("Unexpected shape type ${itemMember.type}")
}

return ItemDescriptor(
collectionLiteral + if (isSparse) "?" else "",
collectionLiteral,
targetMember,
itemLiteral,
itemPathLiteral,
Expand Down
Loading