Thanks for the suggestion! That is indeed an alternative workaround for the cross compilation case.
[EDIT: constant value definitions are in the spec here: Basic Definitions | Scala 3.4 and have to have the form final val x = e
(no type annotation) I was not aware that this is a special thing in the language … not sure if that has impact on the below, still not sure how they are treated specially. It seems that constant expressions are “platform dependent” which seems to align with the reasoning below. I did not find anything in the spec about constant expressions being treated differently for control flow.]
If someone else is interested I did test with Scala 3.6.2 and ScalaNative 0.5.6 and the following seem to cause enough optimizations in the branch such that any symbols accessible through the non-taken branch do not make the linker complain:
inline val enableFeature = false
final val enableFeature = false
val enableFeature: false = false
Adding a type annotation breaks things:
final val enableFeature: Boolean = false
For the linker checked case that is probably fine, as the linker will complain. inline val
will require the type to be a singleton, so just using that instead of final val
also prevents this accident.
However, relying on plain if is hoping that the compiler applies some optimization, not relying on language semantics.
Particularly, this is notable from any other inline/metaprogramming facilities.
While this is probably a bit specific, I actually do have a program where this matters. It disables some features when compiling with scala-native. This changes the available command line options, which are generated by a macro that essentially reads the cases in the main method.
Slightly related, playing around a bit with the different final val
variants, when things work and when not was super hard to predict.
This works:
final val enableFeature = true
def main(args: Array[String]): Unit = {
inline def workaround() =
inline val y = if enableFeature then 1 else 2
y
println(workaround())
}
This does not:
final val enableFeature = true
def main(args: Array[String]): Unit = {
inline val y = if enableFeature then 1 else 2
println(y)
}
Maybe because of the staged compilation for inline defs the first case actually has the optimization applied before any of the other checks on the types are done?
I think it would be nice to address papercuts in the inline based metaprogramming as much as possible, because it’s such an elegant system at its core : - )