xref: /aosp_15_r20/external/kotlinpoet/kotlinpoet/src/commonMain/kotlin/com/squareup/kotlinpoet/CodeWriter.kt (revision 3c321d951dd070fb96f8ba59e952ffc3131379a0)
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(
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, Set<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     omitImplicitModifiers: Boolean = false,
242   ) = apply {
243     var a = 0
244     var deferredTypeName: ClassName? = null // used by "import static" logic
245     val partIterator = codeBlock.formatParts.listIterator()
246     while (partIterator.hasNext()) {
247       when (val part = partIterator.next()) {
248         "%L" -> emitLiteral(codeBlock.args[a++], isConstantContext, omitImplicitModifiers)
249 
250         "%N" -> emit(codeBlock.args[a++] as String)
251 
252         "%S" -> {
253           val string = codeBlock.args[a++] as String?
254           // Emit null as a literal null: no quotes.
255           val literal = if (string != null) {
256             stringLiteralWithQuotes(
257               string,
258               isInsideRawString = false,
259               isConstantContext = isConstantContext,
260             )
261           } else {
262             "null"
263           }
264           emit(literal, nonWrapping = true)
265         }
266 
267         "%P" -> {
268           val string = codeBlock.args[a++]?.let { arg ->
269             if (arg is CodeBlock) {
270               arg.toString(this@CodeWriter)
271             } else {
272               arg as String?
273             }
274           }
275           // Emit null as a literal null: no quotes.
276           val literal = if (string != null) {
277             stringLiteralWithQuotes(
278               string,
279               isInsideRawString = true,
280               isConstantContext = isConstantContext,
281             )
282           } else {
283             "null"
284           }
285           emit(literal, nonWrapping = true)
286         }
287 
288         "%T" -> {
289           var typeName = codeBlock.args[a++] as TypeName
290           if (typeName.isAnnotated) {
291             typeName.emitAnnotations(this)
292             typeName = typeName.copy(annotations = emptyList())
293           }
294           // defer "typeName.emit(this)" if next format part will be handled by the default case
295           var defer = false
296           if (typeName is ClassName && partIterator.hasNext()) {
297             if (!codeBlock.formatParts[partIterator.nextIndex()].startsWith("%")) {
298               val candidate = typeName
299               if (candidate.canonicalName in memberImportNames) {
300                 check(deferredTypeName == null) { "pending type for static import?!" }
301                 deferredTypeName = candidate
302                 defer = true
303               }
304             }
305           }
306           if (!defer) typeName.emit(this)
307           typeName.emitNullable(this)
308         }
309 
310         "%M" -> {
311           val memberName = codeBlock.args[a++] as MemberName
312           memberName.emit(this)
313         }
314 
315         "%%" -> emit("%")
316 
317         "⇥" -> indent()
318 
319         "⇤" -> unindent()
320 
321         "«" -> {
322           check(statementLine == -1) {
323             """
324             |Can't open a new statement until the current statement is closed (opening « followed
325             |by another « without a closing »).
326             |Current code block:
327             |- Format parts: ${codeBlock.formatParts.map(::escapeCharacterLiterals)}
328             |- Arguments: ${codeBlock.args}
329             |
330             """.trimMargin()
331           }
332           statementLine = 0
333         }
334 
335         "»" -> {
336           check(statementLine != -1) {
337             """
338             |Can't close a statement that hasn't been opened (closing » is not preceded by an
339             |opening «).
340             |Current code block:
341             |- Format parts: ${codeBlock.formatParts.map(::escapeCharacterLiterals)}
342             |- Arguments: ${codeBlock.args}
343             |
344             """.trimMargin()
345           }
346           if (statementLine > 0) {
347             unindent(2) // End a multi-line statement. Decrease the indentation level.
348           }
349           statementLine = -1
350         }
351 
352         else -> {
353           // Handle deferred type.
354           var doBreak = false
355           if (deferredTypeName != null) {
356             if (part.startsWith(".")) {
357               if (emitStaticImportMember(deferredTypeName.canonicalName, part)) {
358                 // Okay, static import hit and all was emitted, so clean-up and jump to next part.
359                 deferredTypeName = null
360                 doBreak = true
361               }
362             }
363             if (!doBreak) {
364               deferredTypeName!!.emit(this)
365               deferredTypeName = null
366             }
367           }
368           if (!doBreak) {
369             emit(part)
370           }
371         }
372       }
373     }
374     if (ensureTrailingNewline && out.hasPendingSegments) {
375       emit("\n")
376     }
377   }
378 
emitStaticImportMembernull379   private fun emitStaticImportMember(canonical: String, part: String): Boolean {
380     val partWithoutLeadingDot = part.substring(1)
381     if (partWithoutLeadingDot.isEmpty()) return false
382     val first = partWithoutLeadingDot[0]
383     if (!Character.isJavaIdentifierStart(first)) return false
384     val explicit = imports[canonical + "." + extractMemberName(partWithoutLeadingDot)]
385     if (explicit != null) {
386       if (explicit.alias != null) {
387         val memberName = extractMemberName(partWithoutLeadingDot)
388         emit(partWithoutLeadingDot.replaceFirst(memberName, explicit.alias))
389       } else {
390         emit(partWithoutLeadingDot)
391       }
392       return true
393     }
394     return false
395   }
396 
emitLiteralnull397   private fun emitLiteral(o: Any?, isConstantContext: Boolean, omitImplicitModifiers: Boolean) {
398     when (o) {
399       is TypeSpec -> o.emit(this, null)
400       is AnnotationSpec -> o.emit(this, inline = true, asParameter = isConstantContext)
401       is PropertySpec -> o.emit(this, emptySet())
402       is FunSpec -> o.emit(
403         codeWriter = this,
404         enclosingName = null,
405         implicitModifiers = if (omitImplicitModifiers) emptySet() else setOf(KModifier.PUBLIC),
406         includeKdocTags = true,
407       )
408       is TypeAliasSpec -> o.emit(this)
409       is CodeBlock -> emitCode(o, isConstantContext = isConstantContext)
410       else -> emit(o.toString())
411     }
412   }
413 
414   /**
415    * Returns the best name to identify `className` with in the current context. This uses the
416    * available imports and the current scope to find the shortest name available. It does not honor
417    * names visible due to inheritance.
418    */
lookupNamenull419   fun lookupName(className: ClassName): String {
420     // Find the shortest suffix of className that resolves to className. This uses both local type
421     // names (so `Entry` in `Map` refers to `Map.Entry`). Also uses imports.
422     var nameResolved = false
423     var c: ClassName? = className
424     while (c != null) {
425       val alias = imports[c.canonicalName]?.alias
426       val simpleName = alias ?: c.simpleName
427       val resolved = resolve(simpleName)
428       nameResolved = resolved != null
429 
430       // We don't care about nullability and type annotations here, as it's irrelevant for imports.
431       if (resolved == c.copy(nullable = false, annotations = emptyList())) {
432         if (alias == null) {
433           referencedNames.add(className.topLevelClassName().simpleName)
434         }
435         val nestedClassNames = className.simpleNames.subList(
436           c.simpleNames.size,
437           className.simpleNames.size,
438         ).joinToString(".")
439         return "$simpleName.$nestedClassNames"
440       }
441       c = c.enclosingClassName()
442     }
443 
444     // If the name resolved but wasn't a match, we're stuck with the fully qualified name.
445     if (nameResolved) {
446       return className.canonicalName
447     }
448 
449     // If the class is in the same package and there's no import alias for that class, we're done.
450     if (packageName == className.packageName && imports[className.canonicalName]?.alias == null) {
451       referencedNames.add(className.topLevelClassName().simpleName)
452       return className.simpleNames.joinToString(".")
453     }
454 
455     // We'll have to use the fully-qualified name. Mark the type as importable for a future pass.
456     if (!kdoc) {
457       importableType(className)
458     }
459 
460     return className.canonicalName
461   }
462 
lookupNamenull463   fun lookupName(memberName: MemberName): String {
464     val simpleName = imports[memberName.canonicalName]?.alias ?: memberName.simpleName
465     // Match an imported member.
466     val importedMembers = importedMembers[simpleName] ?: emptySet()
467     val found = memberName in importedMembers
468     if (found && !isMethodNameUsedInCurrentContext(simpleName)) {
469       return simpleName
470     } else if (importedMembers.isNotEmpty() && memberName.enclosingClassName != null) {
471       val enclosingClassName = lookupName(memberName.enclosingClassName)
472       return "$enclosingClassName.$simpleName"
473     } else if (found) {
474       return simpleName
475     }
476 
477     // If the member is in the same package, we're done.
478     if (packageName == memberName.packageName && memberName.enclosingClassName == null) {
479       referencedNames.add(memberName.simpleName)
480       return memberName.simpleName
481     }
482 
483     // We'll have to use the fully-qualified name.
484     // Mark the member as importable for a future pass unless the name clashes with
485     // a method in the current context
486     if (!kdoc && (
487         memberName.isExtension ||
488           !isMethodNameUsedInCurrentContext(memberName.simpleName)
489         )
490     ) {
491       importableMember(memberName)
492     }
493 
494     return memberName.canonicalName
495   }
496 
497   // TODO(luqasn): also honor superclass members when resolving names.
isMethodNameUsedInCurrentContextnull498   private fun isMethodNameUsedInCurrentContext(simpleName: String): Boolean {
499     for (it in typeSpecStack.reversed()) {
500       if (it.funSpecs.any { it.name == simpleName }) {
501         return true
502       }
503       if (!it.modifiers.contains(KModifier.INNER)) {
504         break
505       }
506     }
507     return false
508   }
509 
importableTypenull510   private fun importableType(className: ClassName) {
511     val topLevelClassName = className.topLevelClassName()
512     val alias = imports[className.canonicalName]?.alias
513     val simpleName = alias ?: topLevelClassName.simpleName
514     // Check for name clashes with members.
515     if (simpleName !in importableMembers) {
516       // Maintain the inner class name if the alias exists.
517       val newImportTypes = if (alias == null) {
518         topLevelClassName
519       } else {
520         className
521       }
522       importableTypes[simpleName] = importableTypes.getValue(simpleName) + newImportTypes
523     }
524   }
525 
importableMembernull526   private fun importableMember(memberName: MemberName) {
527     val simpleName = imports[memberName.canonicalName]?.alias ?: memberName.simpleName
528     // Check for name clashes with types.
529     if (memberName.isExtension || simpleName !in importableTypes) {
530       importableMembers[simpleName] = importableMembers.getValue(simpleName) + memberName
531     }
532   }
533 
534   /**
535    * Returns the class or enum value referenced by `simpleName`, using the current nesting context and
536    * imports.
537    */
538   // TODO(jwilson): also honor superclass members when resolving names.
resolvenull539   private fun resolve(simpleName: String): ClassName? {
540     // Match a child of the current (potentially nested) class.
541     for (i in typeSpecStack.indices.reversed()) {
542       val typeSpec = typeSpecStack[i]
543       if (simpleName in typeSpec.nestedTypesSimpleNames) {
544         return stackClassName(i, simpleName)
545       }
546     }
547 
548     if (typeSpecStack.size > 0) {
549       val typeSpec = typeSpecStack[0]
550       if (typeSpec.name == simpleName) {
551         // Match the top-level class.
552         return ClassName(packageName, simpleName)
553       }
554       if (typeSpec.isEnum && typeSpec.enumConstants.keys.contains(simpleName)) {
555         // Match a top level enum value.
556         // Enum values are not proper classes but can still be modeled using ClassName.
557         return ClassName(packageName, typeSpec.name!!).nestedClass(simpleName)
558       }
559     }
560 
561     // Match an imported type.
562     val importedType = importedTypes[simpleName]
563     if (importedType != null) return importedType
564 
565     // No match.
566     return null
567   }
568 
569   /** Returns the class named `simpleName` when nested in the class at `stackDepth`.  */
stackClassNamenull570   private fun stackClassName(stackDepth: Int, simpleName: String): ClassName {
571     var className = ClassName(packageName, typeSpecStack[0].name!!)
572     for (i in 1..stackDepth) {
573       className = className.nestedClass(typeSpecStack[i].name!!)
574     }
575     return className.nestedClass(simpleName)
576   }
577 
578   /**
579    * Emits `s` with indentation as required. It's important that all code that writes to
580    * [CodeWriter.out] does it through here, since we emit indentation lazily in order to avoid
581    * unnecessary trailing whitespace.
582    */
<lambda>null583   fun emit(s: String, nonWrapping: Boolean = false) = apply {
584     var first = true
585     for (line in s.split('\n')) {
586       // Emit a newline character. Make sure blank lines in KDoc & comments look good.
587       if (!first) {
588         if ((kdoc || comment) && trailingNewline) {
589           emitIndentation()
590           out.appendNonWrapping(if (kdoc) " *" else "//")
591         }
592         out.newline()
593         trailingNewline = true
594         if (statementLine != -1) {
595           if (statementLine == 0) {
596             indent(2) // Begin multiple-line statement. Increase the indentation level.
597           }
598           statementLine++
599         }
600       }
601 
602       first = false
603       if (line.isEmpty()) continue // Don't indent empty lines.
604 
605       // Emit indentation and comment prefix if necessary.
606       if (trailingNewline) {
607         emitIndentation()
608         if (kdoc) {
609           out.appendNonWrapping(" * ")
610         } else if (comment) {
611           out.appendNonWrapping("// ")
612         }
613       }
614 
615       if (nonWrapping) {
616         out.appendNonWrapping(line)
617       } else {
618         out.append(
619           line,
620           indentLevel = if (kdoc) indentLevel else indentLevel + 2,
621           linePrefix = if (kdoc) " * " else "",
622         )
623       }
624       trailingNewline = false
625     }
626   }
627 
emitIndentationnull628   private fun emitIndentation() {
629     for (j in 0..<indentLevel) {
630       out.appendNonWrapping(indent)
631     }
632   }
633 
634   /**
635    * Returns whether a [KModifier.PUBLIC] should be emitted.
636    *
637    * If [modifiers] contains [KModifier.PUBLIC], this method always returns `true`.
638    *
639    * Otherwise, this will return `true` when [KModifier.PUBLIC] is one of the [implicitModifiers]
640    * and there are no other opposing modifiers (like [KModifier.PROTECTED] etc.) supplied by the
641    * consumer in [modifiers].
642    */
shouldEmitPublicModifiernull643   private fun shouldEmitPublicModifier(
644     modifiers: Set<KModifier>,
645     implicitModifiers: Set<KModifier>,
646   ): Boolean {
647     if (modifiers.contains(KModifier.PUBLIC)) {
648       return true
649     }
650 
651     if (implicitModifiers.contains(KModifier.PUBLIC) && modifiers.contains(KModifier.OVERRIDE)) {
652       return false
653     }
654 
655     if (!implicitModifiers.contains(KModifier.PUBLIC)) {
656       return false
657     }
658 
659     val hasOtherConsumerSpecifiedVisibility =
660       modifiers.containsAnyOf(KModifier.PRIVATE, KModifier.INTERNAL, KModifier.PROTECTED)
661 
662     return !hasOtherConsumerSpecifiedVisibility
663   }
664 
665   /**
666    * Returns the types that should have been imported for this code. If there were any simple name
667    * collisions, import aliases will be generated.
668    */
suggestedTypeImportsnull669   private fun suggestedTypeImports(): Map<String, Set<ClassName>> {
670     return importableTypes.filterKeys { it !in referencedNames }.mapValues { it.value.toSet() }
671   }
672 
673   /**
674    * Returns the members that should have been imported for this code. If there were any simple name
675    * collisions, import aliases will be generated.
676    */
suggestedMemberImportsnull677   private fun suggestedMemberImports(): Map<String, Set<MemberName>> {
678     return importableMembers.mapValues { it.value.toSet() }
679   }
680 
681   /**
682    * Perform emitting actions on the current [CodeWriter] using a custom [Appendable]. The
683    * [CodeWriter] will continue using the old [Appendable] after this method returns.
684    */
emitIntonull685   inline fun emitInto(out: Appendable, action: CodeWriter.() -> Unit) {
686     val codeWrapper = this
687     LineWrapper(out, indent = DEFAULT_INDENT, columnLimit = Int.MAX_VALUE).use { newOut ->
688       val oldOut = codeWrapper.out
689       codeWrapper.out = newOut
690       @Suppress("UNUSED_EXPRESSION", "unused")
691       action()
692       codeWrapper.out = oldOut
693     }
694   }
695 
closenull696   override fun close() {
697     out.close()
698   }
699 
700   companion object {
701     /**
702      * Makes a pass to collect imports by executing [emitStep], and returns an instance of
703      * [CodeWriter] pre-initialized with collected imports.
704      */
withCollectedImportsnull705     fun withCollectedImports(
706       out: Appendable,
707       indent: String,
708       memberImports: Map<String, Import>,
709       emitStep: (importsCollector: CodeWriter) -> Unit,
710     ): CodeWriter {
711       // First pass: emit the entire class, just to collect the types we'll need to import.
712       val importsCollector = CodeWriter(
713         NullAppendable,
714         indent,
715         memberImports,
716         columnLimit = Integer.MAX_VALUE,
717       )
718       emitStep(importsCollector)
719       val generatedImports = mutableMapOf<String, Import>()
720       val importedTypes = importsCollector.suggestedTypeImports()
721         .generateImports(
722           generatedImports,
723           computeCanonicalName = ClassName::canonicalName,
724           capitalizeAliases = true,
725           referencedNames = importsCollector.referencedNames,
726         )
727       val importedMembers = importsCollector.suggestedMemberImports()
728         .generateImports(
729           generatedImports,
730           computeCanonicalName = MemberName::canonicalName,
731           capitalizeAliases = false,
732           referencedNames = importsCollector.referencedNames,
733         )
734       importsCollector.close()
735 
736       return CodeWriter(
737         out = out,
738         indent = indent,
739         imports = memberImports + generatedImports.filterKeys { it !in memberImports },
740         importedTypes = importedTypes.mapValues { it.value.single() },
741         importedMembers = importedMembers,
742       )
743     }
744 
generateImportsnull745     private fun <T> Map<String, Set<T>>.generateImports(
746       generatedImports: MutableMap<String, Import>,
747       computeCanonicalName: T.() -> String,
748       capitalizeAliases: Boolean,
749       referencedNames: Set<String>,
750     ): Map<String, Set<T>> {
751       val imported = mutableMapOf<String, Set<T>>()
752       forEach { (simpleName, qualifiedNames) ->
753         val canonicalNamesToQualifiedNames = qualifiedNames.associateBy { it.computeCanonicalName() }
754         if (canonicalNamesToQualifiedNames.size == 1 && simpleName !in referencedNames) {
755           val canonicalName = canonicalNamesToQualifiedNames.keys.single()
756           generatedImports[canonicalName] = Import(canonicalName)
757 
758           // For types, qualifiedNames should consist of a single name, for which an import will be generated. For
759           // members, there can be more than one qualified name mapping to a single simple name, e.g. overloaded
760           // functions declared in the same package. In these cases, a single import will suffice for all of them.
761           imported[simpleName] = qualifiedNames
762         } else {
763           generateImportAliases(simpleName, canonicalNamesToQualifiedNames, capitalizeAliases)
764             .onEach { (a, qualifiedName) ->
765               val alias = a.escapeAsAlias()
766               val canonicalName = qualifiedName.computeCanonicalName()
767               generatedImports[canonicalName] = Import(canonicalName, alias)
768 
769               imported[alias] = setOf(qualifiedName)
770             }
771         }
772       }
773       return imported
774     }
775 
generateImportAliasesnull776     private fun <T> generateImportAliases(
777       simpleName: String,
778       canonicalNamesToQualifiedNames: Map<String, T>,
779       capitalizeAliases: Boolean,
780     ): List<Pair<String, T>> {
781       val canonicalNameSegmentsToQualifiedNames = canonicalNamesToQualifiedNames.mapKeys { (canonicalName, _) ->
782         canonicalName.split('.')
783           .dropLast(1) // Last segment of the canonical name is the simple name, drop it to avoid repetition.
784           .filter { it != "Companion" }
785           .map { it.replaceFirstChar(Char::uppercaseChar) }
786       }
787       val aliasNames = mutableMapOf<String, T>()
788       var segmentsToUse = 0
789       // Iterate until we have unique aliases for all names.
790       while (aliasNames.size != canonicalNamesToQualifiedNames.size) {
791         segmentsToUse += 1
792         aliasNames.clear()
793         for ((segments, qualifiedName) in canonicalNameSegmentsToQualifiedNames) {
794           val aliasPrefix = segments.takeLast(min(segmentsToUse, segments.size))
795             .joinToString(separator = "")
796             .replaceFirstChar { if (!capitalizeAliases) it.lowercaseChar() else it }
797           val aliasName = aliasPrefix + simpleName.replaceFirstChar(Char::uppercaseChar)
798           aliasNames[aliasName] = qualifiedName
799         }
800       }
801       return aliasNames.toList()
802     }
803   }
804 }
805