From 12f2c740ef2127e61580fd7e28e5cd578a4f46a3 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Fri, 12 Jun 2020 18:14:10 +0200 Subject: [PATCH 1/7] reading TASTy version 23.0 is total --- project/DottySupport.scala | 4 ++-- .../scala/tools/nsc/tasty/TreeUnpickler.scala | 6 ++++-- .../scala/tools/nsc/tasty/bridge/FlagOps.scala | 5 +++-- .../scala/tools/nsc/tasty/bridge/TypeOps.scala | 8 ++++++-- src/compiler/scala/tools/tasty/TastyFlags.scala | 4 +++- src/compiler/scala/tools/tasty/TastyFormat.scala | 11 ++++++++--- test/tasty/pos/src-3/tastytest/SelectIns.scala | 11 +++++++++++ test/tasty/run/src-2/tastytest/TestSuperOps.scala | 12 ++++++++++++ test/tasty/run/src-3/tastytest/SuperOps.scala | 5 +++++ 9 files changed, 54 insertions(+), 12 deletions(-) create mode 100644 test/tasty/pos/src-3/tastytest/SelectIns.scala create mode 100644 test/tasty/run/src-2/tastytest/TestSuperOps.scala create mode 100644 test/tasty/run/src-3/tastytest/SuperOps.scala diff --git a/project/DottySupport.scala b/project/DottySupport.scala index b01b26a708c7..acc02d221cdc 100644 --- a/project/DottySupport.scala +++ b/project/DottySupport.scala @@ -12,8 +12,8 @@ import sbt.librarymanagement.{ * Settings to support validation of TastyUnpickler against the release of dotty with the matching TASTy version */ object TastySupport { - val supportedTASTyRelease = "0.24.0-RC1" // TASTy version 22 - val dottyCompiler = "ch.epfl.lamp" % "dotty-compiler_0.24" % supportedTASTyRelease + val supportedTASTyRelease = "0.25.0-RC1" // TASTy version 23 + val dottyCompiler = "ch.epfl.lamp" % "dotty-compiler_0.25" % supportedTASTyRelease } /** Settings needed to compile with Dotty, diff --git a/src/compiler/scala/tools/nsc/tasty/TreeUnpickler.scala b/src/compiler/scala/tools/nsc/tasty/TreeUnpickler.scala index 2c6ba52af906..06e48c22fa48 100644 --- a/src/compiler/scala/tools/nsc/tasty/TreeUnpickler.scala +++ b/src/compiler/scala/tools/nsc/tasty/TreeUnpickler.scala @@ -581,6 +581,7 @@ class TreeUnpickler[Tasty <: TastyUniverse]( case STATIC => addFlag(Static) case OBJECT => addFlag(Object) case TRAIT => addFlag(Trait) + case SUPERTRAIT => addFlag(SuperTrait) case ENUM => addFlag(Enum) case LOCAL => addFlag(Local) case SYNTHETIC => addFlag(Synthetic) @@ -753,7 +754,7 @@ class TreeUnpickler[Tasty <: TastyUniverse]( else tpe ) case TYPEDEF | TYPEPARAM => - val unsupported = completer.tastyFlagSet &~ (Enum | Open | Opaque | Exported) + val unsupported = completer.tastyFlagSet &~ (Enum | Open | Opaque | Exported | SuperTrait) unsupportedWhen(unsupported.hasFlags, s"flags on $sym: ${showTasty(unsupported)}") if (sym.isClass) { sym.owner.ensureCompleted() @@ -899,7 +900,7 @@ class TreeUnpickler[Tasty <: TastyUniverse]( def completeSelect(name: TastyName)(implicit ctx: Context): Tree = { val localCtx = ctx.selectionCtx(name) val qual = readTerm()(localCtx) - val qualType = qual.tpe + val qualType = qual.tpe // TODO [tasty]: qual.tpe.widenIfUnstable tpd.Select(qual, name)(namedMemberOfPrefix(qualType, name)(localCtx)) } @@ -993,6 +994,7 @@ class TreeUnpickler[Tasty <: TastyUniverse]( case UNAPPLY => unsupportedTermTreeError("unapply pattern") case INLINED => unsupportedTermTreeError("inlined expression") case SELECTouter => metaprogrammingIsUnsupported // only within inline + case SELECTin => unsupportedTermTreeError("selection with a prefix") // TODO [tasty]: find test case to trigger this case HOLE => assertNoMacroHole case _ => readPathTerm() } diff --git a/src/compiler/scala/tools/nsc/tasty/bridge/FlagOps.scala b/src/compiler/scala/tools/nsc/tasty/bridge/FlagOps.scala index f4623885464f..839bb8230483 100644 --- a/src/compiler/scala/tools/nsc/tasty/bridge/FlagOps.scala +++ b/src/compiler/scala/tools/nsc/tasty/bridge/FlagOps.scala @@ -21,8 +21,8 @@ trait FlagOps { self: TastyUniverse => object FlagSets { val TastyOnlyFlags: TastyFlagSet = ( - Erased | Internal | Inline | InlineProxy | Opaque | Extension | Given | Exported | Macro | Enum | Open - | ParamAlias + Erased | Internal | Inline | InlineProxy | Opaque | Extension | Given | Exported | Macro | SuperTrait | Enum + | Open | ParamAlias ) val TermParamOrAccessor: TastyFlagSet = Param | ParamSetter val ObjectCreationFlags: TastyFlagSet = Object | Lazy | Final | Stable @@ -77,6 +77,7 @@ trait FlagOps { self: TastyUniverse => case Given => "given" case Exported => "" case Macro => "" + case SuperTrait => "" case Enum => "enum" case Open => "open" case ParamAlias => "" diff --git a/src/compiler/scala/tools/nsc/tasty/bridge/TypeOps.scala b/src/compiler/scala/tools/nsc/tasty/bridge/TypeOps.scala index d296a15811da..f32f331fbb0f 100644 --- a/src/compiler/scala/tools/nsc/tasty/bridge/TypeOps.scala +++ b/src/compiler/scala/tools/nsc/tasty/bridge/TypeOps.scala @@ -310,8 +310,12 @@ trait TypeOps { self: TastyUniverse => def namedMemberOfPrefix(pre: Type, name: TastyName)(implicit ctx: Context): Type = namedMemberOfTypeWithPrefix(pre, pre, name) - def namedMemberOfTypeWithPrefix(pre: Type, space: Type, tname: TastyName)(implicit ctx: Context): Type = - prefixedRef(pre, namedMemberOfType(space, tname)) + def namedMemberOfTypeWithPrefix(pre: Type, space: Type, tname: TastyName)(implicit ctx: Context): Type = { + val sym = namedMemberOfType(space, tname) + val tpe = prefixedRef(pre, sym) + if (pre != space && !tname.isTypeName) tpe.asSeenFrom(pre, sym.owner) + else tpe + } def lambdaResultType(resType: Type): Type = resType match { case res: LambdaPolyType => res.toNested diff --git a/src/compiler/scala/tools/tasty/TastyFlags.scala b/src/compiler/scala/tools/tasty/TastyFlags.scala index 5a4009314301..76f9b748176d 100644 --- a/src/compiler/scala/tools/tasty/TastyFlags.scala +++ b/src/compiler/scala/tools/tasty/TastyFlags.scala @@ -55,7 +55,8 @@ object TastyFlags { final val Given = Extension.next final val Exported = Given.next final val Macro = Exported.next - final val Enum = Macro.next + final val SuperTrait = Macro.next + final val Enum = SuperTrait.next final val Open = Enum.next final val ParamAlias = Open.next @@ -132,6 +133,7 @@ object TastyFlags { case Given => "Given" case Exported => "Exported" case Macro => "Macro" + case SuperTrait => "SuperTrait" case Enum => "Enum" case Open => "Open" case ParamAlias => "ParamAlias" diff --git a/src/compiler/scala/tools/tasty/TastyFormat.scala b/src/compiler/scala/tools/tasty/TastyFormat.scala index dbc14192924a..47495f6c6b2b 100644 --- a/src/compiler/scala/tools/tasty/TastyFormat.scala +++ b/src/compiler/scala/tools/tasty/TastyFormat.scala @@ -15,7 +15,7 @@ package scala.tools.tasty object TastyFormat { final val header: Array[Int] = Array(0x5C, 0xA1, 0xAB, 0x1F) - val MajorVersion: Int = 22 + val MajorVersion: Int = 23 val MinorVersion: Int = 0 /** Tags used to serialize names */ @@ -98,6 +98,7 @@ object TastyFormat { final val OPEN = 40 final val PARAMEND = 41 final val PARAMalias = 42 + final val SUPERTRAIT = 43 // Cat. 2: tag Nat @@ -196,6 +197,7 @@ object TastyFormat { final val ANNOTATION = 173 final val TERMREFin = 174 final val TYPEREFin = 175 + final val SELECTin = 176 final val METHODtype = 180 @@ -211,7 +213,7 @@ object TastyFormat { /** Useful for debugging */ def isLegalTag(tag: Int): Boolean = - firstSimpleTreeTag <= tag && tag <= PARAMalias || + firstSimpleTreeTag <= tag && tag <= SUPERTRAIT || firstNatTreeTag <= tag && tag <= RENAMED || firstASTTreeTag <= tag && tag <= BOUNDED || firstNatASTTreeTag <= tag && tag <= NAMEDARG || @@ -240,6 +242,7 @@ object TastyFormat { | STATIC | OBJECT | TRAIT + | SUPERTRAIT | ENUM | LOCAL | SYNTHETIC @@ -301,6 +304,7 @@ object TastyFormat { case OBJECT => "OBJECT" case TRAIT => "TRAIT" case ENUM => "ENUM" + case SUPERTRAIT => "SUPERTRAIT" case LOCAL => "LOCAL" case SYNTHETIC => "SYNTHETIC" case ARTIFACT => "ARTIFACT" @@ -390,6 +394,7 @@ object TastyFormat { case SUPERtype => "SUPERtype" case TERMREFin => "TERMREFin" case TYPEREFin => "TYPEREFin" + case SELECTin => "SELECTin" case REFINEDtype => "REFINEDtype" case REFINEDtpt => "REFINEDtpt" @@ -419,7 +424,7 @@ object TastyFormat { */ def numRefs(tag: Int): Int = tag match { case VALDEF | DEFDEF | TYPEDEF | TYPEPARAM | PARAM | NAMEDARG | RETURN | BIND | - SELFDEF | REFINEDtype | TERMREFin | TYPEREFin | HOLE => 1 + SELFDEF | REFINEDtype | TERMREFin | TYPEREFin | SELECTin | HOLE => 1 case RENAMED | PARAMtype => 2 case POLYtype | TYPELAMBDAtype | METHODtype => -1 case _ => 0 diff --git a/test/tasty/pos/src-3/tastytest/SelectIns.scala b/test/tasty/pos/src-3/tastytest/SelectIns.scala new file mode 100644 index 000000000000..99f0f48e46ce --- /dev/null +++ b/test/tasty/pos/src-3/tastytest/SelectIns.scala @@ -0,0 +1,11 @@ +package tastytest + +object SelectIns { + + def test1 = { + val foo = scala.collection.mutable.ArrayBuffer.empty[Seq[Double]] + val bar = Seq.empty[Double] + foo.append(bar) // TODO [tasty]: how to ensure Selectin appears perhaps for an annotation? + } + +} diff --git a/test/tasty/run/src-2/tastytest/TestSuperOps.scala b/test/tasty/run/src-2/tastytest/TestSuperOps.scala new file mode 100644 index 000000000000..2f4e47148037 --- /dev/null +++ b/test/tasty/run/src-2/tastytest/TestSuperOps.scala @@ -0,0 +1,12 @@ +package tastytest + +object TestSuperOps extends Suite("TestSuperOps") { + + sealed trait Collection + object Collection { + final class Set extends Collection with SuperOps + } + + test(assert(new Collection.Set().foo === 23)) + +} diff --git a/test/tasty/run/src-3/tastytest/SuperOps.scala b/test/tasty/run/src-3/tastytest/SuperOps.scala new file mode 100644 index 000000000000..f7ae6a7279b5 --- /dev/null +++ b/test/tasty/run/src-3/tastytest/SuperOps.scala @@ -0,0 +1,5 @@ +package tastytest + +super trait SuperOps { + def foo = 23 +} From 75c83ea4a60729dbf251d88dc31410803dc5a02f Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Mon, 15 Jun 2020 14:25:23 +0200 Subject: [PATCH 2/7] fix SuperOps test --- test/tasty/run/src-2/tastytest/TestSuperOps.scala | 6 ++++-- test/tasty/run/src-3/tastytest/SuperOps.scala | 4 +--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/test/tasty/run/src-2/tastytest/TestSuperOps.scala b/test/tasty/run/src-2/tastytest/TestSuperOps.scala index 2f4e47148037..4873095215c4 100644 --- a/test/tasty/run/src-2/tastytest/TestSuperOps.scala +++ b/test/tasty/run/src-2/tastytest/TestSuperOps.scala @@ -2,9 +2,11 @@ package tastytest object TestSuperOps extends Suite("TestSuperOps") { - sealed trait Collection + sealed trait Collection { + def foo = 23 + } object Collection { - final class Set extends Collection with SuperOps + final class Set extends Collection with SuperOps[Set] } test(assert(new Collection.Set().foo === 23)) diff --git a/test/tasty/run/src-3/tastytest/SuperOps.scala b/test/tasty/run/src-3/tastytest/SuperOps.scala index f7ae6a7279b5..ee1f89f73d64 100644 --- a/test/tasty/run/src-3/tastytest/SuperOps.scala +++ b/test/tasty/run/src-3/tastytest/SuperOps.scala @@ -1,5 +1,3 @@ package tastytest -super trait SuperOps { - def foo = 23 -} +super trait SuperOps[Impl <: SuperOps[Impl]] From 974a47f9755fef6d4c2339dc43d449ca306ff792 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Mon, 15 Jun 2020 16:50:57 +0200 Subject: [PATCH 3/7] type inline final val as FoldableConstantType accessor --- .../scala/tools/nsc/tasty/TreeUnpickler.scala | 6 ++++-- .../scala/tools/nsc/tasty/bridge/TypeOps.scala | 3 +++ test/tasty/run/pre/tastytest/Suite.scala | 13 +++++++++++++ test/tasty/run/src-2/tastytest/TestHello.scala | 13 ++++++++++--- test/tasty/run/src-3/tastytest/HelloWorld.scala | 2 +- 5 files changed, 31 insertions(+), 6 deletions(-) diff --git a/src/compiler/scala/tools/nsc/tasty/TreeUnpickler.scala b/src/compiler/scala/tools/nsc/tasty/TreeUnpickler.scala index 06e48c22fa48..7d808d1d7f29 100644 --- a/src/compiler/scala/tools/nsc/tasty/TreeUnpickler.scala +++ b/src/compiler/scala/tools/nsc/tasty/TreeUnpickler.scala @@ -407,8 +407,8 @@ class TreeUnpickler[Tasty <: TastyUniverse]( if (isClass && flags.is(Trait)) flags |= Abstract if (tag === DEFDEF) flags |= Method if (tag === VALDEF) { + if (flags.is(Inline) || owner.is(Trait)) flags |= FieldAccessor if (flags.not(Mutable)) flags |= Stable - if (owner.is(Trait)) flags |= FieldAccessor } if (tastyFlags.is(Object)) flags = flags | (if (tag === VALDEF) ObjectCreationFlags else ObjectClassCreationFlags) @@ -747,9 +747,11 @@ class TreeUnpickler[Tasty <: TastyUniverse]( val unsupported = completer.tastyFlagSet &~ (Inline | Enum | Extension | Exported) unsupportedWhen(unsupported.hasFlags, s"flags on $sym: ${showTasty(unsupported)}") val tpe = readTpt()(localCtx).tpe - if (isInline) unsupportedWhen(!isConstantType(tpe), s"inline val ${sym.nameString} with non-constant type $tpe") + val isConstant = isConstantType(tpe) + if (isInline) unsupportedWhen(!isConstant, s"inline val ${sym.nameString} with non-constant type $tpe") ctx.setInfo(sym, if (completer.tastyFlagSet.is(Enum)) defn.ConstantType(tpd.Constant((sym, tpe))).tap(_.typeSymbol.set(Final)) + else if (isInline && isConstant) defn.InlineExprType(tpe) else if (sym.isMethod) defn.ExprType(tpe) else tpe ) diff --git a/src/compiler/scala/tools/nsc/tasty/bridge/TypeOps.scala b/src/compiler/scala/tools/nsc/tasty/bridge/TypeOps.scala index f32f331fbb0f..8a4717d08d51 100644 --- a/src/compiler/scala/tools/nsc/tasty/bridge/TypeOps.scala +++ b/src/compiler/scala/tools/nsc/tasty/bridge/TypeOps.scala @@ -54,6 +54,9 @@ trait TypeOps { self: TastyUniverse => def TypeBounds(lo: Type, hi: Type): Type = u.TypeBounds.apply(lo, hi) def SingleType(pre: Type, sym: Symbol): Type = u.singleType(pre, sym) def ExprType(res: Type): Type = ui.nullaryMethodType(res) + def InlineExprType(res: Type): Type = res match { + case u.ConstantType(value) => u.NullaryMethodType(u.FoldableConstantType(value)) + } def PolyType(params: List[Symbol], res: Type): Type = ui.polyType(params, res) def ClassInfoType(parents: List[Type], clazz: Symbol): Type = ui.classInfoType(parents, clazz.rawInfo.decls, clazz.asType) def ThisType(sym: Symbol): Type = ui.thisType(sym) diff --git a/test/tasty/run/pre/tastytest/Suite.scala b/test/tasty/run/pre/tastytest/Suite.scala index b716dcb8ba85..cccf61fb208d 100644 --- a/test/tasty/run/pre/tastytest/Suite.scala +++ b/test/tasty/run/pre/tastytest/Suite.scala @@ -4,6 +4,7 @@ import scala.collection.mutable import tastytest.Suite.Context import scala.util.control.NonFatal +import scala.reflect.ClassTag class Suite(val name: String) { private[this] val counts = mutable.Map.empty[String, Int] @@ -20,6 +21,18 @@ class Suite(val name: String) { def test(code: => Unit): Unit = test("test")(code) + def testExpect[E <: Throwable: reflect.ClassTag](msg: => String)(code: => Unit): Unit = { + test(s"test-expect-${implicitly[ClassTag[E]]}") { + try { + code + throw new IllegalStateException(s"expected ${implicitly[ClassTag[E]]}") + } catch { + case err: E => + assert(err.getMessage() == msg) + } + } + } + def main(args: Array[String]): Unit = { require(reps > 0, s"reps <= 0") val errors = mutable.ArrayBuffer.empty[(Context, Throwable)] diff --git a/test/tasty/run/src-2/tastytest/TestHello.scala b/test/tasty/run/src-2/tastytest/TestHello.scala index db6b92a2ca23..77814f0daf0a 100644 --- a/test/tasty/run/src-2/tastytest/TestHello.scala +++ b/test/tasty/run/src-2/tastytest/TestHello.scala @@ -4,7 +4,11 @@ object TestHello extends Suite("TestHello") { test(assert(HelloWorld.msg1 === "Hello, World!")) test(assert(HelloWorld.msg2 === "Hello, World!")) - test(assert((HelloWorld.msg3: "Hello, World!") === "Hello, World!")) + test(assert(HelloWorld.msg3 === "Hello, World!")) // TODO [tasty]: it is fine for `inline final val` to be qualifier of selection, but not widened as seen below + testExpect[NoSuchMethodError]("tastytest.HelloWorld$.msg3()Ljava/lang/String;") { + // TODO [tasty]: either dotty should have field or fix https://github.com/scala/bug/issues/12040 + assert((HelloWorld.msg3: "Hello, World!") === "Hello, World!") + } test(assert(HelloWorld.ints.exists(_ > 2))) test(assert((HelloWorld.one: 1) === 1)) test(assert(HelloWorld.inferred(1) === 2)) @@ -21,8 +25,11 @@ object TestHello extends Suite("TestHello") { test(assert(HelloWorld.repeated("a","b","c") === "a,b,c")) test(assert(HelloWorld.func(101) === "101")) test(assert(HelloWorld.func1(33) === 33)) - test(assert((HelloWorld.lzy: "lazy") === "lazy")) - test(assert(HelloWorld.acceptsOnlyMsg3(HelloWorld.msg3) === "Hello, World!Hello, World!")) + testExpect[NoSuchMethodError]("tastytest.HelloWorld$.lzy()Ljava/lang/String;") { + // TODO [tasty]: either dotty should have field or fix https://github.com/scala/bug/issues/12040 + assert((HelloWorld.lzy: "lazy") === "lazy") + } + test(assert(HelloWorld.acceptsOnlyMsg4(HelloWorld.msg4) === "Hello, World!Hello, World!")) // test(assert(HelloWorld.`` === 157)) // wait until https://github.com/lampepfl/dotty/issues/7799 type OrInt[A] = Either[Int, A] diff --git a/test/tasty/run/src-3/tastytest/HelloWorld.scala b/test/tasty/run/src-3/tastytest/HelloWorld.scala index 91205394b3ff..fb7f78233e05 100644 --- a/test/tasty/run/src-3/tastytest/HelloWorld.scala +++ b/test/tasty/run/src-3/tastytest/HelloWorld.scala @@ -24,7 +24,7 @@ object HelloWorld { def repeated(s: String*): String = s.mkString(",") val func: Int => String = _.toString def func1[A]: A => A = x => x - def acceptsOnlyMsg3(m: msg3.type): String = m + m + def acceptsOnlyMsg4(m: msg4.type): String = m + m final lazy val lzy = "lazy" // def ``: Int = 157 // broken in https://github.com/lampepfl/dotty/issues/7799 From 6194c591d944d556ea14fa88151989af58317fa9 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Mon, 15 Jun 2020 18:23:08 +0200 Subject: [PATCH 4/7] add failing test case for SELECTin --- test/tasty/pos/src-2/tastytest/TestAnnotated.scala | 1 + test/tasty/pos/src-3/tastytest/Ambiguous.scala | 7 +++++++ test/tasty/pos/src-3/tastytest/Annotated.scala | 11 +++++++++++ test/tasty/pos/src-3/tastytest/Box.scala | 3 +++ test/tasty/pos/src-3/tastytest/SelectIns.scala | 11 ----------- 5 files changed, 22 insertions(+), 11 deletions(-) create mode 100644 test/tasty/pos/src-3/tastytest/Ambiguous.scala create mode 100644 test/tasty/pos/src-3/tastytest/Box.scala delete mode 100644 test/tasty/pos/src-3/tastytest/SelectIns.scala diff --git a/test/tasty/pos/src-2/tastytest/TestAnnotated.scala b/test/tasty/pos/src-2/tastytest/TestAnnotated.scala index 7e7a00366a29..17edcf046bcf 100644 --- a/test/tasty/pos/src-2/tastytest/TestAnnotated.scala +++ b/test/tasty/pos/src-2/tastytest/TestAnnotated.scala @@ -7,4 +7,5 @@ object TestAnnotated { val o = new OuterAnnotated {} o.foo } + def test4 = new SelectInAnnotated.AmbiguousAnnotated {} } diff --git a/test/tasty/pos/src-3/tastytest/Ambiguous.scala b/test/tasty/pos/src-3/tastytest/Ambiguous.scala new file mode 100644 index 000000000000..57564b03f6a9 --- /dev/null +++ b/test/tasty/pos/src-3/tastytest/Ambiguous.scala @@ -0,0 +1,7 @@ +package tastytest + +class Ambiguous[T] { + class annot(elem: Box[T]) extends scala.annotation.StaticAnnotation { + def this(elem: T) = this(new Box[T](elem)) + } +} diff --git a/test/tasty/pos/src-3/tastytest/Annotated.scala b/test/tasty/pos/src-3/tastytest/Annotated.scala index 22da0ff61cd4..4930cfa0d2ef 100644 --- a/test/tasty/pos/src-3/tastytest/Annotated.scala +++ b/test/tasty/pos/src-3/tastytest/Annotated.scala @@ -10,3 +10,14 @@ trait OuterAnnotated extends OuterTrait { @innerAnnot(new Inner) def foo = 1 } + +object SelectInAnnotated { + + val e = new Box[Double](0) + + val ambig = new Ambiguous[Box[Double]] + + @ambig.annot(e) + trait AmbiguousAnnotated + +} diff --git a/test/tasty/pos/src-3/tastytest/Box.scala b/test/tasty/pos/src-3/tastytest/Box.scala new file mode 100644 index 000000000000..ad2413372710 --- /dev/null +++ b/test/tasty/pos/src-3/tastytest/Box.scala @@ -0,0 +1,3 @@ +package tastytest + +final class Box[T](t: T) diff --git a/test/tasty/pos/src-3/tastytest/SelectIns.scala b/test/tasty/pos/src-3/tastytest/SelectIns.scala deleted file mode 100644 index 99f0f48e46ce..000000000000 --- a/test/tasty/pos/src-3/tastytest/SelectIns.scala +++ /dev/null @@ -1,11 +0,0 @@ -package tastytest - -object SelectIns { - - def test1 = { - val foo = scala.collection.mutable.ArrayBuffer.empty[Seq[Double]] - val bar = Seq.empty[Double] - foo.append(bar) // TODO [tasty]: how to ensure Selectin appears perhaps for an annotation? - } - -} From 286244e5daab4b983da0d1c6267e10695533b322 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Tue, 16 Jun 2020 14:00:05 +0200 Subject: [PATCH 5/7] add another failing test case for SELECTin --- .../scala/tools/nsc/tasty/TreeUnpickler.scala | 17 ++++++++++++++++- .../pos/src-2/tastytest/TestAnnotated.scala | 1 + test/tasty/pos/src-3/tastytest/Ambiguous.scala | 6 ++++++ test/tasty/pos/src-3/tastytest/Annotated.scala | 9 +++++++++ 4 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/compiler/scala/tools/nsc/tasty/TreeUnpickler.scala b/src/compiler/scala/tools/nsc/tasty/TreeUnpickler.scala index 7d808d1d7f29..9d0fb9cb19f7 100644 --- a/src/compiler/scala/tools/nsc/tasty/TreeUnpickler.scala +++ b/src/compiler/scala/tools/nsc/tasty/TreeUnpickler.scala @@ -934,6 +934,22 @@ class TreeUnpickler[Tasty <: TastyUniverse]( val end = readEnd() val result = (tag: @switch) match { + case SELECTin => + if (inParentCtor) unsupportedTermTreeError("selection with a prefix in a parent") + else unsupportedTermTreeError("selection with a prefix") + // val sname = readTastyName() + // val qual = readTerm() + // val owner = readType() + // def select(name: TastyName, denot: Symbol) = { + // val prefix = ctx.typeAssigner.maybeSkolemizePrefix(qual.tpe.widenIfUnstable, name) + // makeSelect(qual, name, denot.asSeenFrom(prefix)) + // } + // sname match { + // case TastyName.SignedName(name, sig) => + // select(name, owner.decl(name).atSignature(sig)) + // case name => + // select(name, owner.decl(name)) + // } case SUPER => val qual = readTerm() val (mixId, mixTpe) = ifBefore(end)(readQualId(), (untpd.EmptyTypeIdent, defn.NoType)) @@ -996,7 +1012,6 @@ class TreeUnpickler[Tasty <: TastyUniverse]( case UNAPPLY => unsupportedTermTreeError("unapply pattern") case INLINED => unsupportedTermTreeError("inlined expression") case SELECTouter => metaprogrammingIsUnsupported // only within inline - case SELECTin => unsupportedTermTreeError("selection with a prefix") // TODO [tasty]: find test case to trigger this case HOLE => assertNoMacroHole case _ => readPathTerm() } diff --git a/test/tasty/pos/src-2/tastytest/TestAnnotated.scala b/test/tasty/pos/src-2/tastytest/TestAnnotated.scala index 17edcf046bcf..ad219a4ec5c9 100644 --- a/test/tasty/pos/src-2/tastytest/TestAnnotated.scala +++ b/test/tasty/pos/src-2/tastytest/TestAnnotated.scala @@ -8,4 +8,5 @@ object TestAnnotated { o.foo } def test4 = new SelectInAnnotated.AmbiguousAnnotated {} + def test5 = new SelectInAnnotatedinParent.AmbiguousAnnotated {} } diff --git a/test/tasty/pos/src-3/tastytest/Ambiguous.scala b/test/tasty/pos/src-3/tastytest/Ambiguous.scala index 57564b03f6a9..d99b61a6f3db 100644 --- a/test/tasty/pos/src-3/tastytest/Ambiguous.scala +++ b/test/tasty/pos/src-3/tastytest/Ambiguous.scala @@ -5,3 +5,9 @@ class Ambiguous[T] { def this(elem: T) = this(new Box[T](elem)) } } + +object Ambiguous { + class AmbiguousBox[T] extends Ambiguous[Box[T]] { + class annotBox(elem: T) extends annot(new Box(elem)) + } +} diff --git a/test/tasty/pos/src-3/tastytest/Annotated.scala b/test/tasty/pos/src-3/tastytest/Annotated.scala index 4930cfa0d2ef..c180bf8aca78 100644 --- a/test/tasty/pos/src-3/tastytest/Annotated.scala +++ b/test/tasty/pos/src-3/tastytest/Annotated.scala @@ -21,3 +21,12 @@ object SelectInAnnotated { trait AmbiguousAnnotated } + +object SelectInAnnotatedinParent { + + val ambig = new Ambiguous.AmbiguousBox[Double] + + @ambig.annotBox(0) + trait AmbiguousAnnotated + +} From eb6751bb094089ef99461dd324287e47b50720b6 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Wed, 17 Jun 2020 16:46:46 +0200 Subject: [PATCH 6/7] pass SELECTin tests --- .../scala/tools/nsc/tasty/TreeUnpickler.scala | 34 ++++++++----------- .../tools/nsc/tasty/bridge/ContextOps.scala | 1 - .../tools/nsc/tasty/bridge/SymbolOps.scala | 8 +++-- .../tools/nsc/tasty/bridge/TypeOps.scala | 5 +-- 4 files changed, 21 insertions(+), 27 deletions(-) diff --git a/src/compiler/scala/tools/nsc/tasty/TreeUnpickler.scala b/src/compiler/scala/tools/nsc/tasty/TreeUnpickler.scala index 9d0fb9cb19f7..8b547706033c 100644 --- a/src/compiler/scala/tools/nsc/tasty/TreeUnpickler.scala +++ b/src/compiler/scala/tools/nsc/tasty/TreeUnpickler.scala @@ -780,7 +780,7 @@ class TreeUnpickler[Tasty <: TastyUniverse]( if (nothingButMods(end) && sym.not(ParamSetter)) tpt.tpe else defn.ExprType(tpt.tpe)) } - ctx.log(s"$symAddr typeOf(${showSym(sym)}) =:= ${if (sym.isType) sym.tpe else sym.info}; owned by ${location(sym.owner)}") + ctx.log(s"$symAddr @@@ ${showSym(sym)}.tpe =:= '[${if (sym.isType) sym.tpe else sym.info}]; owned by ${location(sym.owner)}") goto(end) NoCycle(at = symAddr) } catch ctx.onCompletionError(sym) @@ -900,10 +900,9 @@ class TreeUnpickler[Tasty <: TastyUniverse]( def completeSelectType(name: TastyName.TypeName)(implicit ctx: Context): Tree = completeSelect(name) def completeSelect(name: TastyName)(implicit ctx: Context): Tree = { - val localCtx = ctx.selectionCtx(name) - val qual = readTerm()(localCtx) + val qual = readTerm() val qualType = qual.tpe // TODO [tasty]: qual.tpe.widenIfUnstable - tpd.Select(qual, name)(namedMemberOfPrefix(qualType, name)(localCtx)) + tpd.Select(qual, name)(namedMemberOfPrefix(qualType, name)) } def completeSelectionParent(name: TastyName)(implicit ctx: Context): Tree = { @@ -935,21 +934,18 @@ class TreeUnpickler[Tasty <: TastyUniverse]( val result = (tag: @switch) match { case SELECTin => - if (inParentCtor) unsupportedTermTreeError("selection with a prefix in a parent") - else unsupportedTermTreeError("selection with a prefix") - // val sname = readTastyName() - // val qual = readTerm() - // val owner = readType() - // def select(name: TastyName, denot: Symbol) = { - // val prefix = ctx.typeAssigner.maybeSkolemizePrefix(qual.tpe.widenIfUnstable, name) - // makeSelect(qual, name, denot.asSeenFrom(prefix)) - // } - // sname match { - // case TastyName.SignedName(name, sig) => - // select(name, owner.decl(name).atSignature(sig)) - // case name => - // select(name, owner.decl(name)) - // } + val sname = readTastyName() + val qual = readTerm() + if (inParentCtor) { + assert(sname.isSignedConstructor, s"Parent of ${ctx.owner} is not a constructor.") + skipTree() + qual + } + else { + val owner = readType() + val qualTpe = qual.tpe // qual.tpe.widenIfUnstable + tpd.Select(qual, sname)(namedMemberOfTypeWithPrefix(qualTpe, owner, sname)) + } case SUPER => val qual = readTerm() val (mixId, mixTpe) = ifBefore(end)(readQualId(), (untpd.EmptyTypeIdent, defn.NoType)) diff --git a/src/compiler/scala/tools/nsc/tasty/bridge/ContextOps.scala b/src/compiler/scala/tools/nsc/tasty/bridge/ContextOps.scala index 81dacf950434..f6042caca2bc 100644 --- a/src/compiler/scala/tools/nsc/tasty/bridge/ContextOps.scala +++ b/src/compiler/scala/tools/nsc/tasty/bridge/ContextOps.scala @@ -344,7 +344,6 @@ trait ContextOps { self: TastyUniverse => final def withNewScope: Context = fresh(newLocalDummy) - final def selectionCtx(name: TastyName): Context = this // if (name.isConstructorName) this.addMode(Mode.InSuperCall) else this final def fresh(owner: Symbol): FreshContext = new FreshContext(owner, this, this.mode) private def sibling(mode: TastyMode): FreshContext = new FreshContext(this.owner, outerOrThis, mode) diff --git a/src/compiler/scala/tools/nsc/tasty/bridge/SymbolOps.scala b/src/compiler/scala/tools/nsc/tasty/bridge/SymbolOps.scala index 8ec299c3ed0b..74106ac96537 100644 --- a/src/compiler/scala/tools/nsc/tasty/bridge/SymbolOps.scala +++ b/src/compiler/scala/tools/nsc/tasty/bridge/SymbolOps.scala @@ -121,7 +121,7 @@ trait SymbolOps { self: TastyUniverse => } private def signedMemberOfSpace(space: Type, qual: TastyName, sig: MethodSignature[ErasedTypeRef])(implicit ctx: Context): Symbol = { - ctx.log(s"""<<< looking for overload member[$space] @@ $qual: ${showSig(sig)}""") + ctx.log(s"""<<< looking for overload in symbolOf[$space] @@ $qual: ${showSig(sig)}""") val member = space.member(encodeTermName(qual)) if (!(isSymbol(member) && hasType(member))) errorMissing(space, qual) val (tyParamCount, argTpeRefs) = { @@ -133,9 +133,11 @@ trait SymbolOps { self: TastyUniverse => } def compareSym(sym: Symbol): Boolean = sym match { case sym: u.MethodSymbol => - val params = sym.paramss.flatten + val method = sym.tpe.asSeenFrom(space, sym.owner) + ctx.log(s">>> trying $sym: $method") + val params = method.paramss.flatten val isJava = sym.isJavaDefined - NameErasure.sigName(sym.returnType, isJava) === sig.result && + NameErasure.sigName(method.finalResultType, isJava) === sig.result && params.length === argTpeRefs.length && (qual === TastyName.Constructor && tyParamCount === member.owner.typeParams.length || tyParamCount === sym.typeParams.length) && diff --git a/src/compiler/scala/tools/nsc/tasty/bridge/TypeOps.scala b/src/compiler/scala/tools/nsc/tasty/bridge/TypeOps.scala index 8a4717d08d51..f8423b8f1323 100644 --- a/src/compiler/scala/tools/nsc/tasty/bridge/TypeOps.scala +++ b/src/compiler/scala/tools/nsc/tasty/bridge/TypeOps.scala @@ -314,10 +314,7 @@ trait TypeOps { self: TastyUniverse => namedMemberOfTypeWithPrefix(pre, pre, name) def namedMemberOfTypeWithPrefix(pre: Type, space: Type, tname: TastyName)(implicit ctx: Context): Type = { - val sym = namedMemberOfType(space, tname) - val tpe = prefixedRef(pre, sym) - if (pre != space && !tname.isTypeName) tpe.asSeenFrom(pre, sym.owner) - else tpe + prefixedRef(pre, namedMemberOfType(space, tname)) } def lambdaResultType(resType: Type): Type = resType match { From 0b7ccc5b1f34c7f1512759be22f0bc8cea55246b Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Wed, 17 Jun 2020 18:34:49 +0200 Subject: [PATCH 7/7] support macro defs in TASTy --- project/DottySupport.scala | 2 +- .../scala/tools/nsc/tasty/TastyModes.scala | 4 ++ .../scala/tools/nsc/tasty/TreeUnpickler.scala | 30 +++++----- .../tools/nsc/tasty/bridge/ContextOps.scala | 58 +++++++++++++++++++ .../tools/nsc/tasty/bridge/FlagOps.scala | 4 +- .../tools/nsc/tasty/bridge/SymbolOps.scala | 2 +- .../tools/nsc/tasty/bridge/TreeOps.scala | 23 ++++++++ .../tools/nsc/tasty/bridge/TypeOps.scala | 1 + .../scala/tools/nsc/typechecker/Macros.scala | 4 ++ .../scala/reflect/internal/Definitions.scala | 28 +++++++++ .../neg-move-macros/pre-a/Position.scala | 3 + .../tasty/neg-move-macros/pre-a/package.scala | 16 +++++ .../neg-move-macros/pre-b/Position.scala | 14 +++++ .../tasty/neg-move-macros/pre-b/package.scala | 9 +++ .../src-2/TestMacroCompat.check | 4 ++ .../src-2/TestMacroCompat_fail.scala | 11 ++++ .../neg-move-macros/src-3/MacroCompat.scala | 24 ++++++++ test/tasty/run/pre/tastytest/Position.scala | 14 +++++ test/tasty/run/pre/tastytest/package.scala | 11 ++++ .../run/src-2/tastytest/TestMacroCompat.scala | 14 +++++ .../run/src-3/tastytest/MacroCompat.scala | 41 +++++++++++++ .../tools/tastytest/TastyTestJUnit.scala | 9 +++ 22 files changed, 308 insertions(+), 18 deletions(-) create mode 100644 test/tasty/neg-move-macros/pre-a/Position.scala create mode 100644 test/tasty/neg-move-macros/pre-a/package.scala create mode 100644 test/tasty/neg-move-macros/pre-b/Position.scala create mode 100644 test/tasty/neg-move-macros/pre-b/package.scala create mode 100644 test/tasty/neg-move-macros/src-2/TestMacroCompat.check create mode 100644 test/tasty/neg-move-macros/src-2/TestMacroCompat_fail.scala create mode 100644 test/tasty/neg-move-macros/src-3/MacroCompat.scala create mode 100644 test/tasty/run/pre/tastytest/Position.scala create mode 100644 test/tasty/run/src-2/tastytest/TestMacroCompat.scala create mode 100644 test/tasty/run/src-3/tastytest/MacroCompat.scala diff --git a/project/DottySupport.scala b/project/DottySupport.scala index acc02d221cdc..4a26fdeefa48 100644 --- a/project/DottySupport.scala +++ b/project/DottySupport.scala @@ -12,7 +12,7 @@ import sbt.librarymanagement.{ * Settings to support validation of TastyUnpickler against the release of dotty with the matching TASTy version */ object TastySupport { - val supportedTASTyRelease = "0.25.0-RC1" // TASTy version 23 + val supportedTASTyRelease = "0.25.0-RC2" // TASTy version 23 val dottyCompiler = "ch.epfl.lamp" % "dotty-compiler_0.25" % supportedTASTyRelease } diff --git a/src/compiler/scala/tools/nsc/tasty/TastyModes.scala b/src/compiler/scala/tools/nsc/tasty/TastyModes.scala index e853f74192ad..faf5c3707bc9 100644 --- a/src/compiler/scala/tools/nsc/tasty/TastyModes.scala +++ b/src/compiler/scala/tools/nsc/tasty/TastyModes.scala @@ -22,6 +22,8 @@ object TastyModes { final val ReadParents: TastyMode = TastyMode(1 << 0) final val ReadAnnotation: TastyMode = TastyMode(1 << 1) final val OuterTerm: TastyMode = TastyMode(1 << 2) + final val ReadMacro: TastyMode = TastyMode(1 << 3) + final val IndexBody: TastyMode = TastyMode(1 << 4) case class TastyMode(val toInt: Int) extends AnyVal { mode => @@ -39,6 +41,8 @@ object TastyModes { if (mode.is(ReadParents)) sb += "ReadParents" if (mode.is(ReadAnnotation)) sb += "ReadAnnotation" if (mode.is(OuterTerm)) sb += "OuterTerm" + if (mode.is(ReadMacro)) sb += "ReadMacro" + if (mode.is(IndexBody)) sb += "IndexBody" sb.mkString("|") } } diff --git a/src/compiler/scala/tools/nsc/tasty/TreeUnpickler.scala b/src/compiler/scala/tools/nsc/tasty/TreeUnpickler.scala index 8b547706033c..794c294e62e9 100644 --- a/src/compiler/scala/tools/nsc/tasty/TreeUnpickler.scala +++ b/src/compiler/scala/tools/nsc/tasty/TreeUnpickler.scala @@ -521,16 +521,11 @@ class TreeUnpickler[Tasty <: TastyUniverse]( sym.setAnnotations(annotFns.map(_(sym))) ctx.owner match { case cls if cls.isClass && canEnterInClass => + if (ctx.mode.is(IndexBody) && ctx.isLatentCandidate(sym)) + ctx.registerLatent(sym) val decl = if (flags.is(Object) && isClass) sym.sourceObject else sym - val decls = cls.rawInfo.decls - if (allowsOverload(decl)) { - if (ctx.canEnterOverload(decl)) { - decls.enter(decl) - } - } - else { - decls.enterIfNew(decl) - } + if (ctx.canEnter(decl)) + ctx.enter(cls, decl) case _ => } registerSym(start, sym) @@ -722,11 +717,12 @@ class TreeUnpickler[Tasty <: TastyUniverse]( val localCtx = ctx.withOwner(sym) tag match { case DEFDEF => - val unsupported = completer.tastyFlagSet &~ (Extension | Inline | Macro | Exported) + val unsupported = completer.tastyFlagSet &~ (Extension | Inline | Exported | Erased) unsupportedWhen(unsupported.hasFlags, s"flags on $sym: ${showTasty(unsupported)}") if (completer.tastyFlagSet.is(Extension)) ctx.log(s"$tname is a Scala 3 extension method.") - unsupportedWhen(completer.tastyFlagSet.is(Inline, butNot = Macro), s"inline $sym") - unsupportedWhen(completer.tastyFlagSet.is(Inline | Macro), s"macro $sym") + unsupportedWhen(completer.tastyFlagSet.is(Inline), s"${if (sym.is(Macro)) "" else "inline "}$sym") + val isMacroDef = completer.tastyFlagSet.is(Erased) && sym.is(Macro) + unsupportedWhen(completer.tastyFlagSet.is(Erased) && !isMacroDef, s"erased $sym") val isCtor = sym.isClassConstructor val typeParams = { if (isCtor) { @@ -739,6 +735,10 @@ class TreeUnpickler[Tasty <: TastyUniverse]( } val vparamss = readParamss(localCtx) val tpt = readTpt()(localCtx) + if (isMacroDef) { + val impl = tpd.Macro(readTerm()(ctx.addMode(ReadMacro))) + sym.addAnnotation(symbolTable.AnnotationInfo(symbolTable.definitions.MacroTastyImplAnnotation.tpe, List(impl), Nil)) + } val valueParamss = normalizeIfConstructor(vparamss.map(_.map(symFromNoCycle)), isCtor) val resType = effectiveResultType(sym, typeParams, tpt.tpe) ctx.setInfo(sym, defn.DefDefType(if (isCtor) Nil else typeParams, valueParamss, resType)) @@ -803,8 +803,10 @@ class TreeUnpickler[Tasty <: TastyUniverse]( // ** MEMBERS ** ctx.log(s"$symAddr Template: indexing members of $cls:") val bodyIndexer = fork + val bodyCtx = ctx.addMode(IndexBody) while (bodyIndexer.reader.nextByte != DEFDEF) bodyIndexer.skipTree() // skip until primary ctor - bodyIndexer.indexStats(end) + bodyIndexer.indexStats(end)(bodyCtx) + bodyCtx.enterLatents() // ** PARENTS ** ctx.log(s"$symAddr Template: adding parents of $cls:") @@ -990,7 +992,7 @@ class TreeUnpickler[Tasty <: TastyUniverse]( if (alias != untpd.EmptyTree) alias // only for opaque type alias else tpd.TypeBoundsTree(lo, hi) case BLOCK => - if (inParentCtor) { + if (inParentCtor | ctx.mode.is(ReadMacro)) { val exprReader = fork skipTree() until(end)(skipTree()) //val stats = readStats(ctx.owner, end) diff --git a/src/compiler/scala/tools/nsc/tasty/bridge/ContextOps.scala b/src/compiler/scala/tools/nsc/tasty/bridge/ContextOps.scala index f6042caca2bc..d9cc959e8703 100644 --- a/src/compiler/scala/tools/nsc/tasty/bridge/ContextOps.scala +++ b/src/compiler/scala/tools/nsc/tasty/bridge/ContextOps.scala @@ -18,6 +18,7 @@ import scala.reflect.io.AbstractFile import scala.tools.tasty.{TastyName, TastyFlags}, TastyFlags._, TastyName.ObjectName import scala.tools.nsc.tasty.{TastyUniverse, TastyModes, SafeEq}, TastyModes._ import scala.reflect.internal.MissingRequirementError +import scala.collection.mutable trait ContextOps { self: TastyUniverse => import self.{symbolTable => u}, u.{internal => ui} @@ -67,6 +68,25 @@ trait ContextOps { self: TastyUniverse => final def ignoreAnnotations: Boolean = u.settings.YtastyNoAnnotations final def verboseDebug: Boolean = u.settings.debug + def isScala3Macro(sym: Symbol): Boolean = isScala3Inline(sym) && sym.is(Macro) + def isScala3Inline(sym: Symbol): Boolean = sym.completer.tastyFlagSet.is(Inline) + def isScala2Macro(sym: Symbol): Boolean = sym.completer.tastyFlagSet.is(Erased) && sym.is(Macro) + + def isLatentCandidate(sym: Symbol): Boolean = isScala3Inline(sym) || isScala2Macro(sym) + + def canEnter(decl: Symbol): Boolean = !isScala3Macro(decl) + def enter(clazz: Symbol, decl: Symbol): Unit = enter(clazz.rawInfo.decls, decl) + private[ContextOps] def enter(decls: u.Scope, decl: Symbol): Unit = { + if (allowsOverload(decl)) { + if (canEnterOverload(decl)) { + decls.enter(decl) + } + } + else { + decls.enterIfNew(decl) + } + } + def canEnterOverload(decl: Symbol): Boolean = { !(decl.isModule && isSymbol(findObject(decl.name))) } @@ -92,6 +112,9 @@ trait ContextOps { self: TastyUniverse => def source: AbstractFile def mode: TastyMode + def registerLatent(sym: Symbol): Unit + def enterLatents(): Unit + private final def loadingMirror: u.Mirror = u.mirrorThatLoaded(owner) final def requiredPackage(fullname: TastyName): Symbol = { @@ -390,11 +413,46 @@ trait ContextOps { self: TastyUniverse => final class InitialContext(val topLevelClass: Symbol, val source: AbstractFile) extends Context { def mode: TastyMode = EmptyTastyMode def owner: Symbol = topLevelClass.owner + def registerLatent(sym: Symbol): Unit = () + def enterLatents(): Unit = () } final class FreshContext(val owner: Symbol, val outer: Context, val mode: TastyMode) extends Context { private[this] var mySource: AbstractFile = null + private[this] var myLatentDefs: mutable.ArrayBuffer[Symbol] = null + private[this] var myMacros: mutable.ArrayBuffer[Symbol] = null def atSource(source: AbstractFile): this.type = { mySource = source ; this } def source: AbstractFile = if (mySource == null) outer.source else mySource + def registerLatent(sym: Symbol): Unit = { + if (isScala2Macro(sym)) { + val macros = { + if (myMacros == null) myMacros = mutable.ArrayBuffer.empty + myMacros + } + macros += sym + } else { + val defs = { + if (myLatentDefs == null) myLatentDefs = mutable.ArrayBuffer.empty + myLatentDefs + } + defs += sym + } + } + def enterLatents(): Unit = { + for { + owner <- Option.when(owner.isClass)(owner) + defs <- Option(myLatentDefs) + } { + val macros = Option(myMacros).getOrElse(mutable.ArrayBuffer.empty) + val decls = owner.rawInfo.decls + for (d <- defs if !macros.exists(_.name == d.name)) { + enter(decls, d) + } + defs.clear() + macros.clear() + } + myLatentDefs = null + myMacros = null + } } } diff --git a/src/compiler/scala/tools/nsc/tasty/bridge/FlagOps.scala b/src/compiler/scala/tools/nsc/tasty/bridge/FlagOps.scala index 839bb8230483..5133f14d5c00 100644 --- a/src/compiler/scala/tools/nsc/tasty/bridge/FlagOps.scala +++ b/src/compiler/scala/tools/nsc/tasty/bridge/FlagOps.scala @@ -21,7 +21,7 @@ trait FlagOps { self: TastyUniverse => object FlagSets { val TastyOnlyFlags: TastyFlagSet = ( - Erased | Internal | Inline | InlineProxy | Opaque | Extension | Given | Exported | Macro | SuperTrait | Enum + Erased | Internal | Inline | InlineProxy | Opaque | Extension | Given | Exported | SuperTrait | Enum | Open | ParamAlias ) val TermParamOrAccessor: TastyFlagSet = Param | ParamSetter @@ -42,6 +42,7 @@ trait FlagOps { self: TastyUniverse => if (tflags.is(Case)) flags |= Flag.CASE if (tflags.is(Implicit)) flags |= ModifierFlags.IMPLICIT if (tflags.is(Lazy)) flags |= Flag.LAZY + if (tflags.is(Macro)) flags |= Flag.MACRO if (tflags.is(Override)) flags |= Flag.OVERRIDE if (tflags.is(Static)) flags |= ModifierFlags.STATIC if (tflags.is(Object)) flags |= Flags.MODULE @@ -76,7 +77,6 @@ trait FlagOps { self: TastyUniverse => case Extension => "" case Given => "given" case Exported => "" - case Macro => "" case SuperTrait => "" case Enum => "enum" case Open => "open" diff --git a/src/compiler/scala/tools/nsc/tasty/bridge/SymbolOps.scala b/src/compiler/scala/tools/nsc/tasty/bridge/SymbolOps.scala index 74106ac96537..eb19c512bad1 100644 --- a/src/compiler/scala/tools/nsc/tasty/bridge/SymbolOps.scala +++ b/src/compiler/scala/tools/nsc/tasty/bridge/SymbolOps.scala @@ -94,7 +94,7 @@ trait SymbolOps { self: TastyUniverse => } private def hasType(member: Symbol)(implicit ctx: Context) = { - ctx.mode.is(ReadAnnotation) || (member.rawInfo `ne` u.NoType) + ctx.mode.is(ReadAnnotation) || ctx.mode.is(ReadMacro) && (member.info `ne` u.NoType) || (member.rawInfo `ne` u.NoType) } private def errorMissing[T](space: Type, tname: TastyName)(implicit ctx: Context) = { diff --git a/src/compiler/scala/tools/nsc/tasty/bridge/TreeOps.scala b/src/compiler/scala/tools/nsc/tasty/bridge/TreeOps.scala index 9c213cdc65f7..5f31c4c47c13 100644 --- a/src/compiler/scala/tools/nsc/tasty/bridge/TreeOps.scala +++ b/src/compiler/scala/tools/nsc/tasty/bridge/TreeOps.scala @@ -15,6 +15,7 @@ package scala.tools.nsc.tasty.bridge import scala.tools.nsc.tasty.TastyUniverse import scala.tools.tasty.TastyName +import scala.reflect.internal.Flags trait TreeOps { self: TastyUniverse => @@ -56,6 +57,28 @@ trait TreeOps { self: TastyUniverse => u.TypeTree(defn.LambdaFromParams(tparams, body.tpe)) } + def Macro(impl: Tree): Tree = impl match { + case tree @ u.TypeApply(qual, args) => + u.TypeApply(Macro(qual), args).setType(tree.tpe) + case tree @ u.Select(pre, sel) => + val sym = if (sel.isTermName) tree.tpe.termSymbol else tree.tpe.typeSymbol + u.Select(Macro(pre), sym).setType(tree.tpe) + case tree: u.TypeTree if tree.tpe.prefix !== u.NoType => + val sym = tree.tpe match { + case u.SingleType(_, sym) => sym + case u.TypeRef(_, sym, _) => sym + } + if (tree.tpe.prefix === u.NoPrefix && (sym.hasFlag(Flags.PACKAGE) && !sym.isPackageObjectOrClass || sym.isLocalToBlock)) { + if (sym.isLocalToBlock) u.Ident(sym).setType(tree.tpe) + else u.This(sym).setType(tree.tpe) + } + else { + u.Select(Macro(u.TypeTree(tree.tpe.prefix)), sym).setType(tree.tpe) + } + case tree => + tree + } + def Typed(expr: Tree, tpt: Tree): Tree = u.Typed(expr, tpt).setType(tpt.tpe) def Apply(fun: Tree, args: List[Tree]): Tree = u.Apply(fun, args).setType(fnResult(fun.tpe)) diff --git a/src/compiler/scala/tools/nsc/tasty/bridge/TypeOps.scala b/src/compiler/scala/tools/nsc/tasty/bridge/TypeOps.scala index f8423b8f1323..656db1a350e7 100644 --- a/src/compiler/scala/tools/nsc/tasty/bridge/TypeOps.scala +++ b/src/compiler/scala/tools/nsc/tasty/bridge/TypeOps.scala @@ -21,6 +21,7 @@ import scala.util.chaining._ import scala.collection.mutable import scala.reflect.internal.Flags +import scala.tools.tasty.TastyName.QualifiedName trait TypeOps { self: TastyUniverse => import self.{symbolTable => u}, u.{internal => ui} diff --git a/src/compiler/scala/tools/nsc/typechecker/Macros.scala b/src/compiler/scala/tools/nsc/typechecker/Macros.scala index c7536fe4d45e..e5d26ba7b14e 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Macros.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Macros.scala @@ -282,6 +282,10 @@ trait Macros extends MacroRuntimes with Traces with Helpers { macroImplBindingCache.getOrElseUpdate(macroDef, macroDef.getAnnotation(MacroImplAnnotation) collect { case AnnotationInfo(_, List(pickle), _) => MacroImplBinding.unpickle(pickle) + } orElse { + macroDef.getAnnotation(MacroTastyImplAnnotation) collect { + case AnnotationInfo(_, List(unpickled), _) => MacroImplBinding.unpickle(MacroImplBinding.pickle(unpickled)) + } } ) } diff --git a/src/reflect/scala/reflect/internal/Definitions.scala b/src/reflect/scala/reflect/internal/Definitions.scala index 7d1860e8908d..d2fe40a67bf6 100644 --- a/src/reflect/scala/reflect/internal/Definitions.scala +++ b/src/reflect/scala/reflect/internal/Definitions.scala @@ -566,6 +566,34 @@ trait Definitions extends api.StandardDefinitions { def MacroContextWeakTypeTagClass = BlackboxContextClass.map(sym => getTypeMember(sym, tpnme.WeakTypeTag)) def MacroContextTreeType = BlackboxContextClass.map(sym => getTypeMember(sym, tpnme.Tree)) lazy val MacroImplAnnotation = requiredClass[scala.reflect.macros.internal.macroImpl] + lazy val MacroInternalPackage = MacroImplAnnotation.owner.suchThat(_.isPackageClass) + + /** Implementation of a class that is identical to `scala.reflect.macros.internal.macroImpl` but only exists at compileTime + */ + lazy val MacroTastyImplAnnotation = { + val TastyMacroImpl = MacroInternalPackage.newClassSymbol(tpnme.TastyMacroImpl, NoPosition) + TastyMacroImpl.setPrivateWithin(ScalaPackage) + TastyMacroImpl.setInfoAndEnter(ClassInfoType(AnnotationClass.tpe :: Nil, newScope, TastyMacroImpl)) + val unpickledMacroImpl = TermName("unpickledMacroImpl") + // getter + val unpickledMacroImplMeth = TastyMacroImpl.newMethod(unpickledMacroImpl, newFlags = STABLE | ACCESSOR | PARAMACCESSOR) + unpickledMacroImplMeth.setInfo(internal.nullaryMethodType(AnyTpe)).markAllCompleted() + TastyMacroImpl.info.decls enter unpickledMacroImplMeth + // field + val unpickledMacroImplField = TastyMacroImpl.newValue(unpickledMacroImpl, newFlags = PRIVATE | LOCAL | PARAMACCESSOR) + unpickledMacroImplField.setInfo(AnyTpe).markAllCompleted() + TastyMacroImpl.info.decls enter unpickledMacroImplField + // ctor + val ctor = TastyMacroImpl.newConstructor(NoPosition) + val param = ctor.newValueParameter(unpickledMacroImpl).setInfo(AnyTpe) + ctor.setInfo(MethodType(param :: Nil, TastyMacroImpl.tpe)).markAllCompleted() + TastyMacroImpl.info.decls enter ctor + TastyMacroImpl.addAnnotation( + sym = CompileTimeOnlyAttr, + arg = Literal(Constant("TastyMacroImpl is an implementation detail of unpickling TASTy")) + ) + TastyMacroImpl.markAllCompleted() + } lazy val StringContextClass = requiredClass[scala.StringContext] lazy val StringContextModule = requiredModule[scala.StringContext.type] diff --git a/test/tasty/neg-move-macros/pre-a/Position.scala b/test/tasty/neg-move-macros/pre-a/Position.scala new file mode 100644 index 000000000000..fdea1b370140 --- /dev/null +++ b/test/tasty/neg-move-macros/pre-a/Position.scala @@ -0,0 +1,3 @@ +package tastytest + +final case class Position(sourceName: String, line: Int) diff --git a/test/tasty/neg-move-macros/pre-a/package.scala b/test/tasty/neg-move-macros/pre-a/package.scala new file mode 100644 index 000000000000..88d730c3a23b --- /dev/null +++ b/test/tasty/neg-move-macros/pre-a/package.scala @@ -0,0 +1,16 @@ +import scala.util.Random + +import scala.reflect.macros.blackbox.Context + +package object tastytest { + + object Macros { + def posImpl(c: Context): c.Expr[Position] = { + import c.universe._ + val fileName = c.enclosingPosition.source.path.split('/').last + val line = c.enclosingPosition.line + c.Expr(q"new Position($fileName, $line)") + } + } + +} diff --git a/test/tasty/neg-move-macros/pre-b/Position.scala b/test/tasty/neg-move-macros/pre-b/Position.scala new file mode 100644 index 000000000000..6f07a0e1fefc --- /dev/null +++ b/test/tasty/neg-move-macros/pre-b/Position.scala @@ -0,0 +1,14 @@ +package tastytest + +import scala.reflect.macros.blackbox.Context + +final case class Position(sourceName: String, line: Int) + +object Position { + def posImpl(c: Context): c.Expr[Position] = { + import c.universe._ + val fileName = c.enclosingPosition.source.path.split('/').last + val line = c.enclosingPosition.line + c.Expr(q"new Position($fileName, $line)") + } +} diff --git a/test/tasty/neg-move-macros/pre-b/package.scala b/test/tasty/neg-move-macros/pre-b/package.scala new file mode 100644 index 000000000000..6a88d1d5f227 --- /dev/null +++ b/test/tasty/neg-move-macros/pre-b/package.scala @@ -0,0 +1,9 @@ +import scala.util.Random + +package object tastytest { + + object Macros { + + } + +} diff --git a/test/tasty/neg-move-macros/src-2/TestMacroCompat.check b/test/tasty/neg-move-macros/src-2/TestMacroCompat.check new file mode 100644 index 000000000000..f69ad0abe7b5 --- /dev/null +++ b/test/tasty/neg-move-macros/src-2/TestMacroCompat.check @@ -0,0 +1,4 @@ +TestMacroCompat_fail.scala:7: error: can't find term required by object tastytest.MacroCompat: tastytest.`package`.Macros.posImpl; perhaps it is missing from the classpath. + val result = MacroCompat.testCase("foo")(pos) + ^ +1 error diff --git a/test/tasty/neg-move-macros/src-2/TestMacroCompat_fail.scala b/test/tasty/neg-move-macros/src-2/TestMacroCompat_fail.scala new file mode 100644 index 000000000000..6bd87d2ab629 --- /dev/null +++ b/test/tasty/neg-move-macros/src-2/TestMacroCompat_fail.scala @@ -0,0 +1,11 @@ +package tastytest + +object TestMacroCompat { + import MacroCompat._ + + def test = { + val result = MacroCompat.testCase("foo")(pos) + println(result) + } + +} diff --git a/test/tasty/neg-move-macros/src-3/MacroCompat.scala b/test/tasty/neg-move-macros/src-3/MacroCompat.scala new file mode 100644 index 000000000000..dc3e172c77e1 --- /dev/null +++ b/test/tasty/neg-move-macros/src-3/MacroCompat.scala @@ -0,0 +1,24 @@ +package tastytest + +import scala.language.experimental.macros + +object MacroCompat { + + implicit def pos: Position = macro Macros.posImpl // implemented in test/tasty/run/pre/tastytest/package.scala + implicit inline def pos: Position = ${ Macros3.posImpl } + + def testCase(test: => Any)(using Position): String = + s"${String.valueOf(test)} @@ ${summon[Position]}" + + object Macros3 { + import quoted._ + + def posImpl(using qctx: QuoteContext): Expr[Position] = { + import qctx.tasty.{given _} + val name = qctx.tasty.rootPosition.sourceFile.jpath.getFileName.toString + val line = qctx.tasty.rootPosition.startLine + 1 + '{ Position(${Expr(name)}, ${Expr(line)}) } + } + } + +} diff --git a/test/tasty/run/pre/tastytest/Position.scala b/test/tasty/run/pre/tastytest/Position.scala new file mode 100644 index 000000000000..6f07a0e1fefc --- /dev/null +++ b/test/tasty/run/pre/tastytest/Position.scala @@ -0,0 +1,14 @@ +package tastytest + +import scala.reflect.macros.blackbox.Context + +final case class Position(sourceName: String, line: Int) + +object Position { + def posImpl(c: Context): c.Expr[Position] = { + import c.universe._ + val fileName = c.enclosingPosition.source.path.split('/').last + val line = c.enclosingPosition.line + c.Expr(q"new Position($fileName, $line)") + } +} diff --git a/test/tasty/run/pre/tastytest/package.scala b/test/tasty/run/pre/tastytest/package.scala index da7e0d9c172a..433e7189a13a 100644 --- a/test/tasty/run/pre/tastytest/package.scala +++ b/test/tasty/run/pre/tastytest/package.scala @@ -8,6 +8,12 @@ package object tastytest { final def ===[U](u: U)(implicit ev: T =:= U): Boolean = t == u } + class MacroImpl(val c: Context) { + import c.universe._ + def mono: Literal = q"1" + def poly[T: c.WeakTypeTag]: Tree = q"${c.weakTypeOf[T].toString}" + } + object Macros { def hasStaticAnnotImpl[T, A](c: Context)(implicit T: c.WeakTypeTag[T], A: c.WeakTypeTag[A]): c.Expr[Boolean] = { @@ -21,6 +27,11 @@ package object tastytest { } } + def constIntImpl[T](c: Context)(x: c.Expr[T])(implicit T: c.WeakTypeTag[T]): c.Expr[Int] = { + import c.universe._ + c.Expr[Int](q"1") + } + } def getRandomNat: Int = { diff --git a/test/tasty/run/src-2/tastytest/TestMacroCompat.scala b/test/tasty/run/src-2/tastytest/TestMacroCompat.scala new file mode 100644 index 000000000000..afb296256af1 --- /dev/null +++ b/test/tasty/run/src-2/tastytest/TestMacroCompat.scala @@ -0,0 +1,14 @@ +package tastytest + +import scala.language.experimental.macros + +object TestMacroCompat extends Suite("TestMacroCompat") { + import MacroCompat._ + import Bundles._ + + test(assert(testCase(15) === ("15", Position("TestMacroCompat.scala", 9)))) + test(assert(constInt("") === 1)) + test(assert(mono === 1)) + test(assert(poly[String] === "String")) + +} diff --git a/test/tasty/run/src-3/tastytest/MacroCompat.scala b/test/tasty/run/src-3/tastytest/MacroCompat.scala new file mode 100644 index 000000000000..409da0d9f671 --- /dev/null +++ b/test/tasty/run/src-3/tastytest/MacroCompat.scala @@ -0,0 +1,41 @@ +package tastytest + +import scala.language.experimental.macros + +object MacroCompat { + + implicit def pos: Position = macro Position.posImpl + implicit inline def pos: Position = ${ Macros3.posImpl } + + def constInt[T](x: T): Int = macro Macros.constIntImpl[T] + inline def constInt[T](x: T): Int = ${ Macros3.constIntImpl[T]('x) } + + object Bundles { + def mono: Int = macro MacroImpl.mono + inline def mono: Int = ${ Macros3.monoImpl } + + def poly[T]: String = macro MacroImpl.poly[T] + inline def poly[T]: String = ${ Macros3.polyImpl[T] } + } + + def testCase(test: => Any)(implicit pos: Position): (String, Position) = + String.valueOf(test) -> implicitly[Position] + + object Macros3 { + import quoted._ + + def monoImpl(using QuoteContext) = '{1} + def polyImpl[T: Type](using QuoteContext) = Expr(summon[Type[T]].show) + + def posImpl(using qctx: QuoteContext): Expr[Position] = { + import qctx.tasty.{given _} + val name = qctx.tasty.rootPosition.sourceFile.jpath.getFileName.toString + val line = qctx.tasty.rootPosition.startLine + 1 + '{ Position(${Expr(name)}, ${Expr(line)}) } + } + + def constIntImpl[T: Type](x: Expr[T])(using QuoteContext): Expr[Int] = '{1} + + } + +} diff --git a/test/tasty/test/scala/tools/tastytest/TastyTestJUnit.scala b/test/tasty/test/scala/tools/tastytest/TastyTestJUnit.scala index 215bc57f47ff..9cbd69760710 100644 --- a/test/tasty/test/scala/tools/tastytest/TastyTestJUnit.scala +++ b/test/tasty/test/scala/tools/tastytest/TastyTestJUnit.scala @@ -43,6 +43,15 @@ class TastyTestJUnit { additionalDottySettings = Nil ).get + @test def negMoveMacros(): Unit = TastyTest.negChangePreSuite( + src = "neg-move-macros", + srcRoot = assertPropIsSet(propSrc), + pkgName = assertPropIsSet(propPkgName), + outDirs = None, + additionalSettings = Nil, + additionalDottySettings = Nil + ).get + @test def negIsolated(): Unit = TastyTest.negSuiteIsolated( src = "neg-isolated", srcRoot = assertPropIsSet(propSrc),