• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2015 Square, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * https://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.squareup.kotlinpoet
17 
18 import java.io.Closeable
19 import kotlin.math.min
20 
21 /** Sentinel value that indicates that no user-provided package has been set.  */
22 private val NO_PACKAGE = String()
23 
24 internal val NULLABLE_ANY = ANY.copy(nullable = true)
25 
26 private fun extractMemberName(part: String): String {
27   require(Character.isJavaIdentifierStart(part[0])) { "not an identifier: $part" }
28   for (i in 1..part.length) {
29     if (!part.substring(0, i).isIdentifier) {
30       return part.substring(0, i - 1)
31     }
32   }
33   return part
34 }
35 
buildCodeStringnull36 internal inline fun buildCodeString(builderAction: CodeWriter.() -> Unit): String {
37   val stringBuilder = StringBuilder()
38   CodeWriter(stringBuilder, columnLimit = Integer.MAX_VALUE).use {
39     it.builderAction()
40   }
41   return stringBuilder.toString()
42 }
43 
buildCodeStringnull44 internal fun buildCodeString(
45   codeWriter: CodeWriter,
46   builderAction: CodeWriter.() -> Unit,
47 ): String {
48   val stringBuilder = StringBuilder()
49   codeWriter.emitInto(stringBuilder, builderAction)
50   return stringBuilder.toString()
51 }
52 
53 /**
54  * Converts a [FileSpec] to a string suitable to both human- and kotlinc-consumption. This honors
55  * imports, indentation, and deferred variable names.
56  */
57 internal class CodeWriter constructor(
58   out: Appendable,
59   private val indent: String = DEFAULT_INDENT,
60   imports: Map<String, Import> = emptyMap(),
61   private val importedTypes: Map<String, ClassName> = emptyMap(),
62   private val importedMembers: Map<String, MemberName> = emptyMap(),
63   columnLimit: Int = 100,
64 ) : Closeable {
65   private var out = LineWrapper(out, indent, columnLimit)
66   private var indentLevel = 0
67 
68   private var kdoc = false
69   private var comment = false
70   private var packageName = NO_PACKAGE
71   private val typeSpecStack = mutableListOf<TypeSpec>()
72   private val memberImportNames = mutableSetOf<String>()
<lambda>null73   private val importableTypes = mutableMapOf<String, List<ClassName>>().withDefault { emptyList() }
<lambda>null74   private val importableMembers = mutableMapOf<String, List<MemberName>>().withDefault { emptyList() }
75   private val referencedNames = mutableSetOf<String>()
76   private var trailingNewline = false
77 
<lambda>null78   val imports = imports.also {
79     for ((memberName, _) in imports) {
80       val lastDotIndex = memberName.lastIndexOf('.')
81       if (lastDotIndex >= 0) {
82         memberImportNames.add(memberName.substring(0, lastDotIndex))
83       }
84     }
85   }
86 
87   /**
88    * When emitting a statement, this is the line of the statement currently being written. The first
89    * line of a statement is indented normally and subsequent wrapped lines are double-indented. This
90    * is -1 when the currently-written line isn't part of a statement.
91    */
92   var statementLine = -1
93 
<lambda>null94   fun indent(levels: Int = 1) = apply {
95     indentLevel += levels
96   }
97 
<lambda>null98   fun unindent(levels: Int = 1) = apply {
99     require(indentLevel - levels >= 0) { "cannot unindent $levels from $indentLevel" }
100     indentLevel -= levels
101   }
102 
<lambda>null103   fun pushPackage(packageName: String) = apply {
104     check(this.packageName === NO_PACKAGE) { "package already set: ${this.packageName}" }
105     this.packageName = packageName
106   }
107 
<lambda>null108   fun popPackage() = apply {
109     check(packageName !== NO_PACKAGE) { "package already set: $packageName" }
110     packageName = NO_PACKAGE
111   }
112 
<lambda>null113   fun pushType(type: TypeSpec) = apply {
114     this.typeSpecStack.add(type)
115   }
116 
<lambda>null117   fun popType() = apply {
118     this.typeSpecStack.removeAt(typeSpecStack.size - 1)
119   }
120 
emitCommentnull121   fun emitComment(codeBlock: CodeBlock) {
122     trailingNewline = true // Force the '//' prefix for the comment.
123     comment = true
124     try {
125       emitCode(codeBlock)
126       emit("\n")
127     } finally {
128       comment = false
129     }
130   }
131 
emitKdocnull132   fun emitKdoc(kdocCodeBlock: CodeBlock) {
133     if (kdocCodeBlock.isEmpty()) return
134 
135     emit("/**\n")
136     kdoc = true
137     try {
138       emitCode(kdocCodeBlock, ensureTrailingNewline = true)
139     } finally {
140       kdoc = false
141     }
142     emit(" */\n")
143   }
144 
emitAnnotationsnull145   fun emitAnnotations(annotations: List<AnnotationSpec>, inline: Boolean) {
146     for (annotationSpec in annotations) {
147       annotationSpec.emit(this, inline)
148       emit(if (inline) " " else "\n")
149     }
150   }
151 
152   /**
153    * Emits `modifiers` in the standard order. Modifiers in `implicitModifiers` will not
154    * be emitted except for [KModifier.PUBLIC]
155    */
emitModifiersnull156   fun emitModifiers(
157     modifiers: Set<KModifier>,
158     implicitModifiers: Set<KModifier> = emptySet(),
159   ) {
160     if (shouldEmitPublicModifier(modifiers, implicitModifiers)) {
161       emit(KModifier.PUBLIC.keyword)
162       emit(" ")
163     }
164     val uniqueNonPublicExplicitOnlyModifiers =
165       modifiers
166         .filterNot { it == KModifier.PUBLIC }
167         .filterNot { implicitModifiers.contains(it) }
168         .toEnumSet()
169     for (modifier in uniqueNonPublicExplicitOnlyModifiers) {
170       emit(modifier.keyword)
171       emit(" ")
172     }
173   }
174 
175   /**
176    * Emits the `context` block for [contextReceivers].
177    */
emitContextReceiversnull178   fun emitContextReceivers(contextReceivers: List<TypeName>, suffix: String = "") {
179     if (contextReceivers.isNotEmpty()) {
180       val receivers = contextReceivers
181         .map { CodeBlock.of("%T", it) }
182         .joinToCode(prefix = "context(", suffix = ")")
183       emitCode(receivers)
184       emit(suffix)
185     }
186   }
187 
188   /**
189    * Emit type variables with their bounds. If a type variable has more than a single bound - call
190    * [emitWhereBlock] with same input to produce an additional `where` block.
191    *
192    * This should only be used when declaring type variables; everywhere else bounds are omitted.
193    */
emitTypeVariablesnull194   fun emitTypeVariables(typeVariables: List<TypeVariableName>) {
195     if (typeVariables.isEmpty()) return
196 
197     emit("<")
198     typeVariables.forEachIndexed { index, typeVariable ->
199       if (index > 0) emit(", ")
200       if (typeVariable.variance != null) {
201         emit("${typeVariable.variance.keyword} ")
202       }
203       if (typeVariable.isReified) {
204         emit("reified ")
205       }
206       emitCode("%L", typeVariable.name)
207       if (typeVariable.bounds.size == 1 && typeVariable.bounds[0] != NULLABLE_ANY) {
208         emitCode(" : %T", typeVariable.bounds[0])
209       }
210     }
211     emit(">")
212   }
213 
214   /**
215    * Emit a `where` block containing type bounds for each type variable that has at least two
216    * bounds.
217    */
emitWhereBlocknull218   fun emitWhereBlock(typeVariables: List<TypeVariableName>) {
219     if (typeVariables.isEmpty()) return
220 
221     var firstBound = true
222     for (typeVariable in typeVariables) {
223       if (typeVariable.bounds.size > 1) {
224         for (bound in typeVariable.bounds) {
225           if (!firstBound) emitCode(", ") else emitCode(" where ")
226           emitCode("%L : %T", typeVariable.name, bound)
227           firstBound = false
228         }
229       }
230     }
231   }
232 
emitCodenull233   fun emitCode(s: String) = emitCode(CodeBlock.of(s))
234 
235   fun emitCode(format: String, vararg args: Any?) = emitCode(CodeBlock.of(format, *args))
236 
237   fun emitCode(
238     codeBlock: CodeBlock,
239     isConstantContext: Boolean = false,
240     ensureTrailingNewline: Boolean = false,
241   ) = apply {
242     var a = 0
243     var deferredTypeName: ClassName? = null // used by "import static" logic
244     val partIterator = codeBlock.formatParts.listIterator()
245     while (partIterator.hasNext()) {
246       when (val part = partIterator.next()) {
247         "%L" -> emitLiteral(codeBlock.args[a++], isConstantContext)
248 
249         "%N" -> emit(codeBlock.args[a++] as String)
250 
251         "%S" -> {
252           val string = codeBlock.args[a++] as String?
253           // Emit null as a literal null: no quotes.
254           val literal = if (string != null) {
255             stringLiteralWithQuotes(
256               string,
257               isInsideRawString = false,
258               isConstantContext = isConstantContext,
259             )
260           } else {
261             "null"
262           }
263           emit(literal, nonWrapping = true)
264         }
265 
266         "%P" -> {
267           val string = codeBlock.args[a++]?.let { arg ->
268             if (arg is CodeBlock) {
269               arg.toString(this@CodeWriter)
270             } else {
271               arg as String?
272             }
273           }
274           // Emit null as a literal null: no quotes.
275           val literal = if (string != null) {
276             stringLiteralWithQuotes(
277               string,
278               isInsideRawString = true,
279               isConstantContext = isConstantContext,
280             )
281           } else {
282             "null"
283           }
284           emit(literal, nonWrapping = true)
285         }
286 
287         "%T" -> {
288           var typeName = codeBlock.args[a++] as TypeName
289           if (typeName.isAnnotated) {
290             typeName.emitAnnotations(this)
291             typeName = typeName.copy(annotations = emptyList())
292           }
293           // defer "typeName.emit(this)" if next format part will be handled by the default case
294           var defer = false
295           if (typeName is ClassName && partIterator.hasNext()) {
296             if (!codeBlock.formatParts[partIterator.nextIndex()].startsWith("%")) {
297               val candidate = typeName
298               if (candidate.canonicalName in memberImportNames) {
299                 check(deferredTypeName == null) { "pending type for static import?!" }
300                 deferredTypeName = candidate
301                 defer = true
302               }
303             }
304           }
305           if (!defer) typeName.emit(this)
306           typeName.emitNullable(this)
307         }
308 
309         "%M" -> {
310           val memberName = codeBlock.args[a++] as MemberName
311           memberName.emit(this)
312         }
313 
314         "%%" -> emit("%")
315 
316         "⇥" -> indent()
317 
318         "⇤" -> unindent()
319 
320         "«" -> {
321           check(statementLine == -1) {
322             """
323             |Can't open a new statement until the current statement is closed (opening « followed
324             |by another « without a closing »).
325             |Current code block:
326             |- Format parts: ${codeBlock.formatParts.map(::escapeCharacterLiterals)}
327             |- Arguments: ${codeBlock.args}
328             |
329             """.trimMargin()
330           }
331           statementLine = 0
332         }
333 
334         "»" -> {
335           check(statementLine != -1) {
336             """
337             |Can't close a statement that hasn't been opened (closing » is not preceded by an
338             |opening «).
339             |Current code block:
340             |- Format parts: ${codeBlock.formatParts.map(::escapeCharacterLiterals)}
341             |- Arguments: ${codeBlock.args}
342             |
343             """.trimMargin()
344           }
345           if (statementLine > 0) {
346             unindent(2) // End a multi-line statement. Decrease the indentation level.
347           }
348           statementLine = -1
349         }
350 
351         else -> {
352           // Handle deferred type.
353           var doBreak = false
354           if (deferredTypeName != null) {
355             if (part.startsWith(".")) {
356               if (emitStaticImportMember(deferredTypeName.canonicalName, part)) {
357                 // Okay, static import hit and all was emitted, so clean-up and jump to next part.
358                 deferredTypeName = null
359                 doBreak = true
360               }
361             }
362             if (!doBreak) {
363               deferredTypeName!!.emit(this)
364               deferredTypeName = null
365             }
366           }
367           if (!doBreak) {
368             emit(part)
369           }
370         }
371       }
372     }
373     if (ensureTrailingNewline && out.hasPendingSegments) {
374       emit("\n")
375     }
376   }
377 
emitStaticImportMembernull378   private fun emitStaticImportMember(canonical: String, part: String): Boolean {
379     val partWithoutLeadingDot = part.substring(1)
380     if (partWithoutLeadingDot.isEmpty()) return false
381     val first = partWithoutLeadingDot[0]
382     if (!Character.isJavaIdentifierStart(first)) return false
383     val explicit = imports[canonical + "." + extractMemberName(partWithoutLeadingDot)]
384     if (explicit != null) {
385       if (explicit.alias != null) {
386         val memberName = extractMemberName(partWithoutLeadingDot)
387         emit(partWithoutLeadingDot.replaceFirst(memberName, explicit.alias))
388       } else {
389         emit(partWithoutLeadingDot)
390       }
391       return true
392     }
393     return false
394   }
395 
emitLiteralnull396   private fun emitLiteral(o: Any?, isConstantContext: Boolean) {
397     when (o) {
398       is TypeSpec -> o.emit(this, null)
399       is AnnotationSpec -> o.emit(this, inline = true, asParameter = isConstantContext)
400       is PropertySpec -> o.emit(this, emptySet())
401       is FunSpec -> o.emit(
402         codeWriter = this,
403         enclosingName = null,
404         implicitModifiers = setOf(KModifier.PUBLIC),
405         includeKdocTags = true,
406       )
407       is TypeAliasSpec -> o.emit(this)
408       is CodeBlock -> emitCode(o, isConstantContext = isConstantContext)
409       else -> emit(o.toString())
410     }
411   }
412 
413   /**
414    * Returns the best name to identify `className` with in the current context. This uses the
415    * available imports and the current scope to find the shortest name available. It does not honor
416    * names visible due to inheritance.
417    */
lookupNamenull418   fun lookupName(className: ClassName): String {
419     // Find the shortest suffix of className that resolves to className. This uses both local type
420     // names (so `Entry` in `Map` refers to `Map.Entry`). Also uses imports.
421     var nameResolved = false
422     var c: ClassName? = className
423     while (c != null) {
424       val alias = imports[c.canonicalName]?.alias
425       val simpleName = alias ?: c.simpleName
426       val resolved = resolve(simpleName)
427       nameResolved = resolved != null
428 
429       // We don't care about nullability and type annotations here, as it's irrelevant for imports.
430       if (resolved == c.copy(nullable = false, annotations = emptyList())) {
431         if (alias == null) {
432           referencedNames.add(className.topLevelClassName().simpleName)
433         }
434         val nestedClassNames = className.simpleNames.subList(
435           c.simpleNames.size,
436           className.simpleNames.size,
437         ).joinToString(".")
438         return "$simpleName.$nestedClassNames"
439       }
440       c = c.enclosingClassName()
441     }
442 
443     // If the name resolved but wasn't a match, we're stuck with the fully qualified name.
444     if (nameResolved) {
445       return className.canonicalName
446     }
447 
448     // If the class is in the same package and there's no import alias for that class, we're done.
449     if (packageName == className.packageName && imports[className.canonicalName]?.alias == null) {
450       referencedNames.add(className.topLevelClassName().simpleName)
451       return className.simpleNames.joinToString(".")
452     }
453 
454     // We'll have to use the fully-qualified name. Mark the type as importable for a future pass.
455     if (!kdoc) {
456       importableType(className)
457     }
458 
459     return className.canonicalName
460   }
461 
lookupNamenull462   fun lookupName(memberName: MemberName): String {
463     val simpleName = imports[memberName.canonicalName]?.alias ?: memberName.simpleName
464     // Match an imported member.
465     val importedMember = importedMembers[simpleName]
466     if (importedMember == memberName) {
467       return simpleName
468     } else if (importedMember != null && memberName.enclosingClassName != null) {
469       val enclosingClassName = lookupName(memberName.enclosingClassName)
470       return "$enclosingClassName.$simpleName"
471     }
472 
473     // If the member is in the same package, we're done.
474     if (packageName == memberName.packageName && memberName.enclosingClassName == null) {
475       referencedNames.add(memberName.simpleName)
476       return memberName.simpleName
477     }
478 
479     // We'll have to use the fully-qualified name.
480     // Mark the member as importable for a future pass unless the name clashes with
481     // a method in the current context
482     if (!kdoc && (
483         memberName.isExtension ||
484           !isMethodNameUsedInCurrentContext(memberName.simpleName)
485         )
486     ) {
487       importableMember(memberName)
488     }
489 
490     return memberName.canonicalName
491   }
492 
493   // TODO(luqasn): also honor superclass members when resolving names.
isMethodNameUsedInCurrentContextnull494   private fun isMethodNameUsedInCurrentContext(simpleName: String): Boolean {
495     for (it in typeSpecStack.reversed()) {
496       if (it.funSpecs.any { it.name == simpleName }) {
497         return true
498       }
499       if (!it.modifiers.contains(KModifier.INNER)) {
500         break
501       }
502     }
503     return false
504   }
505 
importableTypenull506   private fun importableType(className: ClassName) {
507     val topLevelClassName = className.topLevelClassName()
508     val simpleName = imports[className.canonicalName]?.alias ?: topLevelClassName.simpleName
509     // Check for name clashes with members.
510     if (simpleName !in importableMembers) {
511       importableTypes[simpleName] = importableTypes.getValue(simpleName) + topLevelClassName
512     }
513   }
514 
importableMembernull515   private fun importableMember(memberName: MemberName) {
516     if (memberName.packageName.isNotEmpty()) {
517       val simpleName = imports[memberName.canonicalName]?.alias ?: memberName.simpleName
518       // Check for name clashes with types.
519       if (simpleName !in importableTypes) {
520         importableMembers[simpleName] = importableMembers.getValue(simpleName) + memberName
521       }
522     }
523   }
524 
525   /**
526    * Returns the class or enum value referenced by `simpleName`, using the current nesting context and
527    * imports.
528    */
529   // TODO(jwilson): also honor superclass members when resolving names.
resolvenull530   private fun resolve(simpleName: String): ClassName? {
531     // Match a child of the current (potentially nested) class.
532     for (i in typeSpecStack.indices.reversed()) {
533       val typeSpec = typeSpecStack[i]
534       if (simpleName in typeSpec.nestedTypesSimpleNames) {
535         return stackClassName(i, simpleName)
536       }
537     }
538 
539     if (typeSpecStack.size > 0) {
540       val typeSpec = typeSpecStack[0]
541       if (typeSpec.name == simpleName) {
542         // Match the top-level class.
543         return ClassName(packageName, simpleName)
544       }
545       if (typeSpec.isEnum && typeSpec.enumConstants.keys.contains(simpleName)) {
546         // Match a top level enum value.
547         // Enum values are not proper classes but can still be modeled using ClassName.
548         return ClassName(packageName, typeSpec.name!!).nestedClass(simpleName)
549       }
550     }
551 
552     // Match an imported type.
553     val importedType = importedTypes[simpleName]
554     if (importedType != null) return importedType
555 
556     // No match.
557     return null
558   }
559 
560   /** Returns the class named `simpleName` when nested in the class at `stackDepth`.  */
stackClassNamenull561   private fun stackClassName(stackDepth: Int, simpleName: String): ClassName {
562     var className = ClassName(packageName, typeSpecStack[0].name!!)
563     for (i in 1..stackDepth) {
564       className = className.nestedClass(typeSpecStack[i].name!!)
565     }
566     return className.nestedClass(simpleName)
567   }
568 
569   /**
570    * Emits `s` with indentation as required. It's important that all code that writes to
571    * [CodeWriter.out] does it through here, since we emit indentation lazily in order to avoid
572    * unnecessary trailing whitespace.
573    */
<lambda>null574   fun emit(s: String, nonWrapping: Boolean = false) = apply {
575     var first = true
576     for (line in s.split('\n')) {
577       // Emit a newline character. Make sure blank lines in KDoc & comments look good.
578       if (!first) {
579         if ((kdoc || comment) && trailingNewline) {
580           emitIndentation()
581           out.appendNonWrapping(if (kdoc) " *" else "//")
582         }
583         out.newline()
584         trailingNewline = true
585         if (statementLine != -1) {
586           if (statementLine == 0) {
587             indent(2) // Begin multiple-line statement. Increase the indentation level.
588           }
589           statementLine++
590         }
591       }
592 
593       first = false
594       if (line.isEmpty()) continue // Don't indent empty lines.
595 
596       // Emit indentation and comment prefix if necessary.
597       if (trailingNewline) {
598         emitIndentation()
599         if (kdoc) {
600           out.appendNonWrapping(" * ")
601         } else if (comment) {
602           out.appendNonWrapping("// ")
603         }
604       }
605 
606       if (nonWrapping) {
607         out.appendNonWrapping(line)
608       } else {
609         out.append(
610           line,
611           indentLevel = if (kdoc) indentLevel else indentLevel + 2,
612           linePrefix = if (kdoc) " * " else "",
613         )
614       }
615       trailingNewline = false
616     }
617   }
618 
emitIndentationnull619   private fun emitIndentation() {
620     for (j in 0..<indentLevel) {
621       out.appendNonWrapping(indent)
622     }
623   }
624 
625   /**
626    * Returns whether a [KModifier.PUBLIC] should be emitted.
627    *
628    * If [modifiers] contains [KModifier.PUBLIC], this method always returns `true`.
629    *
630    * Otherwise, this will return `true` when [KModifier.PUBLIC] is one of the [implicitModifiers]
631    * and there are no other opposing modifiers (like [KModifier.PROTECTED] etc.) supplied by the
632    * consumer in [modifiers].
633    */
shouldEmitPublicModifiernull634   private fun shouldEmitPublicModifier(
635     modifiers: Set<KModifier>,
636     implicitModifiers: Set<KModifier>,
637   ): Boolean {
638     if (modifiers.contains(KModifier.PUBLIC)) {
639       return true
640     }
641 
642     if (implicitModifiers.contains(KModifier.PUBLIC) && modifiers.contains(KModifier.OVERRIDE)) {
643       return false
644     }
645 
646     if (!implicitModifiers.contains(KModifier.PUBLIC)) {
647       return false
648     }
649 
650     val hasOtherConsumerSpecifiedVisibility =
651       modifiers.containsAnyOf(KModifier.PRIVATE, KModifier.INTERNAL, KModifier.PROTECTED)
652 
653     return !hasOtherConsumerSpecifiedVisibility
654   }
655 
656   /**
657    * Returns the types that should have been imported for this code. If there were any simple name
658    * collisions, import aliases will be generated.
659    */
suggestedTypeImportsnull660   private fun suggestedTypeImports(): Map<String, Set<ClassName>> {
661     return importableTypes.filterKeys { it !in referencedNames }.mapValues { it.value.toSet() }
662   }
663 
664   /**
665    * Returns the members that should have been imported for this code. If there were any simple name
666    * collisions, import aliases will be generated.
667    */
suggestedMemberImportsnull668   private fun suggestedMemberImports(): Map<String, Set<MemberName>> {
669     return importableMembers.filterKeys { it !in referencedNames }.mapValues { it.value.toSet() }
670   }
671 
672   /**
673    * Perform emitting actions on the current [CodeWriter] using a custom [Appendable]. The
674    * [CodeWriter] will continue using the old [Appendable] after this method returns.
675    */
emitIntonull676   inline fun emitInto(out: Appendable, action: CodeWriter.() -> Unit) {
677     val codeWrapper = this
678     LineWrapper(out, indent = DEFAULT_INDENT, columnLimit = Int.MAX_VALUE).use { newOut ->
679       val oldOut = codeWrapper.out
680       codeWrapper.out = newOut
681       action()
682       codeWrapper.out = oldOut
683     }
684   }
685 
closenull686   override fun close() {
687     out.close()
688   }
689 
690   companion object {
691     /**
692      * Makes a pass to collect imports by executing [emitStep], and returns an instance of
693      * [CodeWriter] pre-initialized with collected imports.
694      */
withCollectedImportsnull695     fun withCollectedImports(
696       out: Appendable,
697       indent: String,
698       memberImports: Map<String, Import>,
699       emitStep: (importsCollector: CodeWriter) -> Unit,
700     ): CodeWriter {
701       // First pass: emit the entire class, just to collect the types we'll need to import.
702       val importsCollector = CodeWriter(
703         NullAppendable,
704         indent,
705         memberImports,
706         columnLimit = Integer.MAX_VALUE,
707       )
708       emitStep(importsCollector)
709       val generatedImports = mutableMapOf<String, Import>()
710       val suggestedTypeImports = importsCollector.suggestedTypeImports()
711         .generateImports(
712           generatedImports,
713           canonicalName = ClassName::canonicalName,
714           capitalizeAliases = true,
715         )
716       val suggestedMemberImports = importsCollector.suggestedMemberImports()
717         .generateImports(
718           generatedImports,
719           canonicalName = MemberName::canonicalName,
720           capitalizeAliases = false,
721         )
722       importsCollector.close()
723 
724       return CodeWriter(
725         out = out,
726         indent = indent,
727         imports = memberImports + generatedImports.filterKeys { it !in memberImports },
728         importedTypes = suggestedTypeImports,
729         importedMembers = suggestedMemberImports,
730       )
731     }
732 
generateImportsnull733     private fun <T> Map<String, Set<T>>.generateImports(
734       generatedImports: MutableMap<String, Import>,
735       canonicalName: T.() -> String,
736       capitalizeAliases: Boolean,
737     ): Map<String, T> {
738       return flatMap { (simpleName, qualifiedNames) ->
739         if (qualifiedNames.size == 1) {
740           listOf(simpleName to qualifiedNames.first()).also {
741             val canonicalName = qualifiedNames.first().canonicalName()
742             generatedImports[canonicalName] = Import(canonicalName)
743           }
744         } else {
745           generateImportAliases(simpleName, qualifiedNames, canonicalName, capitalizeAliases)
746             .onEach { (alias, qualifiedName) ->
747               val canonicalName = qualifiedName.canonicalName()
748               generatedImports[canonicalName] = Import(canonicalName, alias)
749             }
750         }
751       }.toMap()
752     }
753 
generateImportAliasesnull754     private fun <T> generateImportAliases(
755       simpleName: String,
756       qualifiedNames: Set<T>,
757       canonicalName: T.() -> String,
758       capitalizeAliases: Boolean,
759     ): List<Pair<String, T>> {
760       val canonicalNameSegments = qualifiedNames.associateWith { qualifiedName ->
761         qualifiedName.canonicalName().split('.')
762           .dropLast(1) // Last segment of the canonical name is the simple name, drop it to avoid repetition.
763           .filter { it != "Companion" }
764           .map { it.replaceFirstChar(Char::uppercaseChar) }
765       }
766       val aliasNames = mutableMapOf<String, T>()
767       var segmentsToUse = 0
768       // Iterate until we have unique aliases for all names.
769       while (aliasNames.size != qualifiedNames.size) {
770         segmentsToUse += 1
771         aliasNames.clear()
772         for ((qualifiedName, segments) in canonicalNameSegments) {
773           val aliasPrefix = segments.takeLast(min(segmentsToUse, segments.size))
774             .joinToString(separator = "")
775             .replaceFirstChar { if (!capitalizeAliases) it.lowercaseChar() else it }
776           val aliasName = aliasPrefix + simpleName.replaceFirstChar(Char::uppercaseChar)
777           aliasNames[aliasName] = qualifiedName
778         }
779       }
780       return aliasNames.toList()
781     }
782   }
783 }
784