Repository: square/kotlinpoet
Issue: Infinite loop in CodeWriter.generateImportAliases

**Describe the bug**

Ambiguous import aliases can result in the call to `CodeWriter.generateImportAliases` failing to return.

**To Reproduce**

I've narrowed this down in the debugger. The call to `generateImportAliases` passes these arguments:

- `simpleName`: `"Round"`
- `qualifiedNames`: `setOf(MemberName("androidx.compose.ui.graphics.StrokeCap.Companion.Round"), MemberName("androidx.compose.ui.graphics.StrokeJoin.Companion.Round"))`
- `packageName`: `CodeWriter$Companion$withCollectedImports$suggestedMemberImports$2@10685`
- `capitalizeAliases`: `false`

This results in the `while` loop repeating forever, clearing `aliasNames` and writing both qualified names to the same key `"androidxComposeUiGraphicsRound"`. This fails the length check, which expects there to be a unique alias for both qualified names.


**Expected behavior**

`generateImportAliases` should take the containing class names into account, so there isn't a collision between nested types with the same name in the same package.

Changed files:
kotlinpoet/src/main/java/com/squareup/kotlinpoet/CodeWriter.kt
kotlinpoet/src/test/java/com/squareup/kotlinpoet/KotlinPoetTest.kt
kotlinpoet/src/test/java/com/squareup/kotlinpoet/MemberNameTest.kt

