-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Add QuoteContext #6700
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
Add QuoteContext #6700
Conversation
cbac1cc
to
f72acbd
Compare
bc8b23d
to
e4d5bf1
Compare
53ce0da
to
c22eb4b
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Otherwise, LGTM
The concern I have is the following, which we can discuss during the meeting:
- stability of macro interface (it's planned to be changed Move reflection inside QuoteContext #6723 )
- usability: macro authors have to deal with
QuoteContext
, which they may not care
def run[T](expr: Expr[T]): T | ||
def show[T](expr: Expr[T]): String | ||
def show[T](tpe: Type[T]): String | ||
def run[T](expr: QuoteContext => Expr[T]): T | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible to merge the concept Toolbox
with QuoteContext
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not really. The toolbox represents an instance of the compiler (including caches) while the QuoteContext corresponds to a single compilation run.
|
||
def apply[T](expr: given QuoteContext => Expr[T]) given (toolbox: Toolbox): String = run(expr.show.toExpr) | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not just expr.show
instead of run(expr.show.toExpr)
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because the prefix is of type Expr[T]
but we need a given QuoteContext => Expr[T]
which we cannot encode as an extension method
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aslo, this show
intanciated a new compiler run which we should never do inside a run or a macro. Better to not get the two method confused
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be clearer for the users if we remove this variant of show
and use only the Expr.show
. The latter one can only be called within the a run { ... }
or a macro ${ ... }
. In case they write tests and only care about showing the result of computing some closed expression they could write run(expr.show.toExpr)
themselves.
// For backward compat with macros | ||
implicit def reflectionToQuoteContext(implicit reflect: Reflection): scala.quoted.QuoteContext = | ||
new scala.quoted.QuoteContext(reflect) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As discussed, in macros it will improve usability if we can reduce the number of concepts.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This one will be simplified the next time we update the reference compiler. The de facto implicit will be QuoteContext
.
@@ -5,6 +5,6 @@ object Test { | |||
val z: $t = $x | |||
} | |||
implicit val toolbox: scala.quoted.Toolbox = scala.quoted.Toolbox.make(getClass.getClassLoader) | |||
println(f('{2})(Type.IntTag).show) | |||
println(show(f('{2})(Type.IntTag))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems to me for staging, having Expr[T].show
will make debugging way easier.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that one is supported as well. In this case I used the variant of show to minimize the diff.
The test could also have been rewritten as
implicit val toolbox: scala.quoted.Toolbox = scala.quoted.Toolbox.make(getClass.getClassLoader)
run {
println(f('{2})(Type.IntTag).show)
'{}
}
or
implicit val toolbox: scala.quoted.Toolbox = scala.quoted.Toolbox.make(getClass.getClassLoader)
println(run {
f('{2})(Type.IntTag).show.toExpr
})
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems a little unnatural that we need to run
in order to show expression.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to be inside the macro ${ ... }
or the run { ... }
that generated the expression in the first place. The only usecase of using show
without a run
of a $
splice is in tests.
1dbb191
to
10d3078
Compare
@@ -34,7 +34,7 @@ class QuoteDriver(appClassloader: ClassLoader) extends Driver { | |||
val ctx = setToolboxSettings(ctx0.fresh.setSetting(ctx0.settings.outputDir, outDir), settings) | |||
|
|||
val driver = new QuoteCompiler | |||
driver.newRun(ctx).compileExpr(expr) | |||
driver.newRun(ctx).compileExpr(exprBuilder) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems after driver.newRun(ctx)
, we already be able to create an instance of QuoteContext
to call exprBuilder()
, such that we can keep ExprCompilationUnit
and related API unchanged as before?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We do have a context but it is not the correct one. In compileExpr
we call compileUnits
which setup the correct context . I would not dare duplicating this logic here.
sealed abstract class Expr[+T] { | ||
|
||
/** Evaluate the contents of this expression and return the result. | ||
* | ||
* May throw a FreeVariableError on expressions that came from a macro. | ||
*/ | ||
@deprecated("Use scala.quoted.run", "") | ||
final def run(implicit toolbox: Toolbox): T = toolbox.run(this) | ||
final def run(implicit toolbox: Toolbox): T = toolbox.run(_ => this) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks to the explanation by @nicolasstucki, now I understand run
is double-meaning for a compiled language: it means to execute the expression, but also mean inside a specific compiler run --- Expr[T]
may only be created for a specific compiler run.
If that is the case, it seems make sense to use path-dependent types to make Expr[T]
depend on compiler run.
After a thorough discussion with @nicolasstucki , I have the following feedback:
Points 5 and 6 may be addressed later, as it's not a priority. And I propose wait 2 release cycles (12 weeks) before we remove the support for the old macro interface, so that we may get feedback if people are already using reflection macros. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Update the staging doc and the description of the PR with the motivation and its ok with me.
For motivation I propose this #5161 (comment) (at least that helped me understand)
edit: after discussions a new withFreshContext { show ... }
will be used instead of run(show ...)
for improved understandability
6409b8e
to
9dc1087
Compare
* | ||
* This method shoul not be called in a context where there is already has a QuoteContext. | ||
*/ | ||
def withNewQuoteContext[T](thunk: given QuoteContext => T) given (toolbox: Toolbox): T = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick: withQuoteContext
seems to be a better name, as the provided QuoteContext
is assumed to be scoped by the thunk
--- there is no old context there, so new
does not convey any meaning.
Another thing for documentation is: what if programmer nest run
or withQuoteContext
--- it's forbidden or allowed.
The main idea is to correctly keep track of the current compiler. This is paramount to support correctly `show` and quote patterns. This PR only contains the addition of the `QuoteContext` to the `run` but not the top level `$` (which will come in a second step). * Add `QuoteContext` * Define a single `Expr.show` entry point in `QuoteContext`(for `expr.show`) * Define `show(expr)` which is an alias of `run(expr.show.toExpr)` that provides a `QuoteContext` * Remove coloring options from the `Reflection.Context` and `Toolbox` settings (`show` never has colors unless a coloring format is given) * Replace `TreeCleaner` with `@quoteTypeTag` and logic in the printers * Fix scala#5161 * Regression local definitions are not renamed in quotes (postponed for future PR). See tests/run-with-compiler/quote-run-2.check.
`scala.quoted.show.apply` was only intorduced to reduce the diff in the tests. Instead we use the equivalent `run(expr.show.toExpr)` notation explicitly. This has the added advantage of showing explicitly where new compiler runs are located.
This provides a way to materialize a QuoteContext without calling `run` and hence performing the full compilation. It is useful if some user only wants to show or inspect an expression without evaluating it. Added a general optimization where if the expression passed to `run` is a known literal, the literal is returned directly without compiling the code.
6496692
to
59cc2d3
Compare
Rebased |
59cc2d3
to
68387bb
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
The main idea is to correctly keep track of the current compiler. This is paramount to support correctly
show
and quote patterns. This PR only contains the addition of theQuoteContext
to therun
but not the top level$
(which will come in a second step).QuoteContext
Expr.show
entry point inQuoteContext
(forexpr.show
)Reflection.Context
andToolbox
settings (show
never has colors unless a coloring format is given)TreeCleaner
with@quoteTypeTag
and logic in the printersFollowed by #6723