@@ -28,138 +28,221 @@ class ElimRepeated extends MiniPhase with InfoTransformer { thisPhase =>
28
28
29
29
override def phaseName : String = ElimRepeated .name
30
30
31
- override def changesMembers : Boolean = true // the phase adds vararg bridges
31
+ override def changesMembers : Boolean = true // the phase adds vararg forwarders
32
32
33
33
def transformInfo (tp : Type , sym : Symbol )(using Context ): Type =
34
34
elimRepeated(tp)
35
35
36
36
override def transform (ref : SingleDenotation )(using Context ): SingleDenotation =
37
- super .transform(ref) match {
37
+ super .transform(ref) match
38
38
case ref1 : SymDenotation if (ref1 ne ref) && overridesJava(ref1.symbol) =>
39
39
// 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.
41
41
ref1.copySymDenotation(initFlags = ref1.flags &~ Override )
42
42
case ref1 =>
43
43
ref1
44
- }
45
44
46
45
override def mayChange (sym : Symbol )(using Context ): Boolean = sym.is(Method )
47
46
48
47
private def overridesJava (sym : Symbol )(using Context ) = sym.allOverriddenSymbols.exists(_.is(JavaDefined ))
49
48
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
51
55
case tp @ MethodTpe (paramNames, paramTypes, resultType) =>
52
56
val resultType1 = elimRepeated(resultType)
53
57
val paramTypes1 =
54
- if ( paramTypes.nonEmpty && paramTypes.last.isRepeatedParam) {
58
+ if paramTypes.nonEmpty && paramTypes.last.isRepeatedParam then
55
59
val last = paramTypes.last.translateFromRepeated(toArray = tp.isJavaMethod)
56
60
paramTypes.init :+ last
57
- }
58
61
else paramTypes
59
62
tp.derivedLambdaType(paramNames, paramTypes1, resultType1)
60
63
case tp : PolyType =>
61
64
tp.derivedLambdaType(tp.paramNames, tp.paramInfos, elimRepeated(tp.resultType))
62
65
case tp =>
63
66
tp
64
- }
65
67
66
68
def transformTypeOfTree (tree : Tree )(using Context ): Tree =
67
69
tree.withType(elimRepeated(tree.tpe))
68
70
71
+ override def transformTypeApply (tree : TypeApply )(using Context ): Tree =
72
+ transformTypeOfTree(tree)
73
+
69
74
override def transformIdent (tree : Ident )(using Context ): Tree =
70
75
transformTypeOfTree(tree)
71
76
72
77
override def transformSelect (tree : Select )(using Context ): Tree =
73
78
transformTypeOfTree(tree)
74
79
75
- override def transformApply (tree : Apply )(using Context ): Tree = {
80
+ override def transformApply (tree : Apply )(using Context ): Tree =
76
81
val args = tree.args.mapConserve {
77
82
case arg : Typed if isWildcardStarArg(arg) =>
78
83
val isJavaDefined = tree.fun.symbol.is(JavaDefined )
79
84
val tpe = arg.expr.tpe
80
- if ( isJavaDefined && tpe.derivesFrom(defn.SeqClass ))
85
+ if isJavaDefined && tpe.derivesFrom(defn.SeqClass ) then
81
86
seqToArray(arg.expr)
82
- else if ( ! isJavaDefined && tpe.derivesFrom(defn.ArrayClass ) )
87
+ else if ! isJavaDefined && tpe.derivesFrom(defn.ArrayClass )
83
88
arrayToSeq(arg.expr)
84
89
else
85
90
arg.expr
86
91
case arg => arg
87
92
}
88
93
transformTypeOfTree(cpy.Apply (tree)(tree.fun, args))
89
- }
90
94
91
95
/** 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
93
97
case SeqLiteral (elems, elemtpt) =>
94
98
JavaSeqLiteral (elems, elemtpt)
95
99
case _ =>
96
100
val elemType = tree.tpe.elemType
97
101
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
99
105
ref(defn.DottyArraysModule )
100
106
.select(nme.seqToArray)
101
107
.appliedToType(elemType)
102
108
.appliedTo(tree, clsOf(elemClass.typeRef))
103
- }
104
109
105
110
/** Convert Java array argument to Scala Seq */
106
111
private def arrayToSeq (tree : Tree )(using Context ): Tree =
107
112
tpd.wrapArray(tree, tree.tpe.elemType)
108
113
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.
114
116
*/
115
117
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)
119
140
else
120
141
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)
122
150
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`.
129
174
*
130
- * A bridge is necessary because the following hold
175
+ * A forwarder is necessary because the following hold:
131
176
* - 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
135
179
* forwards it to `ddef`.
136
180
*/
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)
155
221
156
222
/** 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
158
224
case tp : PolyType =>
159
225
tp.derivedLambdaType(tp.paramNames, tp.paramInfos, toJavaVarArgs(tp.resultType))
160
226
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]
165
248
}
0 commit comments