-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Allow customisation of metaprogramming via scalac flags #12038
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
This looks like overkill. Wouldn't it be simpler to just use java properties and simply define a macro that reads them? import scala.quoted.*
inline def envGet(inline key: String): Option[String] =
${ getLogLevelExpr(key) }
def envGetExpr(key: Expr[String])(using Quotes): Expr[Option[String]] =
Expr(scala.util.Properties.envOrNone(key.valueOrError)) This does not need to be in the standard library. |
If we really need a compiler flag for the environment, then it this should be exposed in |
|
This is also incompatible with incremental compilation. |
BTW, it is possible to implement such a logger without the need of an external flag. We have already done that in dotty.tools.dotc.config.Printers. |
I initially had that thought too but it doesn't work. Let me get into the details: Why not use java properties?
Incremental compilationI don't see how this would be incompatible with incremental compilation. Incremental compilation already takes scalac flags into consideration; when certain scalac flags change, certain things are recompiled. Whether that's incremental compilation reacting to the flags, or sbt reacting to the flags and forcing invalidation, the point is that there's already a mechanism in place. If we used java props then that would *really be incompatible with incremental compilation. If anything, this approach of using scalac flags is what gives us (or can easily be made to give us) correct incremental compilation. TASTY recompilation differences
|
@nicolasstucki Not sure if this is already on your radar but |
Ok, seems that most of my worries have some solutions already. What would you expect when setting |
Could the tasty files just not store all the options it was compiled with, bypassing the problem, and providing an official way to do things that is less prone to breaking? |
@nicolasstucki Sorry I'm not sure what you mean. From the inlining point of view, access to this little env-map would be read-only and the only way to modify it, would be to go to the build tool and change the scalac flags. I'm not quite sure how tasty works but I imagine that the inline methods would always access the env-map as it's defined at expansion time. Let me provide an example below that I hope will be clear...
@Katrix There may be some cases where you would want those options stored in TASTY so that it's repeatable and wont change downstream, but I didn't think about that. In my (limited) real-world scenarios that have motivated this proposal, I explicitly do not want that behaviour though. I want upstream inline code to be re-evaluated downstream according to downstream config (and ignoring upstream's config). Supporting the ability to lock-in/remember certain env settings might be a missing feature in the current version of this proposal. It can be partially-supported already in userland for some cases (see the example below). One way of achieving proper, full support could be to specify alongside the scalac option, that you want the setting remembered and stored in TASTY (eg maybe ExampleImagine: Library 1: // compiled without any -E flags
inline def logInDebugMode(inline msg: String): Unit =
inline envGet("mode") match {
case "debug" => println(msg)
case _ => ()
}
inline def square_v1(n: Int): Int =
val result = n * n
logInDebugMode("Calculating $n² = $result")
result
// As above but not inline
def square_v2(n: Int): Int =
square_v1(n) and now a downstream project that depends on Library 1: // compiled with -E:mode=debug
@main def main =
val x = square_v1(2)
val y = square_v2(4)
println(s"x = $x, y = $y") I'd expect the output to be:
because:
So basically the env-map is only considered at the time when inline methods are being reified/spliced. The author of our fake library above has the ability to decide whose |
I just realised, this could probably be done as a compiler plugin. Maybe I should do that first, let it get some use and then see in the future if there's a good reason to implement this in Scala directly? If I were to (try to) support retention of flags by storing them in TASTY somehow, is that something that can be done from a compiler plugin? |
A compiler plugin sounds like a good way to start.
One way could be adding them into an annotation in the top-level class. See also |
Hey @nicolasstucki , would you be able to give me a bit of guidance? This was so easy to implement in Scala directly but I've been pulling my hair out trying to implement this as a compiler plugin. Three questions if that's ok?
|
- final val x: String = null
+ final val x: "abc" = "abc"
|
Is the proposed |
I'm currently facing a problem that would be easily solved if I had access to the Whilst it's not covered by this proposal, the problem is similar. Is there a concern for exposing the whole set of compiler settings via a metaprogramming API ? |
@Baccata I think you can retrieve scala.quoted.quotes.reflect.Position.ofMacroExpansion.sourceFile.getJPath This retrieves the absolute path to the current source file. I thihk the macro below solves a similar problem to yours - 7mind/sbtgen@e6f96a4#diff-1fa20673ba6904c5702fdceacd1fa15adf52b5e86dc66b4ccce8efebe3586b94R73 |
Pretty please. We actually need this. |
This 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.
-E:key=value
(similar to how java accepts-Dkey=value
args)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.
logging = true
then calls tolog()
generateprintln
statementslogging = false
then calls tolog()
generate nothingCurrently, 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)Logger
were a library, downstream users would have no way of configuring itIf 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: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: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.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.
The text was updated successfully, but these errors were encountered: