diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index a6118732d4ae..7699a635748b 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -92,6 +92,7 @@ class Compiler { new StringInterpolatorOpt, // Optimizes raw and s and f string interpolators by rewriting them to string concatenations or formats new DropBreaks) :: // Optimize local Break throws by rewriting them List(new PruneErasedDefs, // Drop erased definitions from scopes and simplify erased expressions + new PruneQuotes, // Drop non-pickled copies of the quotes new UninitializedDefs, // Replaces `compiletime.uninitialized` by `_` new InlinePatterns, // Remove placeholders of inlined patterns new VCInlineMethods, // Inlines calls to value class methods diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index 9f6aa3a3db70..d1d669bd008e 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -1061,6 +1061,11 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => val quoteType = tree.tpe // `Quotes ?=> Expr[T]` or `Quotes ?=> Type[T]` val exprType = quoteType.argInfos.last // `Expr[T]` or `Type[T]` exprType.argInfos.head // T + + /** Returns scala.quoted.{Expr,Type} depending if this is a term or type quote */ + def quoteKind(using Context): Type = + if tree.isTypeQuote then defn.QuotedTypeClass.typeRef + else defn.QuotedExprClass.typeRef end extension extension (tree: tpd.QuotePattern) diff --git a/compiler/src/dotty/tools/dotc/core/Phases.scala b/compiler/src/dotty/tools/dotc/core/Phases.scala index 2a3828004525..221f73a2e476 100644 --- a/compiler/src/dotty/tools/dotc/core/Phases.scala +++ b/compiler/src/dotty/tools/dotc/core/Phases.scala @@ -213,6 +213,7 @@ object Phases { private var myInliningPhase: Phase = _ private var myStagingPhase: Phase = _ private var mySplicingPhase: Phase = _ + private var myPickleQuotesPhase: Phase = _ private var myFirstTransformPhase: Phase = _ private var myCollectNullableFieldsPhase: Phase = _ private var myRefChecksPhase: Phase = _ @@ -238,6 +239,7 @@ object Phases { final def inliningPhase: Phase = myInliningPhase final def stagingPhase: Phase = myStagingPhase final def splicingPhase: Phase = mySplicingPhase + final def pickleQuotesPhase: Phase = myPickleQuotesPhase final def firstTransformPhase: Phase = myFirstTransformPhase final def collectNullableFieldsPhase: Phase = myCollectNullableFieldsPhase final def refchecksPhase: Phase = myRefChecksPhase @@ -266,6 +268,7 @@ object Phases { myInliningPhase = phaseOfClass(classOf[Inlining]) myStagingPhase = phaseOfClass(classOf[Staging]) mySplicingPhase = phaseOfClass(classOf[Splicing]) + myPickleQuotesPhase = phaseOfClass(classOf[PickleQuotes]) myFirstTransformPhase = phaseOfClass(classOf[FirstTransform]) myCollectNullableFieldsPhase = phaseOfClass(classOf[CollectNullableFields]) myRefChecksPhase = phaseOfClass(classOf[RefChecks]) @@ -452,8 +455,9 @@ object Phases { def sbtExtractDependenciesPhase(using Context): Phase = ctx.base.sbtExtractDependenciesPhase def picklerPhase(using Context): Phase = ctx.base.picklerPhase def inliningPhase(using Context): Phase = ctx.base.inliningPhase - def stagingPhase(using Context): Phase = ctx.base.stagingPhase + def stagingPhase(using Context): Phase = ctx.base.stagingPhase def splicingPhase(using Context): Phase = ctx.base.splicingPhase + def pickleQuotesPhase(using Context): Phase = ctx.base.pickleQuotesPhase def firstTransformPhase(using Context): Phase = ctx.base.firstTransformPhase def refchecksPhase(using Context): Phase = ctx.base.refchecksPhase def elimRepeatedPhase(using Context): Phase = ctx.base.elimRepeatedPhase diff --git a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala index 03639c8af689..c24c6d874d22 100644 --- a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala +++ b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala @@ -184,7 +184,8 @@ class FirstTransform extends MiniPhase with InfoTransformer { thisPhase => if (!ctx.mode.is(Mode.Pattern)) constToLiteral(tree) else tree override def transformBlock(tree: Block)(using Context): Tree = - constToLiteral(tree) + if tree.isType then tree + else constToLiteral(tree) override def transformIf(tree: If)(using Context): Tree = tree.cond.tpe match { diff --git a/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala b/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala index 8b58f18bca52..911ef7ab8624 100644 --- a/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala @@ -80,10 +80,10 @@ class PickleQuotes extends MacroTransform { override def checkPostCondition(tree: Tree)(using Context): Unit = tree match - case tree: Quote => - assert(Inlines.inInlineMethod) - case tree: Splice => - assert(Inlines.inInlineMethod) + // case tree: Quote => + // assert(Inlines.inInlineMethod) + // case tree: Splice => + // assert(Inlines.inInlineMethod) case _ => override def run(using Context): Unit = @@ -93,16 +93,24 @@ class PickleQuotes extends MacroTransform { override def transform(tree: tpd.Tree)(using Context): tpd.Tree = tree match case Apply(Select(quote: Quote, nme.apply), List(quotes)) => - val (holeContents, quote1) = extractHolesContents(quote) + val (contents, quote1) = extractHolesContents(quote) val quote2 = encodeTypeArgs(quote1) - val holeContents1 = holeContents.map(transform(_)) - PickleQuotes.pickle(quote2, quotes, holeContents1) + val contents1 = contents.map(transform(_)) ::: quote.tags + val pickled = PickleQuotes.pickle(quote2, quotes, contents1) + cpy.Block(tree)(removeHoleContents(quote) :: Nil, pickled) case tree: DefDef if !tree.rhs.isEmpty && tree.symbol.isInlineMethod => tree case _ => super.transform(tree) } + private def removeHoleContents(tree: Tree)(using Context) = + new TreeMap { + override def transform(tree: Tree)(using Context): Tree = tree match + case tree: Hole => cpy.Hole(tree)(content = EmptyTree) + case _ => super.transform(tree) + }.transform(tree) + private def extractHolesContents(quote: tpd.Quote)(using Context): (List[Tree], tpd.Quote) = class HoleContentExtractor extends Transformer: private val holeContents = List.newBuilder[Tree] diff --git a/compiler/src/dotty/tools/dotc/transform/PruneQuotes.scala b/compiler/src/dotty/tools/dotc/transform/PruneQuotes.scala new file mode 100644 index 000000000000..133a8763989a --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/PruneQuotes.scala @@ -0,0 +1,31 @@ +package dotty.tools.dotc +package transform + +import dotty.tools.dotc.ast.tpd +import dotty.tools.dotc.core.Contexts._ +import dotty.tools.dotc.core.Decorators.* +import dotty.tools.dotc.core.Symbols._ +import dotty.tools.dotc.inlines.Inlines +import dotty.tools.dotc.transform.MegaPhase.MiniPhase +import dotty.tools.dotc.staging.StagingLevel.level + +/** This phase removes all non-pickled quotes */ +class PruneQuotes extends MiniPhase { thisTransform => + import tpd._ + import PruneQuotes._ + + override def phaseName: String = PruneQuotes.name + + override def description: String = PruneQuotes.description + + override def transformQuote(tree: Quote)(using Context): Tree = + if Inlines.inInlineMethod || level > 0 then tree + else Thicket() +} + +object PruneQuotes { + import tpd._ + + val name: String = "pruneQuotes" + val description: String = "Drop non-pickled copies of the quotes. Only keep the pickled version." +} diff --git a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala index de611db6c14e..fb3a24fbda39 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -677,7 +677,7 @@ object TreeChecker { assert(tree.tags.isEmpty, i"unexpected tags in Quote before staging phase: ${tree.tags}") else assert(!tree.body.isInstanceOf[untpd.Splice] || inInlineMethod, i"missed quote cancellation in $tree") - assert(!tree.body.isInstanceOf[untpd.Hole] || inInlineMethod, i"missed quote cancellation in $tree") + // assert(!tree.body.isInstanceOf[untpd.Hole] || inInlineMethod, i"missed quote cancellation in $tree") if StagingLevel.level != 0 then assert(tree.tags.isEmpty, i"unexpected tags in Quote at staging level ${StagingLevel.level}: ${tree.tags}") @@ -690,7 +690,7 @@ object TreeChecker { tree1 match case Quote(body, targ :: Nil) if body.isType => - assert(!(body.tpe =:= targ.tpe.select(tpnme.Underlying)), i"missed quote cancellation in $tree1") + // assert(!(body.tpe =:= targ.tpe.select(tpnme.Underlying)), i"missed quote cancellation in $tree1") case _ => tree1 @@ -734,26 +734,29 @@ object TreeChecker { if isTerm then assert(tree1.typeOpt <:< pt) else assert(tree1.typeOpt =:= pt) - // Check that the types of the args conform to the types of the contents of the hole - val argQuotedTypes = args.map { arg => - if arg.isTerm then - val tpe = arg.typeOpt.widenTermRefExpr match - case _: MethodicType => - // Special erasure for captured function references - // See `SpliceTransformer.transformCapturedApplication` - defn.AnyType - case tpe => tpe - defn.QuotedExprClass.typeRef.appliedTo(tpe) - else defn.QuotedTypeClass.typeRef.appliedTo(arg.typeOpt.widenTermRefExpr) - } - val expectedResultType = - if isTerm then defn.QuotedExprClass.typeRef.appliedTo(tree1.typeOpt) - else defn.QuotedTypeClass.typeRef.appliedTo(tree1.typeOpt) - val contextualResult = - defn.FunctionOf(List(defn.QuotesClass.typeRef), expectedResultType, isContextual = true) - val expectedContentType = - defn.FunctionOf(argQuotedTypes, contextualResult) - assert(content.typeOpt =:= expectedContentType, i"unexpected content of hole\nexpected: ${expectedContentType}\nwas: ${content.typeOpt}") + if pickleQuotesPhase <= ctx.phase then + assert(content.isEmpty) + else + // Check that the types of the args conform to the types of the contents of the hole + val argQuotedTypes = args.map { arg => + if arg.isTerm then + val tpe = arg.typeOpt.widenTermRefExpr match + case _: MethodicType => + // Special erasure for captured function references + // See `SpliceTransformer.transformCapturedApplication` + defn.AnyType + case tpe => tpe + defn.QuotedExprClass.typeRef.appliedTo(tpe) + else defn.QuotedTypeClass.typeRef.appliedTo(arg.typeOpt.widenTermRefExpr) + } + val expectedResultType = + if isTerm then defn.QuotedExprClass.typeRef.appliedTo(tree1.typeOpt) + else defn.QuotedTypeClass.typeRef.appliedTo(tree1.typeOpt) + val contextualResult = + defn.FunctionOf(List(defn.QuotesClass.typeRef), expectedResultType, isContextual = true) + val expectedContentType = + defn.FunctionOf(argQuotedTypes, contextualResult) + assert(content.typeOpt =:= expectedContentType, i"unexpected content of hole\nexpected: ${expectedContentType}\nwas: ${content.typeOpt}") tree1 } diff --git a/tests/neg-custom-args/deprecation/i17400b.scala b/tests/neg-custom-args/deprecation/i17400b.scala new file mode 100644 index 000000000000..ff12ac8cded2 --- /dev/null +++ b/tests/neg-custom-args/deprecation/i17400b.scala @@ -0,0 +1,10 @@ +import scala.quoted.* + +class B: + @deprecated def dep: Int = 1 + +def checkDeprecated(using Quotes) = + '{ + val b = new B + b.dep // error + } diff --git a/tests/neg-custom-args/no-experimental/i17400c.scala b/tests/neg-custom-args/no-experimental/i17400c.scala new file mode 100644 index 000000000000..d1d598b4de87 --- /dev/null +++ b/tests/neg-custom-args/no-experimental/i17400c.scala @@ -0,0 +1,9 @@ +import scala.annotation.experimental +import scala.quoted.* + +@experimental class C + +def checkExperimental(using Quotes) = + '{ + println(new C) // error + } diff --git a/tests/neg-macros/i15700.scala b/tests/neg-macros/i15700.scala new file mode 100644 index 000000000000..665e7476c1fa --- /dev/null +++ b/tests/neg-macros/i15700.scala @@ -0,0 +1,20 @@ +import scala.quoted.* + +trait Foo: + def xxx: Int + +inline def foo(inline cond: Boolean) = ${ fooImpl('cond) } + +def fooImpl(cond: Expr[Boolean])(using Quotes) = + if cond.valueOrAbort then + '{ + new Foo { + override def xxx = 2 + } + } + else + '{ + new Foo { // error: object creation impossible, since def xxx: Int in trait Foo is not defined + override def xxxx = 1 // error: method xxxx overrides nothing + } + } diff --git a/tests/neg-macros/i17400a.scala b/tests/neg-macros/i17400a.scala new file mode 100644 index 000000000000..1ca3e9bf0985 --- /dev/null +++ b/tests/neg-macros/i17400a.scala @@ -0,0 +1,11 @@ +import scala.quoted.* + +class A: + def a: Int = 1 + +def checkOverride(using Quotes) = + '{ + class Sub extends A: + def a: String = "" // error + new Sub + } diff --git a/tests/pos-macros/i7405b.scala b/tests/pos-macros/i7405b.scala index 6c73c275e15f..542b5b0b1f32 100644 --- a/tests/pos-macros/i7405b.scala +++ b/tests/pos-macros/i7405b.scala @@ -5,7 +5,7 @@ class Foo { '{ trait X extends A { type Y - def y: Y = ??? + override def y: Y = ??? } val x: X = ??? type Z = x.Y