Skip to content

Commit

Permalink
Three fixes for SAM type handling
Browse files Browse the repository at this point in the history
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
  • Loading branch information
odersky committed Sep 15, 2024
1 parent 83efd23 commit f0b6763
Show file tree
Hide file tree
Showing 6 changed files with 105 additions and 31 deletions.
75 changes: 56 additions & 19 deletions compiler/src/dotty/tools/dotc/ast/tpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -315,24 +315,53 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
def TypeDef(sym: TypeSymbol)(using Context): TypeDef =
ta.assignType(untpd.TypeDef(sym.name, TypeTree(sym.info)), sym)

def ClassDef(cls: ClassSymbol, constr: DefDef, body: List[Tree], superArgs: List[Tree] = Nil)(using Context): TypeDef = {
/** Create a class definition
* @param cls the class symbol of the created class
* @param constr its primary constructor
* @param body the statements in its template
* @param superArgs the arguments to pass to the superclass constructor
* @param adaptVarargs if true, allow matching a vararg superclass constructor
* with a missing argument in superArgs, and synthesize an
* empty repeated parameter in the supercall in this case
*/
def ClassDef(cls: ClassSymbol, constr: DefDef, body: List[Tree],
superArgs: List[Tree] = Nil, adaptVarargs: Boolean = false)(using Context): TypeDef =
val firstParent :: otherParents = cls.info.parents: @unchecked

def isApplicable(constr: Symbol): Boolean =
def recur(ctpe: Type): Boolean = ctpe match
case ctpe: PolyType =>
recur(ctpe.instantiate(firstParent.argTypes))
case ctpe: MethodType =>
var paramInfos = ctpe.paramInfos
if adaptVarargs && paramInfos.length == superArgs.length + 1
&& atPhaseNoLater(Phases.elimRepeatedPhase)(constr.info.isVarArgsMethod)
then // accept missing argument for varargs parameter
paramInfos = paramInfos.init
superArgs.corresponds(paramInfos)(_.tpe <:< _)
case _ =>
false
recur(constr.info)

def adaptedSuperArgs(ctpe: Type): List[Tree] = ctpe match
case ctpe: PolyType =>
adaptedSuperArgs(ctpe.instantiate(firstParent.argTypes))
case ctpe: MethodType
if ctpe.paramInfos.length == superArgs.length + 1 =>
// last argument must be a vararg, otherwise isApplicable would have failed
superArgs :+
repeated(Nil, TypeTree(ctpe.paramInfos.last.argInfos.head, inferred = true))
case _ =>
superArgs

val superRef =
if (cls.is(Trait)) TypeTree(firstParent)
else {
def isApplicable(ctpe: Type): Boolean = ctpe match {
case ctpe: PolyType =>
isApplicable(ctpe.instantiate(firstParent.argTypes))
case ctpe: MethodType =>
(superArgs corresponds ctpe.paramInfos)(_.tpe <:< _)
case _ =>
false
}
val constr = firstParent.decl(nme.CONSTRUCTOR).suchThat(constr => isApplicable(constr.info))
New(firstParent, constr.symbol.asTerm, superArgs)
}
if cls.is(Trait) then TypeTree(firstParent)
else
val constr = firstParent.decl(nme.CONSTRUCTOR).suchThat(isApplicable)
New(firstParent, constr.symbol.asTerm, adaptedSuperArgs(constr.info))

ClassDefWithParents(cls, constr, superRef :: otherParents.map(TypeTree(_)), body)
}
end ClassDef

