Skip to content

Commit 9bf82f9

Browse files
authored
Three fixes to SAM type handling (#21596)
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
2 parents 6e852d2 + 3af67ba commit 9bf82f9

File tree

7 files changed

+120
-34
lines changed

7 files changed

+120
-34
lines changed

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

+44-19
Original file line numberDiff line numberDiff line change
@@ -315,24 +315,41 @@ 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 adaptedSuperArgs(ctpe: Type): List[Tree] = ctpe match
332+
case ctpe: PolyType =>
333+
adaptedSuperArgs(ctpe.instantiate(firstParent.argTypes))
334+
case ctpe: MethodType
335+
if ctpe.paramInfos.length == superArgs.length + 1 =>
336+
// last argument must be a vararg, otherwise isApplicable would have failed
337+
superArgs :+
338+
repeated(Nil, TypeTree(ctpe.paramInfos.last.argInfos.head, inferred = true))
339+
case _ =>
340+
superArgs
341+
320342
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-
}
343+
if cls.is(Trait) then TypeTree(firstParent)
344+
else
345+
val parentConstr = firstParent.applicableConstructors(superArgs.tpes, adaptVarargs) match
346+
case Nil => assert(false, i"no applicable parent constructor of $firstParent for supercall arguments $superArgs")
347+
case constr :: Nil => constr
348+
case _ => assert(false, i"multiple applicable parent constructors of $firstParent for supercall arguments $superArgs")
349+
New(firstParent, parentConstr.asTerm, adaptedSuperArgs(parentConstr.info))
350+
334351
ClassDefWithParents(cls, constr, superRef :: otherParents.map(TypeTree(_)), body)
335-
}
352+
end ClassDef
336353

