Skip to content

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

Merged
merged 12 commits into from
Jun 28, 2019
Merged

Add QuoteContext #6700

merged 12 commits into from
Jun 28, 2019

Conversation

nicolasstucki
Copy link
Contributor

@nicolasstucki nicolasstucki commented Jun 17, 2019

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)
  • 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 ArrayIndexOutOfBoundsException while unpickling quote #5161
  • Regression local definitions are not renamed in quotes (postponed for future PR). See tests/run-with-compiler/quote-run-2.check.

Followed by #6723

@nicolasstucki nicolasstucki self-assigned this Jun 17, 2019
@nicolasstucki nicolasstucki mentioned this pull request Jun 17, 2019
16 tasks
@nicolasstucki nicolasstucki force-pushed the add-quote-context branch 9 times, most recently from cbac1cc to f72acbd Compare June 20, 2019 15:15
@nicolasstucki nicolasstucki requested a review from liufengyun June 20, 2019 15:35
@nicolasstucki nicolasstucki force-pushed the add-quote-context branch 2 times, most recently from bc8b23d to e4d5bf1 Compare June 20, 2019 20:19
@nicolasstucki nicolasstucki marked this pull request as ready for review June 20, 2019 20:59
@nicolasstucki nicolasstucki force-pushed the add-quote-context branch 3 times, most recently from 53ce0da to c22eb4b Compare June 21, 2019 08:07
Copy link
Contributor

@liufengyun liufengyun left a 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:

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
}
Copy link
Contributor

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?

Copy link
Contributor Author

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)

}
Copy link
Contributor

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)?

Copy link
Contributor Author

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

Copy link
Contributor Author

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

Copy link
Contributor Author

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)

Copy link
Contributor

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.

Copy link
Contributor Author

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)))
Copy link
Contributor

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.

Copy link
Contributor Author

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
})

Copy link
Contributor

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.

Copy link
Contributor Author

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.

@@ -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)
Copy link
Contributor

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?

Copy link
Contributor Author

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)
Copy link
Contributor

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.

@liufengyun
Copy link
Contributor

After a thorough discussion with @nicolasstucki , I have the following feedback:

  1. The overall design of QuoteContext is a good direction to unify quoted macros and staging.
  2. The refactoring of the macro interface is based on the assumption that most macros will be quoted macros, only advanced macros will be reflection macros.
  3. @nicolasstucki mentioned the constraint that Expr[T] unpicked from a compiler run context may only compile in that compiler run. It would be good to document this compiler invariant.
  4. If the constraint generalizes to Expr[T] always dependent on a compiler run, then a logical conclusion would be make Expr[T] path-dependent on QuoteContext --- which will have a big impact on the usability of Expr[T]. It would be good to document this.
  5. For staging, current PR only exposes run that gives a QuoteContext, which does not make sense if a meta-programmer only wants to generate C code from staging or just show an Expr (no compile nor run).
  6. It would be good to evaluate how does the same-run constraint play with the scenario above and how does it impact the API design.

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.

/cc: @odersky @biboudis you may want to have a look too.

Copy link
Contributor

@biboudis biboudis left a 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

@nicolasstucki nicolasstucki force-pushed the add-quote-context branch 2 times, most recently from 6409b8e to 9dc1087 Compare June 26, 2019 15:24
*
* 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 = {
Copy link
Contributor

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.
@nicolasstucki
Copy link
Contributor Author

Rebased

Copy link
Contributor

@liufengyun liufengyun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@nicolasstucki nicolasstucki merged commit 8212f60 into scala:master Jun 28, 2019
@nicolasstucki nicolasstucki deleted the add-quote-context branch June 28, 2019 08:41
@nicolasstucki nicolasstucki changed the title Add quote context Add QuoteContext Jul 1, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

ArrayIndexOutOfBoundsException while unpickling quote
3 participants