def ClassDefWithParents(cls: ClassSymbol, constr: DefDef, parents: List[Tree], body: List[Tree])(using Context): TypeDef = {
val selfType =
Expand All @@ -359,13 +388,18 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
* @param parents a non-empty list of class types
* @param termForwarders a non-empty list of forwarding definitions specified by their name and the definition they forward to.
* @param typeMembers a possibly-empty list of type members specified by their name and their right hand side.
* @param adaptVarargs if true, allow matching a vararg superclass constructor
* with a missing argument in superArgs, and synthesize an
* empty repeated parameter in the supercall in this case
*
* The class has the same owner as the first function in `termForwarders`.
* Its position is the union of all symbols in `termForwarders`.
*/
def AnonClass(parents: List[Type], termForwarders: List[(TermName, TermSymbol)],
typeMembers: List[(TypeName, TypeBounds)] = Nil)(using Context): Block = {
AnonClass(termForwarders.head._2.owner, parents, termForwarders.map(_._2.span).reduceLeft(_ union _)) { cls =>
def AnonClass(parents: List[Type],
termForwarders: List[(TermName, TermSymbol)],
typeMembers: List[(TypeName, TypeBounds)],
adaptVarargs: Boolean)(using Context): Block = {
AnonClass(termForwarders.head._2.owner, parents, termForwarders.map(_._2.span).reduceLeft(_ union _), adaptVarargs) { cls =>
def forwarder(name: TermName, fn: TermSymbol) = {
val fwdMeth = fn.copy(cls, name, Synthetic | Method | Final).entered.asTerm
for overridden <- fwdMeth.allOverriddenSymbols do
Expand All @@ -385,6 +419,9 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
* with the specified owner and position.
*/
def AnonClass(owner: Symbol, parents: List[Type], coord: Coord)(body: ClassSymbol => List[Tree])(using Context): Block =
AnonClass(owner, parents, coord, adaptVarargs = false)(body)

private def AnonClass(owner: Symbol, parents: List[Type], coord: Coord, adaptVarargs: Boolean)(body: ClassSymbol => List[Tree])(using Context): Block =
val parents1 =
if (parents.head.classSymbol.is(Trait)) {
val head = parents.head.parents.head
Expand All @@ -393,7 +430,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
else parents
val cls = newNormalizedClassSymbol(owner, tpnme.ANON_CLASS, Synthetic | Final, parents1, coord = coord)
val constr = newConstructor(cls, Synthetic, Nil, Nil).entered
val cdef = ClassDef(cls, DefDef(constr), body(cls))
val cdef = ClassDef(cls, DefDef(constr), body(cls), Nil, adaptVarargs)
Block(cdef :: Nil, New(cls.typeRef, Nil))

def Import(expr: Tree, selectors: List[untpd.ImportSelector])(using Context): Import =
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/Phases.scala
Original file line number Diff line number Diff line change
Expand Up @@ -532,7 +532,7 @@ object Phases {
def sbtExtractAPIPhase(using Context): Phase = ctx.base.sbtExtractAPIPhase
def picklerPhase(using Context): Phase = ctx.base.picklerPhase
def inliningPhase(using Context): Phase = ctx.base.inliningPhase
def stagingPhase(using Context): Phase = ctx.base.stagingPhase
def stagingPhase(using Context): Phase = ctx.base.stagingPhase
def splicingPhase(using Context): Phase = ctx.base.splicingPhase
def firstTransformPhase(using Context): Phase = ctx.base.firstTransformPhase
def refchecksPhase(using Context): Phase = ctx.base.refchecksPhase
Expand Down
19 changes: 12 additions & 7 deletions compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5945,16 +5945,21 @@ object Types extends TypeUtils {
def samClass(tp: Type)(using Context): Symbol = tp match
case tp: ClassInfo =>
def zeroParams(tp: Type): Boolean = tp.stripPoly match
case mt: MethodType => mt.paramInfos.isEmpty && !mt.resultType.isInstanceOf[MethodType]
case mt: MethodType =>
val noArgsNeeded = mt.paramInfos match
case Nil => true
case info :: Nil => info.isRepeatedParam
case _ => false
noArgsNeeded && !mt.resultType.isInstanceOf[MethodType]
case et: ExprType => true
case _ => false
val cls = tp.cls
val validCtor =
def validCtor(cls: Symbol): Boolean =
val ctor = cls.primaryConstructor
// `ContextFunctionN` does not have constructors
!ctor.exists || zeroParams(ctor.info)
val isInstantiable = !cls.isOneOf(FinalOrSealed) && (tp.appliedRef <:< tp.selfType)
if validCtor && isInstantiable then tp.cls
(!ctor.exists || zeroParams(ctor.info)) // `ContextFunctionN` does not have constructors
&& (!cls.is(Trait) || validCtor(cls.info.parents.head.classSymbol))
def isInstantiable =
!tp.cls.isOneOf(FinalOrSealed) && (tp.appliedRef <:< tp.selfType)
if validCtor(tp.cls) && isInstantiable then tp.cls
else NoSymbol
case tp: AppliedType =>
samClass(tp.superType)
Expand Down
10 changes: 6 additions & 4 deletions compiler/src/dotty/tools/dotc/transform/ExpandSAMs.scala
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,12 @@ class ExpandSAMs extends MiniPhase:
val tpe1 = collectAndStripRefinements(tpe)
val Seq(samDenot) = tpe1.possibleSamMethods
cpy.Block(tree)(stats,
AnonClass(List(tpe1),
List(samDenot.symbol.asTerm.name -> fn.symbol.asTerm),
refinements.toList
)
transformFollowingDeep:
AnonClass(List(tpe1),
List(samDenot.symbol.asTerm.name -> fn.symbol.asTerm),
refinements.toList,
adaptVarargs = true
)
)
}
case _ =>
Expand Down
10 changes: 10 additions & 0 deletions tests/neg/i15855.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// crash.scala
import scala.language.implicitConversions

class MyFunction(args: String)

trait MyFunction0[+R] extends MyFunction {
def apply(): R
}

def fromFunction0[R](f: Function0[R]): MyFunction0[R] = () => f() // error
20 changes: 20 additions & 0 deletions tests/run/i15855.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// crash.scala
import scala.language.implicitConversions

class MyFunction(args: String*)

trait MyFunction0[+R] extends MyFunction {
def apply(): R
}

abstract class MyFunction1[R](args: R*):
def apply(): R

def fromFunction0[R](f: Function0[R]): MyFunction0[R] = () => f()
def fromFunction1[R](f: Function0[R]): MyFunction1[R] = () => f()

@main def Test =
val m0: MyFunction0[Int] = fromFunction0(() => 1)
val m1: MyFunction1[Int] = fromFunction1(() => 2)
assert(m0() == 1)
assert(m1() == 2)

0 comments on commit f0b6763

Please sign in to comment.