From f6a790062637b9adbfc6c5ad183b4f47acf8f161 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Tue, 25 Oct 2022 15:57:25 +0100 Subject: [PATCH 01/10] Support polymorphic signatures --- .../dotty/tools/backend/jvm/CoreBTypes.scala | 4 +- .../dotty/tools/backend/sjs/JSCodeGen.scala | 2 +- .../tools/backend/sjs/JSDefinitions.scala | 3 - .../dotty/tools/dotc/core/Definitions.scala | 23 ++++++ .../tools/dotc/core/tasty/TastyPrinter.scala | 2 +- .../tools/dotc/core/tasty/TreePickler.scala | 8 +++ .../tools/dotc/core/tasty/TreeUnpickler.scala | 71 ++++++++++--------- .../semanticdb/SemanticSymbolBuilder.scala | 5 +- .../tools/dotc/transform/PostTyper.scala | 3 +- .../dotty/tools/dotc/transform/Recheck.scala | 9 ++- .../dotty/tools/dotc/typer/Applications.scala | 25 +++++-- project/Build.scala | 7 +- project/MiMaFilters.scala | 3 + tasty/src/dotty/tools/tasty/TastyFormat.scala | 5 +- tests/run/i11332.scala | 63 ++++++++++++++++ tests/run/t12348.scala | 22 ++++++ 16 files changed, 202 insertions(+), 53 deletions(-) create mode 100644 tests/run/i11332.scala create mode 100644 tests/run/t12348.scala diff --git a/compiler/src/dotty/tools/backend/jvm/CoreBTypes.scala b/compiler/src/dotty/tools/backend/jvm/CoreBTypes.scala index e94bda16fbb8..d5fce3f53627 100644 --- a/compiler/src/dotty/tools/backend/jvm/CoreBTypes.scala +++ b/compiler/src/dotty/tools/backend/jvm/CoreBTypes.scala @@ -134,8 +134,8 @@ class CoreBTypes[BTFS <: BTypesFromSymbols[_ <: DottyBackendInterface]](val bTyp private lazy val jliCallSiteRef : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.invoke.CallSite]) private lazy val jliLambdaMetafactoryRef : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.invoke.LambdaMetafactory]) - private lazy val jliMethodHandleRef : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.invoke.MethodHandle]) - private lazy val jliMethodHandlesLookupRef : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.invoke.MethodHandles.Lookup]) + private lazy val jliMethodHandleRef : ClassBType = classBTypeFromSymbol(defn.MethodHandleClass) + private lazy val jliMethodHandlesLookupRef : ClassBType = classBTypeFromSymbol(defn.MethodHandlesLookupClass) private lazy val jliMethodTypeRef : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.invoke.MethodType]) private lazy val jliStringConcatFactoryRef : ClassBType = classBTypeFromSymbol(requiredClass("java.lang.invoke.StringConcatFactory")) // since JDK 9 private lazy val srLambdaDeserialize : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.LambdaDeserialize]) diff --git a/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala b/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala index 6714f664620b..de0de0304c76 100644 --- a/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala +++ b/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala @@ -4726,7 +4726,7 @@ class JSCodeGen()(using genCtx: Context) { Set[Symbol]( defn.BoxedUnitClass, defn.BoxedBooleanClass, defn.BoxedCharClass, defn.BoxedByteClass, defn.BoxedShortClass, defn.BoxedIntClass, defn.BoxedLongClass, defn.BoxedFloatClass, - defn.BoxedDoubleClass, defn.StringClass, jsdefn.JavaLangVoidClass + defn.BoxedDoubleClass, defn.StringClass, defn.VoidClass ) } diff --git a/compiler/src/dotty/tools/backend/sjs/JSDefinitions.scala b/compiler/src/dotty/tools/backend/sjs/JSDefinitions.scala index c252ac892548..d728c6924ac4 100644 --- a/compiler/src/dotty/tools/backend/sjs/JSDefinitions.scala +++ b/compiler/src/dotty/tools/backend/sjs/JSDefinitions.scala @@ -26,9 +26,6 @@ final class JSDefinitions()(using Context) { @threadUnsafe lazy val NoinlineAnnotType: TypeRef = requiredClassRef("scala.noinline") def NoinlineAnnot(using Context) = NoinlineAnnotType.symbol.asClass - @threadUnsafe lazy val JavaLangVoidType: TypeRef = requiredClassRef("java.lang.Void") - def JavaLangVoidClass(using Context) = JavaLangVoidType.symbol.asClass - @threadUnsafe lazy val ScalaJSJSPackageVal = requiredPackage("scala.scalajs.js") @threadUnsafe lazy val ScalaJSJSPackageClass = ScalaJSJSPackageVal.moduleClass.asClass @threadUnsafe lazy val JSPackage_typeOfR = ScalaJSJSPackageClass.requiredMethodRef("typeOf") diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index b43857b7d28c..db3378863fd8 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -485,6 +485,9 @@ class Definitions { } def NullType: TypeRef = NullClass.typeRef + @tu lazy val VoidType: TypeRef = requiredClassRef("java.lang.Void") + def VoidClass(using Context) = VoidType.symbol.asClass + @tu lazy val InvokerModule = requiredModule("scala.runtime.coverage.Invoker") @tu lazy val InvokedMethodRef = InvokerModule.requiredMethodRef("invoked") @@ -732,6 +735,26 @@ class Definitions { } def JavaEnumType = JavaEnumClass.typeRef + + @tu lazy val MethodHandleClass: ClassSymbol = requiredClass("java.lang.invoke.MethodHandle") + @tu lazy val MethodHandlesLookupClass: ClassSymbol = requiredClass("java.lang.invoke.MethodHandles.Lookup") + @tu lazy val VarHandleClass: ClassSymbol = requiredClass("java.lang.invoke.VarHandle") + + // from the Java language spec: https://docs.oracle.com/javase/specs/jls/se11/html/jls-15.html#jls-15.12.3 + def isPolymorphicSignature(sym: Symbol) = sym.is(JavaDefined) && { + val owner = sym.maybeOwner + (owner == MethodHandleClass || owner == VarHandleClass) + && sym.hasAnnotation(NativeAnnot) + && sym.paramSymss.match + case List(List(p)) => p.info.isRepeatedParam + case _ => false + } + + def wasPolymorphicSignature(sym: Symbol) = + val owner = sym.maybeOwner + (owner == MethodHandleClass || owner == VarHandleClass) + && isPolymorphicSignature(owner.info.member(sym.name).symbol) + @tu lazy val StringBuilderClass: ClassSymbol = requiredClass("scala.collection.mutable.StringBuilder") @tu lazy val MatchErrorClass : ClassSymbol = requiredClass("scala.MatchError") @tu lazy val ConversionClass : ClassSymbol = requiredClass("scala.Conversion").typeRef.symbol.asClass diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TastyPrinter.scala b/compiler/src/dotty/tools/dotc/core/tasty/TastyPrinter.scala index 5876b69edfde..a5f4d848c040 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TastyPrinter.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TastyPrinter.scala @@ -129,7 +129,7 @@ class TastyPrinter(bytes: Array[Byte]) { printName(); printName() case VALDEF | DEFDEF | TYPEDEF | TYPEPARAM | PARAM | NAMEDARG | BIND => printName(); printTrees() - case REFINEDtype | TERMREFin | TYPEREFin | SELECTin => + case REFINEDtype | TERMREFin | TYPEREFin | SELECTin | SELECTinPoly => printName(); printTree(); printTrees() case RETURN | HOLE => printNat(); printTrees() diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index 34c22439a932..6484526f8d87 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -413,6 +413,14 @@ class TreePickler(pickler: TastyPickler) { writeByte(if name.isTypeName then SELECTtpt else SELECT) pickleNameAndSig(name, sig, ename) pickleTree(qual) + else if defn.wasPolymorphicSignature(tree.symbol) then + writeByte(SELECTinPoly) + withLength { + pickleNameAndSig(name, tree.symbol.signature, ename) + pickleTree(qual) + pickleType(tree.symbol.owner.typeRef) + pickleType(tree.tpe.widenSingleton, richTypes = true) // this widens to a MethodType, so need richTypes + } else // select from owner writeByte(SELECTin) withLength { diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 617a2c55a7ad..c19008b286c6 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -1131,7 +1131,6 @@ class TreeUnpickler(reader: TastyReader, def makeSelect(qual: Tree, name: Name, denot: Denotation): Select = var qualType = qual.tpe.widenIfUnstable - val owner = denot.symbol.maybeOwner val tpe0 = name match case name: TypeName => TypeRef(qualType, name, denot) case name: TermName => TermRef(qualType, name, denot) @@ -1143,6 +1142,43 @@ class TreeUnpickler(reader: TastyReader, val denot = accessibleDenot(qual.tpe.widenIfUnstable, name, sig, target) makeSelect(qual, name, denot) + def readSelectIn(): Select = + var sname = readName() + val qual = readTerm() + val ownerTpe = readType() + val owner = ownerTpe.typeSymbol + val SignedName(name, sig, target) = sname: @unchecked // only methods with params use SELECTin + val qualType = qual.tpe.widenIfUnstable + val prefix = ctx.typeAssigner.maybeSkolemizePrefix(qualType, name) + + /** Tasty should still be able to resolve a method from another root class, + * even if it has been moved to a super type, + * or an override has been removed. + * + * This is tested in + * - sbt-test/tasty-compat/remove-override + * - sbt-test/tasty-compat/move-method + */ + def lookupInSuper = + val cls = ownerTpe.classSymbol + if cls.exists then + cls.asClass.classDenot + .findMember(name, cls.thisType, EmptyFlags, excluded=Private) + .atSignature(sig, target) + else + NoDenotation + + val denot = + val d = ownerTpe.decl(name).atSignature(sig, target) + (if !d.exists then lookupInSuper else d).asSeenFrom(prefix) + + makeSelect(qual, name, denot) + + def readSelectInPoly(): Select = + val tree = readSelectIn() + val info = readType() + tree.withType(tree.symbol.copy(info = info).termRef) + def readQualId(): (untpd.Ident, TypeRef) = val qual = readTerm().asInstanceOf[untpd.Ident] (untpd.Ident(qual.name).withSpan(qual.span), qual.tpe.asInstanceOf[TypeRef]) @@ -1292,37 +1328,8 @@ class TreeUnpickler(reader: TastyReader, case SELECTouter => val levels = readNat() readTerm().outerSelect(levels, SkolemType(readType())) - case SELECTin => - var sname = readName() - val qual = readTerm() - val ownerTpe = readType() - val owner = ownerTpe.typeSymbol - val SignedName(name, sig, target) = sname: @unchecked // only methods with params use SELECTin - val qualType = qual.tpe.widenIfUnstable - val prefix = ctx.typeAssigner.maybeSkolemizePrefix(qualType, name) - - /** Tasty should still be able to resolve a method from another root class, - * even if it has been moved to a super type, - * or an override has been removed. - * - * This is tested in - * - sbt-test/tasty-compat/remove-override - * - sbt-test/tasty-compat/move-method - */ - def lookupInSuper = - val cls = ownerTpe.classSymbol - if cls.exists then - cls.asClass.classDenot - .findMember(name, cls.thisType, EmptyFlags, excluded=Private) - .atSignature(sig, target) - else - NoDenotation - - val denot = - val d = ownerTpe.decl(name).atSignature(sig, target) - (if !d.exists then lookupInSuper else d).asSeenFrom(prefix) - - makeSelect(qual, name, denot) + case SELECTin => readSelectIn() + case SELECTinPoly => readSelectInPoly() case REPEATED => val elemtpt = readTpt() SeqLiteral(until(end)(readTerm()), elemtpt) diff --git a/compiler/src/dotty/tools/dotc/semanticdb/SemanticSymbolBuilder.scala b/compiler/src/dotty/tools/dotc/semanticdb/SemanticSymbolBuilder.scala index c825032373f8..e8c87f6c0f84 100644 --- a/compiler/src/dotty/tools/dotc/semanticdb/SemanticSymbolBuilder.scala +++ b/compiler/src/dotty/tools/dotc/semanticdb/SemanticSymbolBuilder.scala @@ -90,7 +90,10 @@ class SemanticSymbolBuilder: case _ => end find val sig = sym.signature - find(_.signature == sig) + // the polymorphic signature methods (invoke/invokeExact) are never overloaded + // we changed the signature at the call site, which means they won't match, + // but we still shouldn't add any overload index in this case. + find(_.signature == sig || defn.wasPolymorphicSignature(sym)) def addDescriptor(sym: Symbol): Unit = if sym.is(ModuleClass) then diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index 05aaa745bb18..d4891dc14fa6 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -204,7 +204,8 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase return transformSelect(cpy.Select(tree)(qual.select(pobj).withSpan(qual.span), tree.name), targs) case _ => } - val tree1 = super.transform(tree) + val tree0 = super.transform(tree) + val tree1 = if defn.wasPolymorphicSignature(tree.symbol) then tree0.withType(tree.tpe) else tree0 constToLiteral(tree1) match { case _: Literal => tree1 case _ => superAcc.transformSelect(tree1, targs) diff --git a/compiler/src/dotty/tools/dotc/transform/Recheck.scala b/compiler/src/dotty/tools/dotc/transform/Recheck.scala index 0ac9087a08c0..f6532a3b6214 100644 --- a/compiler/src/dotty/tools/dotc/transform/Recheck.scala +++ b/compiler/src/dotty/tools/dotc/transform/Recheck.scala @@ -261,8 +261,13 @@ abstract class Recheck extends Phase, SymTransformer: mt.instantiate(argTypes) def recheckApply(tree: Apply, pt: Type)(using Context): Type = - val funtpe = recheck(tree.fun) - funtpe.widen match + val wasPolymorphicSignature = tree.fun.tpe match + case funRef: TermRef => defn.wasPolymorphicSignature(funRef.symbol) + case _ => false + if wasPolymorphicSignature then tree.fun.tpe.widenTermRefExpr.finalResultType + else + val funtpe = recheck(tree.fun) + funtpe.widen match case fntpe: MethodType => assert(fntpe.paramInfos.hasSameLengthAs(tree.args)) val formals = diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index ec72c48b2422..2e78b260dafe 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -23,7 +23,7 @@ import Inferencing._ import reporting._ import transform.TypeUtils._ import transform.SymUtils._ -import Nullables._ +import Nullables._, NullOpsDecorator.* import config.Feature import collection.mutable @@ -937,11 +937,24 @@ trait Applications extends Compatibility { def simpleApply(fun1: Tree, proto: FunProto)(using Context): Tree = methPart(fun1).tpe match { case funRef: TermRef => - val app = ApplyTo(tree, fun1, funRef, proto, pt) - convertNewGenericArray( - widenEnumCase( - postProcessByNameArgs(funRef, app).computeNullable(), - pt)) + if defn.isPolymorphicSignature(funRef.symbol) then + val originalResultType = funRef.symbol.info.resultType.stripNull + val expectedResultType = AvoidWildcardsMap()(proto.deepenProto.resultType) + val resultType = + if !originalResultType.isRef(defn.ObjectClass) then originalResultType + else if isFullyDefined(expectedResultType, ForceDegree.all) then expectedResultType + else expectedResultType match + case SelectionProto(nme.asInstanceOf_, PolyProto(_, resTp), _, _) => resTp + case _ => defn.ObjectType + val info = MethodType(proto.typedArgs().map(_.tpe.widen), resultType) + val fun2 = fun1.withType(funRef.symbol.copy(info = info).termRef) + simpleApply(fun2, proto) + else + val app = ApplyTo(tree, fun1, funRef, proto, pt) + convertNewGenericArray( + widenEnumCase( + postProcessByNameArgs(funRef, app).computeNullable(), + pt)) case _ => handleUnexpectedFunType(tree, fun1) } diff --git a/project/Build.scala b/project/Build.scala index d735db6a8a5b..9b21c585ac45 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1816,9 +1816,10 @@ object Build { settings(disableDocSetting). settings( versionScheme := Some("semver-spec"), - if (mode == Bootstrapped) { - commonMiMaSettings - } else { + if (mode == Bootstrapped) Def.settings( + commonMiMaSettings, + mimaBinaryIssueFilters ++= MiMaFilters.TastyCore, + ) else { Nil } ) diff --git a/project/MiMaFilters.scala b/project/MiMaFilters.scala index 408930bbeee9..6524f76279d1 100644 --- a/project/MiMaFilters.scala +++ b/project/MiMaFilters.scala @@ -22,4 +22,7 @@ object MiMaFilters { ProblemFilters.exclude[MissingClassProblem]("scala.caps$Pure"), ProblemFilters.exclude[MissingClassProblem]("scala.caps$unsafe$"), ) + val TastyCore: Seq[ProblemFilter] = Seq( + ProblemFilters.exclude[MissingMethodProblem]("dotty.tools.tasty.TastyFormat.SELECTinPoly"), + ) } diff --git a/tasty/src/dotty/tools/tasty/TastyFormat.scala b/tasty/src/dotty/tools/tasty/TastyFormat.scala index 66b56d30a6a4..75c5114d9c2d 100644 --- a/tasty/src/dotty/tools/tasty/TastyFormat.scala +++ b/tasty/src/dotty/tools/tasty/TastyFormat.scala @@ -86,6 +86,7 @@ Standard-Section: "ASTs" TopLevelStat* IDENT NameRef Type -- Used when term ident’s type is not a TermRef SELECT possiblySigned_NameRef qual_Term -- qual.name SELECTin Length possiblySigned_NameRef qual_Term owner_Type -- qual.name, referring to a symbol declared in owner that has the given signature (see note below) + SELECTinPoly Length possiblySigned_NameRef qual_Term owner_Type method_Type -- like SELECTin, but with the method_Type too QUALTHIS typeIdent_Tree -- id.this, different from THIS in that it contains a qualifier ident with position. NEW clsType_Term -- new cls THROW throwableExpr_Term -- throw throwableExpr @@ -578,6 +579,7 @@ object TastyFormat { // final val ??? = 178 // final val ??? = 179 final val METHODtype = 180 + final val SELECTinPoly = 181 final val MATCHtype = 190 final val MATCHtpt = 191 @@ -777,6 +779,7 @@ object TastyFormat { case TERMREFin => "TERMREFin" case TYPEREFin => "TYPEREFin" case SELECTin => "SELECTin" + case SELECTinPoly => "SELECTinPoly" case REFINEDtype => "REFINEDtype" case REFINEDtpt => "REFINEDtpt" @@ -807,7 +810,7 @@ object TastyFormat { */ def numRefs(tag: Int): Int = tag match { case VALDEF | DEFDEF | TYPEDEF | TYPEPARAM | PARAM | NAMEDARG | RETURN | BIND | - SELFDEF | REFINEDtype | TERMREFin | TYPEREFin | SELECTin | HOLE => 1 + SELFDEF | REFINEDtype | TERMREFin | TYPEREFin | SELECTin | SELECTinPoly | HOLE => 1 case RENAMED | PARAMtype => 2 case POLYtype | TYPELAMBDAtype | METHODtype => -1 case _ => 0 diff --git a/tests/run/i11332.scala b/tests/run/i11332.scala new file mode 100644 index 000000000000..1114e90559fb --- /dev/null +++ b/tests/run/i11332.scala @@ -0,0 +1,63 @@ +// scalajs: --skip +import scala.language.unsafeNulls + +import java.lang.invoke._, MethodType.methodType + +object Test extends O: + def main(args: Array[String]): Unit = () + +class O { + def bar(x: Int): Int = -x + def baz(s: String): String = s.reverse + def dingo(l: Long): String = "long" + def dingo(i: Int): String = "int" + def putStr(s: String): Unit = () + def returnAny(s: String): Object = s + def id[T](x: T): T = x + + val l = MethodHandles.lookup() + val mh1 = l.findVirtual(classOf[O], "bar", methodType(classOf[Int], classOf[Int])) + val mh2 = l.findVirtual(classOf[O], "baz", methodType(classOf[String], classOf[String])) + val mh3 = l.findVirtual(classOf[O], "dingo", methodType(classOf[String], classOf[Long])) + val mh4 = l.findVirtual(classOf[O], "dingo", methodType(classOf[String], classOf[Int])) + val mh6 = l.findVirtual(classOf[O], "putStr", methodType(classOf[Unit], classOf[String])) + val mh7 = l.findVirtual(classOf[O], "returnAny", methodType(classOf[Any], classOf[String])) + val mh8 = l.findStatic(classOf[ClassLoader], "getPlatformClassLoader", methodType(classOf[ClassLoader])) + + val test1_1 = assert(-42 == (mh1.invokeExact(this, 42): Int)) + val test1_2 = assert(-33 == (mh1.invokeExact(this, 33): Int)) + + val test2_1 = assert("oof" == (mh2.invokeExact(this, "foo"): String)) + val test2_2 = assert("rab" == (mh2.invokeExact(this, "bar"): String)) + val test3 = assert("long" == (mh3.invokeExact(this, 1L): String)) + val test4 = assert("int" == (mh4.invokeExact(this, 1): String)) + val test5_1 = assert(-3 == (id(mh1.invokeExact(this, 3)): Int)) + val test5_2 = expectWrongMethod(mh1.invokeExact(this, 4)) + + val test6_1 = mh6.invokeExact(this, "hi"): Unit + val test6_2 = { val hi2: Unit = mh6.invokeExact(this, "hi2"); assert((()) == hi2) } + val test6_3 = { def hi3: Unit = mh6.invokeExact(this, "hi3"); assert((()) == hi3) } + + val test7_statbk = { mh7.invokeExact(this, "any"); () } + val test7_valdef = { val any2 = mh7.invokeExact(this, "any2"); assert("any2" == any2) } + val test7_defdef = { def any3 = mh7.invokeExact(this, "any3"); assert("any3" == any3) } + + val cl1: ClassLoader = mh8.invoke() + val cl2: ClassLoader = mh8.invoke().asInstanceOf[ClassLoader] + + val test9_1 = expectWrongMethod(assert(-1 == mh1.invokeExact(this, 1))) + val test9_3 = expectWrongMethod { + l.findVirtual(classOf[O], "bar", methodType(classOf[Int], classOf[Int])).invokeExact(this, 3) // testing inline + } + val test9_4 = + val res = l + .findVirtual(classOf[O], "bar", methodType(classOf[Int], classOf[Int])) // testing inline + .invokeExact(this, 4): Int + assert(-4 == res) + + def expectWrongMethod(op: => Any) = + try + op + throw new AssertionError("expected operation to fail but it didn't") + catch case expected: WrongMethodTypeException => () +} diff --git a/tests/run/t12348.scala b/tests/run/t12348.scala new file mode 100644 index 000000000000..8ffe96ca3d23 --- /dev/null +++ b/tests/run/t12348.scala @@ -0,0 +1,22 @@ +// test: -jvm 11+ +// scalajs: --skip +import java.lang.invoke._ +import scala.runtime.IntRef + +object Test { + def main(args: Array[String]): Unit = { + val ref = new scala.runtime.IntRef(0) + val varHandle = MethodHandles.lookup() + .in(classOf[IntRef]) + .findVarHandle(classOf[IntRef], "elem", classOf[Int]) + assert(0 == (varHandle.getAndSet(ref, 1): Int)) + assert(1 == (varHandle.getAndSet(ref, 2): Int)) + assert(2 == ref.elem) + + assert((()) == (varHandle.set(ref, 3): Any)) + assert(3 == (varHandle.get(ref): Int)) + + assert(true == (varHandle.compareAndSet(ref, 3, 4): Any)) + assert(4 == (varHandle.get(ref): Int)) + } +} From 14a80fdf45ad9cf7ef864d6ab0b00dffcd45e688 Mon Sep 17 00:00:00 2001 From: Seth Tisue Date: Thu, 27 Oct 2022 14:01:21 +0200 Subject: [PATCH 02/10] add a few comments --- compiler/src/dotty/tools/dotc/core/Definitions.scala | 3 ++- compiler/src/dotty/tools/dotc/typer/Applications.scala | 4 ++++ tasty/src/dotty/tools/tasty/TastyFormat.scala | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index db3378863fd8..9bbd940825ed 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -740,7 +740,8 @@ class Definitions { @tu lazy val MethodHandlesLookupClass: ClassSymbol = requiredClass("java.lang.invoke.MethodHandles.Lookup") @tu lazy val VarHandleClass: ClassSymbol = requiredClass("java.lang.invoke.VarHandle") - // from the Java language spec: https://docs.oracle.com/javase/specs/jls/se11/html/jls-15.html#jls-15.12.3 + // Java language spec: https://docs.oracle.com/javase/specs/jls/se11/html/jls-15.html#jls-15.12.3 + // Scala 2 spec: https://scala-lang.org/files/archive/spec/2.13/06-expressions.html#signature-polymorphic-methods def isPolymorphicSignature(sym: Symbol) = sym.is(JavaDefined) && { val owner = sym.maybeOwner (owner == MethodHandleClass || owner == VarHandleClass) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 2e78b260dafe..4da3e832a0dd 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -946,6 +946,10 @@ trait Applications extends Compatibility { else expectedResultType match case SelectionProto(nme.asInstanceOf_, PolyProto(_, resTp), _, _) => resTp case _ => defn.ObjectType + // synthesize a method type based on the types at the call site. + // one can imagine the original signature-polymorphic method as + // being infinitely overloaded, with each individual overload only + // being brought into existence as needed val info = MethodType(proto.typedArgs().map(_.tpe.widen), resultType) val fun2 = fun1.withType(funRef.symbol.copy(info = info).termRef) simpleApply(fun2, proto) diff --git a/tasty/src/dotty/tools/tasty/TastyFormat.scala b/tasty/src/dotty/tools/tasty/TastyFormat.scala index 75c5114d9c2d..6052d6a7760b 100644 --- a/tasty/src/dotty/tools/tasty/TastyFormat.scala +++ b/tasty/src/dotty/tools/tasty/TastyFormat.scala @@ -86,7 +86,7 @@ Standard-Section: "ASTs" TopLevelStat* IDENT NameRef Type -- Used when term ident’s type is not a TermRef SELECT possiblySigned_NameRef qual_Term -- qual.name SELECTin Length possiblySigned_NameRef qual_Term owner_Type -- qual.name, referring to a symbol declared in owner that has the given signature (see note below) - SELECTinPoly Length possiblySigned_NameRef qual_Term owner_Type method_Type -- like SELECTin, but with the method_Type too + SELECTinPoly Length possiblySigned_NameRef qual_Term owner_Type method_Type -- like SELECTin, but with the method_Type too (because signature polymorphic) QUALTHIS typeIdent_Tree -- id.this, different from THIS in that it contains a qualifier ident with position. NEW clsType_Term -- new cls THROW throwableExpr_Term -- throw throwableExpr From 2b7ceedb1d0278c1eb2f42ab8498fe68a7600711 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 2 Nov 2022 11:37:48 +0000 Subject: [PATCH 03/10] Re-implement with PolySig support w/ a Typed --- .../dotty/tools/backend/sjs/JSCodeGen.scala | 2 +- .../tools/backend/sjs/JSDefinitions.scala | 3 + .../dotty/tools/dotc/core/Definitions.scala | 4 - .../tools/dotc/core/tasty/TastyPrinter.scala | 2 +- .../tools/dotc/core/tasty/TreePickler.scala | 8 -- .../tools/dotc/core/tasty/TreeUnpickler.scala | 87 ++++++++------- .../dotty/tools/dotc/typer/Applications.scala | 3 +- project/Build.scala | 7 +- project/MiMaFilters.scala | 3 - tasty/src/dotty/tools/tasty/TastyFormat.scala | 5 +- tests/run/i11332.scala | 102 +++++++++--------- 11 files changed, 107 insertions(+), 119 deletions(-) diff --git a/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala b/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala index de0de0304c76..6714f664620b 100644 --- a/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala +++ b/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala @@ -4726,7 +4726,7 @@ class JSCodeGen()(using genCtx: Context) { Set[Symbol]( defn.BoxedUnitClass, defn.BoxedBooleanClass, defn.BoxedCharClass, defn.BoxedByteClass, defn.BoxedShortClass, defn.BoxedIntClass, defn.BoxedLongClass, defn.BoxedFloatClass, - defn.BoxedDoubleClass, defn.StringClass, defn.VoidClass + defn.BoxedDoubleClass, defn.StringClass, jsdefn.JavaLangVoidClass ) } diff --git a/compiler/src/dotty/tools/backend/sjs/JSDefinitions.scala b/compiler/src/dotty/tools/backend/sjs/JSDefinitions.scala index d728c6924ac4..c252ac892548 100644 --- a/compiler/src/dotty/tools/backend/sjs/JSDefinitions.scala +++ b/compiler/src/dotty/tools/backend/sjs/JSDefinitions.scala @@ -26,6 +26,9 @@ final class JSDefinitions()(using Context) { @threadUnsafe lazy val NoinlineAnnotType: TypeRef = requiredClassRef("scala.noinline") def NoinlineAnnot(using Context) = NoinlineAnnotType.symbol.asClass + @threadUnsafe lazy val JavaLangVoidType: TypeRef = requiredClassRef("java.lang.Void") + def JavaLangVoidClass(using Context) = JavaLangVoidType.symbol.asClass + @threadUnsafe lazy val ScalaJSJSPackageVal = requiredPackage("scala.scalajs.js") @threadUnsafe lazy val ScalaJSJSPackageClass = ScalaJSJSPackageVal.moduleClass.asClass @threadUnsafe lazy val JSPackage_typeOfR = ScalaJSJSPackageClass.requiredMethodRef("typeOf") diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 9bbd940825ed..5e1e5957e9b1 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -485,9 +485,6 @@ class Definitions { } def NullType: TypeRef = NullClass.typeRef - @tu lazy val VoidType: TypeRef = requiredClassRef("java.lang.Void") - def VoidClass(using Context) = VoidType.symbol.asClass - @tu lazy val InvokerModule = requiredModule("scala.runtime.coverage.Invoker") @tu lazy val InvokedMethodRef = InvokerModule.requiredMethodRef("invoked") @@ -735,7 +732,6 @@ class Definitions { } def JavaEnumType = JavaEnumClass.typeRef - @tu lazy val MethodHandleClass: ClassSymbol = requiredClass("java.lang.invoke.MethodHandle") @tu lazy val MethodHandlesLookupClass: ClassSymbol = requiredClass("java.lang.invoke.MethodHandles.Lookup") @tu lazy val VarHandleClass: ClassSymbol = requiredClass("java.lang.invoke.VarHandle") diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TastyPrinter.scala b/compiler/src/dotty/tools/dotc/core/tasty/TastyPrinter.scala index a5f4d848c040..5876b69edfde 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TastyPrinter.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TastyPrinter.scala @@ -129,7 +129,7 @@ class TastyPrinter(bytes: Array[Byte]) { printName(); printName() case VALDEF | DEFDEF | TYPEDEF | TYPEPARAM | PARAM | NAMEDARG | BIND => printName(); printTrees() - case REFINEDtype | TERMREFin | TYPEREFin | SELECTin | SELECTinPoly => + case REFINEDtype | TERMREFin | TYPEREFin | SELECTin => printName(); printTree(); printTrees() case RETURN | HOLE => printNat(); printTrees() diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index 6484526f8d87..34c22439a932 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -413,14 +413,6 @@ class TreePickler(pickler: TastyPickler) { writeByte(if name.isTypeName then SELECTtpt else SELECT) pickleNameAndSig(name, sig, ename) pickleTree(qual) - else if defn.wasPolymorphicSignature(tree.symbol) then - writeByte(SELECTinPoly) - withLength { - pickleNameAndSig(name, tree.symbol.signature, ename) - pickleTree(qual) - pickleType(tree.symbol.owner.typeRef) - pickleType(tree.tpe.widenSingleton, richTypes = true) // this widens to a MethodType, so need richTypes - } else // select from owner writeByte(SELECTin) withLength { diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index c19008b286c6..4f3c69027c63 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -1131,6 +1131,7 @@ class TreeUnpickler(reader: TastyReader, def makeSelect(qual: Tree, name: Name, denot: Denotation): Select = var qualType = qual.tpe.widenIfUnstable + val owner = denot.symbol.maybeOwner val tpe0 = name match case name: TypeName => TypeRef(qualType, name, denot) case name: TermName => TermRef(qualType, name, denot) @@ -1142,43 +1143,6 @@ class TreeUnpickler(reader: TastyReader, val denot = accessibleDenot(qual.tpe.widenIfUnstable, name, sig, target) makeSelect(qual, name, denot) - def readSelectIn(): Select = - var sname = readName() - val qual = readTerm() - val ownerTpe = readType() - val owner = ownerTpe.typeSymbol - val SignedName(name, sig, target) = sname: @unchecked // only methods with params use SELECTin - val qualType = qual.tpe.widenIfUnstable - val prefix = ctx.typeAssigner.maybeSkolemizePrefix(qualType, name) - - /** Tasty should still be able to resolve a method from another root class, - * even if it has been moved to a super type, - * or an override has been removed. - * - * This is tested in - * - sbt-test/tasty-compat/remove-override - * - sbt-test/tasty-compat/move-method - */ - def lookupInSuper = - val cls = ownerTpe.classSymbol - if cls.exists then - cls.asClass.classDenot - .findMember(name, cls.thisType, EmptyFlags, excluded=Private) - .atSignature(sig, target) - else - NoDenotation - - val denot = - val d = ownerTpe.decl(name).atSignature(sig, target) - (if !d.exists then lookupInSuper else d).asSeenFrom(prefix) - - makeSelect(qual, name, denot) - - def readSelectInPoly(): Select = - val tree = readSelectIn() - val info = readType() - tree.withType(tree.symbol.copy(info = info).termRef) - def readQualId(): (untpd.Ident, TypeRef) = val qual = readTerm().asInstanceOf[untpd.Ident] (untpd.Ident(qual.name).withSpan(qual.span), qual.tpe.asInstanceOf[TypeRef]) @@ -1269,13 +1233,25 @@ class TreeUnpickler(reader: TastyReader, val fn = readTerm() val args = until(end)(readTerm()) if fn.symbol.isConstructor then constructorApply(fn, args) - else tpd.Apply(fn, args) + else if defn.isPolymorphicSignature(fn.symbol) then + val info = MethodType(args.map(_.tpe.widen), defn.ObjectType) + val fun2 = fn.withType(fn.symbol.copy(info = info).termRef) + tpd.Apply(fun2, args) + else + tpd.Apply(fn, args) case TYPEAPPLY => tpd.TypeApply(readTerm(), until(end)(readTpt())) case TYPED => val expr = readTerm() val tpt = readTpt() - Typed(expr, tpt) + expr match + case Apply(fun, args) if defn.wasPolymorphicSignature(fun.symbol) => + val info = MethodType(args.map(_.tpe.widen), tpt.tpe) + val fun2 = fun.withType(fun.symbol.copy(info = info).termRef) + val expr2 = tpd.cpy.Apply(expr)(fun2, args) + Typed(expr2, tpt) + case _ => + Typed(expr, tpt) case ASSIGN => Assign(readTerm(), readTerm()) case BLOCK => @@ -1328,8 +1304,37 @@ class TreeUnpickler(reader: TastyReader, case SELECTouter => val levels = readNat() readTerm().outerSelect(levels, SkolemType(readType())) - case SELECTin => readSelectIn() - case SELECTinPoly => readSelectInPoly() + case SELECTin => + var sname = readName() + val qual = readTerm() + val ownerTpe = readType() + val owner = ownerTpe.typeSymbol + val SignedName(name, sig, target) = sname: @unchecked // only methods with params use SELECTin + val qualType = qual.tpe.widenIfUnstable + val prefix = ctx.typeAssigner.maybeSkolemizePrefix(qualType, name) + + /** Tasty should still be able to resolve a method from another root class, + * even if it has been moved to a super type, + * or an override has been removed. + * + * This is tested in + * - sbt-test/tasty-compat/remove-override + * - sbt-test/tasty-compat/move-method + */ + def lookupInSuper = + val cls = ownerTpe.classSymbol + if cls.exists then + cls.asClass.classDenot + .findMember(name, cls.thisType, EmptyFlags, excluded=Private) + .atSignature(sig, target) + else + NoDenotation + + val denot = + val d = ownerTpe.decl(name).atSignature(sig, target) + (if !d.exists then lookupInSuper else d).asSeenFrom(prefix) + + makeSelect(qual, name, denot) case REPEATED => val elemtpt = readTpt() SeqLiteral(until(end)(readTerm()), elemtpt) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 4da3e832a0dd..4ecca9da2c2e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -952,7 +952,8 @@ trait Applications extends Compatibility { // being brought into existence as needed val info = MethodType(proto.typedArgs().map(_.tpe.widen), resultType) val fun2 = fun1.withType(funRef.symbol.copy(info = info).termRef) - simpleApply(fun2, proto) + val app = simpleApply(fun2, proto) + Typed(app, TypeTree(resultType)) else val app = ApplyTo(tree, fun1, funRef, proto, pt) convertNewGenericArray( diff --git a/project/Build.scala b/project/Build.scala index 9b21c585ac45..d735db6a8a5b 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1816,10 +1816,9 @@ object Build { settings(disableDocSetting). settings( versionScheme := Some("semver-spec"), - if (mode == Bootstrapped) Def.settings( - commonMiMaSettings, - mimaBinaryIssueFilters ++= MiMaFilters.TastyCore, - ) else { + if (mode == Bootstrapped) { + commonMiMaSettings + } else { Nil } ) diff --git a/project/MiMaFilters.scala b/project/MiMaFilters.scala index 6524f76279d1..408930bbeee9 100644 --- a/project/MiMaFilters.scala +++ b/project/MiMaFilters.scala @@ -22,7 +22,4 @@ object MiMaFilters { ProblemFilters.exclude[MissingClassProblem]("scala.caps$Pure"), ProblemFilters.exclude[MissingClassProblem]("scala.caps$unsafe$"), ) - val TastyCore: Seq[ProblemFilter] = Seq( - ProblemFilters.exclude[MissingMethodProblem]("dotty.tools.tasty.TastyFormat.SELECTinPoly"), - ) } diff --git a/tasty/src/dotty/tools/tasty/TastyFormat.scala b/tasty/src/dotty/tools/tasty/TastyFormat.scala index 6052d6a7760b..66b56d30a6a4 100644 --- a/tasty/src/dotty/tools/tasty/TastyFormat.scala +++ b/tasty/src/dotty/tools/tasty/TastyFormat.scala @@ -86,7 +86,6 @@ Standard-Section: "ASTs" TopLevelStat* IDENT NameRef Type -- Used when term ident’s type is not a TermRef SELECT possiblySigned_NameRef qual_Term -- qual.name SELECTin Length possiblySigned_NameRef qual_Term owner_Type -- qual.name, referring to a symbol declared in owner that has the given signature (see note below) - SELECTinPoly Length possiblySigned_NameRef qual_Term owner_Type method_Type -- like SELECTin, but with the method_Type too (because signature polymorphic) QUALTHIS typeIdent_Tree -- id.this, different from THIS in that it contains a qualifier ident with position. NEW clsType_Term -- new cls THROW throwableExpr_Term -- throw throwableExpr @@ -579,7 +578,6 @@ object TastyFormat { // final val ??? = 178 // final val ??? = 179 final val METHODtype = 180 - final val SELECTinPoly = 181 final val MATCHtype = 190 final val MATCHtpt = 191 @@ -779,7 +777,6 @@ object TastyFormat { case TERMREFin => "TERMREFin" case TYPEREFin => "TYPEREFin" case SELECTin => "SELECTin" - case SELECTinPoly => "SELECTinPoly" case REFINEDtype => "REFINEDtype" case REFINEDtpt => "REFINEDtpt" @@ -810,7 +807,7 @@ object TastyFormat { */ def numRefs(tag: Int): Int = tag match { case VALDEF | DEFDEF | TYPEDEF | TYPEPARAM | PARAM | NAMEDARG | RETURN | BIND | - SELFDEF | REFINEDtype | TERMREFin | TYPEREFin | SELECTin | SELECTinPoly | HOLE => 1 + SELFDEF | REFINEDtype | TERMREFin | TYPEREFin | SELECTin | HOLE => 1 case RENAMED | PARAMtype => 2 case POLYtype | TYPELAMBDAtype | METHODtype => -1 case _ => 0 diff --git a/tests/run/i11332.scala b/tests/run/i11332.scala index 1114e90559fb..fe92a1727aa9 100644 --- a/tests/run/i11332.scala +++ b/tests/run/i11332.scala @@ -3,61 +3,59 @@ import scala.language.unsafeNulls import java.lang.invoke._, MethodType.methodType -object Test extends O: +object Test extends Foo: def main(args: Array[String]): Unit = () -class O { - def bar(x: Int): Int = -x - def baz(s: String): String = s.reverse - def dingo(l: Long): String = "long" - def dingo(i: Int): String = "int" - def putStr(s: String): Unit = () - def returnAny(s: String): Object = s +class Foo { + def neg(x: Int): Int = -x + def rev(s: String): String = s.reverse + def over(l: Long): String = "long" + def over(i: Int): String = "int" + def unit(s: String): Unit = () + def obj(s: String): Object = s def id[T](x: T): T = x val l = MethodHandles.lookup() - val mh1 = l.findVirtual(classOf[O], "bar", methodType(classOf[Int], classOf[Int])) - val mh2 = l.findVirtual(classOf[O], "baz", methodType(classOf[String], classOf[String])) - val mh3 = l.findVirtual(classOf[O], "dingo", methodType(classOf[String], classOf[Long])) - val mh4 = l.findVirtual(classOf[O], "dingo", methodType(classOf[String], classOf[Int])) - val mh6 = l.findVirtual(classOf[O], "putStr", methodType(classOf[Unit], classOf[String])) - val mh7 = l.findVirtual(classOf[O], "returnAny", methodType(classOf[Any], classOf[String])) - val mh8 = l.findStatic(classOf[ClassLoader], "getPlatformClassLoader", methodType(classOf[ClassLoader])) - - val test1_1 = assert(-42 == (mh1.invokeExact(this, 42): Int)) - val test1_2 = assert(-33 == (mh1.invokeExact(this, 33): Int)) - - val test2_1 = assert("oof" == (mh2.invokeExact(this, "foo"): String)) - val test2_2 = assert("rab" == (mh2.invokeExact(this, "bar"): String)) - val test3 = assert("long" == (mh3.invokeExact(this, 1L): String)) - val test4 = assert("int" == (mh4.invokeExact(this, 1): String)) - val test5_1 = assert(-3 == (id(mh1.invokeExact(this, 3)): Int)) - val test5_2 = expectWrongMethod(mh1.invokeExact(this, 4)) - - val test6_1 = mh6.invokeExact(this, "hi"): Unit - val test6_2 = { val hi2: Unit = mh6.invokeExact(this, "hi2"); assert((()) == hi2) } - val test6_3 = { def hi3: Unit = mh6.invokeExact(this, "hi3"); assert((()) == hi3) } - - val test7_statbk = { mh7.invokeExact(this, "any"); () } - val test7_valdef = { val any2 = mh7.invokeExact(this, "any2"); assert("any2" == any2) } - val test7_defdef = { def any3 = mh7.invokeExact(this, "any3"); assert("any3" == any3) } - - val cl1: ClassLoader = mh8.invoke() - val cl2: ClassLoader = mh8.invoke().asInstanceOf[ClassLoader] - - val test9_1 = expectWrongMethod(assert(-1 == mh1.invokeExact(this, 1))) - val test9_3 = expectWrongMethod { - l.findVirtual(classOf[O], "bar", methodType(classOf[Int], classOf[Int])).invokeExact(this, 3) // testing inline - } - val test9_4 = - val res = l - .findVirtual(classOf[O], "bar", methodType(classOf[Int], classOf[Int])) // testing inline - .invokeExact(this, 4): Int - assert(-4 == res) - - def expectWrongMethod(op: => Any) = - try - op - throw new AssertionError("expected operation to fail but it didn't") - catch case expected: WrongMethodTypeException => () + val mhNeg = l.findVirtual(classOf[Foo], "neg", methodType(classOf[Int], classOf[Int])) + val mhRev = l.findVirtual(classOf[Foo], "rev", methodType(classOf[String], classOf[String])) + val mhOverL = l.findVirtual(classOf[Foo], "over", methodType(classOf[String], classOf[Long])) + val mhOverI = l.findVirtual(classOf[Foo], "over", methodType(classOf[String], classOf[Int])) + val mhUnit = l.findVirtual(classOf[Foo], "unit", methodType(classOf[Unit], classOf[String])) + val mhObj = l.findVirtual(classOf[Foo], "obj", methodType(classOf[Any], classOf[String])) + val mhCL = l.findStatic(classOf[ClassLoader], "getPlatformClassLoader", methodType(classOf[ClassLoader])) + + val testNeg1 = assert(-42 == (mhNeg.invokeExact(this, 42): Int)) + val testNeg2 = assert(-33 == (mhNeg.invokeExact(this, 33): Int)) + + val testRev1 = assert("oof" == (mhRev.invokeExact(this, "foo"): String)) + val testRev2 = assert("rab" == (mhRev.invokeExact(this, "bar"): String)) + + val testOverL = assert("long" == (mhOverL.invokeExact(this, 1L): String)) + val testOVerI = assert("int" == (mhOverI.invokeExact(this, 1): String)) + + val testNeg_tvar = assert(-3 == (id(mhNeg.invokeExact(this, 3)): Int)) + val testNeg_obj = expectWrongMethod(mhNeg.invokeExact(this, 4)) + + val testUnit_exp = { mhUnit.invokeExact(this, "hi"): Unit; () } + val testUnit_val = { val hi2: Unit = mhUnit.invokeExact(this, "hi2"); assert((()) == hi2) } + val testUnit_def = { def hi3: Unit = mhUnit.invokeExact(this, "hi3"); assert((()) == hi3) } + + val testObj_exp = { mhObj.invokeExact(this, "any"); () } + val testObj_val = { val any2 = mhObj.invokeExact(this, "any2"); assert("any2" == any2) } + val testObj_def = { def any3 = mhObj.invokeExact(this, "any3"); assert("any3" == any3) } + + val testCl1_pass = assert(null != (mhCL.invoke(): ClassLoader)) + val testCl2_cast = assert(null != (mhCL.invoke().asInstanceOf[ClassLoader]: ClassLoader)) + + val testNeg_inline_obj = expectWrongMethod(l + .findVirtual(classOf[Foo], "neg", methodType(classOf[Int], classOf[Int])) + .invokeExact(this, 3)) + val testNeg_inline_pass = assert(-4 == (l + .findVirtual(classOf[Foo], "neg", methodType(classOf[Int], classOf[Int])) + .invokeExact(this, 4): Int)) + + def expectWrongMethod(op: => Any) = try { + op + throw new AssertionError("expected operation to fail but it didn't") + } catch case expected: WrongMethodTypeException => () } From 6c015ca4cef225ef1eb58fe6a0a242841ee078b0 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 2 Nov 2022 15:20:21 +0000 Subject: [PATCH 04/10] Avoid double symbols unpickling PolySig --- .../tools/dotc/core/tasty/TreeUnpickler.scala | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 4f3c69027c63..67a93a90b6b4 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -1233,25 +1233,27 @@ class TreeUnpickler(reader: TastyReader, val fn = readTerm() val args = until(end)(readTerm()) if fn.symbol.isConstructor then constructorApply(fn, args) - else if defn.isPolymorphicSignature(fn.symbol) then - val info = MethodType(args.map(_.tpe.widen), defn.ObjectType) - val fun2 = fn.withType(fn.symbol.copy(info = info).termRef) - tpd.Apply(fun2, args) - else - tpd.Apply(fn, args) + else tpd.Apply(fn, args) case TYPEAPPLY => tpd.TypeApply(readTerm(), until(end)(readTpt())) case TYPED => - val expr = readTerm() - val tpt = readTpt() - expr match - case Apply(fun, args) if defn.wasPolymorphicSignature(fun.symbol) => + val rdr = fork + val start = rdr.reader.currentAddr + if rdr.reader.readByte() == APPLY then + val end = rdr.reader.readEnd() + val fn = rdr.readTerm() + if defn.isPolymorphicSignature(fn.symbol) then + skipTree() // expr + skipTree() // tpt + val args = rdr.reader.until(end)(rdr.readTerm()) + val tpt = rdr.readTpt() val info = MethodType(args.map(_.tpe.widen), tpt.tpe) - val fun2 = fun.withType(fun.symbol.copy(info = info).termRef) - val expr2 = tpd.cpy.Apply(expr)(fun2, args) - Typed(expr2, tpt) - case _ => - Typed(expr, tpt) + val fun2 = fn.withType(fn.symbol.copy(info = info).termRef) + val app = Apply(fun2, args) + rdr.setSpan(start, app) + Typed(app, tpt) + else Typed(readTerm(), readTpt()) + else Typed(readTerm(), readTpt()) case ASSIGN => Assign(readTerm(), readTerm()) case BLOCK => From f282614a0eb8641e47e82a4dd8d6cdc5a122e417 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 2 Nov 2022 17:48:14 +0000 Subject: [PATCH 05/10] Move out of Defn, revert unneeded --- .../dotty/tools/dotc/core/Definitions.scala | 16 ------- .../tools/dotc/core/SymDenotations.scala | 20 ++++++++ .../tools/dotc/core/tasty/TreeUnpickler.scala | 19 ++++---- .../semanticdb/SemanticSymbolBuilder.scala | 9 ++-- .../tools/dotc/transform/PostTyper.scala | 3 +- .../dotty/tools/dotc/transform/Recheck.scala | 10 ++-- .../dotty/tools/dotc/typer/Applications.scala | 46 +++++++++---------- 7 files changed, 60 insertions(+), 63 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 5e1e5957e9b1..8ef9fb380000 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -736,22 +736,6 @@ class Definitions { @tu lazy val MethodHandlesLookupClass: ClassSymbol = requiredClass("java.lang.invoke.MethodHandles.Lookup") @tu lazy val VarHandleClass: ClassSymbol = requiredClass("java.lang.invoke.VarHandle") - // Java language spec: https://docs.oracle.com/javase/specs/jls/se11/html/jls-15.html#jls-15.12.3 - // Scala 2 spec: https://scala-lang.org/files/archive/spec/2.13/06-expressions.html#signature-polymorphic-methods - def isPolymorphicSignature(sym: Symbol) = sym.is(JavaDefined) && { - val owner = sym.maybeOwner - (owner == MethodHandleClass || owner == VarHandleClass) - && sym.hasAnnotation(NativeAnnot) - && sym.paramSymss.match - case List(List(p)) => p.info.isRepeatedParam - case _ => false - } - - def wasPolymorphicSignature(sym: Symbol) = - val owner = sym.maybeOwner - (owner == MethodHandleClass || owner == VarHandleClass) - && isPolymorphicSignature(owner.info.member(sym.name).symbol) - @tu lazy val StringBuilderClass: ClassSymbol = requiredClass("scala.collection.mutable.StringBuilder") @tu lazy val MatchErrorClass : ClassSymbol = requiredClass("scala.MatchError") @tu lazy val ConversionClass : ClassSymbol = requiredClass("scala.Conversion").typeRef.symbol.asClass diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index a4f1bf3c5e80..4cd0e400e2d1 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -960,6 +960,26 @@ object SymDenotations { def isSkolem: Boolean = name == nme.SKOLEM + // Java language spec: https://docs.oracle.com/javase/specs/jls/se11/html/jls-15.html#jls-15.12.3 + // Scala 2 spec: https://scala-lang.org/files/archive/spec/2.13/06-expressions.html#signature-polymorphic-methods + def isSignaturePolymorphic(using Context): Boolean = + containsSignaturePolymorphic + && is(JavaDefined) + && hasAnnotation(defn.NativeAnnot) + && atPhase(typerPhase)(symbol.denot).paramSymss.match + case List(List(p)) => p.info.isRepeatedParam + case _ => false + + def containsSignaturePolymorphic(using Context): Boolean = + maybeOwner == defn.MethodHandleClass + || maybeOwner == defn.VarHandleClass + + def originalSignaturePolymorphic(using Context): Denotation = + if containsSignaturePolymorphic && !isSignaturePolymorphic then + val d = owner.info.member(name) + if d.symbol.isSignaturePolymorphic then d else NoDenotation + else NoDenotation + def isInlineMethod(using Context): Boolean = isAllOf(InlineMethod, butNot = Accessor) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 67a93a90b6b4..0d444c3804b3 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -1240,18 +1240,17 @@ class TreeUnpickler(reader: TastyReader, val rdr = fork val start = rdr.reader.currentAddr if rdr.reader.readByte() == APPLY then - val end = rdr.reader.readEnd() - val fn = rdr.readTerm() - if defn.isPolymorphicSignature(fn.symbol) then - skipTree() // expr - skipTree() // tpt + val end = rdr.reader.readEnd() + val fn = rdr.readTerm() + if fn.symbol.isSignaturePolymorphic then val args = rdr.reader.until(end)(rdr.readTerm()) - val tpt = rdr.readTpt() + skipTree() // expr + val tpt = readTpt() val info = MethodType(args.map(_.tpe.widen), tpt.tpe) - val fun2 = fn.withType(fn.symbol.copy(info = info).termRef) - val app = Apply(fun2, args) - rdr.setSpan(start, app) - Typed(app, tpt) + val sym2 = fn.symbol.copy(info = info) // still not entered (like simpleApply) + val fun2 = fn.withType(sym2.termRef) + val app = Apply(fun2, args) + Typed(setSpan(start, app), tpt) else Typed(readTerm(), readTpt()) else Typed(readTerm(), readTpt()) case ASSIGN => diff --git a/compiler/src/dotty/tools/dotc/semanticdb/SemanticSymbolBuilder.scala b/compiler/src/dotty/tools/dotc/semanticdb/SemanticSymbolBuilder.scala index e8c87f6c0f84..c7b0dfd437db 100644 --- a/compiler/src/dotty/tools/dotc/semanticdb/SemanticSymbolBuilder.scala +++ b/compiler/src/dotty/tools/dotc/semanticdb/SemanticSymbolBuilder.scala @@ -74,7 +74,9 @@ class SemanticSymbolBuilder: def addOwner(owner: Symbol): Unit = if !owner.isRoot then addSymName(b, owner) - def addOverloadIdx(sym: Symbol): Unit = + def addOverloadIdx(initSym: Symbol): Unit = + // revert from the compiler-generated overload of the signature polymorphic method + val sym = initSym.originalSignaturePolymorphic.symbol.orElse(initSym) val decls = val decls0 = sym.owner.info.decls.lookupAll(sym.name) if sym.owner.isAllOf(JavaModule) then @@ -90,10 +92,7 @@ class SemanticSymbolBuilder: case _ => end find val sig = sym.signature - // the polymorphic signature methods (invoke/invokeExact) are never overloaded - // we changed the signature at the call site, which means they won't match, - // but we still shouldn't add any overload index in this case. - find(_.signature == sig || defn.wasPolymorphicSignature(sym)) + find(_.signature == sig) def addDescriptor(sym: Symbol): Unit = if sym.is(ModuleClass) then diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index d4891dc14fa6..05aaa745bb18 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -204,8 +204,7 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase return transformSelect(cpy.Select(tree)(qual.select(pobj).withSpan(qual.span), tree.name), targs) case _ => } - val tree0 = super.transform(tree) - val tree1 = if defn.wasPolymorphicSignature(tree.symbol) then tree0.withType(tree.tpe) else tree0 + val tree1 = super.transform(tree) constToLiteral(tree1) match { case _: Literal => tree1 case _ => superAcc.transformSelect(tree1, targs) diff --git a/compiler/src/dotty/tools/dotc/transform/Recheck.scala b/compiler/src/dotty/tools/dotc/transform/Recheck.scala index f6532a3b6214..abcd0623b2a5 100644 --- a/compiler/src/dotty/tools/dotc/transform/Recheck.scala +++ b/compiler/src/dotty/tools/dotc/transform/Recheck.scala @@ -261,13 +261,9 @@ abstract class Recheck extends Phase, SymTransformer: mt.instantiate(argTypes) def recheckApply(tree: Apply, pt: Type)(using Context): Type = - val wasPolymorphicSignature = tree.fun.tpe match - case funRef: TermRef => defn.wasPolymorphicSignature(funRef.symbol) - case _ => false - if wasPolymorphicSignature then tree.fun.tpe.widenTermRefExpr.finalResultType - else - val funtpe = recheck(tree.fun) - funtpe.widen match + // reuse the tree's type if the function was a signature polymorphic method, instead of rechecking it + val funtpe = if tree.fun.symbol.originalSignaturePolymorphic.exists then tree.fun.tpe else recheck(tree.fun) + funtpe.widen match case fntpe: MethodType => assert(fntpe.paramInfos.hasSameLengthAs(tree.args)) val formals = diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 4ecca9da2c2e..757c49786375 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -936,30 +936,30 @@ trait Applications extends Compatibility { /** Type application where arguments come from prototype, and no implicits are inserted */ def simpleApply(fun1: Tree, proto: FunProto)(using Context): Tree = methPart(fun1).tpe match { + case funRef: TermRef if funRef.symbol.isSignaturePolymorphic => + // synthesize a method type based on the types at the call site. + // one can imagine the original signature-polymorphic method as + // being infinitely overloaded, with each individual overload only + // being brought into existence as needed + val originalResultType = funRef.symbol.info.resultType.stripNull + val expectedResultType = AvoidWildcardsMap()(proto.deepenProto.resultType) + val resultType = + if !originalResultType.isRef(defn.ObjectClass) then originalResultType + else if isFullyDefined(expectedResultType, ForceDegree.all) then expectedResultType + else expectedResultType match + case SelectionProto(nme.asInstanceOf_, PolyProto(_, resTp), _, _) => resTp + case _ => defn.ObjectType + val info = MethodType(proto.typedArgs().map(_.tpe.widen), resultType) + val sym2 = funRef.symbol.copy(info = info) // not entered, to avoid overload resolution problems + val fun2 = fun1.withType(sym2.termRef) + val app = simpleApply(fun2, proto) + Typed(app, TypeTree(resultType)) case funRef: TermRef => - if defn.isPolymorphicSignature(funRef.symbol) then - val originalResultType = funRef.symbol.info.resultType.stripNull - val expectedResultType = AvoidWildcardsMap()(proto.deepenProto.resultType) - val resultType = - if !originalResultType.isRef(defn.ObjectClass) then originalResultType - else if isFullyDefined(expectedResultType, ForceDegree.all) then expectedResultType - else expectedResultType match - case SelectionProto(nme.asInstanceOf_, PolyProto(_, resTp), _, _) => resTp - case _ => defn.ObjectType - // synthesize a method type based on the types at the call site. - // one can imagine the original signature-polymorphic method as - // being infinitely overloaded, with each individual overload only - // being brought into existence as needed - val info = MethodType(proto.typedArgs().map(_.tpe.widen), resultType) - val fun2 = fun1.withType(funRef.symbol.copy(info = info).termRef) - val app = simpleApply(fun2, proto) - Typed(app, TypeTree(resultType)) - else - val app = ApplyTo(tree, fun1, funRef, proto, pt) - convertNewGenericArray( - widenEnumCase( - postProcessByNameArgs(funRef, app).computeNullable(), - pt)) + val app = ApplyTo(tree, fun1, funRef, proto, pt) + convertNewGenericArray( + widenEnumCase( + postProcessByNameArgs(funRef, app).computeNullable(), + pt)) case _ => handleUnexpectedFunType(tree, fun1) } From fb8bed817184588a77c5b6c74a2705d28be5310a Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Thu, 3 Nov 2022 10:24:56 +0000 Subject: [PATCH 06/10] Switch to a special APPLYsigpoly tasty tag --- .../tools/dotc/core/tasty/TreePickler.scala | 7 +++++ .../tools/dotc/core/tasty/TreeUnpickler.scala | 26 +++++++------------ .../dotty/tools/dotc/typer/Applications.scala | 12 ++++----- project/Build.scala | 7 ++--- project/MiMaFilters.scala | 3 +++ tasty/src/dotty/tools/tasty/TastyFormat.scala | 3 +++ tests/run/i11332.scala | 6 +++-- 7 files changed, 36 insertions(+), 28 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index 34c22439a932..29b84f1c3be5 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -426,6 +426,13 @@ class TreePickler(pickler: TastyPickler) { writeByte(THROW) pickleTree(args.head) } + else if fun.symbol.originalSignaturePolymorphic.exists then + writeByte(APPLYsigpoly) + withLength { + pickleTree(fun) + pickleType(fun.tpe.widenTermRefExpr, richTypes = true) // this widens to a MethodType, so need richTypes + args.foreach(pickleTree) + } else { writeByte(APPLY) withLength { diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 0d444c3804b3..6e18dc33e66f 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -1236,23 +1236,17 @@ class TreeUnpickler(reader: TastyReader, else tpd.Apply(fn, args) case TYPEAPPLY => tpd.TypeApply(readTerm(), until(end)(readTpt())) + case APPLYsigpoly => + val fn = readTerm() + val methType = readType() + val args = until(end)(readTerm()) + val sym2 = fn.symbol.copy(info = methType) // symbol not entered (same as in simpleApply) + val fun2 = fn.withType(sym2.termRef) + tpd.Apply(fun2, args) case TYPED => - val rdr = fork - val start = rdr.reader.currentAddr - if rdr.reader.readByte() == APPLY then - val end = rdr.reader.readEnd() - val fn = rdr.readTerm() - if fn.symbol.isSignaturePolymorphic then - val args = rdr.reader.until(end)(rdr.readTerm()) - skipTree() // expr - val tpt = readTpt() - val info = MethodType(args.map(_.tpe.widen), tpt.tpe) - val sym2 = fn.symbol.copy(info = info) // still not entered (like simpleApply) - val fun2 = fn.withType(sym2.termRef) - val app = Apply(fun2, args) - Typed(setSpan(start, app), tpt) - else Typed(readTerm(), readTpt()) - else Typed(readTerm(), readTpt()) + val expr = readTerm() + val tpt = readTpt() + Typed(expr, tpt) case ASSIGN => Assign(readTerm(), readTerm()) case BLOCK => diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 757c49786375..e766daf2b410 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -942,18 +942,16 @@ trait Applications extends Compatibility { // being infinitely overloaded, with each individual overload only // being brought into existence as needed val originalResultType = funRef.symbol.info.resultType.stripNull - val expectedResultType = AvoidWildcardsMap()(proto.deepenProto.resultType) val resultType = if !originalResultType.isRef(defn.ObjectClass) then originalResultType - else if isFullyDefined(expectedResultType, ForceDegree.all) then expectedResultType - else expectedResultType match + else AvoidWildcardsMap()(proto.resultType.deepenProtoTrans) match case SelectionProto(nme.asInstanceOf_, PolyProto(_, resTp), _, _) => resTp + case resTp if isFullyDefined(resTp, ForceDegree.all) => resTp case _ => defn.ObjectType - val info = MethodType(proto.typedArgs().map(_.tpe.widen), resultType) - val sym2 = funRef.symbol.copy(info = info) // not entered, to avoid overload resolution problems + val methType = MethodType(proto.typedArgs().map(_.tpe.widen), resultType) + val sym2 = funRef.symbol.copy(info = methType) // symbol not entered, to avoid overload resolution problems val fun2 = fun1.withType(sym2.termRef) - val app = simpleApply(fun2, proto) - Typed(app, TypeTree(resultType)) + simpleApply(fun2, proto) case funRef: TermRef => val app = ApplyTo(tree, fun1, funRef, proto, pt) convertNewGenericArray( diff --git a/project/Build.scala b/project/Build.scala index d735db6a8a5b..9b21c585ac45 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1816,9 +1816,10 @@ object Build { settings(disableDocSetting). settings( versionScheme := Some("semver-spec"), - if (mode == Bootstrapped) { - commonMiMaSettings - } else { + if (mode == Bootstrapped) Def.settings( + commonMiMaSettings, + mimaBinaryIssueFilters ++= MiMaFilters.TastyCore, + ) else { Nil } ) diff --git a/project/MiMaFilters.scala b/project/MiMaFilters.scala index 408930bbeee9..706f1b273dfa 100644 --- a/project/MiMaFilters.scala +++ b/project/MiMaFilters.scala @@ -22,4 +22,7 @@ object MiMaFilters { ProblemFilters.exclude[MissingClassProblem]("scala.caps$Pure"), ProblemFilters.exclude[MissingClassProblem]("scala.caps$unsafe$"), ) + val TastyCore: Seq[ProblemFilter] = Seq( + ProblemFilters.exclude[MissingMethodProblem]("dotty.tools.tasty.TastyFormat.APPLYsigpoly"), + ) } diff --git a/tasty/src/dotty/tools/tasty/TastyFormat.scala b/tasty/src/dotty/tools/tasty/TastyFormat.scala index 66b56d30a6a4..2d18923e1b0c 100644 --- a/tasty/src/dotty/tools/tasty/TastyFormat.scala +++ b/tasty/src/dotty/tools/tasty/TastyFormat.scala @@ -91,6 +91,7 @@ Standard-Section: "ASTs" TopLevelStat* THROW throwableExpr_Term -- throw throwableExpr NAMEDARG paramName_NameRef arg_Term -- paramName = arg APPLY Length fn_Term arg_Term* -- fn(args) + APPLYsigpoly Length fn_Term meth_Type arg_Term* -- The application of a signature-polymorphic method TYPEAPPLY Length fn_Term arg_Type* -- fn[args] SUPER Length this_Term mixinTypeIdent_Tree? -- super[mixin] TYPED Length expr_Term ascriptionType_Term -- expr: ascription @@ -578,6 +579,7 @@ object TastyFormat { // final val ??? = 178 // final val ??? = 179 final val METHODtype = 180 + final val APPLYsigpoly = 181 final val MATCHtype = 190 final val MATCHtpt = 191 @@ -744,6 +746,7 @@ object TastyFormat { case BOUNDED => "BOUNDED" case APPLY => "APPLY" case TYPEAPPLY => "TYPEAPPLY" + case APPLYsigpoly => "APPLYsigpoly" case NEW => "NEW" case THROW => "THROW" case TYPED => "TYPED" diff --git a/tests/run/i11332.scala b/tests/run/i11332.scala index fe92a1727aa9..20d994bf2d63 100644 --- a/tests/run/i11332.scala +++ b/tests/run/i11332.scala @@ -44,8 +44,10 @@ class Foo { val testObj_val = { val any2 = mhObj.invokeExact(this, "any2"); assert("any2" == any2) } val testObj_def = { def any3 = mhObj.invokeExact(this, "any3"); assert("any3" == any3) } - val testCl1_pass = assert(null != (mhCL.invoke(): ClassLoader)) - val testCl2_cast = assert(null != (mhCL.invoke().asInstanceOf[ClassLoader]: ClassLoader)) + val testCl_pass = assert(null != (mhCL.invoke(): ClassLoader)) + val testCl_cast = assert(null != (mhCL.invoke().asInstanceOf[ClassLoader]: ClassLoader)) + val testCl_passX = assert(null != (mhCL.invokeExact(): ClassLoader)) + val testCl_castX = assert(null != (mhCL.invokeExact().asInstanceOf[ClassLoader]: ClassLoader)) val testNeg_inline_obj = expectWrongMethod(l .findVirtual(classOf[Foo], "neg", methodType(classOf[Int], classOf[Int])) From 8415b6b21e9242c3b6746cee170a0cde03fa30e2 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Fri, 4 Nov 2022 16:03:45 +0000 Subject: [PATCH 07/10] Make SigPoly symbols resistant to withPrefix changes --- .../tools/dotc/core/tasty/TreeUnpickler.scala | 3 +- .../dotty/tools/dotc/typer/Applications.scala | 9 +++-- tests/explicit-nulls/pos/i11332.scala | 18 ++++++++++ tests/run/i11332.scala | 33 ++++++++++--------- 4 files changed, 43 insertions(+), 20 deletions(-) create mode 100644 tests/explicit-nulls/pos/i11332.scala diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 6e18dc33e66f..c0d105fecea0 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -1240,8 +1240,7 @@ class TreeUnpickler(reader: TastyReader, val fn = readTerm() val methType = readType() val args = until(end)(readTerm()) - val sym2 = fn.symbol.copy(info = methType) // symbol not entered (same as in simpleApply) - val fun2 = fn.withType(sym2.termRef) + val fun2 = typer.Applications.retypeSignaturePolymorphicFn(fn, methType) tpd.Apply(fun2, args) case TYPED => val expr = readTerm() diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index e766daf2b410..4596d3894932 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -340,6 +340,12 @@ object Applications { val getter = findDefaultGetter(fn, n, testOnly) if getter.isEmpty then getter else spliceMeth(getter.withSpan(fn.span), fn) + + def retypeSignaturePolymorphicFn(fun: Tree, methType: Type)(using Context): Tree = + val sym1 = fun.symbol + val flags2 = sym1.flags | NonMember // ensures Select typing doesn't let TermRef#withPrefix revert the type + val sym2 = sym1.copy(info = methType, flags = flags2) // symbol not entered, to avoid overload resolution problems + fun.withType(sym2.termRef) } trait Applications extends Compatibility { @@ -949,8 +955,7 @@ trait Applications extends Compatibility { case resTp if isFullyDefined(resTp, ForceDegree.all) => resTp case _ => defn.ObjectType val methType = MethodType(proto.typedArgs().map(_.tpe.widen), resultType) - val sym2 = funRef.symbol.copy(info = methType) // symbol not entered, to avoid overload resolution problems - val fun2 = fun1.withType(sym2.termRef) + val fun2 = Applications.retypeSignaturePolymorphicFn(fun1, methType) simpleApply(fun2, proto) case funRef: TermRef => val app = ApplyTo(tree, fun1, funRef, proto, pt) diff --git a/tests/explicit-nulls/pos/i11332.scala b/tests/explicit-nulls/pos/i11332.scala new file mode 100644 index 000000000000..a773a55e987e --- /dev/null +++ b/tests/explicit-nulls/pos/i11332.scala @@ -0,0 +1,18 @@ +// scalajs: --skip +import scala.language.unsafeNulls + +import java.lang.invoke._, MethodType.methodType + +// A copy of tests/run/i11332.scala +// to test the bootstrap minimisation which failed +// (because bootstrap runs under explicit nulls) +class Foo: + def neg(x: Int): Int = -x + + val l = MethodHandles.lookup() + val self = new Foo() + + val test = // testing as a expression tree - previously derivedSelect broke the type + l + .findVirtual(classOf[Foo], "neg", methodType(classOf[Int], classOf[Int])) + .invokeExact(self, 4): Int diff --git a/tests/run/i11332.scala b/tests/run/i11332.scala index 20d994bf2d63..18ff61b5f660 100644 --- a/tests/run/i11332.scala +++ b/tests/run/i11332.scala @@ -16,6 +16,7 @@ class Foo { def id[T](x: T): T = x val l = MethodHandles.lookup() + val self = new Foo() val mhNeg = l.findVirtual(classOf[Foo], "neg", methodType(classOf[Int], classOf[Int])) val mhRev = l.findVirtual(classOf[Foo], "rev", methodType(classOf[String], classOf[String])) val mhOverL = l.findVirtual(classOf[Foo], "over", methodType(classOf[String], classOf[Long])) @@ -24,25 +25,25 @@ class Foo { val mhObj = l.findVirtual(classOf[Foo], "obj", methodType(classOf[Any], classOf[String])) val mhCL = l.findStatic(classOf[ClassLoader], "getPlatformClassLoader", methodType(classOf[ClassLoader])) - val testNeg1 = assert(-42 == (mhNeg.invokeExact(this, 42): Int)) - val testNeg2 = assert(-33 == (mhNeg.invokeExact(this, 33): Int)) + val testNeg1 = assert(-42 == (mhNeg.invokeExact(self, 42): Int)) + val testNeg2 = assert(-33 == (mhNeg.invokeExact(self, 33): Int)) - val testRev1 = assert("oof" == (mhRev.invokeExact(this, "foo"): String)) - val testRev2 = assert("rab" == (mhRev.invokeExact(this, "bar"): String)) + val testRev1 = assert("oof" == (mhRev.invokeExact(self, "foo"): String)) + val testRev2 = assert("rab" == (mhRev.invokeExact(self, "bar"): String)) - val testOverL = assert("long" == (mhOverL.invokeExact(this, 1L): String)) - val testOVerI = assert("int" == (mhOverI.invokeExact(this, 1): String)) + val testOverL = assert("long" == (mhOverL.invokeExact(self, 1L): String)) + val testOVerI = assert("int" == (mhOverI.invokeExact(self, 1): String)) - val testNeg_tvar = assert(-3 == (id(mhNeg.invokeExact(this, 3)): Int)) - val testNeg_obj = expectWrongMethod(mhNeg.invokeExact(this, 4)) + val testNeg_tvar = assert(-3 == (id(mhNeg.invokeExact(self, 3)): Int)) + val testNeg_obj = expectWrongMethod(mhNeg.invokeExact(self, 4)) - val testUnit_exp = { mhUnit.invokeExact(this, "hi"): Unit; () } - val testUnit_val = { val hi2: Unit = mhUnit.invokeExact(this, "hi2"); assert((()) == hi2) } - val testUnit_def = { def hi3: Unit = mhUnit.invokeExact(this, "hi3"); assert((()) == hi3) } + val testUnit_exp = { mhUnit.invokeExact(self, "hi"): Unit; () } + val testUnit_val = { val hi2: Unit = mhUnit.invokeExact(self, "hi2"); assert((()) == hi2) } + val testUnit_def = { def hi3: Unit = mhUnit.invokeExact(self, "hi3"); assert((()) == hi3) } - val testObj_exp = { mhObj.invokeExact(this, "any"); () } - val testObj_val = { val any2 = mhObj.invokeExact(this, "any2"); assert("any2" == any2) } - val testObj_def = { def any3 = mhObj.invokeExact(this, "any3"); assert("any3" == any3) } + val testObj_exp = { mhObj.invokeExact(self, "any"); () } + val testObj_val = { val any2 = mhObj.invokeExact(self, "any2"); assert("any2" == any2) } + val testObj_def = { def any3 = mhObj.invokeExact(self, "any3"); assert("any3" == any3) } val testCl_pass = assert(null != (mhCL.invoke(): ClassLoader)) val testCl_cast = assert(null != (mhCL.invoke().asInstanceOf[ClassLoader]: ClassLoader)) @@ -51,10 +52,10 @@ class Foo { val testNeg_inline_obj = expectWrongMethod(l .findVirtual(classOf[Foo], "neg", methodType(classOf[Int], classOf[Int])) - .invokeExact(this, 3)) + .invokeExact(self, 3)) val testNeg_inline_pass = assert(-4 == (l .findVirtual(classOf[Foo], "neg", methodType(classOf[Int], classOf[Int])) - .invokeExact(this, 4): Int)) + .invokeExact(self, 4): Int)) def expectWrongMethod(op: => Any) = try { op From 0f24589a7a011b0a0d28de5ef3ede488d70a9f72 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Mon, 7 Nov 2022 10:39:16 +0000 Subject: [PATCH 08/10] Fix accidental recursiveness in test --- tests/explicit-nulls/pos/i11332.scala | 16 +++-- tests/run/i11332.scala | 86 +++++++++++++++------------ 2 files changed, 57 insertions(+), 45 deletions(-) diff --git a/tests/explicit-nulls/pos/i11332.scala b/tests/explicit-nulls/pos/i11332.scala index a773a55e987e..73fb48839c16 100644 --- a/tests/explicit-nulls/pos/i11332.scala +++ b/tests/explicit-nulls/pos/i11332.scala @@ -9,10 +9,14 @@ import java.lang.invoke._, MethodType.methodType class Foo: def neg(x: Int): Int = -x - val l = MethodHandles.lookup() - val self = new Foo() +object Test: + def main(args: Array[String]): Unit = + val l = MethodHandles.lookup() + val self = new Foo() - val test = // testing as a expression tree - previously derivedSelect broke the type - l - .findVirtual(classOf[Foo], "neg", methodType(classOf[Int], classOf[Int])) - .invokeExact(self, 4): Int + val res4 = { + l // explicit chain method call - previously derivedSelect broke the type + .findVirtual(classOf[Foo], "neg", methodType(classOf[Int], classOf[Int])) + .invokeExact(self, 4): Int + } + assert(-4 == res4) diff --git a/tests/run/i11332.scala b/tests/run/i11332.scala index 18ff61b5f660..a8a7f8941c6b 100644 --- a/tests/run/i11332.scala +++ b/tests/run/i11332.scala @@ -3,62 +3,70 @@ import scala.language.unsafeNulls import java.lang.invoke._, MethodType.methodType -object Test extends Foo: - def main(args: Array[String]): Unit = () - -class Foo { +class Foo: def neg(x: Int): Int = -x def rev(s: String): String = s.reverse def over(l: Long): String = "long" def over(i: Int): String = "int" def unit(s: String): Unit = () def obj(s: String): Object = s - def id[T](x: T): T = x - val l = MethodHandles.lookup() - val self = new Foo() - val mhNeg = l.findVirtual(classOf[Foo], "neg", methodType(classOf[Int], classOf[Int])) - val mhRev = l.findVirtual(classOf[Foo], "rev", methodType(classOf[String], classOf[String])) - val mhOverL = l.findVirtual(classOf[Foo], "over", methodType(classOf[String], classOf[Long])) - val mhOverI = l.findVirtual(classOf[Foo], "over", methodType(classOf[String], classOf[Int])) - val mhUnit = l.findVirtual(classOf[Foo], "unit", methodType(classOf[Unit], classOf[String])) - val mhObj = l.findVirtual(classOf[Foo], "obj", methodType(classOf[Any], classOf[String])) - val mhCL = l.findStatic(classOf[ClassLoader], "getPlatformClassLoader", methodType(classOf[ClassLoader])) +object Test: + def main(args: Array[String]): Unit = + val l = MethodHandles.lookup() + val self = new Foo() + val mhNeg = l.findVirtual(classOf[Foo], "neg", methodType(classOf[Int], classOf[Int])) + val mhRev = l.findVirtual(classOf[Foo], "rev", methodType(classOf[String], classOf[String])) + val mhOverL = l.findVirtual(classOf[Foo], "over", methodType(classOf[String], classOf[Long])) + val mhOverI = l.findVirtual(classOf[Foo], "over", methodType(classOf[String], classOf[Int])) + val mhUnit = l.findVirtual(classOf[Foo], "unit", methodType(classOf[Unit], classOf[String])) + val mhObj = l.findVirtual(classOf[Foo], "obj", methodType(classOf[Any], classOf[String])) + val mhCL = l.findStatic(classOf[ClassLoader], "getPlatformClassLoader", methodType(classOf[ClassLoader])) - val testNeg1 = assert(-42 == (mhNeg.invokeExact(self, 42): Int)) - val testNeg2 = assert(-33 == (mhNeg.invokeExact(self, 33): Int)) + assert(-42 == (mhNeg.invokeExact(self, 42): Int)) + assert(-33 == (mhNeg.invokeExact(self, 33): Int)) - val testRev1 = assert("oof" == (mhRev.invokeExact(self, "foo"): String)) - val testRev2 = assert("rab" == (mhRev.invokeExact(self, "bar"): String)) + assert("oof" == (mhRev.invokeExact(self, "foo"): String)) + assert("rab" == (mhRev.invokeExact(self, "bar"): String)) - val testOverL = assert("long" == (mhOverL.invokeExact(self, 1L): String)) - val testOVerI = assert("int" == (mhOverI.invokeExact(self, 1): String)) + assert("long" == (mhOverL.invokeExact(self, 1L): String)) + assert("int" == (mhOverI.invokeExact(self, 1): String)) - val testNeg_tvar = assert(-3 == (id(mhNeg.invokeExact(self, 3)): Int)) - val testNeg_obj = expectWrongMethod(mhNeg.invokeExact(self, 4)) + assert(-3 == (id(mhNeg.invokeExact(self, 3)): Int)) + expectWrongMethod(mhNeg.invokeExact(self, 4)) - val testUnit_exp = { mhUnit.invokeExact(self, "hi"): Unit; () } - val testUnit_val = { val hi2: Unit = mhUnit.invokeExact(self, "hi2"); assert((()) == hi2) } - val testUnit_def = { def hi3: Unit = mhUnit.invokeExact(self, "hi3"); assert((()) == hi3) } + { mhUnit.invokeExact(self, "hi"): Unit; () } // explicit block + val hi2: Unit = mhUnit.invokeExact(self, "hi2") + assert((()) == hi2) + def hi3: Unit = mhUnit.invokeExact(self, "hi3") + assert((()) == hi3) - val testObj_exp = { mhObj.invokeExact(self, "any"); () } - val testObj_val = { val any2 = mhObj.invokeExact(self, "any2"); assert("any2" == any2) } - val testObj_def = { def any3 = mhObj.invokeExact(self, "any3"); assert("any3" == any3) } + { mhObj.invokeExact(self, "any"); () } // explicit block + val any2 = mhObj.invokeExact(self, "any2") + assert("any2" == any2) + def any3 = mhObj.invokeExact(self, "any3") + assert("any3" == any3) - val testCl_pass = assert(null != (mhCL.invoke(): ClassLoader)) - val testCl_cast = assert(null != (mhCL.invoke().asInstanceOf[ClassLoader]: ClassLoader)) - val testCl_passX = assert(null != (mhCL.invokeExact(): ClassLoader)) - val testCl_castX = assert(null != (mhCL.invokeExact().asInstanceOf[ClassLoader]: ClassLoader)) + assert(null != (mhCL.invoke(): ClassLoader)) + assert(null != (mhCL.invoke().asInstanceOf[ClassLoader]: ClassLoader)) + assert(null != (mhCL.invokeExact(): ClassLoader)) + assert(null != (mhCL.invokeExact().asInstanceOf[ClassLoader]: ClassLoader)) - val testNeg_inline_obj = expectWrongMethod(l - .findVirtual(classOf[Foo], "neg", methodType(classOf[Int], classOf[Int])) - .invokeExact(self, 3)) - val testNeg_inline_pass = assert(-4 == (l - .findVirtual(classOf[Foo], "neg", methodType(classOf[Int], classOf[Int])) - .invokeExact(self, 4): Int)) + expectWrongMethod { + l // explicit chain method call + .findVirtual(classOf[Foo], "neg", methodType(classOf[Int], classOf[Int])) + .invokeExact(self, 3) + } + val res4 = { + l // explicit chain method call + .findVirtual(classOf[Foo], "neg", methodType(classOf[Int], classOf[Int])) + .invokeExact(self, 4): Int + } + assert(-4 == res4) + + def id[T](x: T): T = x def expectWrongMethod(op: => Any) = try { op throw new AssertionError("expected operation to fail but it didn't") } catch case expected: WrongMethodTypeException => () -} From 2a24379630ca2f1d58dada126d22c6fb59be8165 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Mon, 7 Nov 2022 10:40:33 +0000 Subject: [PATCH 09/10] Run the explicit null minimisation --- tests/explicit-nulls/{pos => run}/i11332.scala | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/explicit-nulls/{pos => run}/i11332.scala (100%) diff --git a/tests/explicit-nulls/pos/i11332.scala b/tests/explicit-nulls/run/i11332.scala similarity index 100% rename from tests/explicit-nulls/pos/i11332.scala rename to tests/explicit-nulls/run/i11332.scala From 1b0b830dda2e2ab966a5f31eb1b500998e603f52 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Mon, 7 Nov 2022 20:43:17 +0000 Subject: [PATCH 10/10] Still recheck Apply's function tree --- compiler/src/dotty/tools/dotc/transform/Recheck.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/Recheck.scala b/compiler/src/dotty/tools/dotc/transform/Recheck.scala index abcd0623b2a5..c524bbb7702f 100644 --- a/compiler/src/dotty/tools/dotc/transform/Recheck.scala +++ b/compiler/src/dotty/tools/dotc/transform/Recheck.scala @@ -261,8 +261,9 @@ abstract class Recheck extends Phase, SymTransformer: mt.instantiate(argTypes) def recheckApply(tree: Apply, pt: Type)(using Context): Type = - // reuse the tree's type if the function was a signature polymorphic method, instead of rechecking it - val funtpe = if tree.fun.symbol.originalSignaturePolymorphic.exists then tree.fun.tpe else recheck(tree.fun) + val funTp = recheck(tree.fun) + // reuse the tree's type on signature polymorphic methods, instead of using the (wrong) rechecked one + val funtpe = if tree.fun.symbol.originalSignaturePolymorphic.exists then tree.fun.tpe else funTp funtpe.widen match case fntpe: MethodType => assert(fntpe.paramInfos.hasSameLengthAs(tree.args))