Skip to content

Commit 38553ce

Browse files
authored
Merge pull request #9271 from TheElectronWill/varargs-annotation
2 parents a162b7b + d8100b2 commit 38553ce

File tree

18 files changed

+331
-101
lines changed

18 files changed

+331
-101
lines changed

.gitmodules

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@
6767
url = https://github.com/dotty-staging/upickle.git
6868
[submodule "community-build/community-projects/sconfig"]
6969
path = community-build/community-projects/sconfig
70-
url = https://github.com/ekrich/sconfig.git
70+
url = https://github.com/dotty-staging/sconfig.git
7171
[submodule "community-build/community-projects/zio"]
7272
path = community-build/community-projects/zio
7373
url = https://github.com/dotty-staging/zio.git

compiler/src/dotty/tools/dotc/core/Definitions.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -829,6 +829,7 @@ class Definitions {
829829
@tu lazy val FunctionalInterfaceAnnot: ClassSymbol = ctx.requiredClass("java.lang.FunctionalInterface")
830830
@tu lazy val InfixAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.infix")
831831
@tu lazy val AlphaAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.alpha")
832+
@tu lazy val VarargsAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.varargs")
832833

833834
// A list of annotations that are commonly used to indicate that a field/method argument or return
834835
// type is not null. These annotations are used by the nullification logic in JavaNullInterop to

compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala

Lines changed: 140 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -28,138 +28,221 @@ class ElimRepeated extends MiniPhase with InfoTransformer { thisPhase =>
2828

2929
override def phaseName: String = ElimRepeated.name
3030

31-
override def changesMembers: Boolean = true // the phase adds vararg bridges
31+
override def changesMembers: Boolean = true // the phase adds vararg forwarders
3232

3333
def transformInfo(tp: Type, sym: Symbol)(using Context): Type =
3434
elimRepeated(tp)
3535

3636
override def transform(ref: SingleDenotation)(using Context): SingleDenotation =
37-
super.transform(ref) match {
37+
super.transform(ref) match
3838
case ref1: SymDenotation if (ref1 ne ref) && overridesJava(ref1.symbol) =>
3939
// This method won't override the corresponding Java method at the end of this phase,
40-
// only the bridge added by `addVarArgsBridge` will.
40+
// only the forwarder added by `addVarArgsForwarder` will.
4141
ref1.copySymDenotation(initFlags = ref1.flags &~ Override)
4242
case ref1 =>
4343
ref1
44-
}
4544

4645
override def mayChange(sym: Symbol)(using Context): Boolean = sym.is(Method)
4746

4847
private def overridesJava(sym: Symbol)(using Context) = sym.allOverriddenSymbols.exists(_.is(JavaDefined))
4948

50-
private def elimRepeated(tp: Type)(using Context): Type = tp.stripTypeVar match {
49+
private def hasVarargsAnnotation(sym: Symbol)(using Context) = sym.hasAnnotation(defn.VarargsAnnot)
50+
51+
private def parentHasAnnotation(sym: Symbol)(using Context) = sym.allOverriddenSymbols.exists(hasVarargsAnnotation)
52+
53+
/** Eliminate repeated parameters from method types. */
54+
private def elimRepeated(tp: Type)(using Context): Type = tp.stripTypeVar match
5155
case tp @ MethodTpe(paramNames, paramTypes, resultType) =>
5256
val resultType1 = elimRepeated(resultType)
5357
val paramTypes1 =
54-
if (paramTypes.nonEmpty && paramTypes.last.isRepeatedParam) {
58+
if paramTypes.nonEmpty && paramTypes.last.isRepeatedParam then
5559
val last = paramTypes.last.translateFromRepeated(toArray = tp.isJavaMethod)
5660
paramTypes.init :+ last
57-
}
5861
else paramTypes
5962
tp.derivedLambdaType(paramNames, paramTypes1, resultType1)
6063
case tp: PolyType =>
6164
tp.derivedLambdaType(tp.paramNames, tp.paramInfos, elimRepeated(tp.resultType))
6265
case tp =>
6366
tp
64-
}
6567

6668
def transformTypeOfTree(tree: Tree)(using Context): Tree =
6769
tree.withType(elimRepeated(tree.tpe))
6870

71+
override def transformTypeApply(tree: TypeApply)(using Context): Tree =
72+
transformTypeOfTree(tree)
73+
6974
override def transformIdent(tree: Ident)(using Context): Tree =
7075
transformTypeOfTree(tree)
7176

7277
override def transformSelect(tree: Select)(using Context): Tree =
7378
transformTypeOfTree(tree)
7479

75-
override def transformApply(tree: Apply)(using Context): Tree = {
80+
override def transformApply(tree: Apply)(using Context): Tree =
7681
val args = tree.args.mapConserve {
7782
case arg: Typed if isWildcardStarArg(arg) =>
7883
val isJavaDefined = tree.fun.symbol.is(JavaDefined)
7984
val tpe = arg.expr.tpe
80-
if (isJavaDefined && tpe.derivesFrom(defn.SeqClass))
85+
if isJavaDefined && tpe.derivesFrom(defn.SeqClass) then
8186
seqToArray(arg.expr)
82-
else if (!isJavaDefined && tpe.derivesFrom(defn.ArrayClass))
87+
else if !isJavaDefined && tpe.derivesFrom(defn.ArrayClass)
8388
arrayToSeq(arg.expr)
8489
else
8590
arg.expr
8691
case arg => arg
8792
}
8893
transformTypeOfTree(cpy.Apply(tree)(tree.fun, args))
89-
}
9094

9195
/** Convert sequence argument to Java array */
92-
private def seqToArray(tree: Tree)(using Context): Tree = tree match {
96+
private def seqToArray(tree: Tree)(using Context): Tree = tree match
9397
case SeqLiteral(elems, elemtpt) =>
9498
JavaSeqLiteral(elems, elemtpt)
9599
case _ =>
96100
val elemType = tree.tpe.elemType
97101
var elemClass = erasure(elemType).classSymbol
98-
if (defn.NotRuntimeClasses.contains(elemClass)) elemClass = defn.ObjectClass
102+
if defn.NotRuntimeClasses.contains(elemClass) then
103+
elemClass = defn.ObjectClass
104+
end if
99105
ref(defn.DottyArraysModule)
100106
.select(nme.seqToArray)
101107
.appliedToType(elemType)
102108
.appliedTo(tree, clsOf(elemClass.typeRef))
103-
}
104109

105110
/** Convert Java array argument to Scala Seq */
106111
private def arrayToSeq(tree: Tree)(using Context): Tree =
107112
tpd.wrapArray(tree, tree.tpe.elemType)
108113

109-
override def transformTypeApply(tree: TypeApply)(using Context): Tree =
110-
transformTypeOfTree(tree)
111-
112-
/** If method overrides a Java varargs method, add a varargs bridge.
113-
* Also transform trees inside method annotation
114+
/** If method overrides a Java varargs method or is annotated with @varargs, add a varargs bridge.
115+
* Also transform trees inside method annotation.
114116
*/
115117
override def transformDefDef(tree: DefDef)(using Context): Tree =
116-
atPhase(thisPhase) {
117-
if (tree.symbol.info.isVarArgsMethod && overridesJava(tree.symbol))
118-
addVarArgsBridge(tree)
118+
val sym = tree.symbol
119+
val hasAnnotation = hasVarargsAnnotation(sym)
120+
121+
// atPhase(thisPhase) is used where necessary to see the repeated
122+
// parameters before their elimination
123+
val hasRepeatedParam = atPhase(thisPhase)(hasRepeatedParams(sym))
124+
if hasRepeatedParam then
125+
val isOverride = atPhase(thisPhase)(overridesJava(sym))
126+
if isOverride || hasAnnotation || parentHasAnnotation(sym) then
127+
// java varargs are more restrictive than scala's
128+
// see https://github.com/scala/bug/issues/11714
129+
val validJava = atPhase(thisPhase)(isValidJavaVarArgs(sym.info))
130+
if !validJava then
131+
ctx.error("""To generate java-compatible varargs:
132+
| - there must be a single repeated parameter
133+
| - it must be the last argument in the last parameter list
134+
|""".stripMargin,
135+
sym.sourcePos)
136+
tree
137+
else
138+
// non-overrides cannot be synthetic otherwise javac refuses to call them
139+
addVarArgsForwarder(tree, isBridge = isOverride)
119140
else
120141
tree
121-
}
142+
else
143+
if hasAnnotation then
144+
ctx.error("A method without repeated parameters cannot be annotated with @varargs", sym.sourcePos)
145+
tree
146+
147+
/** Is there a repeated parameter in some parameter list? */
148+
private def hasRepeatedParams(sym: Symbol)(using Context): Boolean =
149+
sym.info.paramInfoss.flatten.exists(_.isRepeatedParam)
122150

123-
/** Add a Java varargs bridge
124-
* @param ddef the original method definition which is assumed to override
125-
* a Java varargs method JM up to this phase.
126-
* @return a thicket consisting of `ddef` and a varargs bridge method
127-
* which overrides the Java varargs method JM from this phase on
128-
* and forwards to `ddef`.
151+
/** Is this the type of a method that has a repeated parameter type as
152+
* its last parameter in the last parameter list?
153+
*/
154+
private def isValidJavaVarArgs(tp: Type)(using Context): Boolean = tp match
155+
case mt: MethodType =>
156+
val initp :+ lastp = mt.paramInfoss
157+
initp.forall(_.forall(!_.isRepeatedParam)) &&
158+
lastp.nonEmpty &&
159+
lastp.init.forall(!_.isRepeatedParam) &&
160+
lastp.last.isRepeatedParam
161+
case pt: PolyType =>
162+
isValidJavaVarArgs(pt.resultType)
163+
case _ =>
164+
throw new Exception("Match error in @varargs checks. This should not happen, please open an issue " + tp)
165+
166+
167+
/** Add a Java varargs forwarder
168+
* @param ddef the original method definition
169+
* @param isBridge true if we are generating a "bridge" (synthetic override forwarder)
170+
*
171+
* @return a thicket consisting of `ddef` and an additional method
172+
* that forwards java varargs to `ddef`. It retains all the
173+
* flags of `ddef` except `Private`.
129174
*
130-
* A bridge is necessary because the following hold
175+
* A forwarder is necessary because the following hold:
131176
* - the varargs in `ddef` will change from `RepeatedParam[T]` to `Seq[T]` after this phase
132-
* - _but_ the callers of `ddef` expect its varargs to be changed to `Array[? <: T]`, since it overrides
133-
* a Java varargs
134-
* The solution is to add a "bridge" method that converts its argument from `Array[? <: T]` to `Seq[T]` and
177+
* - _but_ the callers of `ddef` expect its varargs to be changed to `Array[? <: T]`
178+
* The solution is to add a method that converts its argument from `Array[? <: T]` to `Seq[T]` and
135179
* forwards it to `ddef`.
136180
*/
137-
private def addVarArgsBridge(ddef: DefDef)(using Context): Tree = {
138-
val original = ddef.symbol.asTerm
139-
val bridge = original.copy(
140-
flags = ddef.symbol.flags &~ Private | Artifact,
141-
info = toJavaVarArgs(ddef.symbol.info)).enteredAfter(thisPhase).asTerm
142-
val bridgeDef = polyDefDef(bridge, trefs => vrefss => {
143-
val (vrefs :+ varArgRef) :: vrefss1 = vrefss
144-
// Can't call `.argTypes` here because the underlying array type is of the
145-
// form `Array[? <: SomeType]`, so we need `.argInfos` to get the `TypeBounds`.
146-
val elemtp = varArgRef.tpe.widen.argInfos.head
147-
ref(original.termRef)
148-
.appliedToTypes(trefs)
149-
.appliedToArgs(vrefs :+ tpd.wrapArray(varArgRef, elemtp))
150-
.appliedToArgss(vrefss1)
151-
})
152-
153-
Thicket(ddef, bridgeDef)
154-
}
181+
private def addVarArgsForwarder(ddef: DefDef, isBridge: Boolean)(using ctx: Context): Tree =
182+
val original = ddef.symbol
183+
184+
// The java-compatible forwarder symbol
185+
val sym = atPhase(thisPhase) {
186+
// Capture the flags before they get modified by #transform.
187+
// For simplicity we always set the varargs flag,
188+
// although it's not strictly necessary for overrides.
189+
val flags = original.flags | JavaVarargs
190+
original.copy(
191+
flags = if isBridge then flags | Artifact else flags,
192+
info = toJavaVarArgs(ddef.symbol.info)
193+
).asTerm
194+
}
195+
196+
// Find a method that would conflict with the forwarder if the latter existed.
197+
// This needs to be done at thisPhase so that parent @varargs don't conflict.
198+
val conflict = atPhase(thisPhase) {
199+
currentClass.info.member(sym.name).alternatives.find { s =>
200+
s.matches(sym) &&
201+
!(isBridge && s.asSymDenotation.is(JavaDefined))
202+
}
203+
}
204+
205+
conflict match
206+
case Some(conflict) =>
207+
ctx.error(s"@varargs produces a forwarder method that conflicts with ${conflict.showDcl}", original.sourcePos)
208+
ddef
209+
case None =>
210+
val bridgeDef = polyDefDef(sym.enteredAfter(thisPhase), trefs => vrefss => {
211+
val init :+ (last :+ vararg) = vrefss
212+
// Can't call `.argTypes` here because the underlying array type is of the
213+
// form `Array[? <: SomeType]`, so we need `.argInfos` to get the `TypeBounds`.
214+
val elemtp = vararg.tpe.widen.argInfos.head
215+
ref(original.termRef)
216+
.appliedToTypes(trefs)
217+
.appliedToArgss(init)
218+
.appliedToArgs(last :+ tpd.wrapArray(vararg, elemtp))
219+
})
220+
Thicket(ddef, bridgeDef)
155221

156222
/** Convert type from Scala to Java varargs method */
157-
private def toJavaVarArgs(tp: Type)(using Context): Type = tp match {
223+
private def toJavaVarArgs(tp: Type)(using Context): Type = tp match
158224
case tp: PolyType =>
159225
tp.derivedLambdaType(tp.paramNames, tp.paramInfos, toJavaVarArgs(tp.resultType))
160226
case tp: MethodType =>
161-
val inits :+ last = tp.paramInfos
162-
val last1 = last.translateFromRepeated(toArray = true)
163-
tp.derivedLambdaType(tp.paramNames, inits :+ last1, tp.resultType)
164-
}
227+
tp.resultType match
228+
case m: MethodType => // multiple param lists
229+
tp.derivedLambdaType(tp.paramNames, tp.paramInfos, toJavaVarArgs(m))
230+
case _ =>
231+
val init :+ last = tp.paramInfos
232+
val vararg = varargArrayType(last)
233+
tp.derivedLambdaType(tp.paramNames, init :+ vararg, tp.resultType)
234+
235+
/** Translate a repeated type T* to an `Array[? <: Upper]`
236+
* such that it is compatible with java varargs.
237+
*
238+
* When necessary we set `Upper = T & AnyRef`
239+
* to prevent the erasure of `Array[? <: Upper]` to Object,
240+
* which would break the varargs from Java.
241+
*/
242+
private def varargArrayType(tp: Type)(using Context): Type =
243+
val array = tp.translateFromRepeated(toArray = true) // Array[? <: T]
244+
val element = array.elemType.hiBound // T
245+
246+
if element <:< defn.AnyRefType || element.typeSymbol.isPrimitiveValueClass then array
247+
else defn.ArrayOf(TypeBounds.upper(AndType(element, defn.AnyRefType))) // Array[? <: T & AnyRef]
165248
}

compiler/src/dotty/tools/dotc/transform/GenericSignatures.scala

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -186,10 +186,11 @@ object GenericSignatures {
186186
case defn.ArrayOf(elemtp) =>
187187
if (isUnboundedGeneric(elemtp))
188188
jsig(defn.ObjectType)
189-
else {
189+
else
190190
builder.append(ClassfileConstants.ARRAY_TAG)
191-
jsig(elemtp)
192-
}
191+
elemtp match
192+
case TypeBounds(lo, hi) => jsig(hi.widenDealias)
193+
case _ => jsig(elemtp)
193194

194195
case RefOrAppliedType(sym, pre, args) =>
195196
if (sym == defn.PairClass && tp.tupleArity > Definitions.MaxTupleArity)
@@ -313,8 +314,8 @@ object GenericSignatures {
313314
* - If the list contains one or more occurrences of scala.Array with
314315
* type parameters El1, El2, ... then the dominator is scala.Array with
315316
* type parameter of intersectionDominator(List(El1, El2, ...)). <--- @PP: not yet in spec.
316-
* - Otherwise, the list is reduced to a subsequence containing only types
317-
* which are not subtypes of other listed types (the span.)
317+
* - Otherwise, the list is reduced to a subsequence containing only the
318+
* types that are not supertypes of other listed types (the span.)
318319
* - If the span is empty, the dominator is Object.
319320
* - If the span contains a class Tc which is not a trait and which is
320321
* not Object, the dominator is Tc. <--- @PP: "which is not Object" not in spec.

compiler/test/dotc/run-test-pickling.blacklist

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,5 @@ i7868.scala
2626
enum-java
2727
zero-arity-case-class.scala
2828
tuple-ops.scala
29+
i7212
30+
varargs-abstract

tests/neg/varargs-annot.scala

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import annotation.varargs
2+
3+
// Failing varargs annotation
4+
object Test {
5+
6+
trait A {
7+
def v1(a: Int, b: Array[String]) = a
8+
}
9+
10+
trait B extends A {
11+
@varargs def v1(a: Int, b: String*) = a + b.length // error
12+
}
13+
14+
@varargs def nov(a: Int) = 0 // error: A method without repeated parameters cannot be annotated with @varargs
15+
@varargs def v(a: Int, b: String*) = a + b.length // ok
16+
def v(a: Int, b: String) = a // ok
17+
18+
@varargs def v2(a: Int, b: String*) = 0 // error
19+
def v2(a: Int, b: Array[String]) = 0
20+
21+
@varargs def v3(a: String*)(b: Int) = b + a.length // error
22+
@varargs def v4(a: String)(b: Int) = b + a.length // error
23+
@varargs def v5(a: String)(b: Int*) = a + b.sum // ok
24+
25+
@varargs def v6: Int = 1 // error
26+
@varargs def v7(i: Int*)() = i.sum // error
27+
28+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import scala.annotation.varargs
2+
3+
object VarArgs {
4+
@varargs
5+
def two(a: Int)(b: String*): Nothing = ???
6+
7+
@varargs
8+
def twoPrimitive(a: Int)(b: Int*): Nothing = ???
9+
10+
@varargs
11+
def three(a3: Int)(b3: String)(c3: String*): Nothing = ???
12+
13+
@varargs
14+
def threePrimitive(a3: Int)(b3: String)(c3: Int*): Nothing = ???
15+
16+
@varargs
17+
def emptyOk()()(xs: String*): Nothing = ???
18+
}

0 commit comments

Comments
 (0)