scala / bug

Scala 2 bug reports only. Please, no questions — proper bug reports only.
https://scala-lang.org
232 stars 21 forks source link

Position.point on NoPosition when using: macro implicit, Wmacros:both and Xlint:unused #12895

Closed MateuszKubuszok closed 6 months ago

MateuszKubuszok commented 11 months ago

Reproduction steps

Scala version: I reproduced it on were 2.13.3 and 2.13.12.

//> using scala 2.13.3
//> using options -Wmacros:both -Xlint:unused
//> using javaOpt -Xss64m
//> using dep io.scalaland::chimney::0.8.0

import io.scalaland.chimney.dsl._

object ChimneyBug {
  private def handleEvent(state: State, event: Event) =
    (state, event) match {
      case (active: State.Active, updated: Event.A) =>
        active.patchUsing(updated)
    }

  sealed trait Event
  object Event {
    case class A(
                  a: String,
                ) extends Event
  }

  sealed trait State
  object State {
    case class Active(
                       a: String,
                     ) extends State
  }

  def test = handleEvent(ChimneyBug.State.Active("A"), ChimneyBug.Event.A("B"))
}

object Main {
  def main(args: Array[String]): Unit =
    println(ChimneyBug.test)
}

Problem

User reported that updating Chimney the newest version resulted in compilation errors. After discussion I managed to reproduce the error:

[error] ## Exception when compiling 1 sources to /Users/dev/Workspaces/GitHub/toescik2/dest/target/scala-2.13/classes
[error] java.lang.UnsupportedOperationException: Position.point on NoPosition
[error] scala.reflect.internal.util.Position.fail(Position.scala:24)
[error] scala.reflect.internal.util.UndefinedPosition.point(Position.scala:102)
[error] scala.reflect.internal.util.UndefinedPosition.point(Position.scala:97)
[error] scala.tools.nsc.typechecker.TypeDiagnostics$UnusedPrivates.sympos(TypeDiagnostics.scala:623)
[error] scala.tools.nsc.typechecker.TypeDiagnostics$UnusedPrivates.$anonfun$unusedParams$2(TypeDiagnostics.scala:642)
[error] scala.tools.nsc.typechecker.TypeDiagnostics$UnusedPrivates.$anonfun$unusedParams$2$adapted(TypeDiagnostics.scala:642)
[error] scala.math.Ordering$$anon$1.compare(Ordering.scala:140)
[error] java.base/java.util.TimSort.countRunAndMakeAscending(TimSort.java:355)
[error] java.base/java.util.TimSort.sort(TimSort.java:220)
[error] java.base/java.util.Arrays.sort(Arrays.java:1233)
[error] scala.collection.SeqOps.sorted(Seq.scala:705)
[error] scala.collection.SeqOps.sorted$(Seq.scala:697)
[error] scala.collection.immutable.List.scala$collection$immutable$StrictOptimizedSeqOps$$super$sorted(List.scala:79)
[error] scala.collection.immutable.StrictOptimizedSeqOps.sorted(StrictOptimizedSeqOps.scala:78)
[error] scala.collection.immutable.StrictOptimizedSeqOps.sorted$(StrictOptimizedSeqOps.scala:78)
[error] scala.collection.immutable.List.sorted(List.scala:79)
[error] scala.collection.SeqOps.sortBy(Seq.scala:759)
[error] scala.collection.SeqOps.sortBy$(Seq.scala:759)
[error] scala.collection.AbstractSeq.sortBy(Seq.scala:1154)
[error] scala.tools.nsc.typechecker.TypeDiagnostics$UnusedPrivates.unusedParams(TypeDiagnostics.scala:642)
[error] scala.tools.nsc.typechecker.TypeDiagnostics$checkUnused.run(TypeDiagnostics.scala:748)
[error] scala.tools.nsc.typechecker.TypeDiagnostics$checkUnused.apply(TypeDiagnostics.scala:759)
[error] scala.tools.nsc.typechecker.Analyzer$typerFactory$TyperPhase.apply(Analyzer.scala:123)
[error] scala.tools.nsc.Global$GlobalPhase.applyPhase(Global.scala:454)
[error] scala.tools.nsc.typechecker.Analyzer$typerFactory$TyperPhase.run(Analyzer.scala:105)
[error] scala.tools.nsc.Global$Run.compileUnitsInternal(Global.scala:1515)
[error] scala.tools.nsc.Global$Run.compileUnits(Global.scala:1499)
[error] scala.tools.nsc.Global$Run.compileSources(Global.scala:1491)
[error] scala.tools.nsc.Global$Run.compileFiles(Global.scala:1605)
[error] xsbt.CachedCompiler0.run(CompilerBridge.scala:163)
[error] xsbt.CachedCompiler0.run(CompilerBridge.scala:134)
[error] xsbt.CompilerBridge.run(CompilerBridge.scala:39)
[error] sbt.internal.inc.AnalyzingCompiler.compile(AnalyzingCompiler.scala:91)
[error] sbt.internal.inc.MixedAnalyzingCompiler.$anonfun$compile$7(MixedAnalyzingCompiler.scala:193)
[error] scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.java:23)
[error] sbt.internal.inc.MixedAnalyzingCompiler.timed(MixedAnalyzingCompiler.scala:248)
[error] sbt.internal.inc.MixedAnalyzingCompiler.$anonfun$compile$4(MixedAnalyzingCompiler.scala:183)
[error] sbt.internal.inc.MixedAnalyzingCompiler.$anonfun$compile$4$adapted(MixedAnalyzingCompiler.scala:163)
[error] sbt.internal.inc.JarUtils$.withPreviousJar(JarUtils.scala:239)
[error] sbt.internal.inc.MixedAnalyzingCompiler.compileScala$1(MixedAnalyzingCompiler.scala:163)
[error] sbt.internal.inc.MixedAnalyzingCompiler.compile(MixedAnalyzingCompiler.scala:211)
[error] sbt.internal.inc.IncrementalCompilerImpl.$anonfun$compileInternal$1(IncrementalCompilerImpl.scala:534)
...

