Skip to content

Commit f671a0d

Browse files
Detect wrong usages of @varargs + tests
1 parent f3c4f8c commit f671a0d

File tree

3 files changed

+93
-22
lines changed

3 files changed

+93
-22
lines changed

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

Lines changed: 65 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ class ElimRepeated extends MiniPhase with InfoTransformer { thisPhase =>
6868
def transformTypeOfTree(tree: Tree)(using Context): Tree =
6969
tree.withType(elimRepeated(tree.tpe))
7070

71+
override def transformTypeApply(tree: TypeApply)(using Context): Tree =
72+
transformTypeOfTree(tree)
73+
7174
override def transformIdent(tree: Ident)(using Context): Tree =
7275
transformTypeOfTree(tree)
7376

@@ -108,25 +111,52 @@ class ElimRepeated extends MiniPhase with InfoTransformer { thisPhase =>
108111
private def arrayToSeq(tree: Tree)(using Context): Tree =
109112
tpd.wrapArray(tree, tree.tpe.elemType)
110113

111-
override def transformTypeApply(tree: TypeApply)(using Context): Tree =
112-
transformTypeOfTree(tree)
113-
114114
/** If method overrides a Java varargs method or is annotated with @varargs, add a varargs bridge.
115115
* Also transform trees inside method annotation.
116116
*/
117117
override def transformDefDef(tree: DefDef)(using Context): Tree =
118118
atPhase(thisPhase) {
119119
val sym = tree.symbol
120-
val isOverride = overridesJava(sym)
121-
val hasAnnotation = hasVarargsAnnotation(sym) || parentHasAnnotation(sym)
122-
if tree.symbol.info.isVarArgsMethod && (isOverride || hasAnnotation) then
123-
// non-overrides need the varargs bytecode flag and cannot be synthetic
124-
// otherwise javac refuses to call them.
125-
addVarArgsBridge(tree, isOverride)
120+
val hasAnnotation = hasVarargsAnnotation(sym)
121+
if hasRepeatedParams(sym) then
122+
val isOverride = overridesJava(sym)
123+
if isOverride || hasAnnotation || parentHasAnnotation(sym) then
124+
// java varargs are more restrictive than scala's
125+
// see https://github.com/scala/bug/issues/11714
126+
if !isValidJavaVarArgs(sym.info) then
127+
ctx.error("""To generate java-compatible varargs:
128+
| - there must be a single repeated parameter
129+
| - it must be the last argument in the last parameter list
130+
|""".stripMargin,
131+
tree.sourcePos)
132+
tree
133+
else
134+
addVarArgsBridge(tree, isOverride)
135+
else
136+
tree
126137
else
138+
if hasAnnotation then
139+
ctx.error("A method without repeated parameters cannot be annotated with @varargs", tree.sourcePos)
127140
tree
128141
}
129142

143+
/** Is there a repeated parameter in some parameter list? */
144+
private def hasRepeatedParams(sym: Symbol)(using Context): Boolean =
145+
sym.info.paramInfoss.flatten.exists(_.isRepeatedParam)
146+
147+
/** Is this the type of a method that has a repeated parameter type as
148+
* its last parameter in the last parameter list?
149+
*/
150+
private def isValidJavaVarArgs(t: Type)(using Context): Boolean = t match
151+
case mt: MethodType =>
152+
val initp :+ lastp = mt.paramInfoss
153+
initp.forall(_.forall(!_.isRepeatedParam)) &&
154+
lastp.nonEmpty &&
155+
lastp.init.forall(!_.isRepeatedParam) &&
156+
lastp.last.isRepeatedParam
157+
case _ => false
158+
159+
130160
/** Add a Java varargs bridge
131161
* @param ddef the original method definition
132162
* @param addFlag the flag to add to the method symbol
@@ -141,38 +171,52 @@ class ElimRepeated extends MiniPhase with InfoTransformer { thisPhase =>
141171
* The solution is to add a "bridge" method that converts its argument from `Array[? <: T]` to `Seq[T]` and
142172
* forwards it to `ddef`.
143173
*/
144-
private def addVarArgsBridge(ddef: DefDef, synthetic: Boolean)(using Context): Tree =
174+
private def addVarArgsBridge(ddef: DefDef, javaOverride: Boolean)(using ctx: Context): Tree =
145175
val original = ddef.symbol.asTerm
146176
// For simplicity we always set the varargs flag
147177
// although it's not strictly necessary for overrides
148-
val flags = ddef.symbol.flags | JavaVarargs &~ Private
178+
// (but it is for non-overrides)
179+
val flags = ddef.symbol.flags | JavaVarargs
149180
val bridge = original.copy(
150-
flags = if synthetic then flags | Artifact else flags,
181+
// non-overrides cannot be synthetic otherwise javac refuses to call them
182+
flags = if javaOverride then flags | Artifact else flags,
151183
info = toJavaVarArgs(ddef.symbol.info)
152184
).enteredAfter(thisPhase).asTerm
153185

154186
val bridgeDef = polyDefDef(bridge, trefs => vrefss => {
155-
val (vrefs :+ varArgRef) :: vrefss1 = vrefss
187+
val init :+ (last :+ vararg) = vrefss
156188
// Can't call `.argTypes` here because the underlying array type is of the
157189
// form `Array[? <: SomeType]`, so we need `.argInfos` to get the `TypeBounds`.
158-
val elemtp = varArgRef.tpe.widen.argInfos.head
190+
val elemtp = vararg.tpe.widen.argInfos.head
159191
ref(original.termRef)
160192
.appliedToTypes(trefs)
161-
.appliedToArgs(vrefs :+ tpd.wrapArray(varArgRef, elemtp))
162-
.appliedToArgss(vrefss1)
193+
.appliedToArgss(init)
194+
.appliedToArgs(last :+ tpd.wrapArray(vararg, elemtp))
163195
})
164196

165-
Thicket(ddef, bridgeDef)
197+
val bridgeDenot = bridge.denot
198+
currentClass.info.member(bridge.name).alternatives.find { s =>
199+
s.matches(bridgeDenot) &&
200+
!(s.asSymDenotation.is(JavaDefined) && javaOverride)
201+
} match
202+
case Some(conflict) =>
203+
ctx.error(s"@varargs produces a forwarder method that conflicts with ${conflict.showDcl}", ddef.sourcePos)
204+
EmptyTree
205+
case None =>
206+
Thicket(ddef, bridgeDef)
166207

167208
/** Convert type from Scala to Java varargs method */
168209
private def toJavaVarArgs(tp: Type)(using Context): Type = tp match
169210
case tp: PolyType =>
170211
tp.derivedLambdaType(tp.paramNames, tp.paramInfos, toJavaVarArgs(tp.resultType))
171212
case tp: MethodType =>
172-
val inits :+ last = tp.paramInfos
173-
val vararg = varargArrayType(last)
174-
val params = inits :+ vararg
175-
tp.derivedLambdaType(tp.paramNames, params, tp.resultType)
213+
tp.resultType match
214+
case m: MethodType => // multiple param lists
215+
tp.derivedLambdaType(tp.paramNames, tp.paramInfos, toJavaVarArgs(m))
216+
case _ =>
217+
val init :+ last = tp.paramInfos
218+
val vararg = varargArrayType(last)
219+
tp.derivedLambdaType(tp.paramNames, init :+ vararg, tp.resultType)
176220

177221
/** Translate a repeated type T* to an `Array[? <: Upper]`
178222
* such that it is compatible with java varargs.

tests/neg/varargs-annot.scala

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
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 { // error (could we get rid of that one?)
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+
17+
@varargs def v2(a: Int, b: String*) = 0 // error
18+
def v2(a: Int, b: Array[String]) = 0
19+
20+
@varargs def v3(a: String*)(b: Int) = b + a.length // error
21+
@varargs def v4(a: String)(b: Int) = b + a.length // error
22+
@varargs def v5(a: String)(b: Int*) = a + b.sum // ok
23+
24+
@varargs def v6: Int = 1 // error
25+
@varargs def v7(i: Int*)() = i.sum // error
26+
27+
}

tests/run/varargs-abstract/Test.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ public static void main(String[] args) {
88
c.y("a", "b", "c");
99
c.z("a", "b", "c");
1010

11-
VarargAbstractClass i = new ClassImplementsClass();
11+
VarargAbstractClass<String> i = new ClassImplementsClass();
1212

1313
i.x("a", "b", "c");
1414
i.y("a", "b", "c");

0 commit comments

Comments
 (0)