Skip to content

Commit f6bc23f

Browse files
committed
Disallow phase inconsitent inline parameters
1 parent 899c59b commit f6bc23f

File tree

66 files changed

+603
-199
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+603
-199
lines changed

compiler/src/dotty/tools/dotc/transform/PCPCheckAndHeal.scala

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,6 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages(
8888
tree match {
8989
case Quoted(_) | Spliced(_) =>
9090
tree
91-
case tree: RefTree if tree.symbol.isAllOf(InlineParam) =>
92-
tree
9391
case _: This =>
9492
assert(checkSymLevel(tree.symbol, tree.tpe, tree.sourcePos).isEmpty)
9593
tree
@@ -197,10 +195,8 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages(
197195
case Some(l) =>
198196
l == level ||
199197
level == -1 && (
200-
// here we assume that Splicer.canBeSpliced was true before going to level -1,
201-
// this implies that all non-inline arguments are quoted and that the following two cases are checked
202-
// on inline parameters or type parameters.
203-
sym.is(Param) ||
198+
// here we assume that Splicer.checkValidMacroBody was true before going to level -1,
199+
// this implies that all arguments are quoted.
204200
sym.isClass // reference to this in inline methods
205201
)
206202
case None =>

compiler/src/dotty/tools/dotc/transform/Splicer.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ object Splicer {
5252
catch {
5353
case ex: CompilationUnit.SuspendException =>
5454
throw ex
55+
case ex: scala.quoted.StopQuotedContext if ctx.reporter.hasErrors =>
56+
// errors have been emitted
57+
EmptyTree
5558
case ex: StopInterpretation =>
5659
ctx.error(ex.msg, ex.pos)
5760
EmptyTree
@@ -389,6 +392,8 @@ object Splicer {
389392
throw new StopInterpretation(sw.toString, pos)
390393
case ex: InvocationTargetException =>
391394
ex.getTargetException match {
395+
case ex: scala.quoted.StopQuotedContext =>
396+
throw ex
392397
case MissingClassDefinedInCurrentRun(sym) =>
393398
if (ctx.settings.XprintSuspension.value)
394399
ctx.echo(i"suspension triggered by a dependency on $sym", pos)

compiler/src/dotty/tools/dotc/transform/TreeMapWithStages.scala

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import dotty.tools.dotc.util.Spans._
1919
import dotty.tools.dotc.util.{Property, SourcePosition}
2020
import dotty.tools.dotc.transform.SymUtils._
2121
import dotty.tools.dotc.typer.Implicits.SearchFailureType
22-
import dotty.tools.dotc.typer.Inliner
2322

2423
import scala.collection.mutable
2524
import scala.annotation.constructorOnly

docs/docs/reference/metaprogramming/macros.md

Lines changed: 24 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -356,11 +356,11 @@ again together with a program that calls `assert`.
356356
```scala
357357
object Macros {
358358

359-
inline def assert(expr: => Boolean): Unit =
359+
inline def assert(inline expr: Boolean): Unit =
360360
${ assertImpl('expr) }
361361

362362
def assertImpl(expr: Expr[Boolean]) =
363-
'{ if !($expr) then throw new AssertionError(s"failed assertion: ${$expr}") }
363+
'{ if !($expr) then throw new AssertionError("failed assertion: " + ${expr.show}) }
364364
}
365365

366366
object App {
@@ -414,33 +414,26 @@ assume that both definitions are local.
414414

415415
The `inline` modifier is used to declare a `val` that is
416416
either a constant or is a parameter that will be a constant when instantiated. This
417-
aspect is also important for macro expansion. To illustrate this,
418-
consider an implementation of the `power` function that makes use of a
419-
statically known exponent:
417+
aspect is also important for macro expansion.
418+
419+
To get values out of expressions containing constants `Expr` provides the methods
420+
`value` (or `getValue`). This will convert the `Expr[T]` into a `T` (or `Some[T]`) when the
421+
expression contains value. Otherwise it will emit an error (or retrun `None`).
422+
To avoid having incidental val bindings generated by the inlining of the `def`
423+
it is recomended to use an inline parameter. To illustrate this, consider an
424+
implementation of the `power` function that makes use of a statically known exponent:
420425
```scala
421-
inline def power(inline n: Int, x: Double) = ${ powerCode(n, 'x) }
426+
inline def power(x: Double, inline n: Int) = ${ powerCode('x, 'n) }
427+
428+
private def powerCode(x: Expr[Double], n: Expr[Int]): Expr[Double] =
429+
powerCode(x, n.value) // Transform `n` into an Int or emit an error if it is not possible
422430

423-
private def powerCode(n: Int, x: Expr[Double]): Expr[Double] =
431+
private def powerCode(x: Expr[Double], n: Int): Expr[Double] =
424432
if (n == 0) '{ 1.0 }
425433
else if (n == 1) x
426-
else if (n % 2 == 0) '{ val y = $x * $x; ${ powerCode(n / 2, 'y) } }
427-
else '{ $x * ${ powerCode(n - 1, x) } }
428-
```
429-
The reference to `n` as an argument in `${ powerCode(n, 'x) }` is not
430-
phase-consistent, since `n` appears in a splice without an enclosing
431-
quote. Normally that would be a problem because it means that we need
432-
the _value_ of `n` at compile time, which is not available for general
433-
parameters. But since `n` is an inline parameter of a macro, we know
434-
that at the macro’s expansion point `n` will be instantiated to a
435-
constant, so the value of `n` will in fact be known at this
436-
point. To reflect this, we loosen the phase consistency requirements
437-
as follows:
438-
439-
- If `x` is a inline value (or a inline parameter of an inline
440-
function) of type Boolean, Byte, Short, Int, Long, Float, Double,
441-
Char or String, it can be accessed in all contexts where the number
442-
of splices minus the number of quotes between use and definition
443-
is either 0 or 1.
434+
else if (n % 2 == 0) '{ val y = $x * $x; ${ powerCode('y, n / 2) } }
435+
else '{ $x * ${ powerCode(x, n - 1) } }
436+
```
444437

445438
### Scope Extrusion
446439

@@ -472,7 +465,7 @@ that invokation of `run` in splices. Consider the following expression:
472465
'{ (x: Int) => ${ run('x); 1 } }
473466
```
474467
This is again phase correct, but will lead us into trouble. Indeed, evaluating
475-
the splice will reduce the expression `('x).run` to `x`. But then the result
468+
the splice will reduce the expression `run('x)` to `x`. But then the result
476469

477470
```scala
478471
'{ (x: Int) => ${ x; 1 } }
@@ -590,9 +583,9 @@ inline method that can calculate either a value of type `Int` or a value of type
590583
`String`.
591584

592585
```scala
593-
inline def defaultOf(inline str: String) <: Any = ${ defaultOfImpl(str) }
586+
inline def defaultOf(inline str: String) <: Any = ${ defaultOfImpl('str) }
594587

595-
def defaultOfImpl(str: String): Expr[Any] = str match {
588+
def defaultOfImpl(strExpr: Expr[String]): Expr[Any] = strExpr.value match {
596589
case "int" => '{1}
597590
case "string" => '{"a"}
598591
}
@@ -624,8 +617,10 @@ It is possible to deconstruct or extract values out of `Expr` using pattern matc
624617
In `scala.quoted.matching` contains object that can help extract values from `Expr`.
625618

626619
* `scala.quoted.matching.Const`: matches an expression a literal value and returns the value.
620+
* `scala.quoted.matching.Value`: matches an expression a value and returns the value.
627621
* `scala.quoted.matching.ExprSeq`: matches an explicit sequence of expresions and returns them. These sequences are useful to get individual `Expr[T]` out of a varargs expression of type `Expr[Seq[T]]`.
628622
* `scala.quoted.matching.ConstSeq`: matches an explicit sequence of literal values and returns them.
623+
* `scala.quoted.matching.ValueSeq`: matches an explicit sequence of values and returns them.
629624

630625
These could be used in the following way to optimize any call to `sum` that has statically known values.
631626
```scala
@@ -661,7 +656,7 @@ optimize {
661656
```
662657

663658
```scala
664-
def sum(args: =>Int*): Int = args.sum
659+
def sum(args: Int*): Int = args.sum
665660
inline def optimize(arg: Int): Int = ${ optimizeExpr('arg) }
666661
private def optimizeExpr(body: Expr[Int])(given QuoteContext): Expr[Int] = body match {
667662
// Match a call to sum without any arguments

library/src/scala/internal/quoted/Matcher.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -229,8 +229,8 @@ private[quoted] object Matcher {
229229
case (While(cond1, body1), While(cond2, body2)) =>
230230
cond1 =?= cond2 && body1 =?= body2
231231

232-
case (New(tpt1), New(tpt2)) =>
233-
tpt1 =?= tpt2
232+
case (New(tpt1), New(tpt2)) if tpt1.tpe.typeSymbol == tpt2.tpe.typeSymbol =>
233+
matched
234234

235235
case (This(_), This(_)) if scrutinee.symbol == pattern.symbol =>
236236
matched

library/src/scala/quoted/Expr.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,14 @@ class Expr[+T] private[scala] {
1818
*/
1919
final def getValue[U >: T](given qctx: QuoteContext, valueOf: ValueOfExpr[U]): Option[U] = valueOf(this)
2020

21+
/** Return the value of this expression.
22+
*
23+
* Emits an error error and throws if the expression does not contain a value or contains side effects.
24+
* Otherwise returns the value.
25+
*/
26+
final def value[U >: T](given qctx: QuoteContext, valueOf: ValueOfExpr[U]): U =
27+
valueOf(this).getOrElse(qctx.throwError(s"Expected a known value. \n\nThe value of: $show\ncould not be recovered using $valueOf", this))
28+
2129
/** Pattern matches `this` against `that`. Effectively performing a deep equality check.
2230
* It does the equivalent of
2331
* ```

library/src/scala/quoted/QuoteContext.scala

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class QuoteContext(val tasty: scala.tasty.Reflection) {
2222
tpe.unseal.show(syntaxHighlight)
2323
}
2424

25-
/** Report an error */
25+
/** Report an error at the position of the macro expansion */
2626
def error(msg: => String): Unit = {
2727
import tasty.{_, given}
2828
tasty.error(msg, rootPosition)(given rootContext)
@@ -34,6 +34,17 @@ class QuoteContext(val tasty: scala.tasty.Reflection) {
3434
tasty.error(msg, expr.unseal.pos)(given rootContext)
3535
}
3636

37+
/** Report an error at the position of the macro expansion and throws a StopQuotedContext */
38+
def throwError(msg: => String): Nothing = {
39+
error(msg)
40+
throw new StopQuotedContext
41+
}
42+
/** Report an error at the on the position of `expr` and throws a StopQuotedContext */
43+
def throwError(msg: => String, expr: Expr[_]): Nothing = {
44+
error(msg, expr)
45+
throw new StopQuotedContext
46+
}
47+
3748
/** Report a warning */
3849
def warning(msg: => String): Unit = {
3950
import tasty.{_, given}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package scala.quoted
2+
3+
/** Stop code generation after an error has been reported */
4+
class StopQuotedContext extends Throwable

0 commit comments

Comments
 (0)