Skip to content

Commit f0b6763

Browse files
committed
Three fixes for SAM type handling
The first two fixes concern characterization of SAM types. One condition of a SAM type is that it can be instantiated with an empty argument list. This was implemented incorrectly. First, we missed the case where the SAM type is a trait with a parent class that takes arguments. In this case the SAM type _cannot_ be instantiated with an empty argument list. Second, we missed the case where the SAM type constructor has a single vararg parameter. In this case the SAM type _can_ be instantiated with an empty argument list. The second case was also translated incorrectly which led to illegal bytecodes. Fixes #15855
1 parent 83efd23 commit f0b6763

File tree

6 files changed

+105
-31
lines changed

6 files changed

+105
-31
lines changed

compiler/src/dotty/tools/dotc/ast/tpd.scala

+56-19
Original file line numberDiff line numberDiff line change
@@ -315,24 +315,53 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
315315
def TypeDef(sym: TypeSymbol)(using Context): TypeDef =
316316
ta.assignType(untpd.TypeDef(sym.name, TypeTree(sym.info)), sym)
317317

318-
def ClassDef(cls: ClassSymbol, constr: DefDef, body: List[Tree], superArgs: List[Tree] = Nil)(using Context): TypeDef = {
318+
/** Create a class definition
319+
* @param cls the class symbol of the created class
320+
* @param constr its primary constructor
321+
* @param body the statements in its template
322+
* @param superArgs the arguments to pass to the superclass constructor
323+
* @param adaptVarargs if true, allow matching a vararg superclass constructor
324+
* with a missing argument in superArgs, and synthesize an
325+
* empty repeated parameter in the supercall in this case
326+
*/
327+
def ClassDef(cls: ClassSymbol, constr: DefDef, body: List[Tree],
328+
superArgs: List[Tree] = Nil, adaptVarargs: Boolean = false)(using Context): TypeDef =
319329
val firstParent :: otherParents = cls.info.parents: @unchecked
330+
331+
def isApplicable(constr: Symbol): Boolean =
332+
def recur(ctpe: Type): Boolean = ctpe match
333+
case ctpe: PolyType =>
334+
recur(ctpe.instantiate(firstParent.argTypes))
335+
case ctpe: MethodType =>
336+
var paramInfos = ctpe.paramInfos
337+
if adaptVarargs && paramInfos.length == superArgs.length + 1
338+
&& atPhaseNoLater(Phases.elimRepeatedPhase)(constr.info.isVarArgsMethod)
339+
then // accept missing argument for varargs parameter
340+
paramInfos = paramInfos.init
341+
superArgs.corresponds(paramInfos)(_.tpe <:< _)
342+
case _ =>
343+
false
344+
recur(constr.info)
345+
346+
def adaptedSuperArgs(ctpe: Type): List[Tree] = ctpe match
347+
case ctpe: PolyType =>
348+
adaptedSuperArgs(ctpe.instantiate(firstParent.argTypes))
349+
case ctpe: MethodType
350+
if ctpe.paramInfos.length == superArgs.length + 1 =>
351+
// last argument must be a vararg, otherwise isApplicable would have failed
352+
superArgs :+
353+
repeated(Nil, TypeTree(ctpe.paramInfos.last.argInfos.head, inferred = true))
354+
case _ =>
355+
superArgs
356+
320357
val superRef =
321-
if (cls.is(Trait)) TypeTree(firstParent)
322-
else {
323-
def isApplicable(ctpe: Type): Boolean = ctpe match {
324-
case ctpe: PolyType =>
325-
isApplicable(ctpe.instantiate(firstParent.argTypes))
326-
case ctpe: MethodType =>
327-
(superArgs corresponds ctpe.paramInfos)(_.tpe <:< _)
328-
case _ =>
329-
false
330-
}
331-
val constr = firstParent.decl(nme.CONSTRUCTOR).suchThat(constr => isApplicable(constr.info))
332-
New(firstParent, constr.symbol.asTerm, superArgs)
333-
}
358+
if cls.is(Trait) then TypeTree(firstParent)
359+
else
360+
val constr = firstParent.decl(nme.CONSTRUCTOR).suchThat(isApplicable)
361+
New(firstParent, constr.symbol.asTerm, adaptedSuperArgs(constr.info))
362+
334363
ClassDefWithParents(cls, constr, superRef :: otherParents.map(TypeTree(_)), body)
335-
}
364+
end ClassDef
336365

