@@ -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}
0 commit comments