Skip to content

Commit

Permalink
Specialized retained inline FunctionN apply methods
Browse files Browse the repository at this point in the history
Fixes #19724
  • Loading branch information
nicolasstucki committed Feb 28, 2024
1 parent 7f410aa commit 22020f3
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 4 deletions.
25 changes: 21 additions & 4 deletions compiler/src/dotty/tools/dotc/transform/SpecializeFunctions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package transform

import ast.Trees.*, ast.tpd, core.*
import Contexts.*, Types.*, Decorators.*, Symbols.*, DenotTransformers.*
import SymDenotations.*, Scopes.*, StdNames.*, NameOps.*, Names.*
import SymDenotations.*, Scopes.*, StdNames.*, NameOps.*, Names.*, NameKinds.*
import MegaPhase.MiniPhase


Expand All @@ -25,7 +25,24 @@ class SpecializeFunctions extends MiniPhase {
/** Create forwarders from the generic applys to the specialized ones.
*/
override def transformDefDef(ddef: DefDef)(using Context) = {
if ddef.name != nme.apply
// Note on special case for inline `apply`s:
// `apply` and `apply$retainedBody` are specialized in this transformation.
// `apply$retainedBody` have the name kind `BodyRetainerName`, these contain
// the runtime implementation of an inline `apply` that implements (or overrides)
// the `FunctionN.apply` method. The inline method is not specialized, it will
// be replaced with the implementation of `apply$retainedBody`. The following code
// inline def apply(x: Int): Double = x.toDouble:Double
// private def apply$retainedBody(x: Int): Double = x.toDouble:Double
// in is transformed into
// inline def apply(x: Int): Double = x.toDouble:Double
// private def apply$retainedBody(x: Int): Double = this.apply$mcDI$sp(x)
// def apply$mcDI$sp(v: Int): Double = x.toDouble:Double
// after erasure it will become
// def apply(v: Int): Double = this.apply$mcDI$sp(v) // from apply$retainedBody
// def apply$mcDI$sp(v: Int): Double = v.toDouble():Double
// def apply(v1: Object): Object = Double.box(this.apply(Int.unbox(v1))) // erasure bridge

if ddef.name.asTermName.exclude(BodyRetainerName) != nme.apply
|| ddef.termParamss.length != 1
|| ddef.termParamss.head.length > 2
|| !ctx.owner.isClass
Expand All @@ -44,12 +61,12 @@ class SpecializeFunctions extends MiniPhase {
defn.isSpecializableFunction(cls, paramTypes, retType)
}

if (sym.is(Flags.Deferred) || !isSpecializable) return ddef
if (sym.is(Flags.Deferred) || sym.is(Flags.Inline) || !isSpecializable) return ddef

val specializedApply = newSymbol(
cls,
specName.nn,
sym.flags | Flags.Synthetic,
(sym.flags | Flags.Synthetic) &~ Flags.Private, // Private flag can be set if the name is a BodyRetainerName
sym.info
).entered

Expand Down
5 changes: 5 additions & 0 deletions tests/pos/i19724.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
object repro:
abstract class Mapper[A, B] extends (A => B)

given Mapper[Int, Double] with
inline def apply(v: Int): Double = v.toDouble
18 changes: 18 additions & 0 deletions tests/run/i19724.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
class F0 extends (() => Double):
inline def apply(): Double = 1.toDouble

class F1 extends (Int => Double):
inline def apply(v: Int): Double = v.toDouble

class F2 extends ((Int, Int) => Double):
inline def apply(v1: Int, v2: Int): Double = (v1 + v2).toDouble

@main def Test =
val f0: (() => Double) = new F0
assert(f0() == 1.0)

val f1: (Int => Double) = new F1
assert(f1(3) == 3.0)

val f2: ((Int, Int) => Double) = new F2
assert(f2(3, 2) == 5.0)

0 comments on commit 22020f3

Please sign in to comment.