diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index 3ce2d1d038dd..d4e585402feb 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -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 = @@ -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 @@ -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 @@ -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 = diff --git a/compiler/src/dotty/tools/dotc/core/Phases.scala b/compiler/src/dotty/tools/dotc/core/Phases.scala index 7f925b0fc322..5dff95fc51fb 100644 --- a/compiler/src/dotty/tools/dotc/core/Phases.scala +++ b/compiler/src/dotty/tools/dotc/core/Phases.scala @@ -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 diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 8181b3c83acf..b626464bc428 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -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) diff --git a/compiler/src/dotty/tools/dotc/transform/ExpandSAMs.scala b/compiler/src/dotty/tools/dotc/transform/ExpandSAMs.scala index cdbef792dfa9..67bf1bebed87 100644 --- a/compiler/src/dotty/tools/dotc/transform/ExpandSAMs.scala +++ b/compiler/src/dotty/tools/dotc/transform/ExpandSAMs.scala @@ -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 _ => diff --git a/tests/neg/i15855.scala b/tests/neg/i15855.scala new file mode 100644 index 000000000000..ba9112032419 --- /dev/null +++ b/tests/neg/i15855.scala @@ -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 diff --git a/tests/run/i15855.scala b/tests/run/i15855.scala new file mode 100644 index 000000000000..880d6d806132 --- /dev/null +++ b/tests/run/i15855.scala @@ -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)