Skip to content

Commit 9573c9a

Browse files
committed
Make data schema declarations nested to their root to minimize potential name conflicts for "domain" names
1 parent c695749 commit 9573c9a

5 files changed

Lines changed: 312 additions & 71 deletions

File tree

core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/codeGen/CodeGeneratorImpl.kt

Lines changed: 100 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ import org.jetbrains.kotlinx.dataframe.codeGen.TypeCastGenerator
3434
import org.jetbrains.kotlinx.dataframe.codeGen.ValidFieldName
3535
import org.jetbrains.kotlinx.dataframe.codeGen.toNullable
3636
import org.jetbrains.kotlinx.dataframe.columns.ColumnGroup
37-
import org.jetbrains.kotlinx.dataframe.columns.ColumnPath
3837
import org.jetbrains.kotlinx.dataframe.impl.toSnakeCase
3938
import org.jetbrains.kotlinx.dataframe.keywords.HardKeywords
4039
import org.jetbrains.kotlinx.dataframe.keywords.ModifierKeywords
@@ -502,83 +501,131 @@ internal class CodeGeneratorImpl(typeRendering: TypeRenderingStrategy = FullyQua
502501
nestedMarkerNameProvider,
503502
)
504503
val marker = context.process(schema, isOpen, visibility)
505-
val declarations = mutableListOf<Code>()
506-
context.generatedMarkers.forEach { itMarker ->
507-
val declaration = if (asDataClass) {
508-
generateClasses(itMarker)
509-
} else {
510-
generateInterface(itMarker, fields, readDfMethod.takeIf { marker == itMarker })
511-
}
512-
declarations.add(declaration)
513-
if (extensionProperties) {
504+
// see [org.jetbrains.kotlinx.dataframe.codeGen.MatchSchemeTests.marker is reused]
505+
val code = if (extensionProperties) {
506+
val declarations = mutableListOf<Code>()
507+
context.generatedMarkers.forEach { itMarker ->
508+
val declaration = if (asDataClass) {
509+
generateClasses(itMarker)
510+
} else {
511+
generateInterface(itMarker, fields, readDfMethod = readDfMethod.takeIf { marker == itMarker })
512+
}
513+
declarations.add(declaration)
514514
declarations.add(generateExtensionProperties(itMarker, withNullable = false))
515515
}
516+
createCodeWithTypeCastGenerator(declarations.joinToString("\n\n"), marker.name)
517+
} else if (context.generatedMarkers.isEmpty()) {
518+
createCodeWithTypeCastGenerator("", marker.name)
519+
} else {
520+
val nested = context.generatedMarkers.filterNot { it == marker }
521+
.map { itMarker ->
522+
if (asDataClass) {
523+
generateClasses(itMarker)
524+
} else {
525+
generateInterface(itMarker, fields)
526+
}
527+
}
528+
529+
val rootDeclaration = if (asDataClass) {
530+
generateClasses(marker, nested, readDfMethod)
531+
} else {
532+
generateInterface(marker, fields, nested, readDfMethod)
533+
}
534+
createCodeWithTypeCastGenerator(rootDeclaration, marker.name)
516535
}
517-
val code = createCodeWithTypeCastGenerator(declarations.joinToString("\n\n"), marker.name)
536+
518537
return CodeGenResult(code, context.generatedMarkers)
519538
}
520539

521-
private fun generateInterface(marker: Marker, fields: Boolean, readDfMethod: DefaultReadDfMethod? = null): Code {
540+
private fun generateInterface(
541+
marker: Marker,
542+
fields: Boolean,
543+
nested: List<Code> = emptyList(),
544+
readDfMethod: DefaultReadDfMethod? = null,
545+
): Code {
522546
val annotationName = DataSchema::class.simpleName
523-
524547
val visibility = renderTopLevelDeclarationVisibility(marker)
525548
val propertyVisibility = renderInternalDeclarationVisibility(marker)
526549

527550
val header =
528551
"@$annotationName${if (marker.isOpen) "" else "(isOpen = false)"}\n${visibility}interface ${marker.name}"
529-
val baseInterfacesDeclaration =
530-
if (marker.superMarkers.isNotEmpty()) {
531-
" : " + marker.superMarkers
532-
.map { it.value.name + it.value.typeArguments }
533-
.joinToString()
534-
} else {
535-
""
536-
}
537-
val resultDeclarations = mutableListOf<String>()
538-
539-
val fieldsDeclaration = if (fields) renderFields(marker, propertyVisibility).join() else ""
552+
val baseInterfaces = if (marker.superMarkers.isNotEmpty()) {
553+
" : " + marker.superMarkers.values.joinToString { it.name + it.typeArguments }
554+
} else {
555+
""
556+
}
540557

541-
val readDfMethodDeclaration = readDfMethod?.toDeclaration(marker, propertyVisibility)
558+
val fieldsCode = (if (fields) renderFields(marker, propertyVisibility) else emptyList()).joinToString("\n")
542559

543-
val body = if (fieldsDeclaration.isNotBlank() || readDfMethodDeclaration?.isNotBlank() == true) {
560+
val body = if (fieldsCode.isNotEmpty() || nested.isNotEmpty() || readDfMethod != null) {
544561
buildString {
545562
append(" {\n")
546-
append(fieldsDeclaration)
547-
if (readDfMethodDeclaration != null) {
548-
append("\n")
549-
val companionObject = buildCodeBlock {
550-
add(" ")
551-
indent()
552-
indent()
553-
add(readDfMethodDeclaration)
554-
}
555-
append(companionObject.toString())
556-
}
563+
appendNestedDeclarations(
564+
fieldsCode = fieldsCode.takeIf { it.isNotEmpty() },
565+
nestedDeclarationsCode = nested,
566+
readDfMethod?.let { renderReadDfMethod(it, marker, propertyVisibility) },
567+
)
557568
append("\n}")
558569
}
559570
} else {
560571
" { }"
561572
}
562-
resultDeclarations.add(header + baseInterfacesDeclaration + body)
563-
return resultDeclarations.join()
573+
return header + baseInterfaces + body
564574
}
565575

566-
private fun generateClasses(marker: Marker): Code {
576+
private fun generateClasses(
577+
marker: Marker,
578+
nested: List<Code> = emptyList(),
579+
readDfMethod: DefaultReadDfMethod? = null,
580+
): Code {
567581
val annotationName = DataSchema::class.simpleName
568-
569582
val visibility = renderTopLevelDeclarationVisibility(marker)
570583
val propertyVisibility = renderInternalDeclarationVisibility(marker)
571-
val header =
572-
"@$annotationName\n${visibility}data class ${marker.name}("
573584

574-
val fieldsDeclaration = renderFields(marker, propertyVisibility).joinToString(",\n")
585+
val header = "@$annotationName\n${visibility}data class ${marker.name}("
586+
val fieldsCode = renderFields(marker, propertyVisibility).joinToString(",\n")
587+
575588
return buildString {
576589
appendLine(header)
577-
appendLine(fieldsDeclaration)
590+
appendLine(fieldsCode)
578591
append(")")
592+
593+
if (nested.isNotEmpty() || readDfMethod != null) {
594+
append(" {\n")
595+
appendNestedDeclarations(
596+
fieldsCode = null,
597+
nested,
598+
readDfMethod?.let {
599+
renderReadDfMethod(it, marker, propertyVisibility)
600+
},
601+
)
602+
append("\n}")
603+
}
579604
}
580605
}
581606

607+
private fun StringBuilder.appendNestedDeclarations(
608+
fieldsCode: Code? = null,
609+
nestedDeclarationsCode: List<Code>,
610+
readDfMethodCode: Code?,
611+
) {
612+
val contents = mutableListOf<String>()
613+
614+
if (fieldsCode != null) {
615+
contents += fieldsCode
616+
}
617+
618+
if (nestedDeclarationsCode.isNotEmpty()) {
619+
contents += nestedDeclarationsCode.joinToString("\n\n") { it.prependIndent(" ") }
620+
}
621+
622+
if (readDfMethodCode != null) {
623+
contents += readDfMethodCode
624+
}
625+
626+
append(contents.joinToString("\n\n"))
627+
}
628+
582629
private fun renderFields(marker: Marker, propertyVisibility: String): List<String> =
583630
marker.fields.map {
584631
val override = if (it.overrides) "override " else ""
@@ -591,6 +638,14 @@ internal class CodeGeneratorImpl(typeRendering: TypeRenderingStrategy = FullyQua
591638
val fieldType = it.renderFieldType()
592639
"$columnNameAnnotation ${propertyVisibility}${override}val ${it.fieldName.quotedIfNeeded}: $fieldType"
593640
}
641+
642+
private fun renderReadDfMethod(readDfMethod: DefaultReadDfMethod, marker: Marker, propertyVisibility: String) =
643+
buildCodeBlock {
644+
add(" ")
645+
indent()
646+
indent()
647+
add(readDfMethod.toDeclaration(marker, propertyVisibility))
648+
}.toString()
594649
}
595650

596651
public fun CodeWithTypeCastGenerator.toStandaloneSnippet(packageName: String, additionalImports: List<String>): String =

core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/codeGen/SchemaProcessorImpl.kt

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -57,18 +57,24 @@ internal class SchemaProcessorImpl(
5757
return result
5858
}
5959

60-
private fun generateUniqueMarkerClassName(prefix: String): String {
61-
if (!usedMarkerNames.contains(prefix)) return prefix
60+
private fun generateUniqueMarkerClassName(prefix: String, contextNames: ContextNames = emptySet()): String {
61+
fun isReserved(name: String) = usedMarkerNames.contains(name) || contextNames.contains(name)
62+
63+
if (!isReserved(prefix)) return prefix
6264
var id = 1
63-
while (usedMarkerNames.contains("$prefix$id")) id++
65+
while (isReserved("$prefix$id")) id++
6466
return "$prefix$id"
6567
}
6668

69+
// for example, properties declared inside the class will conflict with nested classes with the same name.
70+
private typealias ContextNames = Set<String>
71+
6772
private fun generateFields(
6873
schema: DataFrameSchema,
6974
visibility: MarkerVisibility,
7075
requiredSuperMarkers: List<Marker> = emptyList(),
7176
parentPath: ColumnPath = pathOf(),
77+
contextNames: ContextNames,
7278
): List<GeneratedField> {
7379
val usedFieldNames =
7480
requiredSuperMarkers.flatMap { it.allFields.map { it.fieldName.quotedIfNeeded } }.toMutableSet()
@@ -80,13 +86,13 @@ internal class SchemaProcessorImpl(
8086

8187
is ColumnSchema.Group ->
8288
FieldType.GroupFieldType(
83-
process(columnSchema.schema, false, visibility, parentPath + columnName).name,
89+
process(columnSchema.schema, false, visibility, parentPath + columnName, contextNames).name,
8490
renderAsObject = true,
8591
)
8692

8793
is ColumnSchema.Frame ->
8894
FieldType.FrameFieldType(
89-
process(columnSchema.schema, false, visibility, parentPath + columnName).name,
95+
process(columnSchema.schema, false, visibility, parentPath + columnName, contextNames).name,
9096
columnSchema.nullable,
9197
renderAsList = true,
9298
)
@@ -127,6 +133,7 @@ internal class SchemaProcessorImpl(
127133
isOpen: Boolean,
128134
visibility: MarkerVisibility,
129135
parentPath: ColumnPath = pathOf(),
136+
contextNames: ContextNames,
130137
): Marker {
131138
val baseMarkers = mutableListOf<Marker>()
132139
val fields = if (withBaseInterfaces) {
@@ -154,23 +161,24 @@ internal class SchemaProcessorImpl(
154161
}
155162
}
156163
}
157-
generateFields(scheme, visibility, baseMarkers, parentPath)
164+
generateFields(scheme, visibility, baseMarkers, parentPath, contextNames)
158165
} else {
159-
generateFields(scheme, visibility, parentPath = parentPath)
166+
generateFields(scheme, visibility, parentPath = parentPath, contextNames = contextNames)
160167
}
161168
return Marker(name, isOpen, fields, baseMarkers.onlyLeafs(), visibility, emptyList(), emptyList())
162169
}
163170

164171
private fun DataFrameSchema.getRequiredMarkers() = registeredMarkers.filterRequiredForSchema(this)
165172

166173
override fun process(schema: DataFrameSchema, isOpen: Boolean, visibility: MarkerVisibility): Marker =
167-
process(schema, isOpen, visibility, columnPath = pathOf())
174+
process(schema, isOpen, visibility, columnPath = pathOf(), reservedNames = schema.columns.keys)
168175

169176
internal fun process(
170177
schema: DataFrameSchema,
171178
isOpen: Boolean,
172179
visibility: MarkerVisibility,
173180
columnPath: ColumnPath,
181+
reservedNames: ContextNames,
174182
): Marker {
175183
val required = schema.getRequiredMarkers()
176184
val existingMarker = registeredMarkers.firstOrNull {
@@ -185,9 +193,10 @@ internal class SchemaProcessorImpl(
185193

186194
else -> namePrefix
187195
}
188-
val markerName = generateUniqueMarkerClassName(baseName)
196+
val markerName =
197+
generateUniqueMarkerClassName(baseName, if (columnPath.isNotEmpty()) reservedNames else emptySet())
189198
usedMarkerNames.add(markerName)
190-
val marker = createMarkerSchema(schema, markerName, true, isOpen, visibility, columnPath)
199+
val marker = createMarkerSchema(schema, markerName, true, isOpen, visibility, columnPath, reservedNames)
191200
registeredMarkers.add(marker)
192201
generatedMarkers.add(marker)
193202
return marker

0 commit comments

Comments
 (0)