scala3 icon indicating copy to clipboard operation
scala3 copied to clipboard

Incorrect unused import warnings in some cases - private definitions escaping their scope

Open jdrphillips opened this issue 6 months ago • 1 comments

Compiler version

3.7.1

Minimized code

object USED {
  case class A(value: Int)
}

object UNUSED {
  // In reality UNUSED would contain several other necessary members!
  private type A = USED.A
}

object Test {
  import USED.*
  import UNUSED.*

  def foo(a: A): Int = a.value

}

Output

[warn] -- [E198] Unused Symbol Warning: File.scala:92:14 
[warn] 92 |  import USED.*
[warn]    |              ^
[warn]    |              unused import

Expectation

[warn] -- [E198] Unused Symbol Warning: File.scala:93:16 
[warn] 93 |  import UNUSED.*
[warn]    |                ^
[warn]    |                unused import

Some notes:

  • This only happens if UNUSED.A is an alias for USED.A, it doesn't seem to happen in any other case, even if they are otherwise identical case classes
  • It only seems to happen for types, not values
  • private[scope] doesn't seem to affect the bug
  • importing in the other order patches the issue as expected
  • import USED.A explicitly fixes the issue
  • import UNUSED.{everything except A} fixes the issue (as you would expect)

This is quite a common pattern in our projects where we have privately aliased code in their original packages in order to skip painful re-imports in hundreds of files when moving code around to other packages.

This is quite unfortunate as none of the patches where it works are really applicable in general. If we upgraded we'd spend so much time unpicking imports and fighting automatic linting

Bug not observed on 3.7.0

(EDIT: Fixed typo in the script Test1 => USED)

jdrphillips avatar Jun 10 '25 16:06 jdrphillips

Thanks for the thorough notes. Per the bullet point, the symptom happens for

//> using options -Wunused:all -Werror

object USED {
  case class A(value: Int)
}

object Test1 {
  class A
}

object UNUSED {
  // In reality UNUSED would contain several other necessary members!
  //private type A = Int //Test1.A
  //private type A = Test1.A
  private type A = USED.A
  class B
}

object Test {
  import USED.*
  import UNUSED.*

  def foo(a: A): Int = a.value

  def g(b: B) = ()

}

som-snytt avatar Jun 10 '25 18:06 som-snytt