From 2d1ef6386a18f26735e0057a1216c67e7858962c Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 15 Mar 2023 13:51:35 +0100 Subject: [PATCH] Fix references to class members defined in quotes If an inner quote selects a symbol that is defined in an outer quote we need to transform it or reject it. The issue is that the inner quote cannot contain a reference to the type of the class defined in the outer quote. Any such reference is erased the parents of that class that are statically know outside those quotes. If the selected symbol is overriding a symbol in one of those statically known classes, we can use that overridden symbol instead. If not we have to reject the code. Fixes #17103 --- .../dotty/tools/dotc/transform/Splicing.scala | 8 +++++++ tests/neg-macros/i17103.scala | 16 ++++++++++++++ tests/pos-macros/i17103a.scala | 21 +++++++++++++++++++ tests/pos-macros/i17103b.scala | 21 +++++++++++++++++++ tests/pos-macros/i7405b.scala | 6 +++++- 5 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 tests/neg-macros/i17103.scala create mode 100644 tests/pos-macros/i17103a.scala create mode 100644 tests/pos-macros/i17103b.scala diff --git a/compiler/src/dotty/tools/dotc/transform/Splicing.scala b/compiler/src/dotty/tools/dotc/transform/Splicing.scala index 7ecde9400445..47b65ba767bb 100644 --- a/compiler/src/dotty/tools/dotc/transform/Splicing.scala +++ b/compiler/src/dotty/tools/dotc/transform/Splicing.scala @@ -208,6 +208,14 @@ class Splicing extends MacroTransform: override def transform(tree: tpd.Tree)(using Context): tpd.Tree = tree match + case tree: Select if tree.isTerm && isCaptured(tree.symbol) => + tree.symbol.allOverriddenSymbols.find(sym => !isCaptured(sym.owner)) match + case Some(sym) => + // virtualize call on overridden symbol that is not defined in a non static class + transform(tree.qualifier.select(sym)) + case _ => + report.error(em"Can not use reference to staged local ${tree.symbol} defined in an outer quote.\n\nThis can work if ${tree.symbol.owner} would extend a top level interface that defines ${tree.symbol}.", tree) + tree case tree: RefTree => if tree.isTerm then if isCaptured(tree.symbol) then diff --git a/tests/neg-macros/i17103.scala b/tests/neg-macros/i17103.scala new file mode 100644 index 000000000000..bd4b41d8b559 --- /dev/null +++ b/tests/neg-macros/i17103.scala @@ -0,0 +1,16 @@ +import scala.quoted.* + +def test(using Quotes): Expr[Unit] = + '{ + trait C: + def d: Int + val c: C = ??? + ${ + val expr = '{ + val cRef: c.type = ??? + cRef.d // error + () + } + expr + } + } \ No newline at end of file diff --git a/tests/pos-macros/i17103a.scala b/tests/pos-macros/i17103a.scala new file mode 100644 index 000000000000..ffd0c15f28b2 --- /dev/null +++ b/tests/pos-macros/i17103a.scala @@ -0,0 +1,21 @@ +import scala.quoted.* + +trait C0: + def d: Int + +def test(using Quotes): Expr[Unit] = + '{ + trait C1 extends C0: + def d: Int + trait C extends C1: + def d: Int + val c: C = ??? + ${ + val expr = '{ + val cRef: C = ??? + cRef.d // calls C0.d + () + } + expr + } + } diff --git a/tests/pos-macros/i17103b.scala b/tests/pos-macros/i17103b.scala new file mode 100644 index 000000000000..0fbe86f0cf73 --- /dev/null +++ b/tests/pos-macros/i17103b.scala @@ -0,0 +1,21 @@ +import scala.quoted.* + +trait C0: + def d: Int + +def test(using Quotes): Expr[Unit] = + '{ + trait C1 extends C0: + def d: Int + trait C extends C1: + def d: Int + val c: C = ??? + ${ + val expr = '{ + val cRef: c.type = ??? + cRef.d // calls C0.d + () + } + expr + } + } diff --git a/tests/pos-macros/i7405b.scala b/tests/pos-macros/i7405b.scala index df7218608e88..6c73c275e15f 100644 --- a/tests/pos-macros/i7405b.scala +++ b/tests/pos-macros/i7405b.scala @@ -3,7 +3,7 @@ import scala.quoted.* class Foo { def f(using Quotes): Expr[Any] = { '{ - trait X { + trait X extends A { type Y def y: Y = ??? } @@ -17,3 +17,7 @@ class Foo { } } } + +trait A: + type Y + def y: Y = ???