Allow customisation of metaprogramming via scalac flags
See original GitHub issueThis issue is to allow inline
code to customise its output based on user-defined settings provided as scalac flags.
Background
Full detail here: https://contributors.scala-lang.org/t/metaprogramming-configurability/4961
This issue is the first part of the solution I proposed above.
Proposal
Disclaimer: the names used below are just drafts and still need some good bikeshedding.
- Add a new scalac flag that takes a
-E:key=value
(similar to how java accepts-Dkey=value
args) - Add
transparent inline def envGet(inline key: String): Option[String]
toscala.compiletime
that provides access to the settings specified with above scalac flags
Usage Example
The very first example in the inlining doc is a logger that can be configured at compile-time, and is “zero-cost” (as they say) at runtime.
- when
logging = true
then calls tolog()
generateprintln
statements - when
logging = false
then calls tolog()
generate nothing - there is never a logging-config check at runtime
Currently, the problems with this are
Logger
and itsConfig
are static, and changing the setting is a code changeConfig
must be pre-configured and defined alongsideLogger
(elseLogger
wouldn’t compile)- If
Logger
were a library, downstream users would have no way of configuring it
If this issue were implemented, our zero-cost-ish Logger
could be written like this below, to accept a "myLogger.level"
setting that downstream users can populate:
import scala.compiletime.*
object Logging {
private inline val Trace = 0
private inline val Debug = 1
private inline val Info = 2
private inline val Warn = 3
private transparent inline def chosenThreshold: Int =
inline envGet("myLogger.level") match
case Some("TRACE") => Trace
case Some("DEBUG") => Debug
case Some("INFO") => Info
case Some("WARN") => Warn
case None => Warn // let's provide a default out-of-the-box
case Some(x) => error("Unsupported logging level: " + x)
private inline def log(inline lvl: Int, inline msg: String): Unit =
inline if lvl >= chosenThreshold then println(msg) else ()
// This is the public API
inline def trace(inline msg: String) = log(Trace, msg)
inline def debug(inline msg: String) = log(Debug, msg)
inline def info (inline msg: String) = log(Info , msg)
inline def warn (inline msg: String) = log(Warn , msg)
}
And then a downstream user could specify -E:myLogger.level=INFO
in their scalac flags so that our toy logging library effectively becomes this for them:
object Logging {
inline def trace(inline msg: String) = ()
inline def debug(inline msg: String) = ()
inline def info (inline msg: String) = println(msg)
inline def warn (inline msg: String) = println(msg)
}
Reconsidering the three problems above, with this new solution:
Logger
can now be configured dynamically, and via config (no code changes required)Config
is no longer necessary. Whether the library author wants to provide it and/or defaults is now something under their control.- Downstream users can now configure the behaviour of
Logger
PR?
I’m not asking that the busy Scala 3 team take some time out to implement this. This seemed simple enough for me to try implementing it myself and so I have, and it seems to work well! The above logging example is one of the tests and is confirmed to work as excepted.
Issue Analytics
- State:
- Created 2 years ago
- Reactions:4
- Comments:19 (18 by maintainers)
@Baccata I think you can retrieve
sourcepath
usingThis retrieves the absolute path to the current source file. I thihk the macro below solves a similar problem to yours - https://github.com/7mind/sbtgen/pull/181/commits/e6f96a4cf28b002432be79bdf0a27f8d3c528ea1#diff-1fa20673ba6904c5702fdceacd1fa15adf52b5e86dc66b4ccce8efebe3586b94R73
⚠️ Note that this is an extremely dangerous feature as it will introduce instability in the generated artifacts long term. Once we allow recompilation from TASTy, having a different set of flags could change the behavior of the already distributed artifact. Each user might see different behaviors depending on their setup. Additionally, this change of behavior would be different in transparent and non-transparent inlining. ⚠️