From 3ecd4c85a190d0e4309a98ff52ca91081d80e2d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Thu, 22 Dec 2022 14:01:25 +0100 Subject: [PATCH 1/2] Read self types from TASTy and Scala 2 pickles. They are exposed as `ClassSymbol.givenSelfType` (the one actually written in the code, if any) and `ClassSymbol` (the computed one that also includes the applied type ref itself. --- .../main/scala/tastyquery/Definitions.scala | 1 + .../src/main/scala/tastyquery/Symbols.scala | 38 +++++++++++++ .../reader/classfiles/ClassfileParser.scala | 2 + .../reader/pickles/PickleReader.scala | 2 + .../reader/tasties/TreeUnpickler.scala | 1 + .../src/test/scala/tastyquery/TypeSuite.scala | 57 +++++++++++++++++++ .../main/scala/simple_trees/SelfTypes.scala | 23 ++++++++ 7 files changed, 124 insertions(+) create mode 100644 test-sources/src/main/scala/simple_trees/SelfTypes.scala diff --git a/tasty-query/shared/src/main/scala/tastyquery/Definitions.scala b/tasty-query/shared/src/main/scala/tastyquery/Definitions.scala index b7b793b8..f1b52498 100644 --- a/tasty-query/shared/src/main/scala/tastyquery/Definitions.scala +++ b/tasty-query/shared/src/main/scala/tastyquery/Definitions.scala @@ -70,6 +70,7 @@ final class Definitions private[tastyquery] (ctx: Context, rootPackage: PackageS val cls = ClassSymbol.create(name, scalaPackage) cls.withTypeParams(Nil) cls.withParentsDirect(parents) + cls.withGivenSelfType(None) cls.withFlags(flags, None) cls.setAnnotations(Nil) cls.checkCompleted() diff --git a/tasty-query/shared/src/main/scala/tastyquery/Symbols.scala b/tasty-query/shared/src/main/scala/tastyquery/Symbols.scala index 0ff8b3d7..cee3e3d0 100644 --- a/tasty-query/shared/src/main/scala/tastyquery/Symbols.scala +++ b/tasty-query/shared/src/main/scala/tastyquery/Symbols.scala @@ -574,6 +574,7 @@ object Symbols { private var myTypeParams: List[ClassTypeParamSymbol] | Null = null private var myParentsInit: (() => List[Type]) | Null = null private var myParents: List[Type] | Null = null + private var myGivenSelfType: Option[Type] | Null = null // Optional reference fields private var mySpecialErasure: Option[() => ErasedTypeRef] = None @@ -583,12 +584,15 @@ object Symbols { mutable.HashMap[Name, mutable.HashSet[TermOrTypeSymbol]]() // Cache fields + private var myAppliedRef: Type | Null = null + private var mySelfType: Type | Null = null private var myLinearization: List[ClassSymbol] | Null = null protected override def doCheckCompleted(): Unit = super.doCheckCompleted() if myTypeParams == null then failNotCompleted("typeParams not initialized") if myParents == null && myParentsInit == null then failNotCompleted("parents not initialized") + if myGivenSelfType == null then failNotCompleted("givenSelfType not initialized") private[tastyquery] def isValueClass(using Context): Boolean = parents.nonEmpty && parents.head.classSymbol.exists(_ == defn.AnyValClass) @@ -659,6 +663,39 @@ object Symbols { } ) + private[tastyquery] final def withGivenSelfType(givenSelfType: Option[Type]): this.type = + if myGivenSelfType != null then throw new IllegalStateException(s"reassignment of givenSelfType for $this") + myGivenSelfType = givenSelfType + this + + final def givenSelfType(using Context): Option[Type] = + val local = myGivenSelfType + if local == null then throw new IllegalStateException(s"givenSelfType not initialized for $this") + else local + + final def appliedRef(using Context): Type = + val local = myAppliedRef + if local != null then local + else + val computed = typeRef.appliedTo(typeParams.map(_.typeRef)) + myAppliedRef = computed + computed + end appliedRef + + final def selfType(using Context): Type = + val local = mySelfType + if local != null then local + else + val computed = givenSelfType match + case None => + appliedRef + case Some(givenSelf) => + if is(Module) then givenSelf + else AndType(givenSelf, appliedRef) + mySelfType = computed + computed + end selfType + final def linearization(using Context): List[ClassSymbol] = val local = myLinearization if local != null then local @@ -988,6 +1025,7 @@ object Symbols { cls .withTypeParams(Nil) .withParentsDirect(defn.ObjectType :: Nil) + .withGivenSelfType(None) .withFlags(EmptyFlagSet, None) .setAnnotations(Nil) cls.checkCompleted() diff --git a/tasty-query/shared/src/main/scala/tastyquery/reader/classfiles/ClassfileParser.scala b/tasty-query/shared/src/main/scala/tastyquery/reader/classfiles/ClassfileParser.scala index a4e4d5ce..0bcc39c4 100644 --- a/tasty-query/shared/src/main/scala/tastyquery/reader/classfiles/ClassfileParser.scala +++ b/tasty-query/shared/src/main/scala/tastyquery/reader/classfiles/ClassfileParser.scala @@ -160,6 +160,7 @@ private[reader] object ClassfileParser { .withFlags(clsFlags | Flags.ModuleClassCreationFlags, clsPrivateWithin) .setAnnotations(Nil) .withParentsDirect(defn.ObjectType :: Nil) + .withGivenSelfType(None) allRegisteredSymbols += moduleClass val module = TermSymbol @@ -216,6 +217,7 @@ private[reader] object ClassfileParser { cls.withParentsDirect(parents) end initParents + cls.withGivenSelfType(None) cls.withFlags(clsFlags, clsPrivateWithin) cls.setAnnotations(Nil) // TODO Read Java annotations on classes initParents() diff --git a/tasty-query/shared/src/main/scala/tastyquery/reader/pickles/PickleReader.scala b/tasty-query/shared/src/main/scala/tastyquery/reader/pickles/PickleReader.scala index 38994271..13f6ca35 100644 --- a/tasty-query/shared/src/main/scala/tastyquery/reader/pickles/PickleReader.scala +++ b/tasty-query/shared/src/main/scala/tastyquery/reader/pickles/PickleReader.scala @@ -254,8 +254,10 @@ private[pickles] class PickleReader { case tpe: TempClassInfoType => tpe.parentTypes case tpe => throw AssertionError(s"unexpected type $tpe for $cls, owner is $owner") + val givenSelfType = if atEnd then None else Some(readTypeRef()) cls.withParentsDirect(parentTypes) cls.withTypeParams(typeParams) + cls.withGivenSelfType(givenSelfType) cls case VALsym => val sym = TermSymbol.create(name.toTermName, owner) diff --git a/tasty-query/shared/src/main/scala/tastyquery/reader/tasties/TreeUnpickler.scala b/tasty-query/shared/src/main/scala/tastyquery/reader/tasties/TreeUnpickler.scala index e7c58c16..ea0c6951 100644 --- a/tasty-query/shared/src/main/scala/tastyquery/reader/tasties/TreeUnpickler.scala +++ b/tasty-query/shared/src/main/scala/tastyquery/reader/tasties/TreeUnpickler.scala @@ -565,6 +565,7 @@ private[tasties] class TreeUnpickler( } } val self = readSelf + cls.withGivenSelfType(self.map(_.tpt.toType)) // The first entry is the constructor val cstr = readStat.asInstanceOf[DefDef] val body = readStats(end) diff --git a/tasty-query/shared/src/test/scala/tastyquery/TypeSuite.scala b/tasty-query/shared/src/test/scala/tastyquery/TypeSuite.scala index 99708ab9..64dd0959 100644 --- a/tasty-query/shared/src/test/scala/tastyquery/TypeSuite.scala +++ b/tasty-query/shared/src/test/scala/tastyquery/TypeSuite.scala @@ -1939,4 +1939,61 @@ class TypeSuite extends UnrestrictedUnpicklingSuite { end if end for } + + testWithContext("applied-ref") { + val FooClass = ctx.findStaticClass("simple_trees.SelfTypes.Foo") + val BarClass = ctx.findStaticClass("simple_trees.SelfTypes.Bar") + val FooBarClass = ctx.findStaticClass("simple_trees.SelfTypes.FooBar") + + val fooTArg = FooClass.typeParams.head + val List(barTArg1, barTArg2) = BarClass.typeParams: @unchecked + + assert(clue(FooClass.appliedRef).isApplied(_.isRef(FooClass), List(_.isRef(fooTArg)))) + assert(clue(BarClass.appliedRef).isApplied(_.isRef(BarClass), List(_.isRef(barTArg1), _.isRef(barTArg2)))) + assert(clue(FooBarClass.appliedRef).isRef(FooBarClass)) + } + + testWithContext("self-types") { + val FooClass = ctx.findStaticClass("simple_trees.SelfTypes.Foo") + val BarClass = ctx.findStaticClass("simple_trees.SelfTypes.Bar") + val FooBarClass = ctx.findStaticClass("simple_trees.SelfTypes.FooBar") + + val fooTArg = FooClass.typeParams.head + val List(barTArg1, barTArg2) = BarClass.typeParams: @unchecked + + val expectedGivenSelfType: Type => Boolean = + tpe => tpe.isApplied(_.isRef(BarClass), List(_.isRef(fooTArg), _.isRef(defn.IntClass))) + + assert(clue(FooClass.givenSelfType).exists(expectedGivenSelfType)) + assert( + clue(FooClass.selfType).isIntersectionOf( + expectedGivenSelfType, + _.isApplied(_.isRef(FooClass), List(_.isRef(FooClass.typeParams.head))) + ) + ) + + assert(clue(BarClass.givenSelfType).isEmpty) + assert(clue(BarClass.selfType).isApplied(_.isRef(BarClass), List(_.isRef(barTArg1), _.isRef(barTArg2)))) + + assert(clue(FooBarClass.givenSelfType).isEmpty) + assert(clue(FooBarClass.selfType).isRef(FooBarClass)) + } + + testWithContext("scala2-self-types") { + val ClassManifestAlias = ctx.findStaticType("scala.reflect.package.ClassManifest") + val ClassManifestDeprecatedApisClass = ctx.findTopLevelClass("scala.reflect.ClassManifestDeprecatedApis") + + val cmDeprecatedApisTArg = ClassManifestDeprecatedApisClass.typeParams.head + + val expectedGivenSelfType: Type => Boolean = + tpe => tpe.isApplied(_.isRef(ClassManifestAlias), List(_.isRef(cmDeprecatedApisTArg))) + + assert(clue(ClassManifestDeprecatedApisClass.givenSelfType).exists(expectedGivenSelfType)) + assert( + clue(ClassManifestDeprecatedApisClass.selfType).isIntersectionOf( + expectedGivenSelfType, + _.isApplied(_.isRef(ClassManifestDeprecatedApisClass), List(_.isRef(cmDeprecatedApisTArg))) + ) + ) + } } diff --git a/test-sources/src/main/scala/simple_trees/SelfTypes.scala b/test-sources/src/main/scala/simple_trees/SelfTypes.scala new file mode 100644 index 00000000..d3ddbd4a --- /dev/null +++ b/test-sources/src/main/scala/simple_trees/SelfTypes.scala @@ -0,0 +1,23 @@ +package simple_trees + +object SelfTypes: + trait Foo[T]: + self: Bar[T, Int] => + + def throughSelf: Pair[T, Int] = self.bar() + def throughThis: MyPair = this.bar() + def bare: Pair[T, Int] = bar() + end Foo + + trait Bar[A, B]: + def bar(): Pair[A, B] + + type MyPair = Pair[A, B] + end Bar + + class FooBar extends Foo[String] with Bar[String, Int]: + def bar(): Pair[String, Int] = Pair("foo", 4) + end FooBar + + final class Pair[+A, +B](val a: A, val B: B) +end SelfTypes From 0f2e40250bd69b05f909a70f3c7e627624936c7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Thu, 22 Dec 2022 14:03:50 +0100 Subject: [PATCH 2/2] Handle selections via the self types. At the core, this means switching `ThisType.underlying` to the `selfType` of the class. Since that is sometimes an `AndType`, we have to handle `AndType`s in `baseType`. --- .../src/main/scala/tastyquery/Symbols.scala | 24 ++++++++++++++----- .../src/main/scala/tastyquery/Types.scala | 13 +++++++++- .../src/test/scala/tastyquery/TypeSuite.scala | 22 +++++++++++++++++ 3 files changed, 52 insertions(+), 7 deletions(-) diff --git a/tasty-query/shared/src/main/scala/tastyquery/Symbols.scala b/tasty-query/shared/src/main/scala/tastyquery/Symbols.scala index cee3e3d0..1df5e58d 100644 --- a/tasty-query/shared/src/main/scala/tastyquery/Symbols.scala +++ b/tasty-query/shared/src/main/scala/tastyquery/Symbols.scala @@ -862,13 +862,13 @@ object Symbols { /** Compute tp.baseType(this) */ private[tastyquery] final def baseTypeOf(tp: Type)(using Context): Option[Type] = + def combineGlb(bt1: Option[Type], bt2: Option[Type]): Option[Type] = + if bt1.isEmpty then bt2 + else if bt2.isEmpty then bt1 + else Some(bt1.get & bt2.get) + def recur(tp: Type): Option[Type] = tp match case tp: TypeRef => - def combineGlb(bt1: Option[Type], bt2: Option[Type]): Option[Type] = - if bt1.isEmpty then bt2 - else if bt2.isEmpty then bt1 - else Some(bt1.get & bt2.get) - def foldGlb(bt: Option[Type], ps: List[Type]): Option[Type] = ps.foldLeft(bt)((bt, p) => combineGlb(bt, recur(p))) @@ -906,8 +906,20 @@ object Symbols { case tp: TypeProxy => recur(tp.superType) + case tp: AndType => + val tp1 = tp.first + val tp2 = tp.second + // TODO? Opt when this.isStatic && tp.derivesFrom(this) && this.typeParams.isEmpty then this.typeRef + val combined = combineGlb(recur(tp1), recur(tp2)) + combined match + case Some(combined: AndType) if (combined.first eq tp1) && (combined.second eq tp2) => + // Return `tp` itself to allow `Subtyping.level3WithBaseType` to cut off infinite recursions + Some(tp) + case _ => + combined + case _ => - // TODO Handle AndType and OrType, and JavaArrayType + // TODO Handle OrType and JavaArrayType None end recur diff --git a/tasty-query/shared/src/main/scala/tastyquery/Types.scala b/tasty-query/shared/src/main/scala/tastyquery/Types.scala index 000aaa93..dadf8fa0 100644 --- a/tasty-query/shared/src/main/scala/tastyquery/Types.scala +++ b/tasty-query/shared/src/main/scala/tastyquery/Types.scala @@ -951,8 +951,19 @@ object Types { end TypeRef final class ThisType(val tref: TypeRef) extends PathType with SingletonType { + private var myUnderlying: Type | Null = null + override def underlying(using Context): Type = - tref // TODO This is probably wrong + val local = myUnderlying + if local != null then local + else + val cls = this.cls + val computed = + if cls.isStatic then cls.selfType + else cls.selfType.asSeenFrom(tref.prefix, cls) + myUnderlying = computed + computed + end underlying final def cls(using Context): ClassSymbol = tref.symbol.asClass diff --git a/tasty-query/shared/src/test/scala/tastyquery/TypeSuite.scala b/tasty-query/shared/src/test/scala/tastyquery/TypeSuite.scala index 64dd0959..304318bd 100644 --- a/tasty-query/shared/src/test/scala/tastyquery/TypeSuite.scala +++ b/tasty-query/shared/src/test/scala/tastyquery/TypeSuite.scala @@ -1996,4 +1996,26 @@ class TypeSuite extends UnrestrictedUnpicklingSuite { ) ) } + + testWithContext("selections-with-self-types") { + val FooClass = ctx.findStaticClass("simple_trees.SelfTypes.Foo") + val BarClass = ctx.findStaticClass("simple_trees.SelfTypes.Bar") + val PairClass = ctx.findStaticClass("simple_trees.SelfTypes.Pair") + + val fooTArg = FooClass.typeParams.head + val List(barTArg1, barTArg2) = BarClass.typeParams: @unchecked + + val targetMethod = BarClass.findNonOverloadedDecl(termName("bar")) + + for testMethodName <- List("throughSelf", "throughThis", "bare") do + val DefDef(_, _, _, Some(body), _) = FooClass.findNonOverloadedDecl(termName(testMethodName)).tree.get: @unchecked + val Apply(sel @ Select(ths: This, SignedName(SimpleName("bar"), _, _)), Nil) = body: @unchecked + + assert(clue(ths.tpe).isInstanceOf[ThisType]) + assert(clue(ths.tpe.asInstanceOf[ThisType].cls) == FooClass) + assert(clue(sel.tpe).isRef(targetMethod)) + + assert(clue(body.tpe).isApplied(_.isRef(PairClass), List(_.isRef(fooTArg), _.isRef(defn.IntClass)))) + end for + } }