From 94f693f23a5de827b9ebcbd9f32a1d4908d43222 Mon Sep 17 00:00:00 2001 From: bishabosha Date: Tue, 7 Jul 2020 16:35:46 +0200 Subject: [PATCH 1/2] Delay forcing annotations in template body until parents are added to class info. Before this, inherited members may not be visible at the point of forcing the annotation on a definition in the template. Also moved symbol cache to the initial context so that child contexts all pass to the same cache. --- .../scala/tools/nsc/tasty/TreeUnpickler.scala | 85 +++++++----- .../tools/nsc/tasty/bridge/ContextOps.scala | 124 +++++++++--------- .../pos/src-2/tastytest/TestAnnotated.scala | 5 + .../tasty/pos/src-3/tastytest/Annotated.scala | 12 ++ .../pos/src-3/tastytest/OuterClass.scala | 7 + .../pos/src-3/tastytest/basicAnnot.scala | 3 + 6 files changed, 143 insertions(+), 93 deletions(-) create mode 100644 test/tasty/pos/src-3/tastytest/OuterClass.scala create mode 100644 test/tasty/pos/src-3/tastytest/basicAnnot.scala diff --git a/src/compiler/scala/tools/nsc/tasty/TreeUnpickler.scala b/src/compiler/scala/tools/nsc/tasty/TreeUnpickler.scala index 98f0d80dd536..628491af03a3 100644 --- a/src/compiler/scala/tools/nsc/tasty/TreeUnpickler.scala +++ b/src/compiler/scala/tools/nsc/tasty/TreeUnpickler.scala @@ -81,8 +81,9 @@ class TreeUnpickler[Tasty <: TastyUniverse]( this.roots = Set(objectRoot, classRoot) val rdr = new TreeReader(reader).fork ownerTree = new OwnerTree(NoAddr, 0, rdr.fork, reader.endAddr) + def indexTopLevel(implicit ctx: Context): Unit = rdr.indexStats(reader.endAddr) if (rdr.isTopLevel) - rdr.indexStats(reader.endAddr) + inIndexingContext(indexTopLevel(_)) } class Completer(reader: TastyReader, originalFlagSet: TastyFlagSet)(implicit ctx: Context) extends TastyLazyType(originalFlagSet) { self => @@ -604,21 +605,19 @@ class TreeUnpickler[Tasty <: TastyUniverse]( * current address and `end`. */ def indexStats(end: Addr)(implicit ctx: Context): Unit = { - val statsCtx = ctx.addMode(IndexStats) while (currentAddr.index < end.index) { nextByte match { case tag @ (VALDEF | DEFDEF | TYPEDEF | TYPEPARAM | PARAM) => - symbolAtCurrent()(statsCtx) + symbolAtCurrent() skipTree() case IMPORT => skipTree() case PACKAGE => - processPackage(end => implicit ctx => indexStats(end))(statsCtx) + processPackage(end => implicit ctx => indexStats(end)) case _ => skipTree() } } - statsCtx.forceAnnotations() assert(currentAddr.index === end.index) } @@ -774,23 +773,25 @@ class TreeUnpickler[Tasty <: TastyUniverse]( assert(readByte() === TEMPLATE) val end = readEnd() - // ** PARAMETERS ** - ctx.log(s"$currentAddr Template: reading parameters of $cls:") - val tparams = readIndexedParams[NoCycle](TYPEPARAM) - if (tparams.nonEmpty) { - cls.info = defn.PolyType(tparams.map(symFromNoCycle), cls.info) + def completeTypeParameters()(implicit ctx: Context): List[Symbol] = { + ctx.log(s"$currentAddr Template: reading parameters of $cls:") + val tparams = readIndexedParams[NoCycle](TYPEPARAM).map(symFromNoCycle) + if (tparams.nonEmpty) { + cls.info = defn.PolyType(tparams, cls.info) + } + readIndexedParams[NoCycle](PARAM) // skip value parameters + tparams } - readIndexedParams[NoCycle](PARAM) // skip value parameters - // ** MEMBERS ** - ctx.log(s"$currentAddr Template: indexing members of $cls:") - val bodyIndexer = fork - while (bodyIndexer.reader.nextByte != DEFDEF) bodyIndexer.skipTree() // skip until primary ctor - bodyIndexer.indexStats(end) + def indexMembers()(implicit ctx: Context): Unit = { + ctx.log(s"$currentAddr Template: indexing members of $cls:") + val bodyIndexer = fork + while (bodyIndexer.reader.nextByte != DEFDEF) bodyIndexer.skipTree() // skip until primary ctor + bodyIndexer.indexStats(end) + } - // ** PARENTS ** - ctx.log(s"$currentAddr Template: adding parents of $cls:") - val parents = { + def traverseParents()(implicit ctx: Context): List[Type] = { + ctx.log(s"$currentAddr Template: adding parents of $cls:") val parentCtx = ctx.withOwner(localDummy).addMode(ReadParents) val parentWithOuter = parentCtx.addMode(OuterTerm) collectWhile(nextByte != SELFDEF && nextByte != DEFDEF) { @@ -801,7 +802,7 @@ class TreeUnpickler[Tasty <: TastyUniverse]( } } - if (nextByte === SELFDEF) { + def addSelfDef()(implicit ctx: Context): Unit = { ctx.log(s"$currentAddr Template: adding self-type of $cls:") readByte() // read SELFDEF tag readLongNat() // skip Name @@ -810,21 +811,36 @@ class TreeUnpickler[Tasty <: TastyUniverse]( cls.typeOfThis = selfTpe } - val parentTypes = ctx.adjustParents(cls, parents) - - ctx.setInfo(cls, { - val classInfo = defn.ClassInfoType(parentTypes, cls) - // TODO [tasty]: if support opaque types, refine the self type with any opaque members here - if (tparams.isEmpty) classInfo - else defn.PolyType(tparams.map(symFromNoCycle), classInfo) - }) + def setInfoWithParents(tparams: List[Symbol], parentTypes: List[Type])(implicit ctx: Context): Unit = { + def debugMsg = { + val addendum = + if (parentTypes.isEmpty) "" + else parentTypes.map(lzyShow).mkString(" extends ", " with ", "") // don't force types + s"$currentAddr Template: Updated info of $cls$addendum" + } + val info = { + val classInfo = defn.ClassInfoType(parentTypes, cls) + // TODO [tasty]: if support opaque types, refine the self type with any opaque members here + if (tparams.isEmpty) classInfo + else defn.PolyType(tparams, classInfo) + } + ctx.setInfo(cls, info) + ctx.log(debugMsg) + } - ctx.log { - val addendum = - if (parentTypes.isEmpty) "" - else parentTypes.map(lzyShow).mkString(" extends ", " with ", "") // don't force types - s"$currentAddr Template: Updated info of $cls$addendum" + def traverseTemplate()(implicit ctx: Context): Unit = { + val tparams = completeTypeParameters() + indexMembers() + val parents = traverseParents() + if (nextByte === SELFDEF) { + addSelfDef() + } + val parentTypes = ctx.adjustParents(cls, parents) + setInfoWithParents(tparams, parentTypes) } + + inIndexingContext(traverseTemplate()(_)) + } def isTopLevel: Boolean = nextByte === IMPORT || nextByte === PACKAGE @@ -845,7 +861,8 @@ class TreeUnpickler[Tasty <: TastyUniverse]( until(end)(readIndexedStatAsSym(exprOwner)) def readStatsAsSyms(exprOwner: Symbol, end: Addr)(implicit ctx: Context): List[NoCycle] = { - fork.indexStats(end) + def forkAndIndexStats(implicit ctx: Context): Unit = fork.indexStats(end) + inIndexingContext(forkAndIndexStats(_)) readIndexedStatsAsSyms(exprOwner, end) } diff --git a/src/compiler/scala/tools/nsc/tasty/bridge/ContextOps.scala b/src/compiler/scala/tools/nsc/tasty/bridge/ContextOps.scala index 11fc5fd6de37..b9190cfc52e4 100644 --- a/src/compiler/scala/tools/nsc/tasty/bridge/ContextOps.scala +++ b/src/compiler/scala/tools/nsc/tasty/bridge/ContextOps.scala @@ -74,6 +74,30 @@ trait ContextOps { self: TastyUniverse => else u.NoSymbol //throw new AssertionError(s"no module $name in ${location(owner)}") } + /**Perform an operation within a context that has the mode [[IndexStats]] will force any collected annotations + * afterwards*/ + def inIndexingContext(op: Context => Unit)(implicit ctx: Context): Unit = { + val statsCtx = ctx.addMode(IndexStats) + op(statsCtx) + statsCtx.initialContext.forceAnnotations() + } + + + + /**Forces lazy annotations, if one is [[scala.annotation.internal.Child]] then it will add the referenced type as a + * sealed child. + */ + private def analyseAnnotations(sym: Symbol)(implicit ctx: Context): Unit = { + for (annot <- sym.annotations) { + annot.completeInfo() + if (annot.tpe.typeSymbolDirect === defn.ChildAnnot) { + val child = annot.tpe.typeArgs.head.typeSymbolDirect + sym.addChild(child) + ctx.log(s"adding sealed child ${showSym(child)} to ${showSym(sym)}") + } + } + } + /**Maintains state through traversal of a TASTy file, such as the outer scope of the defintion being traversed, the * traversal mode, and the root owners and source path for the TASTy file. * It also provides all operations for manipulation of the symbol table, such as creating/updating symbols and @@ -82,56 +106,14 @@ trait ContextOps { self: TastyUniverse => sealed abstract class Context { thisCtx => - private[this] implicit final def implyThisCtx: thisCtx.type = thisCtx - - private[this] var mySymbolsToForceAnnots: mutable.LinkedHashSet[Symbol] = _ - - private def stageSymbolToForceAnnots(sym: Symbol): Unit = { - if (sym.annotations.nonEmpty) { - if (mySymbolsToForceAnnots == null) { - mySymbolsToForceAnnots = mutable.LinkedHashSet.empty - } - mySymbolsToForceAnnots += sym - } - } - - /** Force any lazy annotations collected from declaration statements directly in this scope. - * - * It is important to call this *after* indexing statements in a scope, otherwise calling - * [[ownertree.findOwner]] can fail, this is because [[ownertree.findOwner]] cannot traverse a definition tree at - * a given address before a symbol has been registered to that address. - */ - def forceAnnotations(): Unit = { - if (mySymbolsToForceAnnots != null) { - val toForce = mySymbolsToForceAnnots - mySymbolsToForceAnnots = null - for (sym <- toForce) { - log(s"!!! forcing annotations on ${showSym(sym)}") - analyseAnnotations(sym) - } - } - } - - /**Forces lazy annotations, if one is [[scala.annotation.internal.Child]] then it will add the referenced type as a - * sealed child. - */ - private def analyseAnnotations(sym: Symbol): Unit = { - for (annot <- sym.annotations) { - annot.completeInfo() - if (annot.tpe.typeSymbolDirect === defn.ChildAnnot) { - val child = annot.tpe.typeArgs.head.typeSymbolDirect - sym.addChild(child) - log(s"adding sealed child ${showSym(child)} to ${showSym(sym)}") - } - } - } + protected implicit final def implyThisCtx: thisCtx.type = thisCtx /**Associates the annotations with the symbol, and will force their evaluation if not reading statements.*/ def adjustAnnotations(sym: Symbol, annots: List[DeferredAnnotation]): Unit = { if (annots.nonEmpty) { if (mode.is(IndexStats)) { log(s"lazily adding annotations to ${showSym(sym)}") - stageSymbolToForceAnnots(sym.setAnnotations(annots.map(_.lzy(sym)))) + initialContext.stageSymbolToForceAnnots(sym.setAnnotations(annots.map(_.lzy(sym)))) } else { log(s"eagerly adding annotations to ${showSym(sym)}") @@ -428,36 +410,30 @@ trait ContextOps { self: TastyUniverse => } final def withOwner(owner: Symbol): Context = - if (owner `ne` this.owner) fresh(owner) else this + if (owner `ne` this.owner) freshSymbol(owner) else this final def withNewScope: Context = - fresh(newLocalDummy) + freshSymbol(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) - private def sibling: FreshContext = sibling(mode) - - private def outerOrThis: Context = this match { - case ctx: FreshContext => ctx.outer - case ctx => ctx - } + final def freshSymbol(owner: Symbol): FreshContext = new FreshContext(owner, this, this.mode) + final def freshMode(mode: TastyMode): FreshContext = new FreshContext(this.owner, this, mode) + final def fresh: FreshContext = new FreshContext(this.owner, this, this.mode) final def addMode(mode: TastyMode): Context = - if (!this.mode.is(mode)) sibling(this.mode | mode) + if (!this.mode.is(mode)) freshMode(this.mode | mode) else this final def retractMode(mode: TastyMode): Context = - if (this.mode.isOneOf(mode)) sibling(this.mode &~ mode) + if (this.mode.isOneOf(mode)) freshMode(this.mode &~ mode) else this final def withMode(mode: TastyMode): Context = - if (mode != this.mode) sibling(mode) + if (mode != this.mode) freshMode(mode) else this final def withSource(source: AbstractFile): Context = - if (source `ne` this.source) sibling.atSource(source) + if (source `ne` this.source) fresh.atSource(source) else this final def withPhaseNoLater[T](phase: String)(op: Context => T): T = @@ -468,6 +444,36 @@ trait ContextOps { self: TastyUniverse => final class InitialContext(val topLevelClass: Symbol, val source: AbstractFile) extends Context { def mode: TastyMode = EmptyTastyMode def owner: Symbol = topLevelClass.owner + + private[this] var mySymbolsToForceAnnots: mutable.LinkedHashSet[Symbol] = _ + + private[ContextOps] def stageSymbolToForceAnnots(sym: Symbol): Unit = { + if (sym.annotations.nonEmpty) { + if (mySymbolsToForceAnnots == null) { + mySymbolsToForceAnnots = mutable.LinkedHashSet.empty + } + mySymbolsToForceAnnots += sym + } + } + + /** Force any lazy annotations collected from declaration statements directly in this scope. + * + * It is important to call this *after* indexing statements in a scope, otherwise calling + * [[ownertree.findOwner]] can fail, this is because [[ownertree.findOwner]] cannot traverse a definition tree at + * a given address before a symbol has been registered to that address. + */ + private[ContextOps] def forceAnnotations(): Unit = { + if (mySymbolsToForceAnnots != null) { + val toForce = mySymbolsToForceAnnots + mySymbolsToForceAnnots = null + for (sym <- toForce) { + log(s"!!! forcing annotations on ${showSym(sym)}") + analyseAnnotations(sym) + } + assert(mySymbolsToForceAnnots == null, "more symbols added while forcing") + } + } + } final class FreshContext(val owner: Symbol, val outer: Context, val mode: TastyMode) extends Context { diff --git a/test/tasty/pos/src-2/tastytest/TestAnnotated.scala b/test/tasty/pos/src-2/tastytest/TestAnnotated.scala index bec803aa300a..269768a16f70 100644 --- a/test/tasty/pos/src-2/tastytest/TestAnnotated.scala +++ b/test/tasty/pos/src-2/tastytest/TestAnnotated.scala @@ -3,4 +3,9 @@ package tastytest object TestAnnotated { def test1 = new Annotated {} def test2 = new RootAnnotated {} + def test3 = { + val o = new OuterClassAnnotated {} + o.foo + } + def test4 = new ParameterizedAnnotated(23).foo } diff --git a/test/tasty/pos/src-3/tastytest/Annotated.scala b/test/tasty/pos/src-3/tastytest/Annotated.scala index cc868a7cd707..d174485a11b9 100644 --- a/test/tasty/pos/src-3/tastytest/Annotated.scala +++ b/test/tasty/pos/src-3/tastytest/Annotated.scala @@ -5,3 +5,15 @@ trait Annotated @rootAnnot(1) trait RootAnnotated + +trait OuterClassAnnotated extends OuterClass { + @basicAnnot(xyz) + def foo = 1 +} + +class ParameterizedAnnotated(@basicAnnot(ParameterizedAnnotated.value) x: Int) { + def foo = x +} +object ParameterizedAnnotated { + final val value = 23 +} diff --git a/test/tasty/pos/src-3/tastytest/OuterClass.scala b/test/tasty/pos/src-3/tastytest/OuterClass.scala new file mode 100644 index 000000000000..bb597dd4198e --- /dev/null +++ b/test/tasty/pos/src-3/tastytest/OuterClass.scala @@ -0,0 +1,7 @@ +package tastytest + +class OuterClass { + + final val xyz = "xyz" + +} diff --git a/test/tasty/pos/src-3/tastytest/basicAnnot.scala b/test/tasty/pos/src-3/tastytest/basicAnnot.scala new file mode 100644 index 000000000000..e442be700a54 --- /dev/null +++ b/test/tasty/pos/src-3/tastytest/basicAnnot.scala @@ -0,0 +1,3 @@ +package tastytest + +final class basicAnnot[T](member: T) extends scala.annotation.StaticAnnotation From f8c2212b616b762abd9e9fcae06ec4493d552b76 Mon Sep 17 00:00:00 2001 From: bishabosha Date: Tue, 7 Jul 2020 17:07:42 +0200 Subject: [PATCH 2/2] Find overloads by converting types to ErasedTypeRef. Inner classes can not be found with Mirror getRequiredClass, so we should have another way to compare types to signatures, this proposes to instead convert method signatures to Signature[ErasedTypeRef] --- .../tools/nsc/tasty/bridge/ContextOps.scala | 18 +--- .../tools/nsc/tasty/bridge/SymbolOps.scala | 63 ++++++++----- .../tools/nsc/tasty/bridge/TypeOps.scala | 89 +++++++++++++++++-- .../scala/tools/tasty/ErasedTypeRef.scala | 1 + .../scala/tools/tasty/TastyName.scala | 53 +++++++---- .../neg-isolated/src-2/TestAnnotated.check | 6 +- .../neg-isolated/src-2/TestChildren.check | 4 +- .../neg-isolated/src-2/TestMethodDeps.check | 2 +- .../neg/src-2/TestCompiletimeQuoteType.check | 2 +- .../pos/src-2/tastytest/TestAnnotated.scala | 4 + .../tasty/pos/src-3/tastytest/Annotated.scala | 5 ++ .../pos/src-3/tastytest/OuterTrait.scala | 9 ++ test/tasty/run/pre/tastytest/package.scala | 2 +- 13 files changed, 190 insertions(+), 68 deletions(-) create mode 100644 test/tasty/pos/src-3/tastytest/OuterTrait.scala diff --git a/src/compiler/scala/tools/nsc/tasty/bridge/ContextOps.scala b/src/compiler/scala/tools/nsc/tasty/bridge/ContextOps.scala index b9190cfc52e4..48a5e0e43279 100644 --- a/src/compiler/scala/tools/nsc/tasty/bridge/ContextOps.scala +++ b/src/compiler/scala/tools/nsc/tasty/bridge/ContextOps.scala @@ -17,7 +17,7 @@ import scala.reflect.io.AbstractFile import collection.mutable -import scala.tools.tasty.{TastyName, TastyFlags}, TastyFlags._ +import scala.tools.tasty.{TastyName, TastyFlags}, TastyFlags._, TastyName.ObjectName import scala.tools.nsc.tasty.{TastyUniverse, TastyModes, SafeEq}, TastyModes._ import scala.reflect.internal.MissingRequirementError @@ -29,7 +29,7 @@ trait ContextOps { self: TastyUniverse => private def describeOwner(owner: Symbol): String = { val kind = - if (owner.is(Param)) { + if (owner.isOneOf(Param | ParamSetter)) { if (owner.isType) "type parameter" else "parameter" } @@ -103,8 +103,7 @@ trait ContextOps { self: TastyUniverse => * It also provides all operations for manipulation of the symbol table, such as creating/updating symbols and * updating their types. */ - sealed abstract class Context { - thisCtx => + sealed abstract class Context { thisCtx => protected implicit final def implyThisCtx: thisCtx.type = thisCtx @@ -151,15 +150,6 @@ trait ContextOps { self: TastyUniverse => symOrDependencyError(false, true, fullname)(loadingMirror.getPackage(encodeTermName(fullname).toString)) } - final def requiredClass(fullname: TastyName.TypeName): Symbol = - symOrDependencyError(false, false, fullname)(loadingMirror.getRequiredClass(encodeTypeName(fullname).toString)) - - final def optionalClass(fullname: TastyName.TypeName): Option[Symbol] = - loadingMirror.getClassIfDefined(encodeTypeName(fullname).toString).toOption - - final def requiredObject(fullname: TastyName.ObjectName): Symbol = - symOrDependencyError(true, false, fullname)(loadingMirror.getRequiredModule(encodeTermName(fullname).toString)) - private def symOrDependencyError(isObject: Boolean, isPackage: Boolean, fullname: TastyName)(sym: => Symbol): Symbol = { try sym catch { @@ -180,7 +170,7 @@ trait ContextOps { self: TastyUniverse => owner.newTypeParameter(u.nme.WILDCARD.toTypeName, u.NoPosition, u.NoFlags).setInfo(info) final def findRootSymbol(roots: Set[Symbol], name: TastyName): Option[Symbol] = { - import TastyName._ + import TastyName.TypeName def isSameRoot(root: Symbol, selector: u.Name): Boolean = (root.owner `eq` this.owner) && selector === root.name diff --git a/src/compiler/scala/tools/nsc/tasty/bridge/SymbolOps.scala b/src/compiler/scala/tools/nsc/tasty/bridge/SymbolOps.scala index 30832afb30b5..b8146aefd66f 100644 --- a/src/compiler/scala/tools/nsc/tasty/bridge/SymbolOps.scala +++ b/src/compiler/scala/tools/nsc/tasty/bridge/SymbolOps.scala @@ -16,6 +16,7 @@ import scala.tools.nsc.tasty.SafeEq import scala.tools.nsc.tasty.{TastyUniverse, TastyModes}, TastyModes._ import scala.tools.tasty.{TastyName, Signature, TastyFlags}, TastyName.SignedName, Signature.MethodSignature, TastyFlags.TastyFlagSet +import scala.tools.tasty.ErasedTypeRef /**This layer deals with selecting a member symbol from a type using a [[TastyName]], * also contains factories for making type references to symbols. @@ -73,7 +74,7 @@ trait SymbolOps { self: TastyUniverse => termParamss def namedMemberOfType(space: Type, tname: TastyName)(implicit ctx: Context): Symbol = tname match { - case SignedName(qual, sig) => signedMemberOfSpace(space, qual, sig.map(resolveErasedTypeRef)) + case SignedName(qual, sig) => signedMemberOfSpace(space, qual, sig.map(_.encode)) case _ => memberOfSpace(space, tname) } @@ -94,40 +95,57 @@ trait SymbolOps { self: TastyUniverse => } else space.member(encodeTermName(tname)) } - if (isSymbol(member)) member - else { - val kind = if (tname.isTypeName) "type" else "term" - def addendum(name: String) = - if (ctx.mode.is(ReadParents)) s"$kind in parents of ${if (ctx.owner.isLocalDummy) ctx.owner.owner else ctx.owner}: $name" - else if (ctx.owner.isClass) s"$kind required by a member of ${ctx.owner}: $name" - else s"$kind $name while unpickling ${ctx.owner}" - val msg = - if (tname.isTypeName && space.typeSymbol.hasPackageFlag) - s"can't find ${addendum(s"${space.typeSymbol.fullNameString}.$tname")}; perhaps it is missing from the classpath." - else - s"can't find ${addendum("" + tname)}, in $space" - typeError(msg) + if (isSymbol(member) && hasType(member)) member + else errorMissing(space, tname) + } + + private def hasType(member: Symbol)(implicit ctx: Context) = { + ctx.mode.is(ReadAnnotation) || (member.rawInfo `ne` u.NoType) + } + + private def errorMissing[T](space: Type, tname: TastyName)(implicit ctx: Context) = { + val kind = if (tname.isTypeName) "type" else "term" + def typeToString(tpe: Type) = { + def inner(sb: StringBuilder, tpe: Type): StringBuilder = tpe match { + case u.SingleType(pre, sym) => inner(sb, pre) append '.' append ( + if (sym.isPackageObjectOrClass) s"`${sym.name}`" + else String valueOf sym.name + ) + case u.TypeRef(pre, sym, _) if sym.isTerm => + if ((pre eq u.NoPrefix) || (pre eq u.NoType)) sb append sym.name + else inner(sb, pre) append '.' append sym.name + case tpe => sb append tpe + } + inner(new StringBuilder(), tpe).toString + } + def addendum(name: String) = { + if (ctx.mode.is(ReadParents)) s"$kind in parents of ${location(if (ctx.owner.isLocalDummy) ctx.owner.owner else ctx.owner)}: $name" + else s"$kind required by ${location(ctx.owner)}: $name" } + val missing = addendum(s"${typeToString(space)}.$tname") + typeError(s"can't find $missing; perhaps it is missing from the classpath.") } - private def signedMemberOfSpace(space: Type, qual: TastyName, sig: MethodSignature[Type])(implicit ctx: Context): Symbol = { - ctx.log(s"""<<< looking for overload member[$space] @@ $qual: ${sig.show}""") + 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)}""") val member = space.member(encodeTermName(qual)) - val (tyParamCount, argTpes) = { + if (!(isSymbol(member) && hasType(member))) errorMissing(space, qual) + val (tyParamCount, argTpeRefs) = { val (tyParamCounts, params) = sig.params.partitionMap(identity) if (tyParamCounts.length > 1) { - unsupportedError(s"multiple type parameter lists on erased method signature ${sig.show}") + unsupportedError(s"multiple type parameter lists on erased method signature ${showSig(sig)}") } (tyParamCounts.headOption.getOrElse(0), params) } def compareSym(sym: Symbol): Boolean = sym match { case sym: u.MethodSymbol => val params = sym.paramss.flatten - sym.returnType.erasure =:= sig.result && - params.length === argTpes.length && + val isJava = sym.isJavaDefined + NameErasure.sigName(sym.returnType, isJava) === sig.result && + params.length === argTpeRefs.length && (qual === TastyName.Constructor && tyParamCount === member.owner.typeParams.length || tyParamCount === sym.typeParams.length) && - params.zip(argTpes).forall { case (param, tpe) => param.tpe.erasure =:= tpe } && { + params.zip(argTpeRefs).forall { case (param, tpe) => NameErasure.sigName(param.tpe, isJava) === tpe } && { ctx.log(s">>> selected ${showSym(sym)}: ${sym.tpe}") true } @@ -136,8 +154,9 @@ trait SymbolOps { self: TastyUniverse => false } member.asTerm.alternatives.find(compareSym).getOrElse( - typeError(s"No matching overload of $space.$qual with signature ${sig.show}")) + typeError(s"No matching overload of $space.$qual with signature ${showSig(sig)}")) } + def showSig(sig: MethodSignature[ErasedTypeRef]): String = sig.map(_.signature).show def showSym(sym: Symbol): String = s"Symbol($sym, #${sym.id})" } diff --git a/src/compiler/scala/tools/nsc/tasty/bridge/TypeOps.scala b/src/compiler/scala/tools/nsc/tasty/bridge/TypeOps.scala index 5062b1f701d4..af1521a8e204 100644 --- a/src/compiler/scala/tools/nsc/tasty/bridge/TypeOps.scala +++ b/src/compiler/scala/tools/nsc/tasty/bridge/TypeOps.scala @@ -175,15 +175,92 @@ trait TypeOps { self: TastyUniverse => bounds } - private[bridge] def resolveErasedTypeRef(ref: ErasedTypeRef)(implicit ctx: Context): Type = { - import TastyName._ + /** This is a port from Dotty of transforming a Method type to an ErasedTypeRef + */ + private[bridge] object NameErasure { + + def isRepeatedParam(self: Type): Boolean = + self.typeSymbol eq u.definitions.RepeatedParamClass + + /** Translate a type of the form From[T] to either To[T] or To[? <: T] (if `wildcardArg` is set). Keep other types as they are. + * `from` and `to` must be static classes, both with one type parameter, and the same variance. + * Do the same for by name types => From[T] and => To[T] + */ + def translateParameterized(self: Type)(from: u.ClassSymbol, to: u.ClassSymbol, wildcardArg: Boolean = false)(implicit ctx: Context): Type = self match { + case self @ u.NullaryMethodType(tp) => + u.NullaryMethodType(translateParameterized(tp)(from, to, wildcardArg=false)) + case _ => + if (self.typeSymbol.isSubClass(from)) { + def elemType(tp: Type): Type = tp.dealiasWiden match { + // case tp: AndOrType => tp.derivedAndOrType(elemType(tp.tp1), elemType(tp.tp2)) + case tp: u.RefinedType => u.intersectionType(tp.parents.map(elemType)) + case _ => tp.baseType(from).typeArgs.head + } + val arg = elemType(self) + val arg1 = if (wildcardArg) u.TypeBounds.upper(arg) else arg + to.ref(arg1 :: Nil) + } + else self + } + + def translateFromRepeated(self: Type)(toArray: Boolean, translateWildcard: Boolean = false)(implicit ctx: Context): Type = { + val seqClass = if (toArray) u.definitions.ArrayClass else u.definitions.SeqClass + if (translateWildcard && self === u.WildcardType) + seqClass.ref(u.WildcardType :: Nil) + else if (isRepeatedParam(self)) + // We want `Array[? <: T]` because arrays aren't covariant until after + // erasure. See `tests/pos/i5140`. + translateParameterized(self)(u.definitions.RepeatedParamClass, seqClass, wildcardArg = toArray) + else self + } - val sym = ref.qualifiedName match { - case TypeName(obj: ObjectName) => ctx.requiredObject(obj) - case clazz => ctx.requiredClass(clazz) + def sigName(tp: Type, isJava: Boolean)(implicit ctx: Context): ErasedTypeRef = { + val normTp = translateFromRepeated(tp)(toArray = isJava) + erasedSigName(normTp.erasure) + } + + private def erasedSigName(erased: Type)(implicit ctx: Context): ErasedTypeRef = erased match { + case erased: u.ExistentialType => erasedSigName(erased.underlying) + case erased: u.TypeRef => + import TastyName._ + if (!isSymbol(erased.sym)) + typeError(s"missing: ${erased.prefix}, ${erased.sym.name}") + var dims = 0 + var clazzRef: Type = erased + while (clazzRef.typeArgs.nonEmpty && clazzRef.typeSymbol.isSubClass(u.definitions.ArrayClass)) { + dims += 1 + clazzRef = clazzRef.typeArgs.head + } + def unpeelName(acc: List[TastyName], tpe: Type): List[TastyName] = { + def mkRef(sym: Symbol) = { + val name = SimpleName(sym.name.toString) + if (sym.isModuleClass && !sym.isPackageClass) ObjectName(name) + else name + } + def rec(pre: Type) = + (pre ne u.NoPrefix) && (pre ne u.NoType) && (pre.typeSymbol != u.rootMirror.RootClass) + tpe match { + case u.TypeRef(pre, sym, _) => + val ref = mkRef(sym) + if (rec(pre)) unpeelName(ref :: acc, pre) + else ref :: acc + case tpe @ u.ThisType(sym) => + val ref = mkRef(sym) + val pre = tpe.prefix + if (rec(pre)) unpeelName(ref :: acc, pre) + else ref :: acc + } + } + val name = (unpeelName(Nil, clazzRef): @unchecked) match { + case single :: Nil => single + case base :: rest => rest.foldLeft(base)((acc, n) => n match { + case ObjectName(base) => ObjectName(QualifiedName(acc, PathSep, base.asSimpleName)) + case name => QualifiedName(acc, PathSep, name.asSimpleName) + }) + } + ErasedTypeRef(name.toTypeName, dims) } - (0 until ref.arrayDims).foldLeft(sym.tpe.erasure)((acc, _) => u.definitions.arrayType(acc)) } /** A type which accepts two type arguments, representing an intersection type diff --git a/src/compiler/scala/tools/tasty/ErasedTypeRef.scala b/src/compiler/scala/tools/tasty/ErasedTypeRef.scala index cc2288751afd..b102183350a4 100644 --- a/src/compiler/scala/tools/tasty/ErasedTypeRef.scala +++ b/src/compiler/scala/tools/tasty/ErasedTypeRef.scala @@ -25,6 +25,7 @@ case class ErasedTypeRef(qualifiedName: TypeName, arrayDims: Int) { val qualified = qualifiedName.source "[" * arrayDims + (if (qualifiedName.toTermName.isObjectName) s"object $qualified" else qualified) } + def encode: ErasedTypeRef = ErasedTypeRef(TastyName.deepEncode(qualifiedName).toTypeName, arrayDims) } object ErasedTypeRef { diff --git a/src/compiler/scala/tools/tasty/TastyName.scala b/src/compiler/scala/tools/tasty/TastyName.scala index b5daa294530c..0d72e023a54f 100644 --- a/src/compiler/scala/tools/tasty/TastyName.scala +++ b/src/compiler/scala/tools/tasty/TastyName.scala @@ -80,14 +80,18 @@ object TastyName { */ object SourceEncoder extends StringBuilderEncoder { def traverse(sb: StringBuilder, name: TastyName): StringBuilder = name match { - case name: SimpleName => sb.append(name.raw) + case name: SimpleName => sb append (name.raw) case name: ObjectName => traverse(sb, name.base) case name: TypeName => traverse(sb, name.base) case name: SignedName => traverse(sb, name.qual) - case name: UniqueName => traverse(traverse(sb, name.qual), name.sep).append(name.num) - case name: DefaultName => traverse(sb, name.qual).append(DefaultGetterStr).append(name.num + 1) - case name: QualifiedName => traverse(traverse(traverse(sb, name.qual), name.sep), name.selector) - case name: PrefixName => traverse(traverse(sb, name.prefix), name.qual) + case name: UniqueName => traverse(sb, name.qual) append (name.sep.raw) append (name.num) + case name: QualifiedName => traverse(traverse(sb, name.qual) append (name.sep.raw), name.selector) + case name: PrefixName => traverse(sb append (name.prefix.raw), name.qual) + + case name: DefaultName if name.qual == Constructor => + sb append DefaultGetterInitStr append (name.num + 1) + + case name: DefaultName => traverse(sb, name.qual) append DefaultGetterStr append (name.num + 1) } } @@ -95,20 +99,22 @@ object TastyName { */ object DebugEncoder extends StringBuilderEncoder { + def merge[T](sb: StringBuilder, sig: Signature[T]) = sig.mergeShow(sb) + def traverse(sb: StringBuilder, name: TastyName): StringBuilder = name match { - case SimpleName(raw) => sb.append(raw) - case DefaultName(qual, num) => traverse(sb, qual).append("[Default ").append(num + 1).append(']') - case PrefixName(prefix, qual) => traverse(traverse(sb, qual).append("[Prefix "), prefix).append(']') - case ObjectName(name) => traverse(sb, name).append("[ModuleClass]") - case TypeName(name) => traverse(sb, name).append("[Type]") - case SignedName(name,sig) => sig.map(_.signature).mergeShow(traverse(sb, name).append("[Signed ")).append(']') + case SimpleName(raw) => sb append raw + case DefaultName(qual, num) => traverse(sb, qual) append "[Default " append (num + 1) append ']' + case PrefixName(prefix, qual) => traverse(sb, qual) append "[Prefix " append (prefix.raw) append ']' + case ObjectName(name) => traverse(sb, name) append "[ModuleClass]" + case TypeName(name) => traverse(sb, name) append "[Type]" + case SignedName(name,sig) => merge(traverse(sb, name) append "[Signed ", sig.map(_.signature)) append ']' case QualifiedName(qual, sep, name) => - traverse(traverse(traverse(sb, qual).append("[Qualified "), sep).append(' '), name).append(']') + traverse(sb, qual) append "[Qualified " append (sep.raw) append ' ' append (name.raw) append ']' case UniqueName(qual, sep, num) => - traverse(traverse(sb, qual).append("[Unique "), sep).append(' ').append(num).append(']') + traverse(sb, qual) append "[Unique " append (sep.raw) append ' ' append num append ']' } @@ -130,17 +136,28 @@ object TastyName { case name: ObjectName => traverse(sb, name.base) case name: TypeName => traverse(sb, name.base) case name: SignedName => traverse(sb, name.qual) - case name: UniqueName => traverse(sb, name.qual).append(name.sep.raw).append(name.num) - case name: QualifiedName => traverse(traverse(sb, name.qual).append(name.sep.raw), name.selector) - case name: PrefixName => traverse(sb.append(name.prefix), name.qual) + case name: UniqueName => traverse(sb, name.qual) append (name.sep.raw) append (name.num) + case name: QualifiedName => traverse(traverse(sb, name.qual) append (name.sep.raw), name.selector) + case name: PrefixName => traverse(sb append (name.prefix.raw), name.qual) - case name: DefaultName if name.qual == Constructor => sb.append(DefaultGetterInitStr).append(name.num + 1) + case name: DefaultName if name.qual == Constructor => sb append DefaultGetterInitStr append (name.num + 1) - case name: DefaultName => traverse(sb, name.qual).append(DefaultGetterStr).append(name.num + 1) + case name: DefaultName => traverse(sb, name.qual) append DefaultGetterStr append (name.num + 1) } } + def deepEncode(name: TastyName): TastyName = name match { + case SimpleName(raw) => SimpleName(NameTransformer.encode(raw)) + case QualifiedName(qual, sep, selector) => QualifiedName(deepEncode(qual), sep, deepEncode(selector).asSimpleName) + case ObjectName(base) => ObjectName(deepEncode(base)) + case UniqueName(qual, sep, num) => UniqueName(deepEncode(qual), sep, num) + case DefaultName(qual, num) => DefaultName(deepEncode(qual), num) + case PrefixName(prefix, qual) => PrefixName(prefix, deepEncode(qual)) + case TypeName(base) => TypeName(deepEncode(base)) + case SignedName(qual, sig) => SignedName(deepEncode(qual), sig.map(_.encode)) + } + } /**This is a data structure representing semantic names. [[TastyName]] is the interface that TASTy uses to select diff --git a/test/tasty/neg-isolated/src-2/TestAnnotated.check b/test/tasty/neg-isolated/src-2/TestAnnotated.check index 2ee179c73404..6e0d8fc8525d 100644 --- a/test/tasty/neg-isolated/src-2/TestAnnotated.check +++ b/test/tasty/neg-isolated/src-2/TestAnnotated.check @@ -1,10 +1,10 @@ -TestAnnotated_fail.scala:5: error: can't find type required by a member of package tastytest: tastytest.annot; perhaps it is missing from the classpath. +TestAnnotated_fail.scala:5: error: can't find type required by package tastytest: tastytest.annot; perhaps it is missing from the classpath. def test1 = new Annotated {} // error: can't find type required by a member of package tastytest: tastytest.annot ^ -TestAnnotated_fail.scala:6: error: could not find class tastytest.Parent whilst reading annotation of trait PublicAnnotated; perhaps it is missing from the classpath. +TestAnnotated_fail.scala:6: error: can't find type required by parameter parent in class tastytest.publicAnnot: tastytest.Parent; perhaps it is missing from the classpath. def test2 = new PublicAnnotated {} // error: could not find class tastytest.Parent ^ -TestAnnotated_fail.scala:7: error: could not find class tastytest.<<< whilst reading annotation of trait SymbollicAnnotated; perhaps it is missing from the classpath. +TestAnnotated_fail.scala:7: error: can't find type required by parameter parent in class tastytest.symbollicAnnot: tastytest.<<<; perhaps it is missing from the classpath. def test3 = new SymbollicAnnotated {} // error: could not find class tastytest.<<< ^ 3 errors diff --git a/test/tasty/neg-isolated/src-2/TestChildren.check b/test/tasty/neg-isolated/src-2/TestChildren.check index f910d39bd745..d5d9f0d654b2 100644 --- a/test/tasty/neg-isolated/src-2/TestChildren.check +++ b/test/tasty/neg-isolated/src-2/TestChildren.check @@ -1,7 +1,7 @@ -TestChildren_fail.scala:5: error: can't find type in parents of class Child: tastytest.Parent; perhaps it is missing from the classpath. +TestChildren_fail.scala:5: error: can't find type in parents of class tastytest.Child: tastytest.Parent; perhaps it is missing from the classpath. def test1 = new Child ^ -TestChildren_fail.scala:6: error: can't find type in parents of object Module: tastytest.Parent; perhaps it is missing from the classpath. +TestChildren_fail.scala:6: error: can't find type in parents of object tastytest.Module: tastytest.Parent; perhaps it is missing from the classpath. def test2 = Module ^ 2 errors diff --git a/test/tasty/neg-isolated/src-2/TestMethodDeps.check b/test/tasty/neg-isolated/src-2/TestMethodDeps.check index a0f185879900..4e1f62d038bb 100644 --- a/test/tasty/neg-isolated/src-2/TestMethodDeps.check +++ b/test/tasty/neg-isolated/src-2/TestMethodDeps.check @@ -1,4 +1,4 @@ -TestMethodDeps_fail.scala:5: error: can't find type tastytest.Parent while unpickling method parent; perhaps it is missing from the classpath. +TestMethodDeps_fail.scala:5: error: can't find type required by method parent in object tastytest.MethodDeps: tastytest.Parent; perhaps it is missing from the classpath. def test = MethodDeps.parent ^ 1 error diff --git a/test/tasty/neg/src-2/TestCompiletimeQuoteType.check b/test/tasty/neg/src-2/TestCompiletimeQuoteType.check index d0b9f388e304..65c9dd9e1a38 100644 --- a/test/tasty/neg/src-2/TestCompiletimeQuoteType.check +++ b/test/tasty/neg/src-2/TestCompiletimeQuoteType.check @@ -1,4 +1,4 @@ -TestCompiletimeQuoteType_fail.scala:4: error: can't find type scala.AnyKind while unpickling type T; perhaps it is missing from the classpath. +TestCompiletimeQuoteType_fail.scala:4: error: can't find type required by type parameter T in class scala.quoted.Type: scala.AnyKind; perhaps it is missing from the classpath. def test = CompiletimeQuoteType.f[Int] ^ 1 error diff --git a/test/tasty/pos/src-2/tastytest/TestAnnotated.scala b/test/tasty/pos/src-2/tastytest/TestAnnotated.scala index 269768a16f70..1f1343fa3357 100644 --- a/test/tasty/pos/src-2/tastytest/TestAnnotated.scala +++ b/test/tasty/pos/src-2/tastytest/TestAnnotated.scala @@ -8,4 +8,8 @@ object TestAnnotated { o.foo } def test4 = new ParameterizedAnnotated(23).foo + def test5 = { + val o = new OuterAnnotated {} + o.foo + } } diff --git a/test/tasty/pos/src-3/tastytest/Annotated.scala b/test/tasty/pos/src-3/tastytest/Annotated.scala index d174485a11b9..772596caca88 100644 --- a/test/tasty/pos/src-3/tastytest/Annotated.scala +++ b/test/tasty/pos/src-3/tastytest/Annotated.scala @@ -17,3 +17,8 @@ class ParameterizedAnnotated(@basicAnnot(ParameterizedAnnotated.value) x: Int) { object ParameterizedAnnotated { final val value = 23 } + +trait OuterAnnotated extends OuterTrait { + @innerAnnot(new Inner) + def foo = 1 +} diff --git a/test/tasty/pos/src-3/tastytest/OuterTrait.scala b/test/tasty/pos/src-3/tastytest/OuterTrait.scala new file mode 100644 index 000000000000..d0481102f67f --- /dev/null +++ b/test/tasty/pos/src-3/tastytest/OuterTrait.scala @@ -0,0 +1,9 @@ +package tastytest + +trait OuterTrait { + + final class Inner + + final class innerAnnot(inner: Inner) extends scala.annotation.StaticAnnotation + +} diff --git a/test/tasty/run/pre/tastytest/package.scala b/test/tasty/run/pre/tastytest/package.scala index 296667df5642..69094d25ba22 100644 --- a/test/tasty/run/pre/tastytest/package.scala +++ b/test/tasty/run/pre/tastytest/package.scala @@ -10,7 +10,7 @@ package object tastytest { object Macros { - def hasStaticAnnotImpl[T, A](c: Context)(implicit T: c.WeakTypeTag[T], A: c.WeakTypeTag[A]) : c.Expr[Boolean] = { + def hasStaticAnnotImpl[T, A](c: Context)(implicit T: c.WeakTypeTag[T], A: c.WeakTypeTag[A]): c.Expr[Boolean] = { import c.universe._ if (weakTypeOf[T].members.filter(_.isMethod).exists(_.annotations.exists(_.tree.tpe =:= weakTypeOf[A]))) { c.Expr[Boolean](q"true")