Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 35 additions & 2 deletions compiler/src/dotty/tools/dotc/transform/Bridges.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import ContextFunctionResults.{contextResultCount, contextFunctionResultTypeAfte
import StdNames.nme
import Constants.Constant
import TypeErasure.transformInfo
import Erasure.Boxing.adaptClosure
import Erasure.{Boxing, partialApply, PostErasureArgs}
import Boxing.adaptClosure

/** A helper class for generating bridge methods in class `root`. */
class Bridges(root: ClassSymbol, thisPhase: DenotTransformer)(using Context) {
Expand Down Expand Up @@ -168,11 +169,43 @@ class Bridges(root: ClassSymbol, thisPhase: DenotTransformer)(using Context) {
expand(args, start, memberCount - otherCount)(using ctx.withOwner(bridge))
end etaExpand

/** Apply `ref` to the flatArgs, in the way that matches pre-eraure signature.
*
* For example: with pre-erasure signature `(using erased Ev)(a: A): String`
* and post-erasure args `flatArgs = [a]`, this method produces
* `Apply(Apply(ref, []), [a])` instead of `Apply(ref, [a])`.
* One `Apply` per pre-erasure list; the first is empty because its only
* parameter is erased.
*/
def applyMemberArgs(ref: Tree, flatArgs: List[Tree])(using Context): Tree =
val preInfo = inContext(preErasureCtx)(member.info) // pre-erasure type info

def loop(fn: Tree, tp: Type, args: List[Tree]): Tree = tp match
case pt: PolyType =>
loop(fn, pt.resultType, args)

case mt: MethodType if fn.tpe.widen.isInstanceOf[MethodType] =>
// `args` contains only non-erased params. Split by pre-erasure list boundary:
// `here` is this list's non-erased args, `rest` goes to later lists.
// e.g. for the 1st args-list of `(using erased Ev)(a: A)`, it's n=0
// if the `args` are `[a]`, here=[], and rest=[a].
val n = inContext(preErasureCtx)(mt.nonErasedParamCount)
val (here, rest) = args.splitAt(n)

// `partialApply` (vs `tpd.Apply`) skips the arity check, which would
// reject the 0-arg intermediate Apply produced by an all-erased list.
// `PostErasureArgs` tells `typedApply` not to re-filter these args.
val next = partialApply(fn, here).withAttachment(PostErasureArgs, ())
loop(next, mt.resultType, rest)
case _ =>
fn
loop(ref, preInfo, flatArgs)

def bridgeRhs(argss: List[List[Tree]]) =
assert(argss.tail.isEmpty)
val ref = This(root).select(member)
if member.info.isParameterless then ref // can happen if `member` is a module
else if memberCount == 0 then ref.appliedToTermArgs(argss.head)
else if memberCount == 0 then applyMemberArgs(ref, argss.head)
else etaExpand(ref, argss.head)

bridges += DefDef(bridge, bridgeRhs(_).withSpan(bridge.span))
Expand Down
9 changes: 8 additions & 1 deletion compiler/src/dotty/tools/dotc/transform/Erasure.scala
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,12 @@ object Erasure {
*/
private val BunchedArgs = new Property.Key[Unit]

/** An attachment on an Apply node whose arguments are already in
* post-erasure form i.e. all arguments are non-erased parameters.
* Generate by `Bridges` so that `typedApply` skips its erased-argument filter.
*/
private[transform] val PostErasureArgs = new Property.Key[Unit]

/** An Apply node which might still be missing some arguments */
def partialApply(fn: Tree, args: List[Tree])(using Context): Tree =
untpd.Apply(fn, args.toList)
Expand Down Expand Up @@ -805,7 +811,8 @@ object Erasure {
val origFun = fun.asInstanceOf[tpd.Tree]
val origFunType = origFun.tpe.widen(using preErasureCtx)
val ownArgs = origFunType match
case mt: MethodType if mt.hasErasedParams =>
case mt: MethodType if mt.hasErasedParams
&& !tree.hasAttachment(PostErasureArgs) =>
args.lazyZip(mt.paramErasureStatuses).flatMap: (arg, isErased) =>
if isErased then
checkPureErased(arg, isArgument = true,
Expand Down
55 changes: 55 additions & 0 deletions tests/run/i25726.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Regression test for https://github.com/scala/scala3/issues/25726
import scala.language.experimental.erasedDefinitions

class Ev

// i25726
trait Svc[A]:
def run(using erased Ev)(a: A): String
class ISvc extends Svc[Int]:
def run(using erased Ev)(a: Int): String = a.toString

// more args-lists
class Token
trait Store[A]:
def put(using erased Token)(key: String)(value: A): String
class IntStore extends Store[Int]:
def put(using erased Token)(key: String)(value: Int): String = s"$key=$value"

// trailing all-erased list
trait Trail[A]:
def id(a: A)(using erased Ev): A
class IntTrail extends Trail[Int]:
def id(a: Int)(using erased Ev): Int = a

// Erased-first inside a single parameter list.
trait Single[A]:
def run(erased a: Ev, b: A): String
class IntSingle extends Single[Int]:
def run(erased a: Ev, b: Int): String = b.toString

// Erased-first within a non-trailing parameter list.
trait Mixed[A]:
def run(erased e: Ev, a: A)(b: Int): String
class IntMixed extends Mixed[Int]:
def run(erased e: Ev, a: Int)(b: Int): String = s"$a+$b"

object Test:
def main(args: Array[String]): Unit =
erased given Ev = new Ev
erased given Token = new Token

val isvc: Svc[Int] = new ISvc
assert(isvc.run(42) == "42", isvc.run(42))

val store: Store[Int] = new IntStore
assert(store.put("answer")(42) == "answer=42", store.put("answer")(42))

val trail: Trail[Int] = new IntTrail
assert(trail.id(7) == 7)

val single: Single[Int] = new IntSingle
assert(single.run(new Ev, 9) == "9", single.run(new Ev, 9))

val mixed: Mixed[Int] = new IntMixed
assert(mixed.run(new Ev, 1)(2) == "1+2", mixed.run(new Ev, 1)(2))
Loading