Skip to content

Specialized retained inline FunctionN apply methods #19801

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 1, 2024
Merged
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
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)