Skip to content

Commit 0535f59

Browse files
Merge pull request #9474 from TheElectronWill/override-varargs
Fix #9463: create varargs forwarder symbols in transform instead of transformDefDef
2 parents 1af1f46 + 61f860a commit 0535f59

File tree

16 files changed

+161
-140
lines changed

16 files changed

+161
-140
lines changed

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

Lines changed: 101 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,14 @@ package transform
44
import core._
55
import StdNames.nme
66
import Types._
7-
import dotty.tools.dotc.transform.MegaPhase._
7+
import transform.MegaPhase._
88
import ast.Trees._
99
import Flags._
1010
import Contexts._
1111
import Symbols._
1212
import Constants._
1313
import Decorators._
1414
import Denotations._, SymDenotations._
15-
import dotty.tools.dotc.ast.tpd
1615
import TypeErasure.erasure
1716
import DenotTransformers._
1817

@@ -34,12 +33,43 @@ class ElimRepeated extends MiniPhase with InfoTransformer { thisPhase =>
3433
def transformInfo(tp: Type, sym: Symbol)(using Context): Type =
3534
elimRepeated(tp)
3635

36+
/** Create forwarder symbols for the methods that are annotated
37+
* with `@varargs` or that override java varargs.
38+
*
39+
* The definitions (DefDef) for these symbols are created by transformDefDef.
40+
*/
3741
override def transform(ref: SingleDenotation)(using Context): SingleDenotation =
42+
def transformVarArgs(sym: Symbol, isJavaVarargsOverride: Boolean): Unit =
43+
val hasAnnotation = hasVarargsAnnotation(sym)
44+
val hasRepeatedParam = hasRepeatedParams(sym)
45+
if hasRepeatedParam then
46+
if isJavaVarargsOverride || hasAnnotation || parentHasAnnotation(sym) then
47+
// java varargs are more restrictive than scala's
48+
// see https://github.com/scala/bug/issues/11714
49+
val validJava = isValidJavaVarArgs(sym.info)
50+
if !validJava then
51+
report.error("""To generate java-compatible varargs:
52+
| - there must be a single repeated parameter
53+
| - it must be the last argument in the last parameter list
54+
|""".stripMargin,
55+
sym.sourcePos)
56+
else
57+
addVarArgsForwarder(sym, isJavaVarargsOverride, hasAnnotation)
58+
else if hasAnnotation
59+
report.error("A method without repeated parameters cannot be annotated with @varargs", sym.sourcePos)
60+
end
61+
3862
super.transform(ref) match
39-
case ref1: SymDenotation if (ref1 ne ref) && overridesJava(ref1.symbol) =>
40-
// This method won't override the corresponding Java method at the end of this phase,
41-
// only the forwarder added by `addVarArgsForwarder` will.
42-
ref1.copySymDenotation(initFlags = ref1.flags &~ Override)
63+
case ref1: SymDenotation if ref1.is(Method, butNot = JavaDefined) =>
64+
val sym = ref1.symbol
65+
val isJavaVarargsOverride = (ref1 ne ref) && overridesJava(sym)
66+
transformVarArgs(sym, isJavaVarargsOverride)
67+
if isJavaVarargsOverride then
68+
// This method won't override the corresponding Java method at the end of this phase,
69+
// only the forwarder added by `addVarArgsForwarder` will.
70+
ref1.copySymDenotation(initFlags = ref1.flags &~ Override)
71+
else
72+
ref1
4373
case ref1 =>
4474
ref1
4575

@@ -51,6 +81,11 @@ class ElimRepeated extends MiniPhase with InfoTransformer { thisPhase =>
5181

5282
private def parentHasAnnotation(sym: Symbol)(using Context) = sym.allOverriddenSymbols.exists(hasVarargsAnnotation)
5383

84+
private def isVarargsMethod(sym: Symbol)(using Context) =
85+
hasVarargsAnnotation(sym) ||
86+
hasRepeatedParams(sym) &&
87+
(sym.allOverriddenSymbols.exists(s => s.is(JavaDefined) || hasVarargsAnnotation(s)))
88+
5489
/** Eliminate repeated parameters from method types. */
5590
private def elimRepeated(tp: Type)(using Context): Type = tp.stripTypeVar match
5691
case tp @ MethodTpe(paramNames, paramTypes, resultType) =>
@@ -162,39 +197,36 @@ class ElimRepeated extends MiniPhase with InfoTransformer { thisPhase =>
162197

163198
/** Convert an Array into a scala.Seq */
164199
private def arrayToSeq(tree: Tree)(using Context): Tree =
165-
tpd.wrapArray(tree, tree.tpe.elemType)
200+
wrapArray(tree, tree.tpe.elemType)
166201

167-
/** If method overrides a Java varargs method or is annotated with @varargs, add a varargs bridge.
168-
* Also transform trees inside method annotation.
169-
*/
202+
/** Generate the method definitions for the varargs forwarders created in transform */
170203
override def transformDefDef(tree: DefDef)(using Context): Tree =
204+
// If transform reported an error, don't go further
205+
if ctx.reporter.hasErrors then
206+
return tree
207+
171208
val sym = tree.symbol
172-
val hasAnnotation = hasVarargsAnnotation(sym)
173-
174-
// atPhase(thisPhase) is used where necessary to see the repeated
175-
// parameters before their elimination
176-
val hasRepeatedParam = atPhase(thisPhase)(hasRepeatedParams(sym))
177-
if hasRepeatedParam then
178-
val isOverride = atPhase(thisPhase)(overridesJava(sym))
179-
if isOverride || hasAnnotation || parentHasAnnotation(sym) then
180-
// java varargs are more restrictive than scala's
181-
// see https://github.com/scala/bug/issues/11714
182-
val validJava = atPhase(thisPhase)(isValidJavaVarArgs(sym.info))
183-
if !validJava then
184-
report.error("""To generate java-compatible varargs:
185-
| - there must be a single repeated parameter
186-
| - it must be the last argument in the last parameter list
187-
|""".stripMargin,
188-
sym.sourcePos)
189-
tree
190-
else
191-
// non-overrides cannot be synthetic otherwise javac refuses to call them
192-
addVarArgsForwarder(tree, isBridge = isOverride)
193-
else
194-
tree
209+
val isVarArgs = atPhase(thisPhase)(isVarargsMethod(sym))
210+
if isVarArgs then
211+
// Get the symbol generated in transform
212+
val forwarderType = atPhase(thisPhase)(toJavaVarArgs(sym.info))
213+
val forwarderSym = currentClass.info.decl(sym.name).alternatives
214+
.find(_.info.matches(forwarderType))
215+
.get
216+
.symbol.asTerm
217+
// Generate the method
218+
val forwarderDef = polyDefDef(forwarderSym, trefs => vrefss => {
219+
val init :+ (last :+ vararg) = vrefss
220+
// Can't call `.argTypes` here because the underlying array type is of the
221+
// form `Array[? <: SomeType]`, so we need `.argInfos` to get the `TypeBounds`.
222+
val elemtp = vararg.tpe.widen.argInfos.head
223+
ref(sym.termRef)
224+
.appliedToTypes(trefs)
225+
.appliedToArgss(init)
226+
.appliedToArgs(last :+ wrapArray(vararg, elemtp))
227+
})
228+
Thicket(tree, forwarderDef)
195229
else
196-
if hasAnnotation then
197-
report.error("A method without repeated parameters cannot be annotated with @varargs", sym.sourcePos)
198230
tree
199231

200232
/** Is there a repeated parameter in some parameter list? */
@@ -216,61 +248,52 @@ class ElimRepeated extends MiniPhase with InfoTransformer { thisPhase =>
216248
case _ =>
217249
throw new Exception("Match error in @varargs checks. This should not happen, please open an issue " + tp)
218250

219-
220-
/** Add a Java varargs forwarder
221-
* @param ddef the original method definition
222-
* @param isBridge true if we are generating a "bridge" (synthetic override forwarder)
251+
/** Add the symbol of a Java varargs forwarder to the scope.
252+
* It retains all the flags of the original method.
223253
*
224-
* @return a thicket consisting of `ddef` and an additional method
225-
* that forwards java varargs to `ddef`. It retains all the
226-
* flags of `ddef` except `Private`.
254+
* @param original the original method symbol
255+
* @param isBridge true if we are generating a "bridge" (synthetic override forwarder)
227256
*
228-
* A forwarder is necessary because the following hold:
229-
* - the varargs in `ddef` will change from `RepeatedParam[T]` to `Seq[T]` after this phase
230-
* - _but_ the callers of `ddef` expect its varargs to be changed to `Array[? <: T]`
257+
* A forwarder is necessary because the following holds:
258+
* - the varargs in `original` will change from `RepeatedParam[T]` to `Seq[T]` after this phase
259+
* - _but_ the callers of the method expect its varargs to be changed to `Array[? <: T]`
231260
* The solution is to add a method that converts its argument from `Array[? <: T]` to `Seq[T]` and
232-
* forwards it to `ddef`.
261+
* forwards it to the original method.
233262
*/
234-
private def addVarArgsForwarder(ddef: DefDef, isBridge: Boolean)(using Context): Tree =
235-
val original = ddef.symbol
263+
private def addVarArgsForwarder(original: Symbol, isBridge: Boolean, hasAnnotation: Boolean)(using Context): Unit =
264+
val owner = original.owner
265+
if !owner.isClass then
266+
report.error("inner methods cannot be annotated with @varargs", original.sourcePos)
267+
return
268+
269+
val classInfo = owner.info
270+
271+
// For simplicity we always set the varargs flag,
272+
// although it's not strictly necessary for overrides.
273+
val flags = original.flags | JavaVarargs
236274

237275
// The java-compatible forwarder symbol
238-
val sym = atPhase(thisPhase) {
239-
// Capture the flags before they get modified by #transform.
240-
// For simplicity we always set the varargs flag,
241-
// although it's not strictly necessary for overrides.
242-
val flags = original.flags | JavaVarargs
276+
val forwarder =
243277
original.copy(
244278
flags = if isBridge then flags | Artifact else flags,
245-
info = toJavaVarArgs(ddef.symbol.info)
279+
info = toJavaVarArgs(original.info)
246280
).asTerm
247-
}
248281

249-
// Find a method that would conflict with the forwarder if the latter existed.
282+
// Find methods that would conflict with the forwarder if the latter existed.
250283
// This needs to be done at thisPhase so that parent @varargs don't conflict.
251-
val conflict = atPhase(thisPhase) {
252-
currentClass.info.member(sym.name).alternatives.find { s =>
253-
s.matches(sym) &&
254-
!(isBridge && s.asSymDenotation.is(JavaDefined))
284+
val conflicts =
285+
classInfo.member(original.name).altsWith { s =>
286+
s.matches(forwarder) && !(isBridge && s.is(JavaDefined))
255287
}
256-
}
257-
258-
conflict match
259-
case Some(conflict) =>
260-
report.error(s"@varargs produces a forwarder method that conflicts with ${conflict.showDcl}", original.sourcePos)
261-
ddef
262-
case None =>
263-
val bridgeDef = polyDefDef(sym.enteredAfter(thisPhase), trefs => vrefss => {
264-
val init :+ (last :+ vararg) = vrefss
265-
// Can't call `.argTypes` here because the underlying array type is of the
266-
// form `Array[? <: SomeType]`, so we need `.argInfos` to get the `TypeBounds`.
267-
val elemtp = vararg.tpe.widen.argInfos.head
268-
ref(original.termRef)
269-
.appliedToTypes(trefs)
270-
.appliedToArgss(init)
271-
.appliedToArgs(last :+ tpd.wrapArray(vararg, elemtp))
272-
})
273-
Thicket(ddef, bridgeDef)
288+
conflicts match
289+
case conflict :: _ =>
290+
val src =
291+
if hasAnnotation then "@varargs"
292+
else if isBridge then "overriding a java varargs method"
293+
else "@varargs (on overriden method)"
294+
report.error(s"$src produces a forwarder method that conflicts with ${conflict.showDcl}", original.sourcePos)
295+
case Nil =>
296+
forwarder.enteredAfter(thisPhase)
274297

275298
/** Convert type from Scala to Java varargs method */
276299
private def toJavaVarArgs(tp: Type)(using Context): Type = tp match

tests/disabled/java-interop/failing/t1459/AbstractBase.java

Lines changed: 0 additions & 5 deletions
This file was deleted.

tests/disabled/java-interop/failing/t1459/App.scala

Lines changed: 0 additions & 18 deletions
This file was deleted.

tests/disabled/java-interop/failing/t2569/Child.scala

Lines changed: 0 additions & 9 deletions
This file was deleted.

tests/disabled/java-interop/failing/t2569/Parent.java

Lines changed: 0 additions & 13 deletions
This file was deleted.

tests/disabled/java-interop/failing/varargs-bridge/A.java

Lines changed: 0 additions & 8 deletions
This file was deleted.

tests/disabled/java-interop/failing/varargs-bridge/B.scala

Lines changed: 0 additions & 7 deletions
This file was deleted.

tests/neg/varargs-annot.scala

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,15 @@ object Test {
1111
@varargs def v1(a: Int, b: String*) = a + b.length // error
1212
}
1313

14+
trait C {
15+
@varargs def v(i: Int*) = ()
16+
}
17+
18+
class D extends C {
19+
override def v(i: Int*) = () // error
20+
def v(i: Array[Int]) = () // error
21+
}
22+
1423
@varargs def nov(a: Int) = 0 // error: A method without repeated parameters cannot be annotated with @varargs
1524
@varargs def v(a: Int, b: String*) = a + b.length // ok
1625
def v(a: Int, b: String) = a // ok
@@ -25,4 +34,10 @@ object Test {
2534
@varargs def v6: Int = 1 // error
2635
@varargs def v7(i: Int*)() = i.sum // error
2736

37+
def f() =
38+
@varargs def inner(s: String*) = () // error
39+
inner("wrong")
40+
2841
}
42+
43+
@varargs def topLevel(s: String*) = () // ok
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import scala.annotation.varargs
2+
3+
abstract class Abs {
4+
@varargs def counter(s: String*) = ()
5+
}
6+
7+
trait T {
8+
@varargs def counter(s: String*): Unit
9+
}
10+
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import scala.annotation.varargs
2+
3+
class Impl extends Abs {
4+
override def counter(s: String*): Unit = ()
5+
}
6+
7+
trait B extends T {
8+
override def counter(s: String*): Unit = ()
9+
}
10+
11+
class C extends B {
12+
override def counter(s: String*) = ()
13+
}
14+
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package base;
2+
3+
public abstract class AbstractBase {
4+
public abstract void doStuff(Object... params);
5+
}

tests/disabled/java-interop/failing/t1459/Caller.java renamed to tests/run/varargs-extend-java-2/Caller.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
public class Caller {
44
public void callDoStuff(AbstractBase impl) {
5-
impl.doStuff("abc"); // was new Object());
5+
impl.doStuff("abc");
6+
impl.doStuff(new Object());
67
}
7-
}
8+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import base._
2+
3+
object Test extends App {
4+
class Concrete extends AbstractBase {
5+
override def doStuff(params: AnyRef*): Unit = println("doStuff invoked")
6+
}
7+
8+
val impl = Concrete()
9+
impl.doStuff(null)
10+
11+
val caller = Caller()
12+
caller.callDoStuff(impl)
13+
}

0 commit comments

Comments
 (0)