From def0b64bc6041e9a439adce42da4fd63a1b7dda7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Thu, 9 Nov 2023 10:36:24 +0100 Subject: [PATCH 1/2] Set the version policy intention to BinaryCompatible for upcoming changes. --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 0f7903b9..bf3ffd98 100644 --- a/build.sbt +++ b/build.sbt @@ -36,7 +36,7 @@ inThisBuild(Def.settings( Developer("sjrd", "Sébastien Doeraene", "sjrdoeraene@gmail.com", url("https://github.com/sjrd/")), Developer("bishabosha", "Jamie Thompson", "bishbashboshjt@gmail.com", url("https://github.com/bishabosha")), ), - versionPolicyIntention := Compatibility.BinaryAndSourceCompatible, + versionPolicyIntention := Compatibility.BinaryCompatible, // Ignore dependencies to internal modules whose version is like `1.2.3+4...` (see https://github.com/scalacenter/sbt-version-policy#how-to-integrate-with-sbt-dynver) versionPolicyIgnoredInternalDependencyVersions := Some("^\\d+\\.\\d+\\.\\d+\\+\\d+".r) )) From 8a004a2fbfdb874267cca944dcbce002daa09e82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Thu, 9 Nov 2023 14:58:12 +0100 Subject: [PATCH 2/2] Introduce `TermSymbol.paramSymss`. It gives access to the symbols of the declared type and term parameters of a method. This is useful for information that is not available in the `MethodicType`s, such as modifiers and annotations. --- .../main/scala/tastyquery/Definitions.scala | 2 + .../main/scala/tastyquery/Substituters.scala | 22 ++ .../src/main/scala/tastyquery/Symbols.scala | 66 +++++- .../reader/classfiles/ClassfileParser.scala | 1 + .../reader/pickles/PickleReader.scala | 99 +++++--- .../reader/tasties/TreeUnpickler.scala | 5 + .../src/test/scala/tastyquery/TypeSuite.scala | 215 ++++++++++++++++++ .../main/scala/simple_trees/Annotations.scala | 2 + 8 files changed, 382 insertions(+), 30 deletions(-) diff --git a/tasty-query/shared/src/main/scala/tastyquery/Definitions.scala b/tasty-query/shared/src/main/scala/tastyquery/Definitions.scala index f8af43a5..31ada48a 100644 --- a/tasty-query/shared/src/main/scala/tastyquery/Definitions.scala +++ b/tasty-query/shared/src/main/scala/tastyquery/Definitions.scala @@ -114,6 +114,7 @@ final class Definitions private[tastyquery] (ctx: Context, rootPackage: PackageS .withFlags(Method | flags, privateWithin = None) .withDeclaredType(tpe) .setAnnotations(Nil) + .autoFillParamSymss() sym.checkCompleted() sym end createSpecialMethod @@ -341,6 +342,7 @@ final class Definitions private[tastyquery] (ctx: Context, rootPackage: PackageS mt => resultTypeParam.localRef ) ) + applyMethod.autoFillParamSymss() applyMethod.setAnnotations(Nil) applyMethod.checkCompleted() diff --git a/tasty-query/shared/src/main/scala/tastyquery/Substituters.scala b/tasty-query/shared/src/main/scala/tastyquery/Substituters.scala index f9c1c8e4..56f8a41d 100644 --- a/tasty-query/shared/src/main/scala/tastyquery/Substituters.scala +++ b/tasty-query/shared/src/main/scala/tastyquery/Substituters.scala @@ -27,6 +27,10 @@ private[tastyquery] object Substituters: if from.isEmpty then tp else new SubstLocalParamsMap(from, to).apply(tp) + def substLocalBoundParams(tp: TypeMappable, from: ParamRefBinder, to: List[Type]): tp.ThisTypeMappableType = + if to.isEmpty then tp + else new SubstLocalBoundParamsMap(from, to).apply(tp) + def substLocalThisClassTypeParams( tp: TypeMappable, from: List[ClassTypeParamSymbol], @@ -142,6 +146,24 @@ private[tastyquery] object Substituters: end transform end SubstLocalParamsMap + private final class SubstLocalBoundParamsMap(from: ParamRefBinder, to: List[TypeOrWildcard]) extends TypeMap: + protected def transform(tp: TypeMappable): TypeMappable = + tp match + case tp: ParamRef => + if tp.binder eq from then to(tp.paramNum) else tp + case tp: NamedType => + tp.prefix match + case NoPrefix | _: PackageRef => tp + case prefix: Type => tp.derivedSelect(apply(prefix)) + case _: ThisType => + tp + case tp: AppliedType => + tp.map(apply(_), apply(_)) + case _ => + mapOver(tp) + end transform + end SubstLocalBoundParamsMap + private final class SubstLocalThisClassTypeParamsMap(from: List[ClassTypeParamSymbol], to: List[Type]) extends TypeMap: protected def transform(tp: TypeMappable): TypeMappable = diff --git a/tasty-query/shared/src/main/scala/tastyquery/Symbols.scala b/tasty-query/shared/src/main/scala/tastyquery/Symbols.scala index f29eca36..e9f109d4 100644 --- a/tasty-query/shared/src/main/scala/tastyquery/Symbols.scala +++ b/tasty-query/shared/src/main/scala/tastyquery/Symbols.scala @@ -82,8 +82,9 @@ object Symbols { * This method is called by the various file readers after reading each * file, for all the `Symbol`s created while reading that file. */ - private[tastyquery] final def checkCompleted(): Unit = + private[tastyquery] final def checkCompleted(): this.type = doCheckCompleted() + this protected final def failNotCompleted(details: String): Nothing = throw IllegalStateException(s"$this of class ${this.getClass().getName()} was not completed: $details") @@ -426,12 +427,15 @@ object Symbols { end matchingSymbol end TermOrTypeSymbol + type ParamSymbolsClause = Either[List[TermSymbol], List[LocalTypeParamSymbol]] + final class TermSymbol private (val name: UnsignedTermName, owner: Symbol) extends TermOrTypeSymbol(owner): type DefiningTreeType = ValOrDefDef | Bind type MatchingSymbolType = TermSymbol // Reference fields (checked in doCheckCompleted) private var myDeclaredType: TypeOrMethodic | Null = null + private var myParamSymss: List[ParamSymbolsClause] | Null = null // Cache fields private var mySignature: Signature | Null = null @@ -442,16 +446,76 @@ object Symbols { super.doCheckCompleted() if myDeclaredType == null then failNotCompleted("declaredType was not initialized") + if flags.is(Method) then + if myParamSymss == null then failNotCompleted("paramSymss was not initialized") + paramSymss.foreach(_.merge.foreach(_.checkCompleted())) + else if myParamSymss == null then myParamSymss = Nil // auto-complete for non-methods + else if myParamSymss != Nil then + throw IllegalArgumentException(s"illegal non-empty paramSymss $myParamSymss for $this") + end doCheckCompleted + private[tastyquery] final def withDeclaredType(tpe: TypeOrMethodic): this.type = if myDeclaredType != null then throw new IllegalStateException(s"reassignment of declared type to $this") myDeclaredType = tpe this + /** You should not need this; it is a hack for patching Scala 2 constructors in `PickleReader`. */ + private[tastyquery] final def overwriteDeclaredType(tpe: TypeOrMethodic): this.type = + myDeclaredType = tpe + this + def declaredType: TypeOrMethodic = val local = myDeclaredType if local != null then local else throw new IllegalStateException(s"$this was not assigned a declared type") + private[tastyquery] final def setParamSymss(paramSymss: List[ParamSymbolsClause]): this.type = + if myParamSymss != null then throw IllegalStateException(s"reassignment of paramSymss to $this") + myParamSymss = paramSymss + this + + private[tastyquery] final def autoFillParamSymss(): this.type = + setParamSymss(autoComputeParamSymss(declaredType)) + + private def autoComputeParamSymss(tpe: TypeOrMethodic): List[ParamSymbolsClause] = tpe match + case tpe: MethodType => + /* For term params, we do not instantiate the paramTypes. + * We only use autoFillParamSymss for Java definitions, which do not + * support term param references at all, and from Definitions, which + * does not use that capability in the term param bounds. + */ + val paramSyms = tpe.paramNames.lazyZip(tpe.paramTypes).map { (name, paramType) => + TermSymbol + .createNotDeclaration(name, this) + .withFlags(EmptyFlagSet, privateWithin = None) + .withDeclaredType(paramType) + .setAnnotations(Nil) + } + Left(paramSyms) :: autoComputeParamSymss(tpe.resultType) + + case tpe: PolyType => + val paramSyms = tpe.paramNames.map { name => + LocalTypeParamSymbol + .create(name, this) + .withFlags(EmptyFlagSet, privateWithin = None) + .setAnnotations(Nil) + } + val paramSymRefs = paramSyms.map(_.localRef) + def subst(t: TypeOrMethodic): t.ThisTypeMappableType = + Substituters.substLocalBoundParams(t, tpe, paramSymRefs) + for (paramSym, paramTypeBounds) <- paramSyms.lazyZip(tpe.paramTypeBounds) do + paramSym.setDeclaredBounds(paramTypeBounds.mapBounds(subst(_))) + Right(paramSyms) :: autoComputeParamSymss(subst(tpe.resultType)) + + case tpe: Type => + Nil + end autoComputeParamSymss + + def paramSymss: List[ParamSymbolsClause] = + val local = myParamSymss + if local != null then local + else throw IllegalStateException(s"$this was not assigned its paramSymss") + /** Is this symbol a module val, i.e., the term of an `object`? * * @return true iff `kind == TermSymbolKind.Module` 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 b2077ab0..3a132098 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 @@ -271,6 +271,7 @@ private[reader] object ClassfileParser { else if sym.isMethod && javaFlags.isVarargsIfMethod then patchForVarargs(sym, parsedType) else parsedType sym.withDeclaredType(adaptedType) + sym.autoFillParamSymss() // Verify after the fact that we don't mark signature-polymorphic methods that should not be if sym.isSignaturePolymorphicMethod then 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 45cc2eca..49fcd4a3 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 @@ -340,21 +340,19 @@ private[pickles] class PickleReader { TermSymbol.createNotDeclaration(name.toTermName, owner) else TermSymbol.create(name.toTermName, owner) storeResultInEntries(sym) // Store the symbol before reading its type, to avoid cycles - val tpe = readSymType() + val storedType = readSymType() match + case storedType: Type => storedType + case storedType => throw Scala2PickleFormatException(s"Type expected for $sym but found $storedType") val unwrappedTpe: TypeOrMethodic = - if flags.is(Method) then - tpe match - case tpe: TypeOrMethodic => translateTempPolyForMethod(tpe) - case _ => throw Scala2PickleFormatException(s"Type or methodic type expected for $sym but found $tpe") - else - tpe match - case tpe: Type => tpe - case _ => throw Scala2PickleFormatException(s"Type expected for $sym but found $tpe") - end unwrappedTpe - val ctorPatchedTpe = - if flags.is(Method) && name == nme.Constructor then patchConstructorType(sym.owner.asClass, unwrappedTpe) - else unwrappedTpe - sym.withDeclaredType(ctorPatchedTpe) + if flags.is(Method) then translateTempMethodAndPolyForMethod(storedType) + else storedType + val paramSymss = paramSymssOf(storedType) + if flags.is(Method) && name == nme.Constructor then + sym.withDeclaredType(patchConstructorType(sym.owner.asClass, unwrappedTpe)) + sym.setParamSymss(patchConstructorParamSymss(sym, paramSymss)) + else + sym.withDeclaredType(unwrappedTpe) + sym.setParamSymss(paramSymss) case MODULEsym => val sym = TermSymbol.create(name.toTermName, owner) storeResultInEntries(sym) @@ -371,6 +369,15 @@ private[pickles] class PickleReader { sym } + private def paramSymssOf(storedType: Type): List[ParamSymbolsClause] = storedType match + case TempMethodType(paramSyms, resType) => + Left(paramSyms) :: paramSymssOf(resType.requireType) + case TempPolyType(paramSyms, resType) => + Right(paramSyms.map(_.asInstanceOf[LocalTypeParamSymbol])) :: paramSymssOf(resType.requireType) + case _ => + Nil + end paramSymssOf + private def patchConstructorType(cls: ClassSymbol, tpe: TypeOrMethodic)(using ReaderContext): TypeOrMethodic = def resultToUnit(tpe: TypeOrMethodic): TypeOrMethodic = tpe match @@ -385,6 +392,41 @@ private[pickles] class PickleReader { cls.makePolyConstructorType(tpe1) end patchConstructorType + private def patchConstructorParamSymss( + ctor: TermSymbol, + paramSymss: List[ParamSymbolsClause] + ): List[ParamSymbolsClause] = + val cls = ctor.owner.asClass + val clsTypeParams = cls.typeParams + + if clsTypeParams.isEmpty then paramSymss + else + // Create the symbols; don't assign bounds yet + val ctorTypeParams = clsTypeParams.map { clsTypeParam => + LocalTypeParamSymbol + .create(clsTypeParam.name, ctor) + .withFlags(EmptyFlagSet, privateWithin = None) + .setAnnotations(Nil) + } + + val ctorTypeParamRefs = ctorTypeParams.map(_.localRef) + def subst(tpe: TypeMappable): tpe.ThisTypeMappableType = + Substituters.substLocalThisClassTypeParams(tpe, clsTypeParams, ctorTypeParamRefs) + + // Assign the bounds; when they refer to each other we need to substitute for the new local refs + for (clsTypeParam, ctorTypeParam) <- clsTypeParams.lazyZip(ctorTypeParams) do + ctorTypeParam.setDeclaredBounds(subst(clsTypeParam.declaredBounds)) + + // Overwrite the types of the existing param syms to refer to the new local refs as well + for + case Left(paramSyms) <- paramSymss + paramSym <- paramSyms + do paramSym.overwriteDeclaredType(subst(paramSym.declaredType)) + + Right(ctorTypeParams) :: paramSymss + end if + end patchConstructorParamSymss + def readChildren()(using ReaderContext, PklStream, Entries, Index): Unit = val tag = pkl.readByte() assert(tag == CHILDREN) @@ -665,18 +707,7 @@ private[pickles] class PickleReader { case METHODtpe | IMPLICITMETHODtpe => val restpe = readTypeOrMethodicRef() val params = pkl.until(end, () => readLocalSymbolRef().asTerm) - val maker = MethodType - /*val maker = MethodType.companion( - isImplicit = tag == IMPLICITMETHODtpe || params.nonEmpty && params.head.is(Implicit))*/ - val result = maker.fromSymbols(params, restpe) - // result.resType match - // case restpe1: MethodType if restpe1 ne restpe => - // val prevResParams = caches.paramsOfMethodType.remove(restpe) - // if prevResParams != null then - // caches.paramsOfMethodType.put(restpe1, prevResParams) - // case _ => - // if params.nonEmpty then caches.paramsOfMethodType.put(result, params) - result + TempMethodType(params, restpe) case POLYtpe => // create PolyType // - PT => register at index @@ -742,18 +773,25 @@ private[pickles] class PickleReader { end translateTempPolyForTypeMember /** Convert temp poly type to PolyType and leave other types alone. */ - private def translateTempPolyForMethod(tp: TypeOrMethodic)(using ReaderContext): TypeOrMethodic = tp match + private def translateTempMethodAndPolyForMethod(tp: TypeOrMethodic)(using ReaderContext): TypeOrMethodic = tp match + case TempMethodType(paramSyms, resType) => + resType match + case resType: TypeOrMethodic => + MethodType.fromSymbols(paramSyms, translateTempMethodAndPolyForMethod(resType)) + case _ => + throw Scala2PickleFormatException(s"Invalid type for method: $tp") + case TempPolyType(tparams, restpe) => val localTParams = tparams.asInstanceOf[List[LocalTypeParamSymbol]] // no class type params in methods restpe match case restpe: TypeOrMethodic => - PolyType.fromParams(localTParams, restpe) + PolyType.fromParams(localTParams, translateTempMethodAndPolyForMethod(restpe)) case _ => throw Scala2PickleFormatException(s"Invalid type for method: $tp") case tp => tp - end translateTempPolyForMethod + end translateTempMethodAndPolyForMethod private def noSuchTypeTag(tag: Int, end: Int): Nothing = errorBadSignature("bad type tag: " + tag) @@ -947,6 +985,9 @@ private[reader] object PickleReader { private val Scala2Constructor: SimpleName = termName("this") private val Scala2TraitConstructor: SimpleName = termName("$init$") + private[tastyquery] case class TempMethodType(paramSyms: List[TermSymbol], resType: TypeMappable) + extends CustomTransientGroundType + private[tastyquery] case class TempPolyType(paramSyms: List[TypeParamSymbol], resType: TypeMappable) extends CustomTransientGroundType 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 559c0b10..44b67815 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 @@ -768,10 +768,15 @@ private[tasties] class TreeUnpickler private ( if name == nme.Constructor then normalizeCtorParamClauses(params) else params symbol.withDeclaredType(ParamsClause.makeDefDefType(normalizedParams, tpt)) + symbol.setParamSymss(normalizedParams.map(paramsClauseToParamSymbolsClause(_))) definingTree(symbol, DefDef(name, normalizedParams, tpt, rhs, symbol)(spn)) } } + private def paramsClauseToParamSymbolsClause(clause: ParamsClause): ParamSymbolsClause = clause match + case Left(termParams) => Left(termParams.map(_.symbol)) + case Right(typeParams) => Right(typeParams.map(_.symbol.asInstanceOf[LocalTypeParamSymbol])) + /** Normalizes the param clauses of a constructor definition. * * Make sure it has at least one non-implicit parameter list. This is done diff --git a/tasty-query/shared/src/test/scala/tastyquery/TypeSuite.scala b/tasty-query/shared/src/test/scala/tastyquery/TypeSuite.scala index 7a7a4b7b..ed9af400 100644 --- a/tasty-query/shared/src/test/scala/tastyquery/TypeSuite.scala +++ b/tasty-query/shared/src/test/scala/tastyquery/TypeSuite.scala @@ -102,6 +102,17 @@ class TypeSuite extends UnrestrictedUnpicklingSuite { case _ => false end extension + extension (bounds: TypeBounds) + def isNothingAnyBounds(using Context): Boolean = + isBounds(_.isNothing, _.isAny) + + def isJavaNothingAnyBounds(using Context): Boolean = + isBounds(_.isNothing, _.isFromJavaObject) + + def isBounds(low: Type => Boolean, high: Type => Boolean)(using Context): Boolean = + low(bounds.low) && high(bounds.high) + end extension + testWithContext("hierarchy-partitions") { /* These no-op matches test that the set of all possible `TypeMappable`s is * partitioned into certain sets of sub-classes and sub-traits. @@ -3219,4 +3230,208 @@ class TypeSuite extends UnrestrictedUnpicklingSuite { assert(!clue(childInnerSym.localRef.underlying).isRef(ParentInnerClass)) assert(clue(childInnerSym.localRef.underlying).isRef(ChildInnerClass)) } + + testWithContext("paramSymss-synthetic") { + assert(clue(defn.Any_##.paramSymss) == Nil) + + locally { + val List(Left(List(thatSym))) = defn.Any_==.paramSymss: @unchecked + assert(clue(thatSym.name) == termName("that")) + assert(clue(thatSym.declaredType).isRef(defn.AnyClass)) + } + + locally { + val List(Right(List(aSym))) = defn.Any_asInstanceOf.paramSymss: @unchecked + assert(clue(aSym.name) == typeName("A")) + assert(clue(aSym.declaredBounds).isNothingAnyBounds) + } + } + + testWithContext("paramSymss-scala-3") { + locally { + val GenericMethodClass = ctx.findTopLevelClass("simple_trees.GenericMethod") + val identity = GenericMethodClass.findNonOverloadedDecl(termName("identity")) + val List(Right(List(t)), Left(List(x))) = identity.paramSymss: @unchecked + + assert(clue(t.name) == typeName("T")) + assert(clue(t.declaredBounds).isNothingAnyBounds) + + assert(clue(x.name) == termName("x")) + assert(clue(x.declaredType).isRef(t)) + } + + locally { + val GenericMethodWithTypeParamDependenciesClass = + ctx.findTopLevelClass("simple_trees.GenericMethodWithTypeParamDependencies") + val foo = GenericMethodWithTypeParamDependenciesClass.findNonOverloadedDecl(termName("foo")) + val List(Right(List(a, b, c, d)), Left(Nil)) = foo.paramSymss: @unchecked + + assert(clue(a.name) == typeName("A")) + + assert(clue(a.declaredBounds).isNothingAnyBounds) + assert(clue(b.declaredBounds).isBounds(_.isRef(d), _.isRef(a))) + assert(clue(c.declaredBounds).isBounds(_.isNothing, _.isRef(b))) + assert(clue(d.declaredBounds).isNothingAnyBounds) + } + + locally { + val GenericClassWithTypeParamDependenciesClass = + ctx.findTopLevelClass("simple_trees.GenericClassWithTypeParamDependencies") + val ctor = GenericClassWithTypeParamDependenciesClass.findNonOverloadedDecl(nme.Constructor) + val List(Right(List(a, b, c, d)), Left(Nil)) = ctor.paramSymss: @unchecked + + assert(clue(a.name) == typeName("A")) + + assert(clue(a.declaredBounds).isNothingAnyBounds) + assert(clue(b.declaredBounds).isBounds(_.isRef(d), _.isRef(a))) + assert(clue(c.declaredBounds).isBounds(_.isNothing, _.isRef(b))) + assert(clue(d.declaredBounds).isNothingAnyBounds) + } + + locally { + val AnnotationsClass = ctx.findTopLevelClass("simple_trees.Annotations") + val renamedParam = AnnotationsClass.findNonOverloadedDecl(termName("renamedParam")) + val List(Left(List(newName))) = renamedParam.paramSymss: @unchecked + + assert(clue(newName.name) == termName("newName")) + + val annot = newName.getAnnotation(ctx.findTopLevelClass("scala.deprecatedName")).get + assert(clue(annot.argCount) == 2) + assert(clue(annot.argIfConstant(0)) == Some(Constant("oldName"))) + assert(clue(annot.argIfConstant(1)) == Some(Constant("forever"))) + } + } + + testWithContext("paramSymss-scala-2") { + val ArrayOpsClass = ctx.findTopLevelClass("scala.collection.ArrayOps") + val CollSeqHeadTailClass = ctx.findStaticModuleClass("scala.collection.package.+:") + val CollSeqClass = ctx.findTopLevelClass("scala.collection.Seq") + val CollSeqOpsClass = ctx.findTopLevelClass("scala.collection.SeqOps") + val ConsClass = ctx.findTopLevelClass("scala.collection.immutable.::") + val ListClass = ctx.findTopLevelClass("scala.collection.immutable.List") + + val deprecatedNameClass = ctx.findTopLevelClass("scala.deprecatedName") + + locally { + val headTailUnapply = CollSeqHeadTailClass.findNonOverloadedDecl(termName("unapply")) + val List(Right(List(a, cc, c)), Left(List(t))) = headTailUnapply.paramSymss: @unchecked + + assert(clue(a.name) == typeName("A")) + assert(clue(t.name) == termName("t")) + + extension (tpe: Type) + def isSeqOpsOf_A_CC_C: Boolean = + tpe.isApplied(_.isRef(CollSeqOpsClass), List(_.isRef(a), _.isRef(cc), _.isRef(c))) + + assert(clue(a.declaredBounds).isNothingAnyBounds) + assert(clue(c.declaredBounds).isBounds(_.isNothing, _.isSeqOpsOf_A_CC_C)) + assert(clue(cc.declaredBounds.low).isNothing) + + (cc.declaredBounds.high: @unchecked) match + case tl: TypeLambda => + assert(clue(tl.paramNames.size) == 1) + val paramRef = tl.paramRefs(0) + assert(tl.resultType.isApplied(_.isRef(CollSeqClass), List(_.isWildcard))) + + assert(clue(t.declaredType).isIntersectionOf(_.isRef(c), _.isSeqOpsOf_A_CC_C)) + } + + locally { + val ctor = ConsClass.findNonOverloadedDecl(nme.Constructor) + val List(Right(List(a)), Left(List(head, next))) = ctor.paramSymss: @unchecked + + assert(clue(a.name) == typeName("A")) + assert(clue(head.name) == termName("head")) + + assert(clue(a.declaredBounds).isNothingAnyBounds) + assert(clue(head.declaredType).isRef(a)) + assert(clue(next.declaredType).isApplied(_.isRef(ListClass), List(_.isRef(a)))) + } + + // TODO Enable this when we can read Scala 2 annotations + /*locally { + val find = ArrayOpsClass.findNonOverloadedDecl(termName("find")) + val List(Left(List(p))) = find.paramSymss: @unchecked + + assert(clue(p.name) == termName("p")) + assert(clue(p.declaredType).isApplied(_.isRef(defn.FunctionNClass(1)), List(_ => true, _.isRef(defn.BooleanClass)))) + + println(p.annotations) + + val annot = p.getAnnotation(deprecatedNameClass).get + assert(clue(annot.argIfConstant(0)) == Some(Constant("f"))) + assert(clue(annot.argIfConstant(1)) == Some(Constant("2.13.3"))) + }*/ + } + + testWithContext("paramSymss-java") { + val JArrayListClass = ctx.findTopLevelClass("java.util.ArrayList") + val JCollectionClass = ctx.findTopLevelClass("java.util.Collection") + val JCollectionsClass = ctx.findTopLevelModuleClass("java.util.Collections") + val JComparableClass = ctx.findTopLevelClass("java.lang.Comparable") + val JListClass = ctx.findTopLevelClass("java.util.List") + + // to disambiguate overloads + def termParamCount(tpe: TypeOrMethodic): Int = tpe match + case tpe: MethodType => tpe.paramNames.size + termParamCount(tpe.resultType) + case tpe: PolyType => termParamCount(tpe.resultType) + case tpe: Type => 0 + + def firstTermParamType(tpe: TypeOrMethodic): Type = tpe match + case tpe: MethodType => tpe.paramTypes.head + case tpe: PolyType => firstTermParamType(tpe.resultType) + case tpe: Type => throw MatchError(tpe) + + locally { + // static void fill(List list, T obj) + val fill = JCollectionsClass.findNonOverloadedDecl(termName("fill")) + val List(Right(List(t)), Left(List(list, obj))) = fill.paramSymss: @unchecked + + assert(clue(t.declaredBounds).isJavaNothingAnyBounds) + assert(clue(obj.declaredType).isRef(t)) + assert(clue(list.declaredType).isApplied(_.isRef(JListClass), List(_.isBounded(_.isRef(t), _.isFromJavaObject)))) + } + + locally { + // static > void sort(List list) + val sort = JCollectionsClass + .findAllOverloadedDecls(termName("sort")) + .find(sym => termParamCount(sym.declaredType) == 1) + .get + val List(Right(List(t)), Left(List(list))) = sort.paramSymss: @unchecked + + assert( + clue(t.declaredBounds).isBounds( + _.isNothing, + _.isApplied(_.isRef(JComparableClass), List(_.isBounded(_.isRef(t), _.isFromJavaObject))) + ) + ) + assert(clue(list.declaredType).isApplied(_.isRef(JListClass), List(_.isRef(t)))) + } + + locally { + // public ArrayList() + val ctor = JArrayListClass + .findAllOverloadedDecls(nme.Constructor) + .find(sym => termParamCount(sym.declaredType) == 0) + .get + val List(Right(List(e)), Left(Nil)) = ctor.paramSymss: @unchecked + + assert(clue(e.declaredBounds).isJavaNothingAnyBounds) + } + + locally { + // public ArrayList(Collection c) + val ctor = JArrayListClass + .findAllOverloadedDecls(nme.Constructor) + .find(sym => + termParamCount(sym.declaredType) == 1 && !firstTermParamType(sym.declaredType).isRef(defn.IntClass) + ) + .get + val List(Right(List(e)), Left(List(c))) = ctor.paramSymss: @unchecked + + assert(clue(e.declaredBounds).isJavaNothingAnyBounds) + assert(clue(c.declaredType).isApplied(_.isRef(JCollectionClass), List(_.isBounded(_.isNothing, _.isRef(e))))) + } + } } diff --git a/test-sources/src/main/scala/simple_trees/Annotations.scala b/test-sources/src/main/scala/simple_trees/Annotations.scala index 1c3b25c3..80e87e0f 100644 --- a/test-sources/src/main/scala/simple_trees/Annotations.scala +++ b/test-sources/src/main/scala/simple_trees/Annotations.scala @@ -26,4 +26,6 @@ class Annotations: @JavaAnnotWithDefault(false) def javaAnnotWithDefaultExplicit(): Int = 1 + + def renamedParam(@deprecatedName("oldName", since = "forever") newName: Int): Int = newName end Annotations