From 72b287f62f0720510c83046c7df6d8d4473cd747 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Thu, 5 Oct 2023 16:31:54 +0200 Subject: [PATCH 01/12] Fix the signature of `Array.`. --- tasty-query/shared/src/main/scala/tastyquery/Signatures.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasty-query/shared/src/main/scala/tastyquery/Signatures.scala b/tasty-query/shared/src/main/scala/tastyquery/Signatures.scala index 6243196b..e066e13f 100644 --- a/tasty-query/shared/src/main/scala/tastyquery/Signatures.scala +++ b/tasty-query/shared/src/main/scala/tastyquery/Signatures.scala @@ -40,7 +40,7 @@ object Signatures: case info: PolyType => rec(info.resultType, acc ::: ParamSig.TypeLen(info.paramTypeBounds.length) :: Nil) case tpe: Type => - val retType = optCtorReturn.map(_.localRef).getOrElse(tpe) + val retType = optCtorReturn.map(_.appliedRefInsideThis).getOrElse(tpe) Signature(acc, ErasedTypeRef.erase(retType, language).toSigFullName) } From 5858e07aa0b78f4a1e7ec144c5405d681b75e8c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Thu, 5 Oct 2023 16:54:35 +0200 Subject: [PATCH 02/12] Create the methods `nn`, `eq` and `ne` on `Predef`. --- .../main/scala/tastyquery/Definitions.scala | 22 +++++++++++++++++++ .../src/main/scala/tastyquery/Names.scala | 2 ++ .../tastyquery/reader/ReaderContext.scala | 3 +++ .../reader/pickles/PickleReader.scala | 1 + 4 files changed, 28 insertions(+) diff --git a/tasty-query/shared/src/main/scala/tastyquery/Definitions.scala b/tasty-query/shared/src/main/scala/tastyquery/Definitions.scala index d1138c64..573438cd 100644 --- a/tasty-query/shared/src/main/scala/tastyquery/Definitions.scala +++ b/tasty-query/shared/src/main/scala/tastyquery/Definitions.scala @@ -273,6 +273,28 @@ final class Definitions private[tastyquery] (ctx: Context, rootPackage: PackageS lazy val String_+ : TermSymbol = StringClass.findNonOverloadedDecl(nme.m_+) + /** Creates the members that are patched from stdLibPatches on Predef. + * + * dotc does that generically, but it does not really work for us, as we + * cannot read other files while loading one file. + */ + private[tastyquery] def createPredefMagicMethods(cls: ClassSymbol): Unit = + val nnMethodType = PolyType(List(typeName("T")))( + _ => List(NothingAnyBounds), + pt => MethodType(List(termName("x")))(_ => List(pt.paramRefs(0)), mt => AndType(mt.paramRefs(0), pt.paramRefs(0))) + ) + createSpecialMethod(cls, termName("nn"), nnMethodType, Inline | Final | Extension) + + val anyRefOrNull = OrType(AnyRefType, NullType) + val eqNeMethodType = MethodType( + List(termName("x")), + List(anyRefOrNull), + MethodType(List(termName("y")), List(anyRefOrNull), BooleanType) + ) + createSpecialMethod(cls, termName("eq"), eqNeMethodType, Inline | Final | Extension) + createSpecialMethod(cls, termName("ne"), eqNeMethodType, Inline | Final | Extension) + end createPredefMagicMethods + private def createSpecialPolyClass( name: TypeName, paramFlags: FlagSet, diff --git a/tasty-query/shared/src/main/scala/tastyquery/Names.scala b/tasty-query/shared/src/main/scala/tastyquery/Names.scala index c45ae402..8d8c97f1 100644 --- a/tasty-query/shared/src/main/scala/tastyquery/Names.scala +++ b/tasty-query/shared/src/main/scala/tastyquery/Names.scala @@ -138,6 +138,8 @@ object Names { private[tastyquery] val scala2LocalChild: TypeName = typeName("") private[tastyquery] val scala2ByName: TypeName = typeName("") + + private[tastyquery] val PredefModule: TypeName = moduleClassName("Predef") } /** Create a type name from a string */ diff --git a/tasty-query/shared/src/main/scala/tastyquery/reader/ReaderContext.scala b/tasty-query/shared/src/main/scala/tastyquery/reader/ReaderContext.scala index d235ddab..80695d09 100644 --- a/tasty-query/shared/src/main/scala/tastyquery/reader/ReaderContext.scala +++ b/tasty-query/shared/src/main/scala/tastyquery/reader/ReaderContext.scala @@ -70,6 +70,9 @@ private[reader] final class ReaderContext(underlying: Context): def createStringMagicMethods(cls: ClassSymbol): Unit = underlying.defn.createStringMagicMethods(cls) + + def createPredefMagicMethods(cls: ClassSymbol): Unit = + underlying.defn.createPredefMagicMethods(cls) end ReaderContext private[reader] object ReaderContext: 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 fe365021..db962144 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 @@ -302,6 +302,7 @@ private[pickles] class PickleReader { val givenSelfType = if atEnd then None else Some(readTrueTypeRef()) cls.withParentsDirect(parentTypes) cls.withGivenSelfType(givenSelfType) + if cls.owner == rctx.scalaPackage && tname == tpnme.PredefModule then rctx.createPredefMagicMethods(cls) cls case VALsym => /* Discard symbols that should not be seen from a Scala 3 point of view: From 8979e25e9d620494cc6786fc3127b8064ec8c124 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Thu, 5 Oct 2023 17:09:49 +0200 Subject: [PATCH 03/12] Patch the type of poly class constructors from Java like from Scala 2. --- .../src/main/scala/tastyquery/Symbols.scala | 15 +++++++++++++++ .../reader/classfiles/ClassfileParser.scala | 3 ++- .../tastyquery/reader/pickles/PickleReader.scala | 14 +------------- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/tasty-query/shared/src/main/scala/tastyquery/Symbols.scala b/tasty-query/shared/src/main/scala/tastyquery/Symbols.scala index d462848a..faeefb06 100644 --- a/tasty-query/shared/src/main/scala/tastyquery/Symbols.scala +++ b/tasty-query/shared/src/main/scala/tastyquery/Symbols.scala @@ -1628,6 +1628,21 @@ object Symbols { case tpe => throw InvalidProgramStructureException(s"Unexpected type $tpe for $annot") end extractSealedChildFromChildAnnot + + private[tastyquery] def makePolyConstructorType(selfReferencingCtorType: TypeOrMethodic): TypeOrMethodic = + val typeParams = this.typeParams + if typeParams.isEmpty then selfReferencingCtorType + else + /* Make a PolyType with the same type parameters as the class, and + * substitute references to them of the form `C.this.T` by the + * corresponding `paramRefs` of the `PolyType`. + */ + PolyType(typeParams.map(_.name))( + pt => + typeParams.map(p => Substituters.substLocalThisClassTypeParams(p.declaredBounds, typeParams, pt.paramRefs)), + pt => Substituters.substLocalThisClassTypeParams(selfReferencingCtorType, typeParams, pt.paramRefs) + ) + end makePolyConstructorType } object ClassSymbol: 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 312fd4a5..e525ab94 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 @@ -243,7 +243,8 @@ private[reader] object ClassfileParser { case SigOrDesc.Desc(desc) => Descriptors.parseDescriptor(sym, desc) case SigOrDesc.Sig(sig) => JavaSignatures.parseSignature(sym, sig, allRegisteredSymbols) val adaptedType = - if sym.isMethod && javaFlags.isVarargsIfMethod then patchForVarargs(sym, parsedType) + if sym.isMethod && sym.name == nme.Constructor then cls.makePolyConstructorType(parsedType) + else if sym.isMethod && javaFlags.isVarargsIfMethod then patchForVarargs(sym, parsedType) else parsedType sym.withDeclaredType(adaptedType) 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 db962144..bcd05440 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 @@ -362,19 +362,7 @@ private[pickles] class PickleReader { rctx.UnitType val tpe1 = resultToUnit(tpe) - - val typeParams = cls.typeParams - if typeParams.isEmpty then tpe1 - else - /* Make a PolyType with the same type parameters as the class, and - * substitute references to them of the form `C.this.T` by the - * corresponding `paramRefs` of the `PolyType`. - */ - PolyType(typeParams.map(_.name))( - pt => - typeParams.map(p => Substituters.substLocalThisClassTypeParams(p.declaredBounds, typeParams, pt.paramRefs)), - pt => Substituters.substLocalThisClassTypeParams(tpe1, typeParams, pt.paramRefs) - ) + cls.makePolyConstructorType(tpe1) end patchConstructorType def readChildren()(using ReaderContext, PklStream, Entries, Index): Unit = From 6343a7558e34acf07e6389edfdf2f5383e97f2a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Thu, 5 Oct 2023 17:50:53 +0200 Subject: [PATCH 04/12] Only avoid param accessors when looking up on non-this type. Instead of "merely" `private[this]`. Apparently we stil find accesses of supposedly `private[this]` things in companion objects from their companion classes, through explicit `Select` nodes. --- tasty-query/shared/src/main/scala/tastyquery/Symbols.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tasty-query/shared/src/main/scala/tastyquery/Symbols.scala b/tasty-query/shared/src/main/scala/tastyquery/Symbols.scala index faeefb06..424dfcc5 100644 --- a/tasty-query/shared/src/main/scala/tastyquery/Symbols.scala +++ b/tasty-query/shared/src/main/scala/tastyquery/Symbols.scala @@ -283,8 +283,8 @@ object Symbols { final def isPublic: Boolean = !flags.isAnyOf(Private | Protected | Local) && privateWithin.isEmpty - private[Symbols] final def isPrivateThis: Boolean = - flags.isAllOf(Private | Local) + private[Symbols] final def isPrivateParamAccessor: Boolean = + flags.isAllOf(Private | Local | ParamAccessor) /** The declared visibility of this symbol. */ final def visibility: Visibility = @@ -1495,7 +1495,7 @@ object Symbols { case _ => false getDecl(name) match - case some @ Some(sym) if !sym.isPrivateThis || isOwnThis => + case some @ Some(sym) if !sym.isPrivateParamAccessor || isOwnThis => some case _ => if name == nme.Constructor then None From 856bd86edefcb173d025869c593d321719ee3463 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Thu, 5 Oct 2023 18:06:17 +0200 Subject: [PATCH 05/12] Fix the erasure of Unit in various positions. Notably, in union types and array types. It almost always erases to `BoxedUnit`, except as the direct result type of a method. --- .../main/scala/tastyquery/Definitions.scala | 1 + .../src/main/scala/tastyquery/Erasure.scala | 54 +++++++++++-------- .../src/main/scala/tastyquery/Names.scala | 1 + .../main/scala/tastyquery/Signatures.scala | 3 +- .../src/main/scala/tastyquery/Types.scala | 3 ++ .../scala/tastyquery/SignatureSuite.scala | 12 +++++ .../test/scala/tastyquery/SymbolSuite.scala | 2 +- .../main/scala/simple_trees/UnionType.scala | 17 ++++++ 8 files changed, 69 insertions(+), 24 deletions(-) diff --git a/tasty-query/shared/src/main/scala/tastyquery/Definitions.scala b/tasty-query/shared/src/main/scala/tastyquery/Definitions.scala index 573438cd..abfd945b 100644 --- a/tasty-query/shared/src/main/scala/tastyquery/Definitions.scala +++ b/tasty-query/shared/src/main/scala/tastyquery/Definitions.scala @@ -416,6 +416,7 @@ final class Definitions private[tastyquery] (ctx: Context, rootPackage: PackageS lazy val ProductClass = scalaPackage.requiredClass("Product") lazy val ErasedNothingClass = scalaRuntimePackage.requiredClass("Nothing$") + lazy val ErasedBoxedUnitClass = scalaRuntimePackage.requiredClass("BoxedUnit") private[tastyquery] lazy val targetNameAnnotClass = scalaAnnotationPackage.optionalClass("targetName") diff --git a/tasty-query/shared/src/main/scala/tastyquery/Erasure.scala b/tasty-query/shared/src/main/scala/tastyquery/Erasure.scala index 9ccc35ec..8357683c 100644 --- a/tasty-query/shared/src/main/scala/tastyquery/Erasure.scala +++ b/tasty-query/shared/src/main/scala/tastyquery/Erasure.scala @@ -19,8 +19,11 @@ private[tastyquery] object Erasure: erase(tpe, SourceLanguage.Scala3) def erase(tpe: Type, language: SourceLanguage)(using Context): ErasedTypeRef = + erase(tpe, language, keepUnit = false) + + def erase(tpe: Type, language: SourceLanguage, keepUnit: Boolean)(using Context): ErasedTypeRef = given SourceLanguage = language - finishErase(preErase(tpe)) + finishErase(preErase(tpe, keepUnit)) end erase /** First pass of erasure, where some special types are preserved as is. @@ -28,12 +31,12 @@ private[tastyquery] object Erasure: * In particular, `Any` is preserved as `Any`, instead of becoming * `java.lang.Object`. */ - private def preErase(tpe: Type)(using Context, SourceLanguage): ErasedTypeRef = + private def preErase(tpe: Type, keepUnit: Boolean)(using Context, SourceLanguage): ErasedTypeRef = def hasArrayErasure(cls: ClassSymbol): Boolean = cls == defn.ArrayClass || (cls == defn.RepeatedParamClass && summon[SourceLanguage] == SourceLanguage.Java) def arrayOfBounds(bounds: TypeBounds): ErasedTypeRef = - preErase(bounds.high) match + preErase(bounds.high, keepUnit = false) match case ClassRef(cls) if cls == defn.AnyClass || cls == defn.AnyValClass => ClassRef(defn.ObjectClass) case typeRef => @@ -50,7 +53,8 @@ private[tastyquery] object Erasure: case _ => arrayOf(tpe.translucentSuperType) case TypeRef.OfClass(cls) => - ClassRef(cls).arrayOf() + if cls == defn.UnitClass then ClassRef(defn.ErasedBoxedUnitClass).arrayOf() + else ClassRef(cls).arrayOf() case tpe: TypeRef => tpe.optSymbol match case Some(sym: TypeMemberSymbol) => @@ -61,7 +65,7 @@ private[tastyquery] object Erasure: case _ => arrayOfBounds(tpe.bounds) case tpe: TypeParamRef => arrayOfBounds(tpe.bounds) - case tpe: Type => preErase(tpe).arrayOf() + case tpe: Type => preErase(tpe, keepUnit = false).arrayOf() case tpe: WildcardTypeArg => arrayOfBounds(tpe.bounds) end arrayOf @@ -74,40 +78,44 @@ private[tastyquery] object Erasure: arrayOf(targ) else ClassRef(cls) case _ => - preErase(tpe.translucentSuperType) + preErase(tpe.translucentSuperType, keepUnit) case TypeRef.OfClass(cls) => - ClassRef(cls) + if !keepUnit && cls == defn.UnitClass then ClassRef(defn.ErasedBoxedUnitClass) + else ClassRef(cls) case tpe: TypeRef => tpe.optSymbol match case Some(sym: TypeMemberSymbol) => sym.typeDef match case TypeMemberDefinition.OpaqueTypeAlias(_, alias) => - preErase(alias) + preErase(alias, keepUnit) case _ => - preErase(tpe.underlying) + preErase(tpe.underlying, keepUnit) case _ => - preErase(tpe.underlying) + preErase(tpe.underlying, keepUnit) case tpe: SingletonType => - preErase(tpe.underlying) + preErase(tpe.underlying, keepUnit) case tpe: TypeParamRef => - preErase(tpe.bounds.high) + preErase(tpe.bounds.high, keepUnit) case tpe: MatchType => tpe.reduced match - case Some(reduced) => preErase(reduced) - case None => preErase(tpe.bound) + case Some(reduced) => preErase(reduced, keepUnit) + case None => preErase(tpe.bound, keepUnit) case tpe: OrType => - erasedLub(preErase(tpe.first), preErase(tpe.second)) + erasedLub(preErase(tpe.first, keepUnit = false), preErase(tpe.second, keepUnit = false)) case tpe: AndType => summon[SourceLanguage] match - case SourceLanguage.Java => preErase(tpe.first) - case SourceLanguage.Scala2 => preErase(Scala2Erasure.eraseAndType(tpe)) - case SourceLanguage.Scala3 => erasedGlb(preErase(tpe.first), preErase(tpe.second)) + case SourceLanguage.Java => + preErase(tpe.first, keepUnit = false) + case SourceLanguage.Scala2 => + preErase(Scala2Erasure.eraseAndType(tpe), keepUnit = false) + case SourceLanguage.Scala3 => + erasedGlb(preErase(tpe.first, keepUnit = false), preErase(tpe.second, keepUnit = false)) case tpe: AnnotatedType => - preErase(tpe.typ) + preErase(tpe.typ, keepUnit) case tpe: RefinedType => - preErase(tpe.parent) + preErase(tpe.parent, keepUnit) case tpe: RecType => - preErase(tpe.parent) + preErase(tpe.parent, keepUnit) case _: ByNameType => defn.Function0Class.erasure case _: NothingType => @@ -168,7 +176,8 @@ private[tastyquery] object Erasure: end erasedLub private def erasedClassRefLub(cls1: ClassSymbol, cls2: ClassSymbol)(using Context): ClassSymbol = - if cls1 == defn.ErasedNothingClass then cls2 + if cls1 == cls2 then cls1 + else if cls1 == defn.ErasedNothingClass then cls2 else if cls2 == defn.ErasedNothingClass then cls1 else if cls1 == defn.NullClass then if cls2.isSubClass(defn.ObjectClass) then cls2 @@ -176,6 +185,7 @@ private[tastyquery] object Erasure: else if cls2 == defn.NullClass then if cls1.isSubClass(defn.ObjectClass) then cls1 else defn.AnyClass + else if cls1 == defn.ErasedBoxedUnitClass || cls2 == defn.ErasedBoxedUnitClass then defn.ObjectClass else /** takeWhile+1 */ def takeUpTo[T](l: List[T])(f: T => Boolean): List[T] = diff --git a/tasty-query/shared/src/main/scala/tastyquery/Names.scala b/tasty-query/shared/src/main/scala/tastyquery/Names.scala index 8d8c97f1..01a18df5 100644 --- a/tasty-query/shared/src/main/scala/tastyquery/Names.scala +++ b/tasty-query/shared/src/main/scala/tastyquery/Names.scala @@ -133,6 +133,7 @@ object Names { val scala2PackageObjectClass: TypeName = termName("package").withObjectSuffix.toTypeName private[tastyquery] val runtimeNothing: TypeName = typeName("Nothing$") + private[tastyquery] val runtimeBoxedUnit: TypeName = typeName("BoxedUnit") private[tastyquery] val internalRepeatedAnnot: TypeName = typeName("Repeated") diff --git a/tasty-query/shared/src/main/scala/tastyquery/Signatures.scala b/tasty-query/shared/src/main/scala/tastyquery/Signatures.scala index e066e13f..b3c8b8f4 100644 --- a/tasty-query/shared/src/main/scala/tastyquery/Signatures.scala +++ b/tasty-query/shared/src/main/scala/tastyquery/Signatures.scala @@ -41,7 +41,8 @@ object Signatures: rec(info.resultType, acc ::: ParamSig.TypeLen(info.paramTypeBounds.length) :: Nil) case tpe: Type => val retType = optCtorReturn.map(_.appliedRefInsideThis).getOrElse(tpe) - Signature(acc, ErasedTypeRef.erase(retType, language).toSigFullName) + val erasedRetType = ErasedTypeRef.erase(retType, language, keepUnit = true) + Signature(acc, erasedRetType.toSigFullName) } rec(info, Nil) diff --git a/tasty-query/shared/src/main/scala/tastyquery/Types.scala b/tasty-query/shared/src/main/scala/tastyquery/Types.scala index e08c9487..61a0ae64 100644 --- a/tasty-query/shared/src/main/scala/tastyquery/Types.scala +++ b/tasty-query/shared/src/main/scala/tastyquery/Types.scala @@ -163,6 +163,9 @@ object Types { def erase(tpe: Type, language: SourceLanguage)(using Context): ErasedTypeRef = Erasure.erase(tpe, language) + def erase(tpe: Type, language: SourceLanguage, keepUnit: Boolean)(using Context): ErasedTypeRef = + Erasure.erase(tpe, language, keepUnit) + extension (typeRef: ArrayTypeRef) def elemType: ErasedTypeRef = if typeRef.dimensions == 1 then typeRef.base diff --git a/tasty-query/shared/src/test/scala/tastyquery/SignatureSuite.scala b/tasty-query/shared/src/test/scala/tastyquery/SignatureSuite.scala index 9f0431df..8aab8839 100644 --- a/tasty-query/shared/src/test/scala/tastyquery/SignatureSuite.scala +++ b/tasty-query/shared/src/test/scala/tastyquery/SignatureSuite.scala @@ -295,6 +295,18 @@ class SignatureSuite extends UnrestrictedUnpicklingSuite: val arrayOfUnion = UnionType.findNonOverloadedDecl(name"arrayOfUnion") assertSigned(arrayOfUnion, "(java.lang.Object[]):java.lang.Object[]") + + val unitOrNull = UnionType.findNonOverloadedDecl(termName("unitOrNull")) + assertSigned(unitOrNull, "(scala.runtime.BoxedUnit):scala.runtime.BoxedUnit") + + val intOrNull = UnionType.findNonOverloadedDecl(termName("intOrNull")) + assertSigned(intOrNull, "(java.lang.Object):java.lang.Object") + + val optionOrNull = UnionType.findNonOverloadedDecl(termName("optionOrNull")) + assertSigned(optionOrNull, "(scala.Option):scala.Option") + + val optionOrUnit = UnionType.findNonOverloadedDecl(termName("optionOrUnit")) + assertSigned(optionOrUnit, "(java.lang.Object):java.lang.Object") } testWithContext("refined types") { diff --git a/tasty-query/shared/src/test/scala/tastyquery/SymbolSuite.scala b/tasty-query/shared/src/test/scala/tastyquery/SymbolSuite.scala index a0d2c75b..36e3c7b2 100644 --- a/tasty-query/shared/src/test/scala/tastyquery/SymbolSuite.scala +++ b/tasty-query/shared/src/test/scala/tastyquery/SymbolSuite.scala @@ -17,7 +17,7 @@ class SymbolSuite extends RestrictedUnpicklingSuite { /** Needed for correct resolving of ctor signatures */ val fundamentalClasses: Seq[String] = - Seq("java.lang.Object", "scala.Unit", "scala.AnyVal", "scala.annotation.targetName") + Seq("java.lang.Object", "scala.Unit", "scala.AnyVal", "scala.annotation.targetName", "scala.runtime.BoxedUnit") def testWithContext(name: String, rootSymbolPath: String, extraRootSymbolPaths: String*)(using munit.Location)( body: Context ?=> Unit diff --git a/test-sources/src/main/scala/simple_trees/UnionType.scala b/test-sources/src/main/scala/simple_trees/UnionType.scala index 3c99e800..897f4780 100644 --- a/test-sources/src/main/scala/simple_trees/UnionType.scala +++ b/test-sources/src/main/scala/simple_trees/UnionType.scala @@ -6,4 +6,21 @@ class UnionType { def classesOrType(x: List[Int] | Vector[String]): Seq[Int | String] = x def arrayOfUnion(x: Array[AnyRef | Null]): Array[AnyRef | Null] = x + + def unitOrNull(x: Unit | Null): Unit | Null = x + + def intOrNull(x: Int | Null): Int | Null = x + + def optionOrNull(x: Option[Int] | Null): Option[Int] | Null = x + + def optionOrUnit(x: Option[Int] | Unit): Option[Int] | Unit = x + + def calls(): Unit = + argWithOrType(5) + classesOrType(Nil) + arrayOfUnion(Array()) + unitOrNull(()) + intOrNull(5) + optionOrNull(None) + optionOrUnit(()) } From 11c1ded845ab523d7f600bc37e0e1eefead3799e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Fri, 6 Oct 2023 11:36:17 +0200 Subject: [PATCH 06/12] Fix subtyping of class type constructors. For example, `s.c.i.Iterable <:< s.c.Iterable` when they are not applied. --- .../src/main/scala/tastyquery/Subtyping.scala | 8 ++++---- .../test/scala/tastyquery/SubtypingSuite.scala | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/tasty-query/shared/src/main/scala/tastyquery/Subtyping.scala b/tasty-query/shared/src/main/scala/tastyquery/Subtyping.scala index 218e4ed1..786a3943 100644 --- a/tasty-query/shared/src/main/scala/tastyquery/Subtyping.scala +++ b/tasty-query/shared/src/main/scala/tastyquery/Subtyping.scala @@ -190,14 +190,14 @@ private[tastyquery] object Subtyping: private def level3(tp1: Type, tp2: Type)(using Context): Boolean = tp2 match case TypeRef.OfClass(cls2) => - if cls2.typeParams.isEmpty then + val tparams2 = cls2.typeParams + if tparams2.isEmpty then if tp1.isLambdaSub then false // should be tp1.hasHigherKind, but the scalalib does not like that else if cls2 == defn.AnyClass then true else if cls2 == defn.SingletonClass && isSingleton(tp1) then true else level3WithBaseType(tp1, tp2, cls2) - else - // TODO Try eta-expansion if tp1.isLambdaSub && !tp1.isAnyKind - level4(tp1, tp2) + else if tp1.isLambdaSub then isSubType(tp1, etaExpand(tp2, tparams2)) + else level4(tp1, tp2) case tp2: TypeRef => isSubType(tp1, tp2.bounds.low) diff --git a/tasty-query/shared/src/test/scala/tastyquery/SubtypingSuite.scala b/tasty-query/shared/src/test/scala/tastyquery/SubtypingSuite.scala index 515f5e2f..e71bbd5a 100644 --- a/tasty-query/shared/src/test/scala/tastyquery/SubtypingSuite.scala +++ b/tasty-query/shared/src/test/scala/tastyquery/SubtypingSuite.scala @@ -1119,6 +1119,21 @@ class SubtypingSuite extends UnrestrictedUnpicklingSuite: ) } + testWithContext("class-type-constructors") { + val GenIterableClass = ctx.findTopLevelClass("scala.collection.Iterable") + val ImmutableIterableClass = ctx.findTopLevelClass("scala.collection.immutable.Iterable") + val MutableIterableClass = ctx.findTopLevelClass("scala.collection.mutable.Iterable") + + // No withRef's because these are not proper types + + assertEquiv(GenIterableClass.staticRef, GenIterableClass.newStaticRef) + + assertStrictSubtype(ImmutableIterableClass.staticRef, GenIterableClass.staticRef) + assertStrictSubtype(MutableIterableClass.staticRef, GenIterableClass.staticRef) + + assertNeitherSubtype(ImmutableIterableClass.staticRef, MutableIterableClass.staticRef) + } + testWithContext("annotated-types") { import scala.annotation.unchecked.uncheckedVariance as uV From d9d8b895df211e4b832acfebcd5e86773584d926 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Fri, 6 Oct 2023 11:59:04 +0200 Subject: [PATCH 07/12] Insert the magic no-arg constructor of java.lang.Enum. --- .../shared/src/main/scala/tastyquery/Definitions.scala | 4 ++++ tasty-query/shared/src/main/scala/tastyquery/Names.scala | 1 + .../src/main/scala/tastyquery/reader/ReaderContext.scala | 3 +++ .../scala/tastyquery/reader/classfiles/ClassfileParser.scala | 1 + 4 files changed, 9 insertions(+) diff --git a/tasty-query/shared/src/main/scala/tastyquery/Definitions.scala b/tasty-query/shared/src/main/scala/tastyquery/Definitions.scala index abfd945b..d0dc841c 100644 --- a/tasty-query/shared/src/main/scala/tastyquery/Definitions.scala +++ b/tasty-query/shared/src/main/scala/tastyquery/Definitions.scala @@ -273,6 +273,10 @@ final class Definitions private[tastyquery] (ctx: Context, rootPackage: PackageS lazy val String_+ : TermSymbol = StringClass.findNonOverloadedDecl(nme.m_+) + private[tastyquery] def createEnumMagicMethods(cls: ClassSymbol): Unit = + createSpecialMethod(cls, nme.Constructor, PolyType(List(typeName("E")), List(NothingAnyBounds), UnitType)) + end createEnumMagicMethods + /** Creates the members that are patched from stdLibPatches on Predef. * * dotc does that generically, but it does not really work for us, as we diff --git a/tasty-query/shared/src/main/scala/tastyquery/Names.scala b/tasty-query/shared/src/main/scala/tastyquery/Names.scala index 01a18df5..e6cedeaa 100644 --- a/tasty-query/shared/src/main/scala/tastyquery/Names.scala +++ b/tasty-query/shared/src/main/scala/tastyquery/Names.scala @@ -121,6 +121,7 @@ object Names { val Tuple: TypeName = typeName("Tuple") val NonEmptyTuple: TypeName = typeName("NonEmptyTuple") val TupleCons: TypeName = typeName("*:") + val Enum: TypeName = typeName("Enum") @deprecated("you probably meant the term name `nme.EmptyTuple` instead", since = "0.8.3") val EmptyTuple: TypeName = typeName("EmptyTuple") diff --git a/tasty-query/shared/src/main/scala/tastyquery/reader/ReaderContext.scala b/tasty-query/shared/src/main/scala/tastyquery/reader/ReaderContext.scala index 80695d09..3e444cc4 100644 --- a/tasty-query/shared/src/main/scala/tastyquery/reader/ReaderContext.scala +++ b/tasty-query/shared/src/main/scala/tastyquery/reader/ReaderContext.scala @@ -71,6 +71,9 @@ private[reader] final class ReaderContext(underlying: Context): def createStringMagicMethods(cls: ClassSymbol): Unit = underlying.defn.createStringMagicMethods(cls) + def createEnumMagicMethods(cls: ClassSymbol): Unit = + underlying.defn.createEnumMagicMethods(cls) + def createPredefMagicMethods(cls: ClassSymbol): Unit = underlying.defn.createPredefMagicMethods(cls) end ReaderContext 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 e525ab94..da07e7a3 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 @@ -237,6 +237,7 @@ private[reader] object ClassfileParser { if cls.owner == rctx.javaLangPackage then if cls.name == tpnme.Object then rctx.createObjectMagicMethods(cls) else if cls.name == tpnme.String then rctx.createStringMagicMethods(cls) + else if cls.name == tpnme.Enum then rctx.createEnumMagicMethods(cls) for (sym, javaFlags, sigOrDesc) <- loadMembers() do val parsedType = sigOrDesc match From 06841d184a8c6672cf026967732e49db8f9904b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Fri, 6 Oct 2023 17:45:40 +0200 Subject: [PATCH 08/12] Implement `OrType.join`. --- .../src/main/scala/tastyquery/TypeOps.scala | 56 +++++++++++++++++++ .../src/main/scala/tastyquery/Types.scala | 30 +++++++++- .../src/test/scala/tastyquery/TypeSuite.scala | 30 ++++++++++ .../scala/simple_trees/UnionTypeJoin.scala | 9 +++ 4 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 test-sources/src/main/scala/simple_trees/UnionTypeJoin.scala diff --git a/tasty-query/shared/src/main/scala/tastyquery/TypeOps.scala b/tasty-query/shared/src/main/scala/tastyquery/TypeOps.scala index 21212b84..645ba9d7 100644 --- a/tasty-query/shared/src/main/scala/tastyquery/TypeOps.scala +++ b/tasty-query/shared/src/main/scala/tastyquery/TypeOps.scala @@ -145,4 +145,60 @@ private[tastyquery] object TypeOps: loop(tp1.paramTypes, tp2.paramTypes) end matchingMethodParams + + // baseClasses + + /** The set of classes inherited by this type, in linearization order. */ + def baseClasses(tp: Type)(using Context): List[ClassSymbol] = + tp match + case TypeRef.OfClass(cls) => + cls.linearization + + case tp: TypeProxy => + baseClasses(tp.superType) + + case tp: AndType => + // Base classes are the merge of the operand base classes. + val baseClasses1 = baseClasses(tp.first) + val baseClasses1Set = baseClasses1.toSet + + def recur(baseClasses2: List[ClassSymbol]): List[ClassSymbol] = baseClasses2 match + case baseClass2 :: baseClasses2Rest => + if baseClasses1Set.contains(baseClass2) then + // Don't add baseClass2 since it's already there; shortcut altogether if it is not a trait + if baseClass2.isTrait then recur(baseClasses2Rest) + else baseClasses1 // common class, therefore the rest is the same in both sequences + else + // Include baseClass2 and continue + baseClass2 :: recur(baseClasses2Rest) + case Nil => + baseClasses1 + end recur + + recur(baseClasses(tp.second)) + + case tp: OrType => + // Base classes are the intersection of the operand base classes. + // scala.Null is ignored on both sides + val baseClasses1 = baseClasses(tp.first).filter(_ != defn.NullClass) + val baseClasses1Set = baseClasses1.toSet + + def recur(baseClasses2: List[ClassSymbol]): List[ClassSymbol] = baseClasses2 match + case baseClass2 :: baseClasses2Rest => + if baseClasses1Set.contains(baseClass2) then + // Include baseClass2 which is common; shortcut altogether if it is not a trait + if baseClass2.isTrait then baseClass2 :: recur(baseClasses2Rest) + else baseClasses2 // common class, therefore the rest is the same in both sequences + else + // Exclude baseClass2 and continue + recur(baseClasses2Rest) + case Nil => + Nil + end recur + + recur(baseClasses(tp.second).filter(_ != defn.NullClass)) + + case _: AnyKindType | _: NothingType | _: TypeLambda | _: CustomTransientGroundType => + Nil + end baseClasses end TypeOps diff --git a/tasty-query/shared/src/main/scala/tastyquery/Types.scala b/tasty-query/shared/src/main/scala/tastyquery/Types.scala index 61a0ae64..46fd79c6 100644 --- a/tasty-query/shared/src/main/scala/tastyquery/Types.scala +++ b/tasty-query/shared/src/main/scala/tastyquery/Types.scala @@ -2342,11 +2342,39 @@ object Types { val myJoin = this.myJoin if (myJoin != null) then myJoin else - val computedJoin = defn.ObjectType // TypeOps.orDominator(this) + val computedJoin = computeJoin() this.myJoin = computedJoin computedJoin } + private def computeJoin()(using Context): Type = + /** The minimal set of classes in `classes` which derive all other classes in `classes` */ + def dominators(classes: List[ClassSymbol], acc: List[ClassSymbol]): List[ClassSymbol] = classes match + case cls :: rest => + if acc.exists(_.isSubClass(cls)) then dominators(rest, acc) + else dominators(rest, cls :: acc) + case Nil => + acc + end dominators + + val prunedNulls = tryPruneNulls(this) + + val commonBaseClasses = TypeOps.baseClasses(prunedNulls) + val doms = dominators(commonBaseClasses, Nil) + doms.flatMap(cls => prunedNulls.baseType(cls)).reduceLeft(AndType.make(_, _)) + end computeJoin + + private def tryPruneNulls(tp: Type)(using Context): Type = tp match + case tp: OrType => + val first = tryPruneNulls(tp.first) + val second = tryPruneNulls(tp.second) + if first.isSubType(defn.NullType) && defn.NullType.isSubType(second) then second + else if second.isSubType(defn.NullType) && defn.NullType.isSubType(first) then first + else tp.derivedOrType(first, second) + case _ => + tp + end tryPruneNulls + private[tastyquery] def resolveMember(name: Name, pre: Type)(using Context): ResolveMemberResult = join.resolveMember(name, pre) diff --git a/tasty-query/shared/src/test/scala/tastyquery/TypeSuite.scala b/tasty-query/shared/src/test/scala/tastyquery/TypeSuite.scala index d2811348..25f9aa26 100644 --- a/tasty-query/shared/src/test/scala/tastyquery/TypeSuite.scala +++ b/tasty-query/shared/src/test/scala/tastyquery/TypeSuite.scala @@ -61,6 +61,17 @@ class TypeSuite extends UnrestrictedUnpicklingSuite { false end isIntersectionOf + def isUnionOf(tpes: (Type => Boolean)*)(using Context): Boolean = + tpe match + case tpe: Type => + def parts(tpe: Type): List[Type] = tpe match + case tpe: OrType => parts(tpe.first) ::: parts(tpe.second) + case _ => tpe :: Nil + parts(tpe).isListOf(tpes*) + case _ => + false + end isUnionOf + def isApplied(cls: Type => Boolean, argRefs: Seq[TypeOrWildcard => Boolean])(using Context): Boolean = tpe match case tpe: TermType => @@ -3012,4 +3023,23 @@ class TypeSuite extends UnrestrictedUnpicklingSuite { val fooSym = SingletonClassTypeClass.findDecl(termName("foo")) assert(clue(fooSym.declaredType).isTypeRefOf(defn.SingletonClass)) } + + testWithContext("join") { + val UnionTypeJoinClass = ctx.findTopLevelModuleClass("simple_trees.UnionTypeJoin") + + val aClass = UnionTypeJoinClass.findDecl(typeName("A")).asClass + val bClass = UnionTypeJoinClass.findDecl(typeName("B")).asClass + val cClass = UnionTypeJoinClass.findDecl(typeName("C")).asClass + val dClass = UnionTypeJoinClass.findDecl(typeName("D")).asClass + val eClass = UnionTypeJoinClass.findDecl(typeName("E")).asClass + + // Spec says that join(A | B) = C[A | B] & D + val orType = OrType(aClass.staticRef, bClass.staticRef) + assert( + clue(orType.join).isIntersectionOf( + _.isApplied(_.isRef(cClass), List(_.isUnionOf(_.isRef(aClass), _.isRef(bClass)))), + _.isRef(dClass) + ) + ) + } } diff --git a/test-sources/src/main/scala/simple_trees/UnionTypeJoin.scala b/test-sources/src/main/scala/simple_trees/UnionTypeJoin.scala new file mode 100644 index 00000000..67a33665 --- /dev/null +++ b/test-sources/src/main/scala/simple_trees/UnionTypeJoin.scala @@ -0,0 +1,9 @@ +package simple_trees + +object UnionTypeJoin: + trait C[+T] + trait D + trait E + class A extends C[A] with D + class B extends C[B] with D with E +end UnionTypeJoin From 8cd68d2bc3ec98439341230e7a1a9e5220371308 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Mon, 9 Oct 2023 14:46:47 +0200 Subject: [PATCH 09/12] Fix the erasure of `*:`. It erases to `Product` and shows up as `Product` in call-site signatures. --- .../shared/src/main/scala/tastyquery/Symbols.scala | 6 +++--- .../src/test/scala/tastyquery/SignatureSuite.scala | 10 +++++++++- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/tasty-query/shared/src/main/scala/tastyquery/Symbols.scala b/tasty-query/shared/src/main/scala/tastyquery/Symbols.scala index 424dfcc5..cf5a484f 100644 --- a/tasty-query/shared/src/main/scala/tastyquery/Symbols.scala +++ b/tasty-query/shared/src/main/scala/tastyquery/Symbols.scala @@ -1156,9 +1156,9 @@ object Symbols { if owner == defn.scalaPackage then // The classes with special erasures that are loaded from Scala 2 pickles or .tasty files name match - case tpnme.AnyVal | tpnme.TupleCons => defn.ObjectClass.erasure - case tpnme.Tuple | tpnme.NonEmptyTuple => defn.ProductClass.erasure - case _ => ErasedTypeRef.ClassRef(this) + case tpnme.AnyVal => defn.ObjectClass.erasure + case tpnme.Tuple | tpnme.NonEmptyTuple | tpnme.TupleCons => defn.ProductClass.erasure + case _ => ErasedTypeRef.ClassRef(this) else ErasedTypeRef.ClassRef(this) end computeErasure diff --git a/tasty-query/shared/src/test/scala/tastyquery/SignatureSuite.scala b/tasty-query/shared/src/test/scala/tastyquery/SignatureSuite.scala index 8aab8839..de6a2095 100644 --- a/tasty-query/shared/src/test/scala/tastyquery/SignatureSuite.scala +++ b/tasty-query/shared/src/test/scala/tastyquery/SignatureSuite.scala @@ -364,10 +364,18 @@ class SignatureSuite extends UnrestrictedUnpicklingSuite: assertSigned(takeNonEmptyTupleSig, "(scala.Product):scala.Unit") val takeStarColonSig = TuplesClass.findNonOverloadedDecl(termName("takeStarColon")) - assertSigned(takeStarColonSig, "(java.lang.Object):scala.Unit") + assertSigned(takeStarColonSig, "(scala.Product):scala.Unit") val takeEmptyTupleSig = TuplesClass.findNonOverloadedDecl(termName("takeEmptyTuple")) assertSigned(takeEmptyTupleSig, "(scala.Tuple$package.EmptyTuple):scala.Unit") + + val TupleClass = ctx.findTopLevelClass("scala.Tuple") + + val colonStar = TupleClass.findNonOverloadedDecl(termName(":*")) + assertSigned(colonStar, "(2,java.lang.Object):scala.Product") + + val starColon = TupleClass.findNonOverloadedDecl(termName("*:")) + assertSigned(starColon, "(2,java.lang.Object):scala.Product") } testWithContext("local object") { From ba92ea4c5830fdc29f8d47171772bc74c9766af2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Mon, 9 Oct 2023 19:06:18 +0200 Subject: [PATCH 10/12] Fix reading of Java type param bounds that do not mention a class. We must only introduce `Any` if there is also no interface. If there is at least one interface but no class, we must keep it as is for the erasure to be consistent with what Java does. --- .../reader/classfiles/JavaSignatures.scala | 9 +++++---- .../src/test/scala/tastyquery/SignatureSuite.scala | 13 +++++++++++++ .../src/test/scala/tastyquery/TypeSuite.scala | 2 +- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/tasty-query/shared/src/main/scala/tastyquery/reader/classfiles/JavaSignatures.scala b/tasty-query/shared/src/main/scala/tastyquery/reader/classfiles/JavaSignatures.scala index d3ff7409..784e0e26 100644 --- a/tasty-query/shared/src/main/scala/tastyquery/reader/classfiles/JavaSignatures.scala +++ b/tasty-query/shared/src/main/scala/tastyquery/reader/classfiles/JavaSignatures.scala @@ -200,12 +200,13 @@ private[classfiles] object JavaSignatures: val tname = identifier().toTypeName val classBound = expect(':') - referenceTypeSignature(env) match - case Some(tpe) => tpe - case _ => rctx.FromJavaObjectType + referenceTypeSignature(env).toList val interfaceBounds = readWhile(':', referenceType(env)) if env.withAddedParam(tname) then emptyTypeBounds // shortcut as we will throw away the bounds - else RealTypeBounds(rctx.NothingType, interfaceBounds.foldLeft(classBound)(AndType(_, _))) + else + val allBounds = classBound ::: interfaceBounds + val bound = if allBounds.isEmpty then rctx.AnyType else allBounds.reduceLeft(AndType(_, _)) + RealTypeBounds(rctx.NothingType, bound) def typeParamsRest(env: JavaSignature): List[TypeBounds] = readUntil('>', typeParameter(env)) diff --git a/tasty-query/shared/src/test/scala/tastyquery/SignatureSuite.scala b/tasty-query/shared/src/test/scala/tastyquery/SignatureSuite.scala index de6a2095..26958471 100644 --- a/tasty-query/shared/src/test/scala/tastyquery/SignatureSuite.scala +++ b/tasty-query/shared/src/test/scala/tastyquery/SignatureSuite.scala @@ -91,6 +91,19 @@ class SignatureSuite extends UnrestrictedUnpicklingSuite: assertSigned(getFirstEntry, "():java.util.TreeMap.Entry") } + testWithContext("Java bounded generic") { + val FilesModuleClass = ctx.findTopLevelModuleClass("java.nio.file.Files") + + val readAttributes = FilesModuleClass + .findAllOverloadedDecls(termName("readAttributes")) + .find(_.declaredType.isInstanceOf[PolyType]) + .get + assertSigned( + readAttributes, + "(1,java.nio.file.Path,java.lang.Class,java.nio.file.LinkOption[]):java.nio.file.attribute.BasicFileAttributes" + ) + } + testWithContext("RichInt") { val RichInt = ctx.findTopLevelClass("scala.runtime.RichInt") diff --git a/tasty-query/shared/src/test/scala/tastyquery/TypeSuite.scala b/tasty-query/shared/src/test/scala/tastyquery/TypeSuite.scala index 25f9aa26..3fb84881 100644 --- a/tasty-query/shared/src/test/scala/tastyquery/TypeSuite.scala +++ b/tasty-query/shared/src/test/scala/tastyquery/TypeSuite.scala @@ -826,7 +826,7 @@ class TypeSuite extends UnrestrictedUnpicklingSuite { val tpe = refInterface.declaredType.asInstanceOf[TypeLambdaType] val List(tparamRefA) = tpe.paramRefs: @unchecked assert( - tparamRefA.bounds.high.isIntersectionOf(_.isFromJavaObject, _.isRef(JavaInterface1), _.isRef(JavaInterface2)), + tparamRefA.bounds.high.isIntersectionOf(_.isRef(JavaInterface1), _.isRef(JavaInterface2)), clues(tparamRefA.bounds) ) } From d9ac25baa72cd22d2eb6f8fde94e24e7935083d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Tue, 10 Oct 2023 09:48:41 +0200 Subject: [PATCH 11/12] Test that we can resolve all the symbols in test-sources. --- .../src/test/scala/tastyquery/TypeSuite.scala | 77 ++++++++++++++++++- 1 file changed, 76 insertions(+), 1 deletion(-) diff --git a/tasty-query/shared/src/test/scala/tastyquery/TypeSuite.scala b/tasty-query/shared/src/test/scala/tastyquery/TypeSuite.scala index 3fb84881..37e9f8e7 100644 --- a/tasty-query/shared/src/test/scala/tastyquery/TypeSuite.scala +++ b/tasty-query/shared/src/test/scala/tastyquery/TypeSuite.scala @@ -4,14 +4,15 @@ import scala.collection.mutable import tastyquery.Constants.* import tastyquery.Contexts.* +import tastyquery.Exceptions.* import tastyquery.Modifiers.* import tastyquery.Names.* import tastyquery.Symbols.* +import tastyquery.Traversers.TreeTraverser import tastyquery.Trees.* import tastyquery.Types.* import TestUtils.* -import tastyquery.Traversers.TreeTraverser class TypeSuite extends UnrestrictedUnpicklingSuite { extension [T](elems: List[T]) @@ -3042,4 +3043,78 @@ class TypeSuite extends UnrestrictedUnpicklingSuite { ) ) } + + testWithContext("all-symbol-resolutions") { + /* Test that we can resolve the `.symbol` of all the `Ident`s and `Select`s + * within the test-sources. + */ + + var successCount = 0 + val errorsBuilder = List.newBuilder[String] + + def testSelect(tree: TermReferenceTree): Unit = + try + tree.symbol + tree.referenceType match + case tpe: NamedType if tpe.prefix != NoPrefix => + successCount += 1 + case _ => + // too "easy"; don't count that as a success + () + catch + case ex: MemberNotFoundException => + val displayPos = + if tree.pos.hasLineColumnInformation then s":${tree.pos.pointLine}:${tree.pos.pointColumn}" + else "" + val prefixDetails = ex.prefix match + case prefix: SingletonType => s" (${prefix.showBasic}: ${prefix.superType.showBasic})" + case prefix: Type => s" (${prefix.showBasic})" + case _ => "" + + errorsBuilder += s"${tree.pos.sourceFile.path}$displayPos: ${ex.getMessage()}$prefixDetails" + end testSelect + + def walkTree(tree: Tree): Unit = + new Traversers.TreeTraverser { + override def traverse(tree: Tree): Unit = tree match + case tree: TermReferenceTree => + super.traverse(tree) + testSelect(tree) + case _ => + super.traverse(tree) + }.traverse(tree) + end walkTree + + def walk(pkg: PackageSymbol): Unit = + for sym <- pkg.declarations do + sym match + case sym: PackageSymbol => walk(sym) + case sym: ClassSymbol => sym.tree.foreach(walkTree(_)) + case _ => () + end walk + + val testSourcesPackages = List( + "companions", + "crosspackagetasty", + "empty_class", + "imported_files", + "imports", + "inheritance", + "javacompat", + "javadefined", + "mixjavascala", + "scala2compat", + "simple_trees", + "subtyping", + "synthetics" + ) + for testSourcesPackage <- testSourcesPackages do walk(ctx.findPackage(testSourcesPackage)) + + val errors = errorsBuilder.result() + if errors.nonEmpty then + fail(errors.mkString("Could not resolved the `symbol` of some trees in the test-sources:\n", "\n", "\n")) + + // As of this writing, there were 1201 successes + assert(clue(successCount) > 1000) + } } From f06a4313fc031c85b091bf08e92a1e83fd3e46d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Tue, 10 Oct 2023 16:47:25 +0200 Subject: [PATCH 12/12] Add more tests about the erasure of Unit itself. Outside of arrays or union types. --- .../scala/tastyquery/SignatureSuite.scala | 22 +++++++++++++++++++ .../main/scala/simple_trees/UnitErasure.scala | 22 +++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 test-sources/src/main/scala/simple_trees/UnitErasure.scala diff --git a/tasty-query/shared/src/test/scala/tastyquery/SignatureSuite.scala b/tasty-query/shared/src/test/scala/tastyquery/SignatureSuite.scala index 26958471..6e65dd9d 100644 --- a/tasty-query/shared/src/test/scala/tastyquery/SignatureSuite.scala +++ b/tasty-query/shared/src/test/scala/tastyquery/SignatureSuite.scala @@ -170,6 +170,28 @@ class SignatureSuite extends UnrestrictedUnpicklingSuite: assertSigned(withArrayExactAnyVal, "(java.lang.Object[]):scala.Unit") } + testWithContext("unit-erasure") { + val UnitErasureClass = ctx.findTopLevelClass("simple_trees.UnitErasure") + + val unitVal = UnitErasureClass.findNonOverloadedDecl(termName("unitVal")) + assertNotSigned(unitVal, "():scala.Unit") + + val unitVar = UnitErasureClass.findNonOverloadedDecl(termName("unitVar")) + assertNotSigned(unitVar, "():scala.Unit") + + val unitVarSetter = UnitErasureClass.findNonOverloadedDecl(termName("unitVar_=")) + assertSigned(unitVarSetter, "(scala.runtime.BoxedUnit):scala.Unit") + + val unitParamelessDef = UnitErasureClass.findNonOverloadedDecl(termName("unitParamelessDef")) + assertNotSigned(unitParamelessDef, "():scala.Unit") + + val unitResult = UnitErasureClass.findNonOverloadedDecl(termName("unitResult")) + assertSigned(unitResult, "():scala.Unit") + + val unitParam = UnitErasureClass.findNonOverloadedDecl(termName("unitParam")) + assertSigned(unitParam, "(scala.runtime.BoxedUnit):java.lang.Object") + } + testWithContext("type-member") { val TypeMember = ctx.findTopLevelClass("simple_trees.TypeMember") diff --git a/test-sources/src/main/scala/simple_trees/UnitErasure.scala b/test-sources/src/main/scala/simple_trees/UnitErasure.scala new file mode 100644 index 00000000..2d8836a1 --- /dev/null +++ b/test-sources/src/main/scala/simple_trees/UnitErasure.scala @@ -0,0 +1,22 @@ +package simple_trees + +class UnitErasure: + val unitVal: Unit = () + + var unitVar: Unit = () + + def unitParamelessDef: Unit = () + + def unitResult(): Unit = () + + def unitParam(x: Unit): Any = x + + def calls(): Unit = + val _ = unitVal + val _ = unitVar + unitVar = () + unitParamelessDef + unitResult() + unitParam(()) + end calls +end UnitErasure