Skip to content

Commit 0c5915c

Browse files
Backport "Three fixes to SAM type handling" to LTS (#22132)
Backports #21596 to the 3.3.5. PR submitted by the release tooling. [skip ci]
2 parents 8a459ab + 81ff618 commit 0c5915c

File tree

7 files changed

+124
-38
lines changed

7 files changed

+124
-38
lines changed

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

Lines changed: 44 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -305,24 +305,41 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
305305
def TypeDef(sym: TypeSymbol)(using Context): TypeDef =
306306
ta.assignType(untpd.TypeDef(sym.name, TypeTree(sym.info)), sym)
307307

308-
def ClassDef(cls: ClassSymbol, constr: DefDef, body: List[Tree], superArgs: List[Tree] = Nil)(using Context): TypeDef = {
308+
/** Create a class definition
309+
* @param cls the class symbol of the created class
310+
* @param constr its primary constructor
311+
* @param body the statements in its template
312+
* @param superArgs the arguments to pass to the superclass constructor
313+
* @param adaptVarargs if true, allow matching a vararg superclass constructor
314+
* with a missing argument in superArgs, and synthesize an
315+
* empty repeated parameter in the supercall in this case
316+
*/
317+
def ClassDef(cls: ClassSymbol, constr: DefDef, body: List[Tree],
318+
superArgs: List[Tree] = Nil, adaptVarargs: Boolean = false)(using Context): TypeDef =
309319
val firstParent :: otherParents = cls.info.parents: @unchecked
320+
321+
def adaptedSuperArgs(ctpe: Type): List[Tree] = ctpe match
322+
case ctpe: PolyType =>
323+
adaptedSuperArgs(ctpe.instantiate(firstParent.argTypes))
324+
case ctpe: MethodType
325+
if ctpe.paramInfos.length == superArgs.length + 1 =>
326+
// last argument must be a vararg, otherwise isApplicable would have failed
327+
superArgs :+
328+
repeated(Nil, TypeTree(ctpe.paramInfos.last.argInfos.head, inferred = true))
329+
case _ =>
330+
superArgs
331+
310332
val superRef =
311-
if (cls.is(Trait)) TypeTree(firstParent)
312-
else {
313-
def isApplicable(ctpe: Type): Boolean = ctpe match {
314-
case ctpe: PolyType =>
315-
isApplicable(ctpe.instantiate(firstParent.argTypes))
316-
case ctpe: MethodType =>
317-
(superArgs corresponds ctpe.paramInfos)(_.tpe <:< _)
318-
case _ =>
319-
false
320-
}
321-
val constr = firstParent.decl(nme.CONSTRUCTOR).suchThat(constr => isApplicable(constr.info))
322-
New(firstParent, constr.symbol.asTerm, superArgs)
323-
}
333+
if cls.is(Trait) then TypeTree(firstParent)
334+
else
335+
val parentConstr = firstParent.applicableConstructors(superArgs.tpes, adaptVarargs) match
336+
case Nil => assert(false, i"no applicable parent constructor of $firstParent for supercall arguments $superArgs")
337+
case constr :: Nil => constr
338+
case _ => assert(false, i"multiple applicable parent constructors of $firstParent for supercall arguments $superArgs")
339+
New(firstParent, parentConstr.asTerm, adaptedSuperArgs(parentConstr.info))
340+
324341
ClassDefWithParents(cls, constr, superRef :: otherParents.map(TypeTree(_)), body)
325-
}
342+
end ClassDef
326343

327344
def ClassDefWithParents(cls: ClassSymbol, constr: DefDef, parents: List[Tree], body: List[Tree])(using Context): TypeDef = {
328345
val selfType =
@@ -349,13 +366,18 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
349366
* @param parents a non-empty list of class types
350367
* @param termForwarders a non-empty list of forwarding definitions specified by their name and the definition they forward to.
351368
* @param typeMembers a possibly-empty list of type members specified by their name and their right hand side.
369+
* @param adaptVarargs if true, allow matching a vararg superclass constructor
370+
* with a missing argument in superArgs, and synthesize an
371+
* empty repeated parameter in the supercall in this case
352372
*
353373
* The class has the same owner as the first function in `termForwarders`.
354374
* Its position is the union of all symbols in `termForwarders`.
355375
*/
356-
def AnonClass(parents: List[Type], termForwarders: List[(TermName, TermSymbol)],
357-
typeMembers: List[(TypeName, TypeBounds)] = Nil)(using Context): Block = {
358-
AnonClass(termForwarders.head._2.owner, parents, termForwarders.map(_._2.span).reduceLeft(_ union _)) { cls =>
376+
def AnonClass(parents: List[Type],
377+
termForwarders: List[(TermName, TermSymbol)],
378+
typeMembers: List[(TypeName, TypeBounds)],
379+
adaptVarargs: Boolean)(using Context): Block = {
380+
AnonClass(termForwarders.head._2.owner, parents, termForwarders.map(_._2.span).reduceLeft(_ union _), adaptVarargs) { cls =>
359381
def forwarder(name: TermName, fn: TermSymbol) = {
360382
val fwdMeth = fn.copy(cls, name, Synthetic | Method | Final).entered.asTerm
361383
for overridden <- fwdMeth.allOverriddenSymbols do
@@ -375,6 +397,9 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
375397
* with the specified owner and position.
376398
*/
377399
def AnonClass(owner: Symbol, parents: List[Type], coord: Coord)(body: ClassSymbol => List[Tree])(using Context): Block =
400+
AnonClass(owner, parents, coord, adaptVarargs = false)(body)
401+
402+
private def AnonClass(owner: Symbol, parents: List[Type], coord: Coord, adaptVarargs: Boolean)(body: ClassSymbol => List[Tree])(using Context): Block =
378403
val parents1 =
379404
if (parents.head.classSymbol.is(Trait)) {
380405
val head = parents.head.parents.head
@@ -383,7 +408,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
383408
else parents
384409
val cls = newNormalizedClassSymbol(owner, tpnme.ANON_CLASS, Synthetic | Final, parents1, coord = coord)
385410
val constr = newConstructor(cls, Synthetic, Nil, Nil).entered
386-
val cdef = ClassDef(cls, DefDef(constr), body(cls))
411+
val cdef = ClassDef(cls, DefDef(constr), body(cls), Nil, adaptVarargs)
387412
Block(cdef :: Nil, New(cls.typeRef, Nil))
388413

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

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -486,7 +486,7 @@ object Phases {
486486
def sbtExtractDependenciesPhase(using Context): Phase = ctx.base.sbtExtractDependenciesPhase
487487
def picklerPhase(using Context): Phase = ctx.base.picklerPhase
488488
def inliningPhase(using Context): Phase = ctx.base.inliningPhase
489-
def stagingPhase(using Context): Phase = ctx.base.stagingPhase
489+
def stagingPhase(using Context): Phase = ctx.base.stagingPhase
490490
def splicingPhase(using Context): Phase = ctx.base.splicingPhase
491491
def firstTransformPhase(using Context): Phase = ctx.base.firstTransformPhase
492492
def refchecksPhase(using Context): Phase = ctx.base.refchecksPhase

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

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@ package core
55
import TypeErasure.ErasedValueType
66
import Types.*, Contexts.*, Symbols.*, Flags.*, Decorators.*
77
import Names.Name
8+
import StdNames.nme
89

9-
class TypeUtils {
10+
class TypeUtils:
1011
/** A decorator that provides methods on types
1112
* that are needed in the transformer pipeline.
1213
*/
13-
extension (self: Type) {
14+
extension (self: Type)
1415

1516
def isErasedValueType(using Context): Boolean =
1617
self.isInstanceOf[ErasedValueType]
@@ -125,5 +126,30 @@ class TypeUtils {
125126
def takesImplicitParams(using Context): Boolean = self.stripPoly match
126127
case mt: MethodType => mt.isImplicitMethod || mt.resType.takesImplicitParams
127128
case _ => false
128-
}
129-
}
129+
130+
/** The constructors of this type that are applicable to `argTypes`, without needing
131+
* an implicit conversion. Curried constructors are always excluded.
132+
* @param adaptVarargs if true, allow a constructor with just a varargs argument to
133+
* match an empty argument list.
134+
*/
135+
def applicableConstructors(argTypes: List[Type], adaptVarargs: Boolean)(using Context): List[Symbol] =
136+
def isApplicable(constr: Symbol): Boolean =
137+
def recur(ctpe: Type): Boolean = ctpe match
138+
case ctpe: PolyType =>
139+
if argTypes.isEmpty then recur(ctpe.resultType) // no need to know instances
140+
else recur(ctpe.instantiate(self.argTypes))
141+
case ctpe: MethodType =>
142+
var paramInfos = ctpe.paramInfos
143+
if adaptVarargs && paramInfos.length == argTypes.length + 1
144+
&& atPhaseNoLater(Phases.elimRepeatedPhase)(constr.info.isVarArgsMethod)
145+
then // accept missing argument for varargs parameter
146+
paramInfos = paramInfos.init
147+
argTypes.corresponds(paramInfos)(_ <:< _) && !ctpe.resultType.isInstanceOf[MethodType]
148+
case _ =>
149+
false
150+
recur(constr.info)
151+
152+
self.decl(nme.CONSTRUCTOR).altsWith(isApplicable).map(_.symbol)
153+
154+
end TypeUtils
155+

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

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5612,17 +5612,18 @@ object Types extends TypeUtils {
56125612

56135613
def samClass(tp: Type)(using Context): Symbol = tp match
56145614
case tp: ClassInfo =>
5615-
def zeroParams(tp: Type): Boolean = tp.stripPoly match
5616-
case mt: MethodType => mt.paramInfos.isEmpty && !mt.resultType.isInstanceOf[MethodType]
5617-
case et: ExprType => true
5618-
case _ => false
56195615
val cls = tp.cls
5620-
val validCtor =
5621-
val ctor = cls.primaryConstructor
5622-
// `ContextFunctionN` does not have constructors
5623-
!ctor.exists || zeroParams(ctor.info)
5624-
val isInstantiable = !cls.isOneOf(FinalOrSealed) && (tp.appliedRef <:< tp.selfType)
5625-
if validCtor && isInstantiable then tp.cls
5616+
def takesNoArgs(tp: Type) =
5617+
!tp.classSymbol.primaryConstructor.exists
5618+
// e.g. `ContextFunctionN` does not have constructors
5619+
|| tp.applicableConstructors(Nil, adaptVarargs = true).lengthCompare(1) == 0
5620+
// we require a unique constructor so that SAM expansion is deterministic
5621+
val noArgsNeeded: Boolean =
5622+
takesNoArgs(tp)
5623+
&& (!tp.cls.is(Trait) || takesNoArgs(tp.parents.head))
5624+
def isInstantiable =
5625+
!tp.cls.isOneOf(FinalOrSealed) && (tp.appliedRef <:< tp.selfType)
5626+
if noArgsNeeded && isInstantiable then tp.cls
56265627
else NoSymbol
56275628
case tp: AppliedType =>
56285629
samClass(tp.superType)

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

Lines changed: 6 additions & 4 deletions
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

Lines changed: 15 additions & 0 deletions
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

Lines changed: 17 additions & 0 deletions
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)