diff --git a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/CodeWriter.kt b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/CodeWriter.kt
index 2884c65b..5d827276 100644
--- a/kotlinpoet/src/main/java/com/squareup/kotlinpoet/CodeWriter.kt
+++ b/kotlinpoet/src/main/java/com/squareup/kotlinpoet/CodeWriter.kt
@@ -706,14 +706,12 @@ internal class CodeWriter constructor(
         .generateImports(
           generatedImports,
           canonicalName = ClassName::canonicalName,
-          packageName = ClassName::packageName,
           capitalizeAliases = true,
         )
       val suggestedMemberImports = importsCollector.suggestedMemberImports()
         .generateImports(
           generatedImports,
           canonicalName = MemberName::canonicalName,
-          packageName = MemberName::packageName,
           capitalizeAliases = false,
         )
       importsCollector.close()
@@ -730,7 +728,6 @@ internal class CodeWriter constructor(
     private fun <T> Map<String, Set<T>>.generateImports(
       generatedImports: MutableMap<String, Import>,
       canonicalName: T.() -> String,
-      packageName: T.() -> String,
       capitalizeAliases: Boolean,
     ): Map<String, T> {
       return flatMap { (simpleName, qualifiedNames) ->
@@ -740,7 +737,7 @@ internal class CodeWriter constructor(
             generatedImports[canonicalName] = Import(canonicalName)
           }
         } else {
-          generateImportAliases(simpleName, qualifiedNames, packageName, capitalizeAliases)
+          generateImportAliases(simpleName, qualifiedNames, canonicalName, capitalizeAliases)
             .onEach { (alias, qualifiedName) ->
               val canonicalName = qualifiedName.canonicalName()
               generatedImports[canonicalName] = Import(canonicalName, alias)
@@ -752,11 +749,14 @@ internal class CodeWriter constructor(
     private fun <T> generateImportAliases(
       simpleName: String,
       qualifiedNames: Set<T>,
-      packageName: T.() -> String,
+      canonicalName: T.() -> String,
       capitalizeAliases: Boolean,
     ): List<Pair<String, T>> {
-      val packageNameSegments = qualifiedNames.associateWith { qualifiedName ->
-        qualifiedName.packageName().split('.').map { it.replaceFirstChar(Char::uppercaseChar) }
+      val canonicalNameSegments = qualifiedNames.associateWith { qualifiedName ->
+        qualifiedName.canonicalName().split('.')
+          .dropLast(1) // Last segment of the canonical name is the simple name, drop it to avoid repetition.
+          .filter { it != "Companion" }
+          .map { it.replaceFirstChar(Char::uppercaseChar) }
       }
       val aliasNames = mutableMapOf<String, T>()
       var segmentsToUse = 0
@@ -764,7 +764,7 @@ internal class CodeWriter constructor(
       while (aliasNames.size != qualifiedNames.size) {
         segmentsToUse += 1
         aliasNames.clear()
-        for ((qualifiedName, segments) in packageNameSegments) {
+        for ((qualifiedName, segments) in canonicalNameSegments) {
           val aliasPrefix = segments.takeLast(min(segmentsToUse, segments.size))
             .joinToString(separator = "")
             .replaceFirstChar { if (!capitalizeAliases) it.lowercaseChar() else it }
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/KotlinPoetTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/KotlinPoetTest.kt
index 9d3f1711..bd021020 100644
--- a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/KotlinPoetTest.kt
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/KotlinPoetTest.kt
@@ -1333,6 +1333,41 @@ class KotlinPoetTest {
     )
   }
 
+  // https://github.com/square/kotlinpoet/issues/1518
+  @Test fun generatedImportAliasesSamePackageDifferentContainingClasses() {
+    val strokeCapRound = MemberName(
+      enclosingClassName = ClassName("androidx.compose.ui.graphics", "StrokeCap").nestedClass("Companion"),
+      simpleName = "Round",
+    )
+    val strokeJoinRound = MemberName(
+      enclosingClassName = ClassName("androidx.compose.ui.graphics", "StrokeJoin").nestedClass("Companion"),
+      simpleName = "Round",
+    )
+    val file = FileSpec.builder("com.example", "Test")
+      .addFunction(
+        FunSpec.builder("main")
+          .addStatement("val strokeCapRound = %M()", strokeCapRound)
+          .addStatement("val strokeJoinRound = %M()", strokeJoinRound)
+          .build(),
+      )
+      .build()
+    assertThat(file.toString()).isEqualTo(
+      """
+      |package com.example
+      |
+      |import kotlin.Unit
+      |import androidx.compose.ui.graphics.StrokeCap.Companion.Round as strokeCapRound
+      |import androidx.compose.ui.graphics.StrokeJoin.Companion.Round as strokeJoinRound
+      |
+      |public fun main(): Unit {
+      |  val strokeCapRound = strokeCapRound()
+      |  val strokeJoinRound = strokeJoinRound()
+      |}
+      |
+      """.trimMargin(),
+    )
+  }
+
   @Test fun memberImportsOverGeneratedImportAliases() {
     val squareTaco = ClassName("com.squareup.tacos", "Taco")
     val blockTaco = ClassName("xyz.block.tacos", "Taco")
diff --git a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/MemberNameTest.kt b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/MemberNameTest.kt
index 308aeb2c..565e61e2 100644
--- a/kotlinpoet/src/test/java/com/squareup/kotlinpoet/MemberNameTest.kt
+++ b/kotlinpoet/src/test/java/com/squareup/kotlinpoet/MemberNameTest.kt
@@ -171,11 +171,11 @@ class MemberNameTest {
       |package com.example
       |
       |import kotlin.Unit
-      |import com.squareup.tacos.SquareTacos.Companion.createTaco as squareupTacosCreateTaco
+      |import com.squareup.tacos.SquareTacos.Companion.createTaco as squareTacosCreateTaco
       |import com.twitter.tacos.TwitterTacos.Companion.createTaco as twitterTacosCreateTaco
       |
       |public fun makeTastyTacos(): Unit {
-      |  squareupTacosCreateTaco()
+      |  squareTacosCreateTaco()
       |  twitterTacosCreateTaco()
       |}
       |
@@ -416,11 +416,11 @@ class MemberNameTest {
       |package com.example
       |
       |import kotlin.Unit
-      |import com.squareup.tacos.SquareTacos.Companion.`when` as squareupTacosWhen
+      |import com.squareup.tacos.SquareTacos.Companion.`when` as squareTacosWhen
       |import com.twitter.tacos.TwitterTacos.Companion.`when` as twitterTacosWhen
       |
       |public fun whenTastyTacos(): Unit {
-      |  squareupTacosWhen()
+      |  squareTacosWhen()
       |  twitterTacosWhen()
       |}
       |