Skip to content

Commit

Permalink
Merge pull request #10157 from joroKr21/sammy-refined
Browse files Browse the repository at this point in the history
Enable SAM syntax to refine type members
  • Loading branch information
lrytz committed Dec 19, 2023
2 parents 343b85b + a92cc22 commit d2f97a3
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 24 deletions.
26 changes: 17 additions & 9 deletions src/compiler/scala/tools/nsc/ast/TreeGen.scala
Expand Up @@ -18,6 +18,7 @@ import scala.collection.mutable.ListBuffer
import symtab.Flags._
import scala.reflect.internal.util.FreshNameCreator
import scala.reflect.internal.util.ListOfNil
import scala.util.chaining._

/** XXX to resolve: TreeGen only assumes global is a SymbolTable, but
* TreeDSL at the moment expects a Global. Can we get by with SymbolTable?
Expand Down Expand Up @@ -366,15 +367,21 @@ abstract class TreeGen extends scala.reflect.internal.TreeGen with TreeDSL {

def expandFunction(localTyper: analyzer.Typer)(fun: Function, inConstructorFlag: Long): Tree = {
val anonClass = fun.symbol.owner.newAnonymousFunctionClass(fun.pos, inConstructorFlag)
val parents = if (isFunctionType(fun.tpe)) {
anonClass addAnnotation SerialVersionUIDAnnotation
addSerializable(abstractFunctionType(fun.vparams.map(_.symbol.tpe), fun.body.tpe.deconst))
} else {
if (fun.tpe.typeSymbol.isSubClass(SerializableClass))
anonClass addAnnotation SerialVersionUIDAnnotation
fun.tpe :: Nil
val typeSym = fun.tpe.typeSymbol
val isFunction = isFunctionSymbol(typeSym)
if (isFunction || typeSym.isSubClass(SerializableClass))
anonClass.addAnnotation(SerialVersionUIDAnnotation)

val rScope = newScope
def parents(tp: Type): List[Type] = tp match {
case RefinedType(ps, scope) =>
assert(scope.forall(_.isType), s"Cannot expand function of type $tp")
ps.flatMap(parents).tap(_ => scope.foreach(rScope.enter))
case _ =>
if (!isFunction) tp :: Nil
else addSerializable(abstractFunctionType(fun.vparams.map(_.symbol.tpe), fun.body.tpe.deconst))
}
anonClass setInfo ClassInfoType(parents, newScope, anonClass)
anonClass.setInfo(ClassInfoType(parents(fun.tpe), rScope, anonClass))

// The original owner is used in the backend for the EnclosingMethod attribute. If fun is
// nested in a value-class method, its owner was already changed to the extension method.
Expand All @@ -387,7 +394,8 @@ abstract class TreeGen extends scala.reflect.internal.TreeGen with TreeDSL {
localTyper.typedPos(fun.pos) {
Block(
ClassDef(anonClass, NoMods, ListOfNil, List(samDef), fun.pos),
Typed(New(anonClass.tpe), TypeTree(fun.tpe)))
Typed(New(anonClass.tpe), TypeTree(fun.tpe)),
)
}
}

Expand Down
11 changes: 9 additions & 2 deletions src/reflect/scala/reflect/internal/Definitions.scala
Expand Up @@ -1019,8 +1019,14 @@ trait Definitions extends api.StandardDefinitions {
* and the caching means that samOf is effectively computed during typer (assuming the same inputs were presented to samOf during that phase).
* It's kind of strange that erasure sees deferredMembers that typer does not (see commented out assert below)
*/
def samOf(tp: Type): Symbol =
if (isNonRefinementClassType(unwrapToClass(tp))) { // TODO: is this really faster than computing tpSym below? how about just `tp.typeSymbol.isClass` (and !tpSym.isRefinementClass)?
def samOf(tp: Type): Symbol = {
@tailrec def isEligible(tp: Type): Boolean = unwrapToClass(tp) match {
case TypeRef(_, sym, _) => sym.isClass && !sym.isRefinementClass
case RefinedType(parent :: Nil, decls) => decls.forall(_.isType) && isEligible(parent)
case _ => false
}

if (isEligible(tp)) {
// look at erased type because we (only) care about what ends up in bytecode
// (e.g., an alias type is fine as long as is compiles to a single-abstract-method)
val tpSym: Symbol = erasure.javaErasure(tp).typeSymbol
Expand Down Expand Up @@ -1065,6 +1071,7 @@ trait Definitions extends api.StandardDefinitions {

samCache.getOrElseUpdate(tpSym, compute)
} else NoSymbol
}

def samOfProto(pt: Type): Symbol =
pt match {
Expand Down
38 changes: 25 additions & 13 deletions test/files/neg/sammy_restrictions.check
@@ -1,62 +1,74 @@
sammy_restrictions.scala:38: error: type mismatch;
sammy_restrictions.scala:40: error: type mismatch;
found : () => Int
required: NoAbstract
def f0 = (() => 0) : NoAbstract
^
sammy_restrictions.scala:39: error: type mismatch;
sammy_restrictions.scala:41: error: type mismatch;
found : Int => Int
required: TwoAbstract
def f1 = ((x: Int) => 0): TwoAbstract
^
sammy_restrictions.scala:40: error: type mismatch;
sammy_restrictions.scala:42: error: type mismatch;
found : Int => Int
required: NoEmptyConstructor
def f2 = ((x: Int) => 0): NoEmptyConstructor
^
sammy_restrictions.scala:41: error: type mismatch;
sammy_restrictions.scala:43: error: type mismatch;
found : Int => Int
required: MultipleConstructorLists
def f3 = ((x: Int) => 0): MultipleConstructorLists
^
sammy_restrictions.scala:42: error: type mismatch;
sammy_restrictions.scala:44: error: type mismatch;
found : Int => Int
required: OneEmptySecondaryConstructor
def f4 = ((x: Int) => 0): OneEmptySecondaryConstructor // derived class must have an empty *primary* to call.
^
sammy_restrictions.scala:43: error: type mismatch;
sammy_restrictions.scala:45: error: type mismatch;
found : Int => Int
required: MultipleMethodLists
def f5 = ((x: Int) => 0): MultipleMethodLists
^
sammy_restrictions.scala:44: error: type mismatch;
sammy_restrictions.scala:46: error: type mismatch;
found : Int => Int
required: ImplicitConstructorParam
def f6 = ((x: Int) => 0): ImplicitConstructorParam
^
sammy_restrictions.scala:45: error: type mismatch;
sammy_restrictions.scala:47: error: type mismatch;
found : Int => Int
required: ImplicitMethodParam
def f7 = ((x: Int) => 0): ImplicitMethodParam
^
sammy_restrictions.scala:46: error: type mismatch;
sammy_restrictions.scala:48: error: type mismatch;
found : Int => Int
required: PolyMethod
def f8 = ((x: Int) => 0): PolyMethod
^
sammy_restrictions.scala:47: error: type mismatch;
sammy_restrictions.scala:49: error: type mismatch;
found : Int => Int
required: SelfTp
def f9 = ((x: Int) => 0): SelfTp
^
sammy_restrictions.scala:48: error: type mismatch;
sammy_restrictions.scala:50: error: type mismatch;
found : Int => Int
required: T1 with U1
def g0 = ((x: Int) => 0): T1 with U1
^
sammy_restrictions.scala:49: error: type mismatch;
sammy_restrictions.scala:51: error: type mismatch;
found : Int => Int
required: Test.NonClassTypeRefinement
(which expands to) DerivedOneAbstract with OneAbstract
def g1 = ((x: Int) => 0): NonClassTypeRefinement
^
12 errors
sammy_restrictions.scala:52: error: type mismatch;
found : Int => Int
required: Test.NonOverridingMethodRefinement
(which expands to) OneAbstract{def apples(): Int}
def h1 = ((x: Int) => 0): NonOverridingMethodRefinement
^
sammy_restrictions.scala:53: error: type mismatch;
found : Int => Int
required: Test.OverridingMethodRefinement
(which expands to) OneAbstract{def ap(a: Int): Int}
def h2 = ((x: Int) => 0): OverridingMethodRefinement
^
14 errors
4 changes: 4 additions & 0 deletions test/files/neg/sammy_restrictions.scala
Expand Up @@ -32,6 +32,8 @@ trait T1 { def t(a: Int): Int }; trait U1
object Test {
implicit val s: String = ""
type NonClassTypeRefinement = DerivedOneAbstract with OneAbstract
type NonOverridingMethodRefinement = OneAbstract { def apples(): Int }
type OverridingMethodRefinement = OneAbstract { def ap(a: Int): Int } // allowed in Scala 3
type NonClassType = DerivedOneAbstract

// errors:
Expand All @@ -47,6 +49,8 @@ object Test {
def f9 = ((x: Int) => 0): SelfTp
def g0 = ((x: Int) => 0): T1 with U1
def g1 = ((x: Int) => 0): NonClassTypeRefinement
def h1 = ((x: Int) => 0): NonOverridingMethodRefinement
def h2 = ((x: Int) => 0): OverridingMethodRefinement

// allowed:
def g2 = ((x: Int) => 0): OneEmptyConstructor
Expand Down
22 changes: 22 additions & 0 deletions test/files/pos/sammy_refined.scala
@@ -0,0 +1,22 @@
trait DepFn[-A] {
type Out
def apply(in: A): Out
}

object DepFn {
type Aux[-A, B] = DepFn[A] { type Out = B }
type AuxF[F[_], A] = Aux[F[A], F[A]] { type B >: A }
val length: DepFn[String] { type Out = Int } = _.length
val upper: Aux[String, String] = _.toUpperCase
val reverse: AuxF[List, Int] = _.reverse
}

class Outer {
// T here does not compile to a SAM in bytecode,
// because of the outer reference to the enclosing class.
trait T { def f(x: Int): Int }
val t1: T = x => x
val t2: T { type U = String } = x => x
val t3: T { type U } = x => x
val t4: (T { type U }) { type V } = x => x
}

0 comments on commit d2f97a3

Please sign in to comment.