Does multi-module incremental compilation affect Scala 3 compiler semantics/behavior?

I believe that I’m observing two distinct bugs in the Scala 3.3.1 compiler, in a project where the relevant source files are split across multiple sbt subprojects.

However, when I try to boil down the problems to minimized examples of the issues in a Scastie, neither of the bugs occur. This is despite the minimal versions seeming to have the same essential structure as the relevant pieces of the project.

Briefly, they both concern situations related to export:

  1. In some cases, for methods that have default parameter values and are then exported, invocations of the method that do not provide the full set of parameters are marked as errors. Ie the default parameters are not used.

    I worked around this using old-fashioned method overloading, like we used to before default parameters were added to Scala.

  2. In the other (stranger) case, an extension method is defined on a trait which is then inherited from by a companion object. This object and its associated case class are then exported. When the extension method is invoked, the compiler sees extension method ambiguity when there is none, by somehow imagining two “paths” to the extension method, when in fact the paths are the same to the same method.

    I worked around this by “manually” exporting the object/class using a type declaration and some handwritten method forwarders.

Ive played around in scastie but have been unable to get either of these situations to exhibit in a single compilation unit.

This leads me to wonder how differently, or not, the compiler behaves when used for incremental compilation of multi-module sbt projects (using zinc I guess?) vs when it is invoked on a single file.

Does anyone know of other example issues that show up in multi-module builds only?

Despite the lack of a reproducible example for 2 above, the diff between the compiling and erroneous versions may hint at where a problem lies, so I’ll show them here:

Compile error:

trait orders extends orders_deps:
  export orders_module.{*, given}

Error is:
Note that implicit extension methods cannot be applied because they are ambiguous; both method Order in trait orders and object Order in object orders_module provide an extension method updated``

Working:

trait orders extends orders_deps:
  export orders_module.{Order as _, *, given}

  type Order = orders_module.Order
  val Order = orders_module.Order

I thought the two lines above were equivalent to export orders_module.Order, but there is some difference between the two that upsets the extension method logic.

Although again I haven’t managed to find a small repro example for the above, I can provide a pair of diffs and an associated compile error. Hope this is a useful hint to where the problem lies.

I have an extension defined that makes use of default parameters to implement a “fallback” strategy; a Circe Encoder is preferred, but if that cannot be found then it will generate a json value as a String using an available cats.Show instance:

  extension (key: String)
    def ->>[T](t: T)(using show: Show[T])(using enc: Encoder[T] = NoEncoderFound[T]): (String, Json) =
      if enc.isInstanceOf[NoEncoderFound[T]] then key->Json.fromString(t.show) else key->enc(t)

If we simply use export, we get compile errors because the default Encoder parameter isn’t honored:

trait circe_ext extends circe_ext_deps:
  export circe_ext_module.{*, given}

results in compiler errors about No given instance of type io.circe.Encoder

But if we change just the export of ->> to a hand-written forwarder, the compiler errors resolve, because the default encoder param is provided:

trait circe_ext extends circe_ext_deps:
  export circe_ext_module.{->> as _, *, given}

  extension (key: String)
    def ->>[T](t: T)(using show: Show[T])(using enc: Encoder[T] = NoEncoderFound[T]): (String, Json) =
      circe_ext_module.->>(key)(t)

So in summary, export doesn’t seem to be working properly yet in several aspects, and this seems to show up for me when invoking exported APIs across module boundaries.

Even if it’s not small, would you be able to share a git branch that reproduces the problem? In any case, issues should be opened on Issues · lampepfl/dotty · GitHub

1 Like

Thanks for the reply @smarter, I’ve filed a reproducible issue at Multi-module export of methods: default parameters are not honoured · Issue #18767 · lampepfl/dotty · GitHub

And the other issue is here Ambiguous Extension Method when Exported · Issue #18768 · lampepfl/dotty · GitHub