Despite my best efforts I couldn't find any relation with what Chimney does, and this error. Using flags for debugging the compilation like -Xlog-implicits, -verbose, -Yquasiquote-debug, -Xprint-pos showed nothing, as if error happened before the macro expansion even started. Increasing stack with -Xss64m doesn't change the outcome.

It might be an error in Chimney, or it might be a particular combination of: an implicit provided by a macro, checking for unused definitions and putting macros into checking unused. Stack trace suggest that error happens in the typer while checking for unused private definitions. Removing any the the flags (-Wmacros:both, -Xlint:unused) makes the error disappear. That's why I created this ticket because I think that without a compiler developer's help I will not be able to tell if the bug is in Chimney or on the compiler.

som-snytt commented 11 months ago

Thanks for the report and the diagnostic work. -Wmacros:both says also lint the tree produced by the macro; I see from the stack trace that it is sorting the warnings by position; presumably it's not careful enough about unpositioned trees.

I see some safety checks for pos.isDefined.

https://github.com/scala/scala/blame/2.13.x/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala#L645

The workaround of course would be not to lint trees that are unpositioned.

som-snytt commented 6 months ago

The unpositioned symbol is the unused active$macro$1 parameter, and the bug is to try to use its referenced which is NoSymbol and has no position either.

The broader question is whether it's doing too much work just to sort by position; positions should be ordered even if "undefined".

{
          final class $anon extends AnyRef with io.scalaland.chimney.Patcher[ChimneyBug.State.Active,ChimneyBug.Event.A] {
            def <init>(): <$anon: io.scalaland.chimney.Patcher[ChimneyBug.State.Active,ChimneyBug.Event.A]> = {
              $anon.super.<init>();
              ()
            };
            def patch(active$macro$1: ChimneyBug.State.Active, a$macro$1: ChimneyBug.Event.A): ChimneyBug.State.Active = new ChimneyBug.State.Active(a$macro$1.a)
          };
          new $anon()
        }
som-snytt commented 6 months ago

Worth adding that for 2.13.14, -Wmacros:default does not check the synthetic parameter introduced by the expansion, so that would pass even without the fix.