Skip to content

Commit b8fafdb

Browse files
committed
Enable SAM syntax to refine type members
That works because type members are erased at runtime. Scala 3 already supports this pattern.
1 parent c80c0ff commit b8fafdb

File tree

5 files changed

+76
-25
lines changed

5 files changed

+76
-25
lines changed

src/compiler/scala/tools/nsc/ast/TreeGen.scala

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -366,15 +366,21 @@ abstract class TreeGen extends scala.reflect.internal.TreeGen with TreeDSL {
366366

367367
def expandFunction(localTyper: analyzer.Typer)(fun: Function, inConstructorFlag: Long): Tree = {
368368
val anonClass = fun.symbol.owner.newAnonymousFunctionClass(fun.pos, inConstructorFlag)
369-
val parents = if (isFunctionType(fun.tpe)) {
370-
anonClass addAnnotation SerialVersionUIDAnnotation
371-
addSerializable(abstractFunctionType(fun.vparams.map(_.symbol.tpe), fun.body.tpe.deconst))
372-
} else {
373-
if (fun.tpe.typeSymbol.isSubClass(SerializableClass))
374-
anonClass addAnnotation SerialVersionUIDAnnotation
375-
fun.tpe :: Nil
376-
}
377-
anonClass setInfo ClassInfoType(parents, newScope, anonClass)
369+
val typeSym = fun.tpe.typeSymbol
370+
val isFunction = isFunctionSymbol(typeSym)
371+
if (isFunction || typeSym.isSubClass(SerializableClass))
372+
anonClass.addAnnotation(SerialVersionUIDAnnotation)
373+
374+
anonClass.setInfo(fun.tpe match {
375+
case tpe @ RefinedType(parents, scope) =>
376+
assert(scope.forall(_.isType), s"Cannot expand function of type $tpe")
377+
ClassInfoType(parents, scope, anonClass)
378+
case tpe =>
379+
val parents =
380+
if (!isFunction) tpe :: Nil
381+
else addSerializable(abstractFunctionType(fun.vparams.map(_.symbol.tpe), fun.body.tpe.deconst))
382+
ClassInfoType(parents, newScope, anonClass)
383+
})
378384

379385
// The original owner is used in the backend for the EnclosingMethod attribute. If fun is
380386
// nested in a value-class method, its owner was already changed to the extension method.
@@ -387,7 +393,8 @@ abstract class TreeGen extends scala.reflect.internal.TreeGen with TreeDSL {
387393
localTyper.typedPos(fun.pos) {
388394
Block(
389395
ClassDef(anonClass, NoMods, ListOfNil, List(samDef), fun.pos),
390-
Typed(New(anonClass.tpe), TypeTree(fun.tpe)))
396+
Typed(New(anonClass.tpe), TypeTree(fun.tpe)),
397+
)
391398
}
392399
}
393400

src/reflect/scala/reflect/internal/Definitions.scala

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1017,8 +1017,14 @@ trait Definitions extends api.StandardDefinitions {
10171017
* and the caching means that samOf is effectively computed during typer (assuming the same inputs were presented to samOf during that phase).
10181018
* It's kind of strange that erasure sees deferredMembers that typer does not (see commented out assert below)
10191019
*/
1020-
def samOf(tp: Type): Symbol =
1021-
if (isNonRefinementClassType(unwrapToClass(tp))) { // TODO: is this really faster than computing tpSym below? how about just `tp.typeSymbol.isClass` (and !tpSym.isRefinementClass)?
1020+
def samOf(tp: Type): Symbol = {
1021+
@tailrec def isEligible(tp: Type): Boolean = unwrapToClass(tp) match {
1022+
case TypeRef(_, sym, _) => sym.isClass && !sym.isRefinementClass
1023+
case RefinedType(parent :: Nil, decls) => decls.forall(_.isType) && isEligible(parent)
1024+
case _ => false
1025+
}
1026+
1027+
if (isEligible(tp)) {
10221028
// look at erased type because we (only) care about what ends up in bytecode
10231029
// (e.g., an alias type is fine as long as is compiles to a single-abstract-method)
10241030
val tpSym: Symbol = erasure.javaErasure(tp).typeSymbol
@@ -1063,6 +1069,7 @@ trait Definitions extends api.StandardDefinitions {
10631069

10641070
samCache.getOrElseUpdate(tpSym, compute)
10651071
} else NoSymbol
1072+
}
10661073

10671074
def samOfProto(pt: Type): Symbol =
10681075
pt match {
Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,74 @@
1-
sammy_restrictions.scala:38: error: type mismatch;
1+
sammy_restrictions.scala:40: error: type mismatch;
22
found : () => Int
33
required: NoAbstract
44
def f0 = (() => 0) : NoAbstract
55
^
6-
sammy_restrictions.scala:39: error: type mismatch;
6+
sammy_restrictions.scala:41: error: type mismatch;
77
found : Int => Int
88
required: TwoAbstract
99
def f1 = ((x: Int) => 0): TwoAbstract
1010
^
11-
sammy_restrictions.scala:40: error: type mismatch;
11+
sammy_restrictions.scala:42: error: type mismatch;
1212
found : Int => Int
1313
required: NoEmptyConstructor
1414
def f2 = ((x: Int) => 0): NoEmptyConstructor
1515
^
16-
sammy_restrictions.scala:41: error: type mismatch;
16+
sammy_restrictions.scala:43: error: type mismatch;
1717
found : Int => Int
1818
required: MultipleConstructorLists
1919
def f3 = ((x: Int) => 0): MultipleConstructorLists
2020
^
21-
sammy_restrictions.scala:42: error: type mismatch;
21+
sammy_restrictions.scala:44: error: type mismatch;
2222
found : Int => Int
2323
required: OneEmptySecondaryConstructor
2424
def f4 = ((x: Int) => 0): OneEmptySecondaryConstructor // derived class must have an empty *primary* to call.
2525
^
26-
sammy_restrictions.scala:43: error: type mismatch;
26+
sammy_restrictions.scala:45: error: type mismatch;
2727
found : Int => Int
2828
required: MultipleMethodLists
2929
def f5 = ((x: Int) => 0): MultipleMethodLists
3030
^
31-
sammy_restrictions.scala:44: error: type mismatch;
31+
sammy_restrictions.scala:46: error: type mismatch;
3232
found : Int => Int
3333
required: ImplicitConstructorParam
3434
def f6 = ((x: Int) => 0): ImplicitConstructorParam
3535
^
36-
sammy_restrictions.scala:45: error: type mismatch;
36+
sammy_restrictions.scala:47: error: type mismatch;
3737
found : Int => Int
3838
required: ImplicitMethodParam
3939
def f7 = ((x: Int) => 0): ImplicitMethodParam
4040
^
41-
sammy_restrictions.scala:46: error: type mismatch;
41+
sammy_restrictions.scala:48: error: type mismatch;
4242
found : Int => Int
4343
required: PolyMethod
4444
def f8 = ((x: Int) => 0): PolyMethod
4545
^
46-
sammy_restrictions.scala:47: error: type mismatch;
46+
sammy_restrictions.scala:49: error: type mismatch;
4747
found : Int => Int
4848
required: SelfTp
4949
def f9 = ((x: Int) => 0): SelfTp
5050
^
51-
sammy_restrictions.scala:48: error: type mismatch;
51+
sammy_restrictions.scala:50: error: type mismatch;
5252
found : Int => Int
5353
required: T1 with U1
5454
def g0 = ((x: Int) => 0): T1 with U1
5555
^
56-
sammy_restrictions.scala:49: error: type mismatch;
56+
sammy_restrictions.scala:51: error: type mismatch;
5757
found : Int => Int
5858
required: Test.NonClassTypeRefinement
5959
(which expands to) DerivedOneAbstract with OneAbstract
6060
def g1 = ((x: Int) => 0): NonClassTypeRefinement
6161
^
62-
12 errors
62+
sammy_restrictions.scala:52: error: type mismatch;
63+
found : Int => Int
64+
required: Test.NonOverridingMethodRefinement
65+
(which expands to) OneAbstract{def apples(): Int}
66+
def h1 = ((x: Int) => 0): NonOverridingMethodRefinement
67+
^
68+
sammy_restrictions.scala:53: error: type mismatch;
69+
found : Int => Int
70+
required: Test.OverridingMethodRefinement
71+
(which expands to) OneAbstract{def ap(a: Int): Int}
72+
def h2 = ((x: Int) => 0): OverridingMethodRefinement
73+
^
74+
14 errors

test/files/neg/sammy_restrictions.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ trait T1 { def t(a: Int): Int }; trait U1
3232
object Test {
3333
implicit val s: String = ""
3434
type NonClassTypeRefinement = DerivedOneAbstract with OneAbstract
35+
type NonOverridingMethodRefinement = OneAbstract { def apples(): Int }
36+
type OverridingMethodRefinement = OneAbstract { def ap(a: Int): Int } // allowed in Scala 3
3537
type NonClassType = DerivedOneAbstract
3638

3739
// errors:
@@ -47,6 +49,8 @@ object Test {
4749
def f9 = ((x: Int) => 0): SelfTp
4850
def g0 = ((x: Int) => 0): T1 with U1
4951
def g1 = ((x: Int) => 0): NonClassTypeRefinement
52+
def h1 = ((x: Int) => 0): NonOverridingMethodRefinement
53+
def h2 = ((x: Int) => 0): OverridingMethodRefinement
5054

5155
// allowed:
5256
def g2 = ((x: Int) => 0): OneEmptyConstructor

test/files/pos/sammy_refined.scala

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
trait DepFn[-A] {
2+
type Out
3+
def apply(in: A): Out
4+
}
5+
6+
object DepFn {
7+
type Aux[-A, B] = DepFn[A] { type Out = B }
8+
type AuxF[F[_], A] = Aux[F[A], F[A]] { type B >: A }
9+
val length: DepFn[String] { type Out = Int } = _.length
10+
val upper: Aux[String, String] = _.toUpperCase
11+
val reverse: AuxF[List, Int] = _.reverse
12+
}
13+
14+
class Outer {
15+
// T here does not compile to a SAM in bytecode,
16+
// because of the outer reference to the enclosing class.
17+
trait T { def f(x: Int): Int }
18+
val t1: T = x => x
19+
val t2: T { type U = String } = x => x
20+
val t3: T { type U } = x => x
21+
}

0 commit comments

Comments
 (0)