From 107cb5d2148b83e77d0dac87ea136e4d3a6f9a9c Mon Sep 17 00:00:00 2001 From: Guillaume Raffin Date: Wed, 1 Jul 2020 17:47:40 +0200 Subject: [PATCH 01/12] Generate varargs bridge with the right flags on `@varargs` annotation --- .../dotty/tools/dotc/core/Definitions.scala | 1 + .../tools/dotc/transform/ElimRepeated.scala | 69 ++++++++++--------- .../test/dotc/run-test-pickling.blacklist | 1 + tests/run/i7212/CompatVargs.scala | 11 +++ tests/run/i7212/Test.java | 9 +++ 5 files changed, 60 insertions(+), 31 deletions(-) create mode 100644 tests/run/i7212/CompatVargs.scala create mode 100644 tests/run/i7212/Test.java diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 875916c62a39..49f281c6521f 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -829,6 +829,7 @@ class Definitions { @tu lazy val FunctionalInterfaceAnnot: ClassSymbol = ctx.requiredClass("java.lang.FunctionalInterface") @tu lazy val InfixAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.infix") @tu lazy val AlphaAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.alpha") + @tu lazy val VarargsAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.varargs") // A list of annotations that are commonly used to indicate that a field/method argument or return // type is not null. These annotations are used by the nullification logic in JavaNullInterop to diff --git a/compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala b/compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala index 069575176bea..713e7b861bc9 100644 --- a/compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala +++ b/compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala @@ -34,34 +34,34 @@ class ElimRepeated extends MiniPhase with InfoTransformer { thisPhase => elimRepeated(tp) override def transform(ref: SingleDenotation)(using Context): SingleDenotation = - super.transform(ref) match { + super.transform(ref) match case ref1: SymDenotation if (ref1 ne ref) && overridesJava(ref1.symbol) => // This method won't override the corresponding Java method at the end of this phase, // only the bridge added by `addVarArgsBridge` will. ref1.copySymDenotation(initFlags = ref1.flags &~ Override) case ref1 => ref1 - } override def mayChange(sym: Symbol)(using Context): Boolean = sym.is(Method) private def overridesJava(sym: Symbol)(using Context) = sym.allOverriddenSymbols.exists(_.is(JavaDefined)) - private def elimRepeated(tp: Type)(using Context): Type = tp.stripTypeVar match { + private def hasVargsAnnotation(sym: Symbol)(using ctx: Context) = sym.hasAnnotation(defn.VarargsAnnot) + + /** Eliminate repeated parameters from method types. */ + private def elimRepeated(tp: Type)(using Context): Type = tp.stripTypeVar match case tp @ MethodTpe(paramNames, paramTypes, resultType) => val resultType1 = elimRepeated(resultType) val paramTypes1 = - if (paramTypes.nonEmpty && paramTypes.last.isRepeatedParam) { + if paramTypes.nonEmpty && paramTypes.last.isRepeatedParam then val last = paramTypes.last.translateFromRepeated(toArray = tp.isJavaMethod) paramTypes.init :+ last - } else paramTypes tp.derivedLambdaType(paramNames, paramTypes1, resultType1) case tp: PolyType => tp.derivedLambdaType(tp.paramNames, tp.paramInfos, elimRepeated(tp.resultType)) case tp => tp - } def transformTypeOfTree(tree: Tree)(using Context): Tree = tree.withType(elimRepeated(tree.tpe)) @@ -72,35 +72,35 @@ class ElimRepeated extends MiniPhase with InfoTransformer { thisPhase => override def transformSelect(tree: Select)(using Context): Tree = transformTypeOfTree(tree) - override def transformApply(tree: Apply)(using Context): Tree = { + override def transformApply(tree: Apply)(using Context): Tree = val args = tree.args.mapConserve { case arg: Typed if isWildcardStarArg(arg) => val isJavaDefined = tree.fun.symbol.is(JavaDefined) val tpe = arg.expr.tpe - if (isJavaDefined && tpe.derivesFrom(defn.SeqClass)) + if isJavaDefined && tpe.derivesFrom(defn.SeqClass) then seqToArray(arg.expr) - else if (!isJavaDefined && tpe.derivesFrom(defn.ArrayClass)) + else if !isJavaDefined && tpe.derivesFrom(defn.ArrayClass) arrayToSeq(arg.expr) else arg.expr case arg => arg } transformTypeOfTree(cpy.Apply(tree)(tree.fun, args)) - } /** Convert sequence argument to Java array */ - private def seqToArray(tree: Tree)(using Context): Tree = tree match { + private def seqToArray(tree: Tree)(using Context): Tree = tree match case SeqLiteral(elems, elemtpt) => JavaSeqLiteral(elems, elemtpt) case _ => val elemType = tree.tpe.elemType var elemClass = erasure(elemType).classSymbol - if (defn.NotRuntimeClasses.contains(elemClass)) elemClass = defn.ObjectClass + if defn.NotRuntimeClasses.contains(elemClass) then + elemClass = defn.ObjectClass + end if ref(defn.DottyArraysModule) .select(nme.seqToArray) .appliedToType(elemType) .appliedTo(tree, clsOf(elemClass.typeRef)) - } /** Convert Java array argument to Scala Seq */ private def arrayToSeq(tree: Tree)(using Context): Tree = @@ -109,36 +109,44 @@ class ElimRepeated extends MiniPhase with InfoTransformer { thisPhase => override def transformTypeApply(tree: TypeApply)(using Context): Tree = transformTypeOfTree(tree) - /** If method overrides a Java varargs method, add a varargs bridge. - * Also transform trees inside method annotation + /** If method overrides a Java varargs method or is annotated with @varargs, add a varargs bridge. + * Also transform trees inside method annotation. */ override def transformDefDef(tree: DefDef)(using Context): Tree = atPhase(thisPhase) { - if (tree.symbol.info.isVarArgsMethod && overridesJava(tree.symbol)) - addVarArgsBridge(tree) + val isOverride = overridesJava(tree.symbol) + val hasAnnotation = hasVargsAnnotation(tree.symbol) + if tree.symbol.info.isVarArgsMethod && (isOverride || hasAnnotation) then + // non-overrides need the varargs bytecode flag and cannot be synthetic + // otherwise javac refuses to call them. + addVarArgsBridge(tree, isOverride) else tree } /** Add a Java varargs bridge - * @param ddef the original method definition which is assumed to override - * a Java varargs method JM up to this phase. - * @return a thicket consisting of `ddef` and a varargs bridge method - * which overrides the Java varargs method JM from this phase on - * and forwards to `ddef`. + * @param ddef the original method definition + * @param addFlag the flag to add to the method symbol + + * @return a thicket consisting of `ddef` and a varargs bridge method + * which forwards java varargs to `ddef`. It retains all the + * flags of `ddef` except `Private`. * - * A bridge is necessary because the following hold + * A bridge is necessary because the following hold: * - the varargs in `ddef` will change from `RepeatedParam[T]` to `Seq[T]` after this phase - * - _but_ the callers of `ddef` expect its varargs to be changed to `Array[? <: T]`, since it overrides - * a Java varargs + * - _but_ the callers of `ddef` expect its varargs to be changed to `Array[? <: T]` * The solution is to add a "bridge" method that converts its argument from `Array[? <: T]` to `Seq[T]` and * forwards it to `ddef`. */ - private def addVarArgsBridge(ddef: DefDef)(using Context): Tree = { + private def addVarArgsBridge(ddef: DefDef, synthetic: Boolean)(using Context): Tree = val original = ddef.symbol.asTerm + // For simplicity we always set the varargs flag + val flags = ddef.symbol.flags | JavaVarargs &~ Private val bridge = original.copy( - flags = ddef.symbol.flags &~ Private | Artifact, - info = toJavaVarArgs(ddef.symbol.info)).enteredAfter(thisPhase).asTerm + flags = if synthetic then flags | Artifact else flags, + info = toJavaVarArgs(ddef.symbol.info) + ).enteredAfter(thisPhase).asTerm + val bridgeDef = polyDefDef(bridge, trefs => vrefss => { val (vrefs :+ varArgRef) :: vrefss1 = vrefss // Can't call `.argTypes` here because the underlying array type is of the @@ -151,15 +159,14 @@ class ElimRepeated extends MiniPhase with InfoTransformer { thisPhase => }) Thicket(ddef, bridgeDef) - } /** Convert type from Scala to Java varargs method */ - private def toJavaVarArgs(tp: Type)(using Context): Type = tp match { + private def toJavaVarArgs(tp: Type)(using Context): Type = tp match case tp: PolyType => tp.derivedLambdaType(tp.paramNames, tp.paramInfos, toJavaVarArgs(tp.resultType)) case tp: MethodType => val inits :+ last = tp.paramInfos val last1 = last.translateFromRepeated(toArray = true) tp.derivedLambdaType(tp.paramNames, inits :+ last1, tp.resultType) - } + } diff --git a/compiler/test/dotc/run-test-pickling.blacklist b/compiler/test/dotc/run-test-pickling.blacklist index ebcf2b861abe..b69d517ca893 100644 --- a/compiler/test/dotc/run-test-pickling.blacklist +++ b/compiler/test/dotc/run-test-pickling.blacklist @@ -26,3 +26,4 @@ i7868.scala enum-java zero-arity-case-class.scala tuple-ops.scala +i7212 diff --git a/tests/run/i7212/CompatVargs.scala b/tests/run/i7212/CompatVargs.scala new file mode 100644 index 000000000000..0269d27c5841 --- /dev/null +++ b/tests/run/i7212/CompatVargs.scala @@ -0,0 +1,11 @@ +import scala.annotation._ + +class CompatVargs { + @varargs + def vargs(args: String*): Unit = println(args) + + def vargsFromScala(): Unit = + vargs("single") + vargs("a", "b") + vargs(Seq("a", "b"): _*) +} diff --git a/tests/run/i7212/Test.java b/tests/run/i7212/Test.java new file mode 100644 index 000000000000..e2d21bd76dfd --- /dev/null +++ b/tests/run/i7212/Test.java @@ -0,0 +1,9 @@ +public class Test { + public static void main(String[] args) { + CompatVargs c = new CompatVargs(); + c.vargs("single"); + c.vargs("a", "b"); + c.vargs(new String[]{"a", "b"}); + c.vargsFromScala(); + } +} From f3c4f8c5fe40257d116946780df8abc08909693b Mon Sep 17 00:00:00 2001 From: Guillaume Raffin Date: Thu, 2 Jul 2020 15:53:30 +0200 Subject: [PATCH 02/12] Check parent methods for varargs annotation --- .../tools/dotc/transform/ElimRepeated.scala | 39 ++++++++++++++----- .../test/dotc/run-test-pickling.blacklist | 1 + tests/run/varargs-abstract/Test.java | 19 +++++++++ tests/run/varargs-abstract/test.scala | 18 +++++++++ 4 files changed, 67 insertions(+), 10 deletions(-) create mode 100644 tests/run/varargs-abstract/Test.java create mode 100644 tests/run/varargs-abstract/test.scala diff --git a/compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala b/compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala index 713e7b861bc9..667516cc97b5 100644 --- a/compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala +++ b/compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala @@ -46,7 +46,9 @@ class ElimRepeated extends MiniPhase with InfoTransformer { thisPhase => private def overridesJava(sym: Symbol)(using Context) = sym.allOverriddenSymbols.exists(_.is(JavaDefined)) - private def hasVargsAnnotation(sym: Symbol)(using ctx: Context) = sym.hasAnnotation(defn.VarargsAnnot) + private def hasVarargsAnnotation(sym: Symbol)(using Context) = sym.hasAnnotation(defn.VarargsAnnot) + + private def parentHasAnnotation(sym: Symbol)(using Context) = sym.allOverriddenSymbols.exists(hasVarargsAnnotation) /** Eliminate repeated parameters from method types. */ private def elimRepeated(tp: Type)(using Context): Type = tp.stripTypeVar match @@ -114,8 +116,9 @@ class ElimRepeated extends MiniPhase with InfoTransformer { thisPhase => */ override def transformDefDef(tree: DefDef)(using Context): Tree = atPhase(thisPhase) { - val isOverride = overridesJava(tree.symbol) - val hasAnnotation = hasVargsAnnotation(tree.symbol) + val sym = tree.symbol + val isOverride = overridesJava(sym) + val hasAnnotation = hasVarargsAnnotation(sym) || parentHasAnnotation(sym) if tree.symbol.info.isVarArgsMethod && (isOverride || hasAnnotation) then // non-overrides need the varargs bytecode flag and cannot be synthetic // otherwise javac refuses to call them. @@ -125,7 +128,7 @@ class ElimRepeated extends MiniPhase with InfoTransformer { thisPhase => } /** Add a Java varargs bridge - * @param ddef the original method definition + * @param ddef the original method definition * @param addFlag the flag to add to the method symbol * @return a thicket consisting of `ddef` and a varargs bridge method @@ -141,6 +144,7 @@ class ElimRepeated extends MiniPhase with InfoTransformer { thisPhase => private def addVarArgsBridge(ddef: DefDef, synthetic: Boolean)(using Context): Tree = val original = ddef.symbol.asTerm // For simplicity we always set the varargs flag + // although it's not strictly necessary for overrides val flags = ddef.symbol.flags | JavaVarargs &~ Private val bridge = original.copy( flags = if synthetic then flags | Artifact else flags, @@ -162,11 +166,26 @@ class ElimRepeated extends MiniPhase with InfoTransformer { thisPhase => /** Convert type from Scala to Java varargs method */ private def toJavaVarArgs(tp: Type)(using Context): Type = tp match - case tp: PolyType => - tp.derivedLambdaType(tp.paramNames, tp.paramInfos, toJavaVarArgs(tp.resultType)) - case tp: MethodType => - val inits :+ last = tp.paramInfos - val last1 = last.translateFromRepeated(toArray = true) - tp.derivedLambdaType(tp.paramNames, inits :+ last1, tp.resultType) + case tp: PolyType => + tp.derivedLambdaType(tp.paramNames, tp.paramInfos, toJavaVarArgs(tp.resultType)) + case tp: MethodType => + val inits :+ last = tp.paramInfos + val vararg = varargArrayType(last) + val params = inits :+ vararg + tp.derivedLambdaType(tp.paramNames, params, tp.resultType) + + /** Translate a repeated type T* to an `Array[? <: Upper]` + * such that it is compatible with java varargs. + * + * If T is not a primitive type, we set `Upper = T & AnyRef` + * to prevent the erasure of `Array[? <: Upper]` to Object, + * which would break the varargs from Java. + */ + private def varargArrayType(tp: Type)(using Context): Type = + val array = tp.translateFromRepeated(toArray = true) + val element = array.elemType.typeSymbol + + if element.isPrimitiveValueClass then array + else defn.ArrayOf(TypeBounds.upper(AndType(element.typeRef, defn.AnyRefType))) } diff --git a/compiler/test/dotc/run-test-pickling.blacklist b/compiler/test/dotc/run-test-pickling.blacklist index b69d517ca893..aefe3b14b017 100644 --- a/compiler/test/dotc/run-test-pickling.blacklist +++ b/compiler/test/dotc/run-test-pickling.blacklist @@ -27,3 +27,4 @@ enum-java zero-arity-case-class.scala tuple-ops.scala i7212 +varargs-abstract diff --git a/tests/run/varargs-abstract/Test.java b/tests/run/varargs-abstract/Test.java new file mode 100644 index 000000000000..31dfcedc07a7 --- /dev/null +++ b/tests/run/varargs-abstract/Test.java @@ -0,0 +1,19 @@ +import java.util.Comparator; + +public class Test { + public static void main(String[] args) { + ClassImplementsClass c = new ClassImplementsClass(); + + c.x("a", "b", "c"); + c.y("a", "b", "c"); + c.z("a", "b", "c"); + + VarargAbstractClass i = new ClassImplementsClass(); + + i.x("a", "b", "c"); + i.y("a", "b", "c"); + // i.z("a", "b", "c"); + // ClassCastException at runtime because the generated + // signature of z doesn't mention the type parameter (it should) + } +} diff --git a/tests/run/varargs-abstract/test.scala b/tests/run/varargs-abstract/test.scala new file mode 100644 index 000000000000..073f8d086f28 --- /dev/null +++ b/tests/run/varargs-abstract/test.scala @@ -0,0 +1,18 @@ +import scala.annotation.varargs + +abstract class VarargAbstractClass[T] { + @varargs + def x(els: String*): Int + + @varargs + def y(els: String*): Int + + @varargs + def z(els: T*): Int +} +class ClassImplementsClass extends VarargAbstractClass[String] { + + override def x(els: String*): Int = els.length + override def y(els: String*): Int = els.length + override def z(els: String*): Int = els.length +} From f671a0df30fed2a68581782714b6f99956749e7d Mon Sep 17 00:00:00 2001 From: Guillaume Raffin Date: Fri, 3 Jul 2020 01:32:04 +0200 Subject: [PATCH 03/12] Detect wrong usages of `@varargs` + tests --- .../tools/dotc/transform/ElimRepeated.scala | 86 ++++++++++++++----- tests/neg/varargs-annot.scala | 27 ++++++ tests/run/varargs-abstract/Test.java | 2 +- 3 files changed, 93 insertions(+), 22 deletions(-) create mode 100644 tests/neg/varargs-annot.scala diff --git a/compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala b/compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala index 667516cc97b5..5e73b68a8ef7 100644 --- a/compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala +++ b/compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala @@ -68,6 +68,9 @@ class ElimRepeated extends MiniPhase with InfoTransformer { thisPhase => def transformTypeOfTree(tree: Tree)(using Context): Tree = tree.withType(elimRepeated(tree.tpe)) + override def transformTypeApply(tree: TypeApply)(using Context): Tree = + transformTypeOfTree(tree) + override def transformIdent(tree: Ident)(using Context): Tree = transformTypeOfTree(tree) @@ -108,25 +111,52 @@ class ElimRepeated extends MiniPhase with InfoTransformer { thisPhase => private def arrayToSeq(tree: Tree)(using Context): Tree = tpd.wrapArray(tree, tree.tpe.elemType) - override def transformTypeApply(tree: TypeApply)(using Context): Tree = - transformTypeOfTree(tree) - /** If method overrides a Java varargs method or is annotated with @varargs, add a varargs bridge. * Also transform trees inside method annotation. */ override def transformDefDef(tree: DefDef)(using Context): Tree = atPhase(thisPhase) { val sym = tree.symbol - val isOverride = overridesJava(sym) - val hasAnnotation = hasVarargsAnnotation(sym) || parentHasAnnotation(sym) - if tree.symbol.info.isVarArgsMethod && (isOverride || hasAnnotation) then - // non-overrides need the varargs bytecode flag and cannot be synthetic - // otherwise javac refuses to call them. - addVarArgsBridge(tree, isOverride) + val hasAnnotation = hasVarargsAnnotation(sym) + if hasRepeatedParams(sym) then + val isOverride = overridesJava(sym) + if isOverride || hasAnnotation || parentHasAnnotation(sym) then + // java varargs are more restrictive than scala's + // see https://github.com/scala/bug/issues/11714 + if !isValidJavaVarArgs(sym.info) then + ctx.error("""To generate java-compatible varargs: + | - there must be a single repeated parameter + | - it must be the last argument in the last parameter list + |""".stripMargin, + tree.sourcePos) + tree + else + addVarArgsBridge(tree, isOverride) + else + tree else + if hasAnnotation then + ctx.error("A method without repeated parameters cannot be annotated with @varargs", tree.sourcePos) tree } + /** Is there a repeated parameter in some parameter list? */ + private def hasRepeatedParams(sym: Symbol)(using Context): Boolean = + sym.info.paramInfoss.flatten.exists(_.isRepeatedParam) + + /** Is this the type of a method that has a repeated parameter type as + * its last parameter in the last parameter list? + */ + private def isValidJavaVarArgs(t: Type)(using Context): Boolean = t match + case mt: MethodType => + val initp :+ lastp = mt.paramInfoss + initp.forall(_.forall(!_.isRepeatedParam)) && + lastp.nonEmpty && + lastp.init.forall(!_.isRepeatedParam) && + lastp.last.isRepeatedParam + case _ => false + + /** Add a Java varargs bridge * @param ddef the original method definition * @param addFlag the flag to add to the method symbol @@ -141,38 +171,52 @@ class ElimRepeated extends MiniPhase with InfoTransformer { thisPhase => * The solution is to add a "bridge" method that converts its argument from `Array[? <: T]` to `Seq[T]` and * forwards it to `ddef`. */ - private def addVarArgsBridge(ddef: DefDef, synthetic: Boolean)(using Context): Tree = + private def addVarArgsBridge(ddef: DefDef, javaOverride: Boolean)(using ctx: Context): Tree = val original = ddef.symbol.asTerm // For simplicity we always set the varargs flag // although it's not strictly necessary for overrides - val flags = ddef.symbol.flags | JavaVarargs &~ Private + // (but it is for non-overrides) + val flags = ddef.symbol.flags | JavaVarargs val bridge = original.copy( - flags = if synthetic then flags | Artifact else flags, + // non-overrides cannot be synthetic otherwise javac refuses to call them + flags = if javaOverride then flags | Artifact else flags, info = toJavaVarArgs(ddef.symbol.info) ).enteredAfter(thisPhase).asTerm val bridgeDef = polyDefDef(bridge, trefs => vrefss => { - val (vrefs :+ varArgRef) :: vrefss1 = vrefss + val init :+ (last :+ vararg) = vrefss // Can't call `.argTypes` here because the underlying array type is of the // form `Array[? <: SomeType]`, so we need `.argInfos` to get the `TypeBounds`. - val elemtp = varArgRef.tpe.widen.argInfos.head + val elemtp = vararg.tpe.widen.argInfos.head ref(original.termRef) .appliedToTypes(trefs) - .appliedToArgs(vrefs :+ tpd.wrapArray(varArgRef, elemtp)) - .appliedToArgss(vrefss1) + .appliedToArgss(init) + .appliedToArgs(last :+ tpd.wrapArray(vararg, elemtp)) }) - Thicket(ddef, bridgeDef) + val bridgeDenot = bridge.denot + currentClass.info.member(bridge.name).alternatives.find { s => + s.matches(bridgeDenot) && + !(s.asSymDenotation.is(JavaDefined) && javaOverride) + } match + case Some(conflict) => + ctx.error(s"@varargs produces a forwarder method that conflicts with ${conflict.showDcl}", ddef.sourcePos) + EmptyTree + case None => + Thicket(ddef, bridgeDef) /** Convert type from Scala to Java varargs method */ private def toJavaVarArgs(tp: Type)(using Context): Type = tp match case tp: PolyType => tp.derivedLambdaType(tp.paramNames, tp.paramInfos, toJavaVarArgs(tp.resultType)) case tp: MethodType => - val inits :+ last = tp.paramInfos - val vararg = varargArrayType(last) - val params = inits :+ vararg - tp.derivedLambdaType(tp.paramNames, params, tp.resultType) + tp.resultType match + case m: MethodType => // multiple param lists + tp.derivedLambdaType(tp.paramNames, tp.paramInfos, toJavaVarArgs(m)) + case _ => + val init :+ last = tp.paramInfos + val vararg = varargArrayType(last) + tp.derivedLambdaType(tp.paramNames, init :+ vararg, tp.resultType) /** Translate a repeated type T* to an `Array[? <: Upper]` * such that it is compatible with java varargs. diff --git a/tests/neg/varargs-annot.scala b/tests/neg/varargs-annot.scala new file mode 100644 index 000000000000..04352263ad52 --- /dev/null +++ b/tests/neg/varargs-annot.scala @@ -0,0 +1,27 @@ +import annotation.varargs + +// Failing varargs annotation +object Test { + + trait A { + def v1(a: Int, b: Array[String]) = a + } + + trait B extends A { // error (could we get rid of that one?) + @varargs def v1(a: Int, b: String*) = a + b.length // error + } + + @varargs def nov(a: Int) = 0 // error: A method without repeated parameters cannot be annotated with @varargs + @varargs def v(a: Int, b: String*) = a + b.length // ok + + @varargs def v2(a: Int, b: String*) = 0 // error + def v2(a: Int, b: Array[String]) = 0 + + @varargs def v3(a: String*)(b: Int) = b + a.length // error + @varargs def v4(a: String)(b: Int) = b + a.length // error + @varargs def v5(a: String)(b: Int*) = a + b.sum // ok + + @varargs def v6: Int = 1 // error + @varargs def v7(i: Int*)() = i.sum // error + +} diff --git a/tests/run/varargs-abstract/Test.java b/tests/run/varargs-abstract/Test.java index 31dfcedc07a7..de19a7d23d57 100644 --- a/tests/run/varargs-abstract/Test.java +++ b/tests/run/varargs-abstract/Test.java @@ -8,7 +8,7 @@ public static void main(String[] args) { c.y("a", "b", "c"); c.z("a", "b", "c"); - VarargAbstractClass i = new ClassImplementsClass(); + VarargAbstractClass i = new ClassImplementsClass(); i.x("a", "b", "c"); i.y("a", "b", "c"); From 5cb324d73b3ad4922661825fa4704521d2675b1c Mon Sep 17 00:00:00 2001 From: Guillaume Raffin Date: Fri, 3 Jul 2020 02:35:45 +0200 Subject: [PATCH 04/12] Validate PolyType for `@varargs` --- compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala b/compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala index 5e73b68a8ef7..9c188b60bb30 100644 --- a/compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala +++ b/compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala @@ -147,13 +147,15 @@ class ElimRepeated extends MiniPhase with InfoTransformer { thisPhase => /** Is this the type of a method that has a repeated parameter type as * its last parameter in the last parameter list? */ - private def isValidJavaVarArgs(t: Type)(using Context): Boolean = t match + private def isValidJavaVarArgs(tp: Type)(using Context): Boolean = tp match case mt: MethodType => val initp :+ lastp = mt.paramInfoss initp.forall(_.forall(!_.isRepeatedParam)) && lastp.nonEmpty && lastp.init.forall(!_.isRepeatedParam) && lastp.last.isRepeatedParam + case pt: PolyType => + isValidJavaVarArgs(pt.resultType) case _ => false From b527a75dd8443472fbef8e156f41bace1b590455 Mon Sep 17 00:00:00 2001 From: Guillaume Raffin Date: Wed, 8 Jul 2020 23:50:06 +0200 Subject: [PATCH 05/12] Fix "types differ" error --- .../tools/dotc/transform/ElimRepeated.scala | 26 +++++++++++-------- tests/neg/varargs-annot.scala | 2 +- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala b/compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala index 9c188b60bb30..be4246c0c693 100644 --- a/compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala +++ b/compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala @@ -185,17 +185,6 @@ class ElimRepeated extends MiniPhase with InfoTransformer { thisPhase => info = toJavaVarArgs(ddef.symbol.info) ).enteredAfter(thisPhase).asTerm - val bridgeDef = polyDefDef(bridge, trefs => vrefss => { - val init :+ (last :+ vararg) = vrefss - // Can't call `.argTypes` here because the underlying array type is of the - // form `Array[? <: SomeType]`, so we need `.argInfos` to get the `TypeBounds`. - val elemtp = vararg.tpe.widen.argInfos.head - ref(original.termRef) - .appliedToTypes(trefs) - .appliedToArgss(init) - .appliedToArgs(last :+ tpd.wrapArray(vararg, elemtp)) - }) - val bridgeDenot = bridge.denot currentClass.info.member(bridge.name).alternatives.find { s => s.matches(bridgeDenot) && @@ -205,6 +194,21 @@ class ElimRepeated extends MiniPhase with InfoTransformer { thisPhase => ctx.error(s"@varargs produces a forwarder method that conflicts with ${conflict.showDcl}", ddef.sourcePos) EmptyTree case None => + val bridgeDef = polyDefDef(bridge, trefs => vrefss => { + val init :+ (last :+ vararg) = vrefss + // Can't call `.argTypes` here because the underlying array type is of the + // form `Array[? <: SomeType]`, so we need `.argInfos` to get the `TypeBounds`. + val elemtp = vararg.tpe.widen.argInfos.head + + // The generation of the forwarding call needs to be deferred, otherwise + // generic and curried methods won't pass the tree checker. + atNextPhase { + ref(original.termRef) + .appliedToTypes(trefs) + .appliedToArgss(init) + .appliedToArgs(last :+ tpd.wrapArray(vararg, elemtp)) + } + }) Thicket(ddef, bridgeDef) /** Convert type from Scala to Java varargs method */ diff --git a/tests/neg/varargs-annot.scala b/tests/neg/varargs-annot.scala index 04352263ad52..f5629e7ae027 100644 --- a/tests/neg/varargs-annot.scala +++ b/tests/neg/varargs-annot.scala @@ -7,7 +7,7 @@ object Test { def v1(a: Int, b: Array[String]) = a } - trait B extends A { // error (could we get rid of that one?) + trait B extends A { @varargs def v1(a: Int, b: String*) = a + b.length // error } From 3ae6cc4351606e47241440f51a9659f5769c8326 Mon Sep 17 00:00:00 2001 From: Guillaume Raffin Date: Thu, 9 Jul 2020 21:00:07 +0200 Subject: [PATCH 06/12] Fix generic signature of java-compatible varargs --- .../src/dotty/tools/dotc/transform/ElimRepeated.scala | 11 +++++------ .../tools/dotc/transform/GenericSignatures.scala | 7 ++++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala b/compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala index be4246c0c693..af34256d9c87 100644 --- a/compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala +++ b/compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala @@ -227,15 +227,14 @@ class ElimRepeated extends MiniPhase with InfoTransformer { thisPhase => /** Translate a repeated type T* to an `Array[? <: Upper]` * such that it is compatible with java varargs. * - * If T is not a primitive type, we set `Upper = T & AnyRef` + * When necessary we set `Upper = T & AnyRef` * to prevent the erasure of `Array[? <: Upper]` to Object, * which would break the varargs from Java. */ private def varargArrayType(tp: Type)(using Context): Type = - val array = tp.translateFromRepeated(toArray = true) - val element = array.elemType.typeSymbol - - if element.isPrimitiveValueClass then array - else defn.ArrayOf(TypeBounds.upper(AndType(element.typeRef, defn.AnyRefType))) + val array = tp.translateFromRepeated(toArray = true) // Array[? <: T] + val element = array.elemType.hiBound // T + if element <:< defn.AnyRefType || element.typeSymbol.isPrimitiveValueClass then array + else defn.ArrayOf(TypeBounds.upper(AndType(element, defn.AnyRefType))) // Array[? <: T & AnyRef] } diff --git a/compiler/src/dotty/tools/dotc/transform/GenericSignatures.scala b/compiler/src/dotty/tools/dotc/transform/GenericSignatures.scala index e2d6362ce1ff..515f2660d85b 100644 --- a/compiler/src/dotty/tools/dotc/transform/GenericSignatures.scala +++ b/compiler/src/dotty/tools/dotc/transform/GenericSignatures.scala @@ -186,10 +186,11 @@ object GenericSignatures { case defn.ArrayOf(elemtp) => if (isUnboundedGeneric(elemtp)) jsig(defn.ObjectType) - else { + else builder.append(ClassfileConstants.ARRAY_TAG) - jsig(elemtp) - } + elemtp match + case TypeBounds(lo, hi) => jsig(hi.widenDealias) + case _ => jsig(elemtp) case RefOrAppliedType(sym, pre, args) => if (sym == defn.PairClass && tp.tupleArity > Definitions.MaxTupleArity) From cced03713b98f5100b01f854c16f536b93275ad4 Mon Sep 17 00:00:00 2001 From: Guillaume Raffin Date: Thu, 9 Jul 2020 21:13:25 +0200 Subject: [PATCH 07/12] Improve `@varargs` errors and fix some doc --- .../tools/dotc/transform/ElimRepeated.scala | 42 ++++++++++--------- .../dotc/transform/GenericSignatures.scala | 4 +- 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala b/compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala index af34256d9c87..e36f315c169c 100644 --- a/compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala +++ b/compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala @@ -128,7 +128,7 @@ class ElimRepeated extends MiniPhase with InfoTransformer { thisPhase => | - there must be a single repeated parameter | - it must be the last argument in the last parameter list |""".stripMargin, - tree.sourcePos) + sym.sourcePos) tree else addVarArgsBridge(tree, isOverride) @@ -136,7 +136,7 @@ class ElimRepeated extends MiniPhase with InfoTransformer { thisPhase => tree else if hasAnnotation then - ctx.error("A method without repeated parameters cannot be annotated with @varargs", tree.sourcePos) + ctx.error("A method without repeated parameters cannot be annotated with @varargs", sym.sourcePos) tree } @@ -156,7 +156,8 @@ class ElimRepeated extends MiniPhase with InfoTransformer { thisPhase => lastp.last.isRepeatedParam case pt: PolyType => isValidJavaVarArgs(pt.resultType) - case _ => false + case _ => + throw new Exception("Match error in @varargs bridge logic. This should not happen, please open an issue " + tp) /** Add a Java varargs bridge @@ -179,22 +180,23 @@ class ElimRepeated extends MiniPhase with InfoTransformer { thisPhase => // although it's not strictly necessary for overrides // (but it is for non-overrides) val flags = ddef.symbol.flags | JavaVarargs + + // The java-compatible bridge symbol val bridge = original.copy( // non-overrides cannot be synthetic otherwise javac refuses to call them flags = if javaOverride then flags | Artifact else flags, info = toJavaVarArgs(ddef.symbol.info) - ).enteredAfter(thisPhase).asTerm + ).asTerm - val bridgeDenot = bridge.denot currentClass.info.member(bridge.name).alternatives.find { s => - s.matches(bridgeDenot) && - !(s.asSymDenotation.is(JavaDefined) && javaOverride) + s.matches(bridge) && + !(javaOverride && s.asSymDenotation.is(JavaDefined)) } match case Some(conflict) => - ctx.error(s"@varargs produces a forwarder method that conflicts with ${conflict.showDcl}", ddef.sourcePos) - EmptyTree + ctx.error(s"@varargs produces a forwarder method that conflicts with ${conflict.showDcl}", original.sourcePos) + ddef case None => - val bridgeDef = polyDefDef(bridge, trefs => vrefss => { + val bridgeDef = polyDefDef(bridge.enteredAfter(thisPhase), trefs => vrefss => { val init :+ (last :+ vararg) = vrefss // Can't call `.argTypes` here because the underlying array type is of the // form `Array[? <: SomeType]`, so we need `.argInfos` to get the `TypeBounds`. @@ -213,16 +215,16 @@ class ElimRepeated extends MiniPhase with InfoTransformer { thisPhase => /** Convert type from Scala to Java varargs method */ private def toJavaVarArgs(tp: Type)(using Context): Type = tp match - case tp: PolyType => - tp.derivedLambdaType(tp.paramNames, tp.paramInfos, toJavaVarArgs(tp.resultType)) - case tp: MethodType => - tp.resultType match - case m: MethodType => // multiple param lists - tp.derivedLambdaType(tp.paramNames, tp.paramInfos, toJavaVarArgs(m)) - case _ => - val init :+ last = tp.paramInfos - val vararg = varargArrayType(last) - tp.derivedLambdaType(tp.paramNames, init :+ vararg, tp.resultType) + case tp: PolyType => + tp.derivedLambdaType(tp.paramNames, tp.paramInfos, toJavaVarArgs(tp.resultType)) + case tp: MethodType => + tp.resultType match + case m: MethodType => // multiple param lists + tp.derivedLambdaType(tp.paramNames, tp.paramInfos, toJavaVarArgs(m)) + case _ => + val init :+ last = tp.paramInfos + val vararg = varargArrayType(last) + tp.derivedLambdaType(tp.paramNames, init :+ vararg, tp.resultType) /** Translate a repeated type T* to an `Array[? <: Upper]` * such that it is compatible with java varargs. diff --git a/compiler/src/dotty/tools/dotc/transform/GenericSignatures.scala b/compiler/src/dotty/tools/dotc/transform/GenericSignatures.scala index 515f2660d85b..5b2592b5009b 100644 --- a/compiler/src/dotty/tools/dotc/transform/GenericSignatures.scala +++ b/compiler/src/dotty/tools/dotc/transform/GenericSignatures.scala @@ -314,8 +314,8 @@ object GenericSignatures { * - If the list contains one or more occurrences of scala.Array with * type parameters El1, El2, ... then the dominator is scala.Array with * type parameter of intersectionDominator(List(El1, El2, ...)). <--- @PP: not yet in spec. - * - Otherwise, the list is reduced to a subsequence containing only types - * which are not subtypes of other listed types (the span.) + * - Otherwise, the list is reduced to a subsequence containing only the + * types that are not supertypes of other listed types (the span.) * - If the span is empty, the dominator is Object. * - If the span contains a class Tc which is not a trait and which is * not Object, the dominator is Tc. <--- @PP: "which is not Object" not in spec. From 5c007b03cb6d7a52bf735daabf48dba5c7f25355 Mon Sep 17 00:00:00 2001 From: Guillaume Raffin Date: Thu, 9 Jul 2020 21:14:45 +0200 Subject: [PATCH 08/12] Add more varargs tests --- tests/pos/varargs-annot-currying.scala | 18 ++++++++++++++++ tests/pos/varargs-annot-generics.scala | 21 +++++++++++++++++++ tests/run/varargs-abstract/Test.java | 20 +++++++++++------- tests/run/varargs-abstract/test.scala | 29 +++++++++++++++++++------- tests/untried/neg/varargs.check | 10 --------- tests/untried/neg/varargs.scala | 27 ------------------------ 6 files changed, 74 insertions(+), 51 deletions(-) create mode 100644 tests/pos/varargs-annot-currying.scala create mode 100644 tests/pos/varargs-annot-generics.scala delete mode 100644 tests/untried/neg/varargs.check delete mode 100644 tests/untried/neg/varargs.scala diff --git a/tests/pos/varargs-annot-currying.scala b/tests/pos/varargs-annot-currying.scala new file mode 100644 index 000000000000..f34e4f2190a9 --- /dev/null +++ b/tests/pos/varargs-annot-currying.scala @@ -0,0 +1,18 @@ +import scala.annotation.varargs + +object VarArgs { + @varargs + def two(a: Int)(b: String*): Nothing = ??? + + @varargs + def twoPrimitive(a: Int)(b: Int*): Nothing = ??? + + @varargs + def three(a3: Int)(b3: String)(c3: String*): Nothing = ??? + + @varargs + def threePrimitive(a3: Int)(b3: String)(c3: Int*): Nothing = ??? + + @varargs + def emptyOk()()(xs: String*): Nothing = ??? +} diff --git a/tests/pos/varargs-annot-generics.scala b/tests/pos/varargs-annot-generics.scala new file mode 100644 index 000000000000..d3e0ee49ea2f --- /dev/null +++ b/tests/pos/varargs-annot-generics.scala @@ -0,0 +1,21 @@ +import scala.annotation.varargs + +object VarArgs { + @varargs + def foo1[A](x: A, xs: String*): A = ??? + + @varargs + def foo2[A](x: List[A], xs: String*): A = ??? + + @varargs + def foo3[A](n: Int*): Unit = ??? + + @varargs + def bar1[A](x: A*): Unit = ??? + + @varargs + def bar2[A](x: A, xs: A*): A = ??? + + @varargs + def bar3[A <: Comparable[A]](xs: A*): A = ??? +} diff --git a/tests/run/varargs-abstract/Test.java b/tests/run/varargs-abstract/Test.java index de19a7d23d57..3233d2412894 100644 --- a/tests/run/varargs-abstract/Test.java +++ b/tests/run/varargs-abstract/Test.java @@ -2,18 +2,24 @@ public class Test { public static void main(String[] args) { - ClassImplementsClass c = new ClassImplementsClass(); + VarargImplClass c = new VarargImplClass(); - c.x("a", "b", "c"); + c.x(0, 1, 2); c.y("a", "b", "c"); c.z("a", "b", "c"); + c.generic("a", "b", "c"); + c.genericBounded("a", "b", "c"); - VarargAbstractClass i = new ClassImplementsClass(); + VarargAbstractClass i = new VarargImplClass(); - i.x("a", "b", "c"); + i.x(0, 1, 2); i.y("a", "b", "c"); - // i.z("a", "b", "c"); - // ClassCastException at runtime because the generated - // signature of z doesn't mention the type parameter (it should) + i.z("a", "b", "c"); + i.generic("a", "b", "c"); + i.genericBounded("a", "b", "c"); + + VarargClassBounded b = new VarargClassBounded<>(); + b.v1("a", "b", "c"); + b.v2("a", "b", "c"); } } diff --git a/tests/run/varargs-abstract/test.scala b/tests/run/varargs-abstract/test.scala index 073f8d086f28..2edb43c4cf50 100644 --- a/tests/run/varargs-abstract/test.scala +++ b/tests/run/varargs-abstract/test.scala @@ -2,17 +2,32 @@ import scala.annotation.varargs abstract class VarargAbstractClass[T] { @varargs - def x(els: String*): Int + def x(v: Int*): Int @varargs - def y(els: String*): Int + def y(v: String*): Int @varargs - def z(els: T*): Int + def z(v: T*): Int + + @varargs + def generic[E](e: E*): Unit = () + + @varargs + def genericBounded[E <: Comparable[E] & Serializable](e: E*): Unit = () +} + +class VarargImplClass extends VarargAbstractClass[String] { + + override def x(v: Int*): Int = v.length + override def y(v: String*): Int = v.length + override def z(v: String*): Int = v.length } -class ClassImplementsClass extends VarargAbstractClass[String] { - override def x(els: String*): Int = els.length - override def y(els: String*): Int = els.length - override def z(els: String*): Int = els.length +class VarargClassBounded[B <: Comparable[B]] { + @varargs + def v1(v: B*): Unit = () + + @varargs + def v2(first: B, more: B*): B = first } diff --git a/tests/untried/neg/varargs.check b/tests/untried/neg/varargs.check deleted file mode 100644 index 5f002fdae4a9..000000000000 --- a/tests/untried/neg/varargs.check +++ /dev/null @@ -1,10 +0,0 @@ -varargs.scala:16: error: A method with a varargs annotation produces a forwarder method with the same signature (a: Int, b: Array[String])Int as an existing method. - @varargs def v1(a: Int, b: String*) = a + b.length - ^ -varargs.scala:19: error: A method without repeated parameters cannot be annotated with the `varargs` annotation. - @varargs def nov(a: Int) = 0 - ^ -varargs.scala:21: error: A method with a varargs annotation produces a forwarder method with the same signature (a: Int, b: Array[String])Int as an existing method. - @varargs def v2(a: Int, b: String*) = 0 - ^ -3 errors found diff --git a/tests/untried/neg/varargs.scala b/tests/untried/neg/varargs.scala deleted file mode 100644 index ce7f0d71fee8..000000000000 --- a/tests/untried/neg/varargs.scala +++ /dev/null @@ -1,27 +0,0 @@ - - - -import annotation.varargs - - - -// Failing varargs annotation -object Test { - - trait A { - def v1(a: Int, b: Array[String]) = a - } - - trait B extends A { - @varargs def v1(a: Int, b: String*) = a + b.length - } - - @varargs def nov(a: Int) = 0 - @varargs def v(a: Int, b: String*) = a + b.length - @varargs def v2(a: Int, b: String*) = 0 - def v2(a: Int, b: Array[String]) = 0 - - def main(args: Array[String]): Unit = { - } - -} From 06c0ef7572fb1022f71796a2752495d1933767ea Mon Sep 17 00:00:00 2001 From: Guillaume Raffin Date: Fri, 10 Jul 2020 10:55:43 +0200 Subject: [PATCH 09/12] Update sconfig submodule --- .gitmodules | 2 +- community-build/community-projects/sconfig | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index 6cf9427b78b5..99cfc8fe442d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -67,7 +67,7 @@ url = https://github.com/dotty-staging/upickle.git [submodule "community-build/community-projects/sconfig"] path = community-build/community-projects/sconfig - url = https://github.com/ekrich/sconfig.git + url = https://github.com/dotty-staging/sconfig.git [submodule "community-build/community-projects/zio"] path = community-build/community-projects/zio url = https://github.com/dotty-staging/zio.git diff --git a/community-build/community-projects/sconfig b/community-build/community-projects/sconfig index a1579a935f79..38d620bdab66 160000 --- a/community-build/community-projects/sconfig +++ b/community-build/community-projects/sconfig @@ -1 +1 @@ -Subproject commit a1579a935f79d34c66a813f0cf71a674c092179b +Subproject commit 38d620bdab6656850ab0358583d2bb3638f83150 From b828c69194a1d68b7de7594f541a9c070164cf8c Mon Sep 17 00:00:00 2001 From: Guillaume Raffin Date: Sat, 11 Jul 2020 19:31:18 +0200 Subject: [PATCH 10/12] Replace "bridge" by more correct "forwarder" --- .../tools/dotc/transform/ElimRepeated.scala | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala b/compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala index e36f315c169c..c53b5fdd6119 100644 --- a/compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala +++ b/compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala @@ -28,7 +28,7 @@ class ElimRepeated extends MiniPhase with InfoTransformer { thisPhase => override def phaseName: String = ElimRepeated.name - override def changesMembers: Boolean = true // the phase adds vararg bridges + override def changesMembers: Boolean = true // the phase adds vararg forwarders def transformInfo(tp: Type, sym: Symbol)(using Context): Type = elimRepeated(tp) @@ -37,7 +37,7 @@ class ElimRepeated extends MiniPhase with InfoTransformer { thisPhase => super.transform(ref) match case ref1: SymDenotation if (ref1 ne ref) && overridesJava(ref1.symbol) => // This method won't override the corresponding Java method at the end of this phase, - // only the bridge added by `addVarArgsBridge` will. + // only the forwarder added by `addVarArgsForwarder` will. ref1.copySymDenotation(initFlags = ref1.flags &~ Override) case ref1 => ref1 @@ -131,7 +131,7 @@ class ElimRepeated extends MiniPhase with InfoTransformer { thisPhase => sym.sourcePos) tree else - addVarArgsBridge(tree, isOverride) + addVarArgsForwarder(tree, isOverride) else tree else @@ -157,46 +157,46 @@ class ElimRepeated extends MiniPhase with InfoTransformer { thisPhase => case pt: PolyType => isValidJavaVarArgs(pt.resultType) case _ => - throw new Exception("Match error in @varargs bridge logic. This should not happen, please open an issue " + tp) + throw new Exception("Match error in @varargs checks. This should not happen, please open an issue " + tp) - /** Add a Java varargs bridge - * @param ddef the original method definition - * @param addFlag the flag to add to the method symbol - - * @return a thicket consisting of `ddef` and a varargs bridge method - * which forwards java varargs to `ddef`. It retains all the + /** Add a Java varargs forwarder + * @param ddef the original method definition + * @param isBridge true if we are generating a "bridge" (synthetic override forwarder) + * + * @return a thicket consisting of `ddef` and an additional method + * that forwards java varargs to `ddef`. It retains all the * flags of `ddef` except `Private`. * - * A bridge is necessary because the following hold: + * A forwarder is necessary because the following hold: * - the varargs in `ddef` will change from `RepeatedParam[T]` to `Seq[T]` after this phase * - _but_ the callers of `ddef` expect its varargs to be changed to `Array[? <: T]` - * The solution is to add a "bridge" method that converts its argument from `Array[? <: T]` to `Seq[T]` and + * The solution is to add a method that converts its argument from `Array[? <: T]` to `Seq[T]` and * forwards it to `ddef`. */ - private def addVarArgsBridge(ddef: DefDef, javaOverride: Boolean)(using ctx: Context): Tree = + private def addVarArgsForwarder(ddef: DefDef, isBridge: Boolean)(using ctx: Context): Tree = val original = ddef.symbol.asTerm // For simplicity we always set the varargs flag // although it's not strictly necessary for overrides // (but it is for non-overrides) val flags = ddef.symbol.flags | JavaVarargs - // The java-compatible bridge symbol - val bridge = original.copy( + // The java-compatible forwarder symbol + val sym = original.copy( // non-overrides cannot be synthetic otherwise javac refuses to call them - flags = if javaOverride then flags | Artifact else flags, + flags = if isBridge then flags | Artifact else flags, info = toJavaVarArgs(ddef.symbol.info) ).asTerm - currentClass.info.member(bridge.name).alternatives.find { s => - s.matches(bridge) && - !(javaOverride && s.asSymDenotation.is(JavaDefined)) + currentClass.info.member(sym.name).alternatives.find { s => + s.matches(sym) && + !(isBridge && s.asSymDenotation.is(JavaDefined)) } match case Some(conflict) => ctx.error(s"@varargs produces a forwarder method that conflicts with ${conflict.showDcl}", original.sourcePos) ddef case None => - val bridgeDef = polyDefDef(bridge.enteredAfter(thisPhase), trefs => vrefss => { + val bridgeDef = polyDefDef(sym.enteredAfter(thisPhase), trefs => vrefss => { val init :+ (last :+ vararg) = vrefss // Can't call `.argTypes` here because the underlying array type is of the // form `Array[? <: SomeType]`, so we need `.argInfos` to get the `TypeBounds`. From 20754c2d7e223a76eb0d12ea8728a1a8cfa90cbe Mon Sep 17 00:00:00 2001 From: Guillaume Raffin Date: Sun, 12 Jul 2020 00:13:59 +0200 Subject: [PATCH 11/12] Remove atNextPhase and use atPhase only where necessary --- .../tools/dotc/transform/ElimRepeated.scala | 88 ++++++++++--------- 1 file changed, 47 insertions(+), 41 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala b/compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala index c53b5fdd6119..c2effb6fc7b0 100644 --- a/compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala +++ b/compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala @@ -115,30 +115,34 @@ class ElimRepeated extends MiniPhase with InfoTransformer { thisPhase => * Also transform trees inside method annotation. */ override def transformDefDef(tree: DefDef)(using Context): Tree = - atPhase(thisPhase) { - val sym = tree.symbol - val hasAnnotation = hasVarargsAnnotation(sym) - if hasRepeatedParams(sym) then - val isOverride = overridesJava(sym) - if isOverride || hasAnnotation || parentHasAnnotation(sym) then - // java varargs are more restrictive than scala's - // see https://github.com/scala/bug/issues/11714 - if !isValidJavaVarArgs(sym.info) then - ctx.error("""To generate java-compatible varargs: - | - there must be a single repeated parameter - | - it must be the last argument in the last parameter list - |""".stripMargin, - sym.sourcePos) - tree - else - addVarArgsForwarder(tree, isOverride) - else + val sym = tree.symbol + val hasAnnotation = hasVarargsAnnotation(sym) + + // atPhase(thisPhase) is used where necessary to see the repeated + // parameters before their elimination + val hasRepeatedParam = atPhase(thisPhase)(hasRepeatedParams(sym)) + if hasRepeatedParam then + val isOverride = atPhase(thisPhase)(overridesJava(sym)) + if isOverride || hasAnnotation || parentHasAnnotation(sym) then + // java varargs are more restrictive than scala's + // see https://github.com/scala/bug/issues/11714 + val validJava = atPhase(thisPhase)(isValidJavaVarArgs(sym.info)) + if !validJava then + ctx.error("""To generate java-compatible varargs: + | - there must be a single repeated parameter + | - it must be the last argument in the last parameter list + |""".stripMargin, + sym.sourcePos) tree + else + // non-overrides cannot be synthetic otherwise javac refuses to call them + addVarArgsForwarder(tree, isBridge = isOverride) else - if hasAnnotation then - ctx.error("A method without repeated parameters cannot be annotated with @varargs", sym.sourcePos) tree - } + else + if hasAnnotation then + ctx.error("A method without repeated parameters cannot be annotated with @varargs", sym.sourcePos) + tree /** Is there a repeated parameter in some parameter list? */ private def hasRepeatedParams(sym: Symbol)(using Context): Boolean = @@ -175,23 +179,30 @@ class ElimRepeated extends MiniPhase with InfoTransformer { thisPhase => * forwards it to `ddef`. */ private def addVarArgsForwarder(ddef: DefDef, isBridge: Boolean)(using ctx: Context): Tree = - val original = ddef.symbol.asTerm - // For simplicity we always set the varargs flag - // although it's not strictly necessary for overrides - // (but it is for non-overrides) - val flags = ddef.symbol.flags | JavaVarargs + val original = ddef.symbol // The java-compatible forwarder symbol - val sym = original.copy( - // non-overrides cannot be synthetic otherwise javac refuses to call them + val sym = atPhase(thisPhase) { + // Capture the flags before they get modified by #transform. + // For simplicity we always set the varargs flag, + // although it's not strictly necessary for overrides. + val flags = original.flags | JavaVarargs + original.copy( flags = if isBridge then flags | Artifact else flags, info = toJavaVarArgs(ddef.symbol.info) ).asTerm + } - currentClass.info.member(sym.name).alternatives.find { s => - s.matches(sym) && - !(isBridge && s.asSymDenotation.is(JavaDefined)) - } match + // Find a method that would conflict with the forwarder if the latter existed. + // This needs to be done at thisPhase so that parent @varargs don't conflict. + val conflict = atPhase(thisPhase) { + currentClass.info.member(sym.name).alternatives.find { s => + s.matches(sym) && + !(isBridge && s.asSymDenotation.is(JavaDefined)) + } + } + + conflict match case Some(conflict) => ctx.error(s"@varargs produces a forwarder method that conflicts with ${conflict.showDcl}", original.sourcePos) ddef @@ -201,15 +212,10 @@ class ElimRepeated extends MiniPhase with InfoTransformer { thisPhase => // Can't call `.argTypes` here because the underlying array type is of the // form `Array[? <: SomeType]`, so we need `.argInfos` to get the `TypeBounds`. val elemtp = vararg.tpe.widen.argInfos.head - - // The generation of the forwarding call needs to be deferred, otherwise - // generic and curried methods won't pass the tree checker. - atNextPhase { - ref(original.termRef) - .appliedToTypes(trefs) - .appliedToArgss(init) - .appliedToArgs(last :+ tpd.wrapArray(vararg, elemtp)) - } + ref(original.termRef) + .appliedToTypes(trefs) + .appliedToArgss(init) + .appliedToArgs(last :+ tpd.wrapArray(vararg, elemtp)) }) Thicket(ddef, bridgeDef) From d8100b272e2f6a7d449f2c221915de2ecad4b2d6 Mon Sep 17 00:00:00 2001 From: Guillaume Raffin Date: Sun, 12 Jul 2020 00:16:49 +0200 Subject: [PATCH 12/12] Add more varargs tests (again) --- tests/neg/varargs-annot.scala | 1 + tests/run/vararg-extend-java/Vararg.java | 3 ++ .../vararg-extend-java/VarargAbstract.java | 4 +++ tests/run/vararg-extend-java/test.scala | 28 +++++++++++++++++++ 4 files changed, 36 insertions(+) create mode 100644 tests/run/vararg-extend-java/Vararg.java create mode 100644 tests/run/vararg-extend-java/VarargAbstract.java create mode 100644 tests/run/vararg-extend-java/test.scala diff --git a/tests/neg/varargs-annot.scala b/tests/neg/varargs-annot.scala index f5629e7ae027..085c211e4081 100644 --- a/tests/neg/varargs-annot.scala +++ b/tests/neg/varargs-annot.scala @@ -13,6 +13,7 @@ object Test { @varargs def nov(a: Int) = 0 // error: A method without repeated parameters cannot be annotated with @varargs @varargs def v(a: Int, b: String*) = a + b.length // ok + def v(a: Int, b: String) = a // ok @varargs def v2(a: Int, b: String*) = 0 // error def v2(a: Int, b: Array[String]) = 0 diff --git a/tests/run/vararg-extend-java/Vararg.java b/tests/run/vararg-extend-java/Vararg.java new file mode 100644 index 000000000000..c3f286640b7b --- /dev/null +++ b/tests/run/vararg-extend-java/Vararg.java @@ -0,0 +1,3 @@ +public class Vararg { + void v(int... i) {} +} diff --git a/tests/run/vararg-extend-java/VarargAbstract.java b/tests/run/vararg-extend-java/VarargAbstract.java new file mode 100644 index 000000000000..be9565f3eaef --- /dev/null +++ b/tests/run/vararg-extend-java/VarargAbstract.java @@ -0,0 +1,4 @@ +public abstract class VarargAbstract { + public abstract void v1(String... s); + public abstract void v2(String... s); +} diff --git a/tests/run/vararg-extend-java/test.scala b/tests/run/vararg-extend-java/test.scala new file mode 100644 index 000000000000..39e3c632124c --- /dev/null +++ b/tests/run/vararg-extend-java/test.scala @@ -0,0 +1,28 @@ +import scala.annotation.varargs + +class VarargImpl extends VarargAbstract { + def v1(s: String*) = () + + override def v2(s: String*) = () +} + +class VarargSub extends Vararg { + override def v(i: Int*) = () +} + +object Test { + def main(args: Array[String]): Unit = + val a: VarargAbstract = VarargImpl() + a.v1("a", "b", "c") + a.v2("a", "b", "c") + + val i: VarargImpl = VarargImpl() + i.v1("a", "b", "c") + i.v2("a", "b", "c") + + val b: Vararg = VarargSub() + b.v(1, 2, 3) + + val c: VarargSub = VarargSub() + c.v(1, 2, 3) +}