For advent of code I have a base trait with a main method, and then each day is an object that inherits that main method. This used to work quite well earlier, but now it seems that I need @main methods for every entry point, and I am no longer able to inherit them.
Is there some other way to accomplish the inheritance of main methods?
Think there is some alternative launching mechanism in sbt, since it works fine when fork := false, but would be nice to be available from a raw java or scala command line.
import scala.collection.mutable.ListBuffer
trait Base:
val _tests: ListBuffer[Runnable] = ListBuffer()
def tests: Seq[Runnable] = _tests.toSeq
def test(thunk: => Unit): Unit =
_tests += (() => thunk)
def main(args: Array[String]): Unit =
for test <- tests do
test.run()
object A extends Base:
test:
assert(1 == 1)
object B extends Base:
test:
assert(1 != 2)
Regular main methods being inherited should still work. Which errors are you seeing?
Do you want a single main that runs all tests ?
If so then inheritance is not enough, as each class would have its own main method
Here is the closest equivalent I could manage:
import scala.collection.mutable
class Base(_test: => Unit):
Base.instances.addOne(this)
def test() = _test
object Base:
var instances = mutable.ListBuffer[Base]()
@main
def testAll(): Unit =
instances.foreach(_.test())
Base{
println("Hello")
assert(1 + 1 == 2)
}
// Prints nothing, as this is a by-name parameter
Base{
println("World")
assert(2 + 2 == 4)
}
// Prints nothing, as this is a by-name parameter
Base.testAll()
// Prints:
// Hello
// World
But note that the anonymous instances only report their existence when they are evaluated, so I’m not sure what will happen if you move them to different files, maybe if you import them again it’s fine ?
Regardless while the above works, I’m not sure I would encourage it, it’s very fragile, and will probably lead to a lot of headaches
When running in sbt with fork = true it returns this error
sbt:main> run
...
Multiple main classes detected. Select one to run:
[1] A
[2] B
Enter number: 1
[info] running (fork) A
[error] Error: Main method must return a value of type void in class A, please
[error] define the main method as:
[error] public static void main(String[] args)
[error] Nonzero exit code returned from runner: 1
[error] (Compile / run) Nonzero exit code returned from runner: 1
[error] Total time: 1 s, completed Dec 2, 2025, 4:56:57 PM
But I think I have narrowed this to some scala options I use, namely
-Yno-imports
-Yimports:xxx
I use a custom predef, and for some reason that breaks the generation of proper static main methods in object that inherit them.
> javap target/scala-3.7.4/classes/A\$.class
Compiled from "test.scala"
public final class A$ implements Base,java.io.Serializable {
...
public scala.runtime.BoxedUnit main(java.lang.String[]);
...
}
This is properly a void method if I omit those options.
Is this a fileable Bug?
Was just looking to run either A or B, and I wasnt trying to hack together some classpath discovery mechanism this is sorta simulating.
But since everyone assumed this would just work, I did some digging internally and it looks like some compiler options I’m using are preventing the creation of static inherited main methods.
Digging further. This looks to be a problem with how Unit is imported / aliased.
if scala.Unit is imported in the file these methods are found.
if it is exported in the custom predef as type Unit = scala.Unit then main method inheritance breaks.
export scala.Unit in the custom predef does not work
[error] 11 |export scala.Unit
[error] | ^
[error] |`Unit` companion object is not allowed in source; instead, use `()` for the unit value
2 Likes
Yes, that’s a bug. It works in Scala 2, but your Unit alias is not erased correctly in Scala 3.
Good one! Advent of Puzzlers!
Edit: Aliased Unit improperly boxed · Issue #24653 · scala/scala3 · GitHub
2 Likes