337354
def ClassDefWithParents(cls: ClassSymbol, constr: DefDef, parents: List[Tree], body: List[Tree])(using Context): TypeDef = {
338355
val selfType =
@@ -359,13 +376,18 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
359376
* @param parents a non-empty list of class types
360377
* @param termForwarders a non-empty list of forwarding definitions specified by their name and the definition they forward to.
361378
* @param typeMembers a possibly-empty list of type members specified by their name and their right hand side.
379+
* @param adaptVarargs if true, allow matching a vararg superclass constructor
380+
* with a missing argument in superArgs, and synthesize an
381+
* empty repeated parameter in the supercall in this case
362382
*
363383
* The class has the same owner as the first function in `termForwarders`.
364384
* Its position is the union of all symbols in `termForwarders`.
365385
*/
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 =>
386+
def AnonClass(parents: List[Type],
387+
termForwarders: List[(TermName, TermSymbol)],
388+
typeMembers: List[(TypeName, TypeBounds)],
389+
adaptVarargs: Boolean)(using Context): Block = {
390+
AnonClass(termForwarders.head._2.owner, parents, termForwarders.map(_._2.span).reduceLeft(_ union _), adaptVarargs) { cls =>
369391
def forwarder(name: TermName, fn: TermSymbol) = {
370392
val fwdMeth = fn.copy(cls, name, Synthetic | Method | Final).entered.asTerm
371393
for overridden <- fwdMeth.allOverriddenSymbols do
@@ -385,6 +407,9 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
385407
* with the specified owner and position.
386408
*/
387409
def AnonClass(owner: Symbol, parents: List[Type], coord: Coord)(body: ClassSymbol => List[Tree])(using Context): Block =
410+
AnonClass(owner, parents, coord, adaptVarargs = false)(body)
411+
412+
private def AnonClass(owner: Symbol, parents: List[Type], coord: Coord, adaptVarargs: Boolean)(body: ClassSymbol => List[Tree])(using Context): Block =
388413
val parents1 =
389414
if (parents.head.classSymbol.is(Trait)) {
390415
val head = parents.head.parents.head
@@ -393,7 +418,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
393418
else parents
394419
val cls = newNormalizedClassSymbol(owner, tpnme.ANON_CLASS, Synthetic | Final, parents1, coord = coord)
395420
val constr = newConstructor(cls, Synthetic, Nil, Nil).entered
396-
val cdef = ClassDef(cls, DefDef(constr), body(cls))
421+
val cdef = ClassDef(cls, DefDef(constr), body(cls), Nil, adaptVarargs)
397422
Block(cdef :: Nil, New(cls.typeRef, Nil))
398423

399424
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/TypeUtils.scala

+26
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import Names.{Name, TermName}
88
import Constants.Constant
99

1010
import Names.Name
11+
import StdNames.nme
1112
import config.Feature
1213

1314
class TypeUtils:
@@ -189,5 +190,30 @@ class TypeUtils:
189190
def stripRefinement: Type = self match
190191
case self: RefinedOrRecType => self.parent.stripRefinement
191192
case seld => self
193+
194+
/** The constructors of this type that are applicable to `argTypes`, without needing
195+
* an implicit conversion. Curried constructors are always excluded.
196+
* @param adaptVarargs if true, allow a constructor with just a varargs argument to
197+
* match an empty argument list.
198+
*/
199+
def applicableConstructors(argTypes: List[Type], adaptVarargs: Boolean)(using Context): List[Symbol] =
200+
def isApplicable(constr: Symbol): Boolean =
201+
def recur(ctpe: Type): Boolean = ctpe match
202+
case ctpe: PolyType =>
203+
if argTypes.isEmpty then recur(ctpe.resultType) // no need to know instances
204+
else recur(ctpe.instantiate(self.argTypes))
205+
case ctpe: MethodType =>
206+
var paramInfos = ctpe.paramInfos
207+
if adaptVarargs && paramInfos.length == argTypes.length + 1
208+
&& atPhaseNoLater(Phases.elimRepeatedPhase)(constr.info.isVarArgsMethod)
209+
then // accept missing argument for varargs parameter
210+
paramInfos = paramInfos.init
211+
argTypes.corresponds(paramInfos)(_ <:< _) && !ctpe.resultType.isInstanceOf[MethodType]
212+
case _ =>
213+
false
214+
recur(constr.info)
215+
216+
self.decl(nme.CONSTRUCTOR).altsWith(isApplicable).map(_.symbol)
217+
192218
end TypeUtils
193219

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

+11-10
Original file line numberDiff line numberDiff line change
@@ -5944,17 +5944,18 @@ object Types extends TypeUtils {
59445944

59455945
def samClass(tp: Type)(using Context): Symbol = tp match
59465946
case tp: ClassInfo =>
5947-
def zeroParams(tp: Type): Boolean = tp.stripPoly match
5948-
case mt: MethodType => mt.paramInfos.isEmpty && !mt.resultType.isInstanceOf[MethodType]
5949-
case et: ExprType => true
5950-
case _ => false
59515947
val cls = tp.cls
5952-
val validCtor =
5953-
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
5948+
def takesNoArgs(tp: Type) =
5949+
!tp.classSymbol.primaryConstructor.exists
5950+
// e.g. `ContextFunctionN` does not have constructors
5951+
|| tp.applicableConstructors(Nil, adaptVarargs = true).lengthCompare(1) == 0
5952+
// we require a unique constructor so that SAM expansion is deterministic
5953+
val noArgsNeeded: Boolean =
5954+
takesNoArgs(tp)
5955+
&& (!tp.cls.is(Trait) || takesNoArgs(tp.parents.head))
5956+
def isInstantiable =
5957+
!tp.cls.isOneOf(FinalOrSealed) && (tp.appliedRef <:< tp.selfType)
5958+
if noArgsNeeded && isInstantiable then tp.cls
59585959
else NoSymbol
59595960
case tp: AppliedType =>
59605961
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

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
class MyFunction(args: String)
2+
3+
trait MyFunction0[+R] extends MyFunction {
4+
def apply(): R
5+
}
6+
7+
def fromFunction0[R](f: Function0[R]): MyFunction0[R] = () => f() // error
8+
9+
class MyFunctionWithImplicit(implicit args: String)
10+
11+
trait MyFunction0WithImplicit[+R] extends MyFunctionWithImplicit {
12+
def apply(): R
13+
}
14+
15+
def fromFunction1[R](f: Function0[R]): MyFunction0WithImplicit[R] = () => f() // error

tests/run/i15855.scala

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

0 commit comments

Comments
 (0)