337366
def ClassDefWithParents(cls: ClassSymbol, constr: DefDef, parents: List[Tree], body: List[Tree])(using Context): TypeDef = {
338367
val selfType =
@@ -359,13 +388,18 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
359388
* @param parents a non-empty list of class types
360389
* @param termForwarders a non-empty list of forwarding definitions specified by their name and the definition they forward to.
361390
* @param typeMembers a possibly-empty list of type members specified by their name and their right hand side.
391+
* @param adaptVarargs if true, allow matching a vararg superclass constructor
392+
* with a missing argument in superArgs, and synthesize an
393+
* empty repeated parameter in the supercall in this case
362394
*
363395
* The class has the same owner as the first function in `termForwarders`.
364396
* Its position is the union of all symbols in `termForwarders`.
365397
*/
366-
def AnonClass(parents: List[Type], termForwarders: List[(TermName, TermSymbol)],
367-
typeMembers: List[(TypeName, TypeBounds)] = Nil)(using Context): Block = {
368-
AnonClass(termForwarders.head._2.owner, parents, termForwarders.map(_._2.span).reduceLeft(_ union _)) { cls =>
398+
def AnonClass(parents: List[Type],
399+
termForwarders: List[(TermName, TermSymbol)],
400+
typeMembers: List[(TypeName, TypeBounds)],
401+
adaptVarargs: Boolean)(using Context): Block = {
402+
AnonClass(termForwarders.head._2.owner, parents, termForwarders.map(_._2.span).reduceLeft(_ union _), adaptVarargs) { cls =>
369403
def forwarder(name: TermName, fn: TermSymbol) = {
370404
val fwdMeth = fn.copy(cls, name, Synthetic | Method | Final).entered.asTerm
371405
for overridden <- fwdMeth.allOverriddenSymbols do
@@ -385,6 +419,9 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
385419
* with the specified owner and position.
386420
*/
387421
def AnonClass(owner: Symbol, parents: List[Type], coord: Coord)(body: ClassSymbol => List[Tree])(using Context): Block =
422+
AnonClass(owner, parents, coord, adaptVarargs = false)(body)
423+
424+
private def AnonClass(owner: Symbol, parents: List[Type], coord: Coord, adaptVarargs: Boolean)(body: ClassSymbol => List[Tree])(using Context): Block =
388425
val parents1 =
389426
if (parents.head.classSymbol.is(Trait)) {
390427
val head = parents.head.parents.head
@@ -393,7 +430,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
393430
else parents
394431
val cls = newNormalizedClassSymbol(owner, tpnme.ANON_CLASS, Synthetic | Final, parents1, coord = coord)
395432
val constr = newConstructor(cls, Synthetic, Nil, Nil).entered
396-
val cdef = ClassDef(cls, DefDef(constr), body(cls))
433+
val cdef = ClassDef(cls, DefDef(constr), body(cls), Nil, adaptVarargs)
397434
Block(cdef :: Nil, New(cls.typeRef, Nil))
398435

399436
def Import(expr: Tree, selectors: List[untpd.ImportSelector])(using Context): Import =

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -532,7 +532,7 @@ object Phases {
532532
def sbtExtractAPIPhase(using Context): Phase = ctx.base.sbtExtractAPIPhase
533533
def picklerPhase(using Context): Phase = ctx.base.picklerPhase
534534
def inliningPhase(using Context): Phase = ctx.base.inliningPhase
535-
def stagingPhase(using Context): Phase = ctx.base.stagingPhase
535+
def stagingPhase(using Context): Phase = ctx.base.stagingPhase
536536
def splicingPhase(using Context): Phase = ctx.base.splicingPhase
537537
def firstTransformPhase(using Context): Phase = ctx.base.firstTransformPhase
538538
def refchecksPhase(using Context): Phase = ctx.base.refchecksPhase

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

+12-7
Original file line numberDiff line numberDiff line change
@@ -5945,16 +5945,21 @@ object Types extends TypeUtils {
59455945
def samClass(tp: Type)(using Context): Symbol = tp match
59465946
case tp: ClassInfo =>
59475947
def zeroParams(tp: Type): Boolean = tp.stripPoly match
5948-
case mt: MethodType => mt.paramInfos.isEmpty && !mt.resultType.isInstanceOf[MethodType]
5948+
case mt: MethodType =>
5949+
val noArgsNeeded = mt.paramInfos match
5950+
case Nil => true
5951+
case info :: Nil => info.isRepeatedParam
5952+
case _ => false
5953+
noArgsNeeded && !mt.resultType.isInstanceOf[MethodType]
59495954
case et: ExprType => true
59505955
case _ => false
5951-
val cls = tp.cls
5952-
val validCtor =
5956+
def validCtor(cls: Symbol): Boolean =
59535957
val ctor = cls.primaryConstructor
5954-
// `ContextFunctionN` does not have constructors
5955-
!ctor.exists || zeroParams(ctor.info)
5956-
val isInstantiable = !cls.isOneOf(FinalOrSealed) && (tp.appliedRef <:< tp.selfType)
5957-
if validCtor && isInstantiable then tp.cls
5958+
(!ctor.exists || zeroParams(ctor.info)) // `ContextFunctionN` does not have constructors
5959+
&& (!cls.is(Trait) || validCtor(cls.info.parents.head.classSymbol))
5960+
def isInstantiable =
5961+
!tp.cls.isOneOf(FinalOrSealed) && (tp.appliedRef <:< tp.selfType)
5962+
if validCtor(tp.cls) && isInstantiable then tp.cls
59585963
else NoSymbol
59595964
case tp: AppliedType =>
59605965
samClass(tp.superType)

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

+6-4
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,12 @@ class ExpandSAMs extends MiniPhase:
6969
val tpe1 = collectAndStripRefinements(tpe)
7070
val Seq(samDenot) = tpe1.possibleSamMethods
7171
cpy.Block(tree)(stats,
72-
AnonClass(List(tpe1),
73-
List(samDenot.symbol.asTerm.name -> fn.symbol.asTerm),
74-
refinements.toList
75-
)
72+
transformFollowingDeep:
73+
AnonClass(List(tpe1),
74+
List(samDenot.symbol.asTerm.name -> fn.symbol.asTerm),
75+
refinements.toList,
76+
adaptVarargs = true
77+
)
7678
)
7779
}
7880
case _ =>

tests/neg/i15855.scala

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// crash.scala
2+
import scala.language.implicitConversions
3+
4+
class MyFunction(args: String)
5+
6+
trait MyFunction0[+R] extends MyFunction {
7+
def apply(): R
8+
}
9+
10+
def fromFunction0[R](f: Function0[R]): MyFunction0[R] = () => f() // error

tests/run/i15855.scala

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// crash.scala
2+
import scala.language.implicitConversions
3+
4+
class MyFunction(args: String*)
5+
6+
trait MyFunction0[+R] extends MyFunction {
7+
def apply(): R
8+
}
9+
10+
abstract class MyFunction1[R](args: R*):
11+
def apply(): R
12+
13+
def fromFunction0[R](f: Function0[R]): MyFunction0[R] = () => f()
14+
def fromFunction1[R](f: Function0[R]): MyFunction1[R] = () => f()
15+
16+
@main def Test =
17+
val m0: MyFunction0[Int] = fromFunction0(() => 1)
18+
val m1: MyFunction1[Int] = fromFunction1(() => 2)
19+
assert(m0() == 1)
20+
assert(m1() == 2)

0 commit comments

Comments
 (0)