Skip to content

Commit 686fb48

Browse files
committed
Merge pull request scala#3037 from gkossakowski/fix-merge-3018
[resubmit] Experimental Single Abstract Method support (sammy meets world)
2 parents d068b16 + b126e5c commit 686fb48

20 files changed

+411
-27
lines changed

build.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1625,15 +1625,15 @@ TODO:
16251625
</target>
16261626

16271627
<!-- See test/build-partest.xml for the macro(s) being used here. -->
1628-
<target name="partest.task" depends="init,pack.done">
1628+
<target name="partest.task" depends="pack.done">
16291629
<!-- note the classpathref! this is the classpath used to run partest,
16301630
so it must have the new compiler.... -->
16311631
<taskdef
16321632
classpathref="partest.compilation.path"
16331633
resource="scala/tools/partest/antlib.xml"/>
16341634
</target>
16351635

1636-
<target name="test.suite.init" depends="pack.done, partest.task">
1636+
<target name="test.suite.init" depends="partest.task">
16371637
<!-- read by test/partest to determine classpath used to run partest -->
16381638
<propertyfile file = "build/pack/partest.properties">
16391639
<entry key = "partest.classpath" value="${toString:partest.compilation.path}"/>

src/compiler/scala/tools/nsc/transform/UnCurry.scala

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,6 @@ abstract class UnCurry extends InfoTransform
8585
transformFunction(result)
8686
}
8787

88-
private lazy val serialVersionUIDAnnotation =
89-
AnnotationInfo(SerialVersionUIDAttr.tpe, List(Literal(Constant(0))), List())
90-
9188
// I don't have a clue why I'm catching TypeErrors here, but it's better
9289
// than spewing stack traces at end users for internal errors. Examples
9390
// which hit at this point should not be hard to come by, but the immediate
@@ -220,7 +217,7 @@ abstract class UnCurry extends InfoTransform
220217
case fun1 if fun1 ne fun => fun1
221218
case _ =>
222219
val parents = addSerializable(abstractFunctionForFunctionType(fun.tpe))
223-
val anonClass = fun.symbol.owner newAnonymousFunctionClass(fun.pos, inConstructorFlag) addAnnotation serialVersionUIDAnnotation
220+
val anonClass = fun.symbol.owner newAnonymousFunctionClass(fun.pos, inConstructorFlag) addAnnotation SerialVersionUIDAnnotation
224221
anonClass setInfo ClassInfoType(parents, newScope, anonClass)
225222

226223
val targs = fun.tpe.typeArgs

src/compiler/scala/tools/nsc/typechecker/Typers.scala

Lines changed: 198 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2503,7 +2503,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
25032503
assert(pt.typeSymbol == PartialFunctionClass, s"PartialFunction synthesis for match in $tree requires PartialFunction expected type, but got $pt.")
25042504
val targs = pt.dealiasWiden.typeArgs
25052505

2506-
// if targs.head isn't fully defined, we can translate --> error
2506+
// if targs.head isn't fully defined, we can't translate --> error
25072507
targs match {
25082508
case argTp :: _ if isFullyDefined(argTp) => // ok
25092509
case _ => // uh-oh
@@ -2517,9 +2517,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
25172517
// targs must conform to Any for us to synthesize an applyOrElse (fallback to apply otherwise -- typically for @cps annotated targs)
25182518
val targsValidParams = targs forall (_ <:< AnyTpe)
25192519

2520-
val anonClass = (context.owner
2521-
newAnonymousFunctionClass tree.pos
2522-
addAnnotation AnnotationInfo(SerialVersionUIDAttr.tpe, List(Literal(Constant(0))), List()))
2520+
val anonClass = context.owner newAnonymousFunctionClass tree.pos addAnnotation SerialVersionUIDAnnotation
25232521

25242522
import CODE._
25252523

@@ -2712,17 +2710,197 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
27122710
}
27132711
}
27142712

2713+
/** Synthesize and type check the implementation of a type with a Single Abstract Method
2714+
*
2715+
* `{ (p1: T1, ..., pN: TN) => body } : S`
2716+
*
2717+
* expands to (where `S` is the expected type that defines a single abstract method named `apply`)
2718+
*
2719+
* `{
2720+
* def apply$body(p1: T1, ..., pN: TN): T = body
2721+
* new S {
2722+
* def apply(p1: T1, ..., pN: TN): T = apply$body(p1,..., pN)
2723+
* }
2724+
* }`
2725+
*
2726+
* If 'T' is not fully defined, it is inferred by type checking
2727+
* `apply$body` without a result type before type checking the block.
2728+
* The method's inferred result type is used instead of T`. [See test/files/pos/sammy_poly.scala]
2729+
*
2730+
* The `apply` method is identified by the argument `sam`; `S` corresponds to the argument `samClassTp`,
2731+
* and `resPt` is derived from `samClassTp` -- it may be fully defined, or not...
2732+
*
2733+
* The function's body is put in a method outside of the class definition to enforce scoping.
2734+
* S's members should not be in scope in `body`.
2735+
*
2736+
* The restriction on implicit arguments (neither S's constructor, nor sam may take an implicit argument list),
2737+
* is largely to keep the implementation of type inference (the computation of `samClassTpFullyDefined`) simple.
2738+
*
2739+
* NOTE: it would be nicer to not have to type check `apply$body` separately when `T` is not fully defined.
2740+
* However T must be fully defined before we type the instantiation, as it'll end up as a parent type,
2741+
* which must be fully defined. Would be nice to have some kind of mechanism to insert type vars in a block of code,
2742+
* and have the instantiation of the first occurrence propagate to the rest of the block.
2743+
*/
2744+
def synthesizeSAMFunction(sam: Symbol, fun: Function, resPt: Type, samClassTp: Type, mode: Mode): Tree = {
2745+
// assert(fun.vparams forall (vp => isFullyDefined(vp.tpt.tpe))) -- by construction, as we take them from sam's info
2746+
val sampos = fun.pos
2747+
2748+
// if the expected sam type is fully defined, use it for the method's result type
2749+
// otherwise, NoType, so that type inference will determine the method's result type
2750+
// resPt is syntactically contained in samClassTp, so if the latter is fully defined, so is the former
2751+
// ultimately, we want to fully define samClassTp as it is used as the superclass of our anonymous class
2752+
val samDefTp = if (isFullyDefined(resPt)) resPt else NoType
2753+
val bodyName = newTermName(sam.name + "$body")
2754+
2755+
// `def '${sam.name}\$body'($p1: $T1, ..., $pN: $TN): $resPt = $body`
2756+
val samBodyDef =
2757+
DefDef(NoMods,
2758+
bodyName,
2759+
Nil,
2760+
List(fun.vparams.map(_.duplicate)), // must duplicate as we're also using them for `samDef`
2761+
TypeTree(samDefTp) setPos sampos.focus,
2762+
fun.body)
2763+
2764+
// If we need to enter the sym for the body def before type checking the block,
2765+
// we'll create a nested context, as explained below.
2766+
var nestedTyper = this
2767+
2768+
// Type check body def before classdef to fully determine samClassTp (if necessary).
2769+
// As `samClassTp` determines a parent type for the class,
2770+
// we can't type check `block` in one go unless `samClassTp` is fully defined.
2771+
val samClassTpFullyDefined =
2772+
if (isFullyDefined(samClassTp)) samClassTp
2773+
else try {
2774+
// This creates a symbol for samBodyDef with a type completer that'll be triggered immediately below.
2775+
// The symbol is entered in the same scope used for the block below, and won't thus be reentered later.
2776+
// It has to be a new scope, though, or we'll "get ambiguous reference to overloaded definition" [pos/sammy_twice.scala]
2777+
// makeSilent: [pos/nonlocal-unchecked.scala -- when translation all functions to sams]
2778+
val nestedCtx = enterSym(context.makeNewScope(context.tree, context.owner).makeSilent(), samBodyDef)
2779+
nestedTyper = newTyper(nestedCtx)
2780+
2781+
// NOTE: this `samBodyDef.symbol.info` runs the type completer set up by the enterSym above
2782+
val actualSamType = samBodyDef.symbol.info
2783+
2784+
// we're trying to fully define the type arguments for this type constructor
2785+
val samTyCon = samClassTp.typeSymbol.typeConstructor
2786+
2787+
// the unknowns
2788+
val tparams = samClassTp.typeSymbol.typeParams
2789+
// ... as typevars
2790+
val tvars = tparams map freshVar
2791+
2792+
// 1. Recover partial information:
2793+
// - derive a type from samClassTp that has the corresponding tparams for type arguments that aren't fully defined
2794+
// - constrain typevars to be equal to type args that are fully defined
2795+
val samClassTpMoreDefined = appliedType(samTyCon,
2796+
(samClassTp.typeArgs, tparams, tvars).zipped map {
2797+
case (a, _, tv) if isFullyDefined(a) => tv =:= a; a
2798+
case (_, p, _) => p.typeConstructor
2799+
})
2800+
2801+
// the method type we're expecting the synthesized sam to have, based on the expected sam type,
2802+
// where fully defined type args to samClassTp have been preserved,
2803+
// with the unknown args replaced by their corresponding type param
2804+
val expectedSamType = samClassTpMoreDefined.memberInfo(sam)
2805+
2806+
// 2. make sure the body def's actual type (formals and result) conforms to
2807+
// sam's expected type (in terms of the typevars that represent the sam's class's type params)
2808+
actualSamType <:< expectedSamType.substituteTypes(tparams, tvars)
2809+
2810+
// solve constraints tracked by tvars
2811+
val targs = solvedTypes(tvars, tparams, tparams map varianceInType(sam.info), upper = false, lubDepth(sam.info :: Nil))
2812+
2813+
debuglog(s"sam infer: $samClassTp --> ${appliedType(samTyCon, targs)} by $actualSamType <:< $expectedSamType --> $targs for $tparams")
2814+
2815+
// a fully defined samClassTp
2816+
appliedType(samTyCon, targs)
2817+
} catch {
2818+
case _: NoInstance | _: TypeError =>
2819+
devWarning(sampos, s"Could not define type $samClassTp using ${samBodyDef.symbol.rawInfo} <:< ${samClassTp memberInfo sam} (for $sam)")
2820+
samClassTp
2821+
}
2822+
2823+
// `final override def ${sam.name}($p1: $T1, ..., $pN: $TN): $resPt = ${sam.name}\$body'($p1, ..., $pN)`
2824+
val samDef =
2825+
DefDef(Modifiers(FINAL | OVERRIDE | SYNTHETIC),
2826+
sam.name.toTermName,
2827+
Nil,
2828+
List(fun.vparams),
2829+
TypeTree(samBodyDef.tpt.tpe) setPos sampos.focus,
2830+
Apply(Ident(bodyName), fun.vparams map (p => Ident(p.name)))
2831+
)
2832+
2833+
val serializableParentAddendum =
2834+
if (typeIsSubTypeOfSerializable(samClassTp)) Nil
2835+
else List(TypeTree(SerializableTpe))
2836+
2837+
val classDef =
2838+
ClassDef(Modifiers(FINAL), tpnme.ANON_FUN_NAME, tparams = Nil,
2839+
gen.mkTemplate(
2840+
parents = TypeTree(samClassTpFullyDefined) :: serializableParentAddendum,
2841+
self = emptyValDef,
2842+
constrMods = NoMods,
2843+
vparamss = ListOfNil,
2844+
body = List(samDef),
2845+
superPos = sampos.focus
2846+
)
2847+
)
2848+
2849+
// type checking the whole block, so that everything is packaged together nicely
2850+
// and we don't have to create any symbols by hand
2851+
val block =
2852+
nestedTyper.typedPos(sampos, mode, samClassTpFullyDefined) {
2853+
Block(
2854+
samBodyDef,
2855+
classDef,
2856+
Apply(Select(New(Ident(tpnme.ANON_FUN_NAME)), nme.CONSTRUCTOR), Nil)
2857+
)
2858+
}
2859+
2860+
classDef.symbol addAnnotation SerialVersionUIDAnnotation
2861+
block
2862+
}
2863+
2864+
/** Type check a function literal.
2865+
*
2866+
* Based on the expected type pt, potentially synthesize an instance of
2867+
* - PartialFunction,
2868+
* - a type with a Single Abstract Method (under -Xexperimental for now).
2869+
*/
27152870
private def typedFunction(fun: Function, mode: Mode, pt: Type): Tree = {
27162871
val numVparams = fun.vparams.length
2717-
if (numVparams > definitions.MaxFunctionArity)
2718-
return MaxFunctionArityError(fun)
2872+
val FunctionSymbol =
2873+
if (numVparams > definitions.MaxFunctionArity) NoSymbol
2874+
else FunctionClass(numVparams)
27192875

2720-
val FunctionSymbol = FunctionClass(numVparams)
2721-
val (argpts, respt) = pt baseType FunctionSymbol match {
2722-
case TypeRef(_, FunctionSymbol, args :+ res) => (args, res)
2723-
case _ => (fun.vparams map (_ => if (pt == ErrorType) ErrorType else NoType), WildcardType)
2724-
}
2725-
if (argpts.lengthCompare(numVparams) != 0)
2876+
/* The Single Abstract Member of pt, unless pt is the built-in function type of the expected arity,
2877+
* as `(a => a): Int => Int` should not (yet) get the sam treatment.
2878+
*/
2879+
val sam =
2880+
if (!settings.Xexperimental || pt.typeSymbol == FunctionSymbol) NoSymbol
2881+
else samOf(pt)
2882+
2883+
/* The SAM case comes first so that this works:
2884+
* abstract class MyFun extends (Int => Int)
2885+
* (a => a): MyFun
2886+
*
2887+
* Note that the arity of the sam must correspond to the arity of the function.
2888+
*/
2889+
val samViable = sam.exists && sameLength(sam.info.params, fun.vparams)
2890+
val (argpts, respt) =
2891+
if (samViable) {
2892+
val samInfo = pt memberInfo sam
2893+
(samInfo.paramTypes, samInfo.resultType)
2894+
} else {
2895+
pt baseType FunctionSymbol match {
2896+
case TypeRef(_, FunctionSymbol, args :+ res) => (args, res)
2897+
case _ => (fun.vparams map (_ => if (pt == ErrorType) ErrorType else NoType), WildcardType)
2898+
}
2899+
}
2900+
2901+
if (!FunctionSymbol.exists)
2902+
MaxFunctionArityError(fun)
2903+
else if (argpts.lengthCompare(numVparams) != 0)
27262904
WrongNumberOfParametersError(fun, argpts)
27272905
else {
27282906
foreach2(fun.vparams, argpts) { (vparam, argpt) =>
@@ -2733,7 +2911,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
27332911
fun match {
27342912
case etaExpansion(vparams, fn, args) =>
27352913
silent(_.typed(fn, mode.forFunMode, pt)) filter (_ => context.undetparams.isEmpty) map { fn1 =>
2736-
// if context,undetparams is not empty, the function was polymorphic,
2914+
// if context.undetparams is not empty, the function was polymorphic,
27372915
// so we need the missing arguments to infer its type. See #871
27382916
//println("typing eta "+fun+":"+fn1.tpe+"/"+context.undetparams)
27392917
val ftpe = normalize(fn1.tpe) baseType FunctionClass(numVparams)
@@ -2761,6 +2939,13 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
27612939
if (p.tpt.tpe == null) p.tpt setType outerTyper.typedType(p.tpt).tpe
27622940

27632941
outerTyper.synthesizePartialFunction(p.name, p.pos, fun.body, mode, pt)
2942+
2943+
// Use synthesizeSAMFunction to expand `(p1: T1, ..., pN: TN) => body`
2944+
// to an instance of the corresponding anonymous subclass of `pt`.
2945+
case _ if samViable =>
2946+
newTyper(context.outer).synthesizeSAMFunction(sam, fun, respt, pt, mode)
2947+
2948+
// regular Function
27642949
case _ =>
27652950
val vparamSyms = fun.vparams map { vparam =>
27662951
enterSym(context, vparam)

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

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -799,6 +799,44 @@ trait Definitions extends api.StandardDefinitions {
799799
(sym eq PartialFunctionClass) || (sym eq AbstractPartialFunctionClass)
800800
}
801801

802+
/** The single abstract method declared by type `tp` (or `NoSymbol` if it cannot be found).
803+
*
804+
* The method must be monomorphic and have exactly one parameter list.
805+
* The class defining the method is a supertype of `tp` that
806+
* has a public no-arg primary constructor.
807+
*/
808+
def samOf(tp: Type): Symbol = {
809+
// if tp has a constructor, it must be public and must not take any arguments
810+
// (not even an implicit argument list -- to keep it simple for now)
811+
val tpSym = tp.typeSymbol
812+
val ctor = tpSym.primaryConstructor
813+
val ctorOk = !ctor.exists || (!ctor.isOverloaded && ctor.isPublic && ctor.info.params.isEmpty && ctor.info.paramSectionCount <= 1)
814+
815+
if (tpSym.exists && ctorOk) {
816+
// find the single abstract member, if there is one
817+
// don't go out requiring DEFERRED members, as you will get them even if there's a concrete override:
818+
// scala> abstract class X { def m: Int }
819+
// scala> class Y extends X { def m: Int = 1}
820+
// scala> typeOf[Y].deferredMembers
821+
// Scopes(method m, method getClass)
822+
//
823+
// scala> typeOf[Y].members.filter(_.isDeferred)
824+
// Scopes()
825+
// must filter out "universal" members (getClass is deferred for some reason)
826+
val deferredMembers = (
827+
tp membersBasedOnFlags (excludedFlags = BridgeAndPrivateFlags, requiredFlags = METHOD)
828+
filter (mem => mem.isDeferredNotDefault && !isUniversalMember(mem)) // TODO: test
829+
)
830+
831+
// if there is only one, it's monomorphic and has a single argument list
832+
if (deferredMembers.size == 1 &&
833+
deferredMembers.head.typeParams.isEmpty &&
834+
deferredMembers.head.info.paramSectionCount == 1)
835+
deferredMembers.head
836+
else NoSymbol
837+
} else NoSymbol
838+
}
839+
802840
def arrayType(arg: Type) = appliedType(ArrayClass, arg)
803841
def byNameType(arg: Type) = appliedType(ByNameParamClass, arg)
804842
def iteratorOfType(tp: Type) = appliedType(IteratorClass, tp)
@@ -1089,6 +1127,7 @@ trait Definitions extends api.StandardDefinitions {
10891127
lazy val ScalaInlineClass = requiredClass[scala.inline]
10901128
lazy val ScalaNoInlineClass = requiredClass[scala.noinline]
10911129
lazy val SerialVersionUIDAttr = requiredClass[scala.SerialVersionUID]
1130+
lazy val SerialVersionUIDAnnotation = AnnotationInfo(SerialVersionUIDAttr.tpe, List(Literal(Constant(0))), List())
10921131
lazy val SpecializedClass = requiredClass[scala.specialized]
10931132
lazy val ThrowsClass = requiredClass[scala.throws[_]]
10941133
lazy val TransientAttr = requiredClass[scala.transient]

src/reflect/scala/reflect/internal/Types.scala

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -974,6 +974,18 @@ trait Types
974974
else (baseClasses.head.newOverloaded(this, alts))
975975
}
976976

977+
/** Find all members meeting the flag requirements.
978+
*
979+
* If you require a DEFERRED member, you will get it if it exists -- even if there's an overriding concrete member.
980+
* If you exclude DEFERRED members, or don't specify any requirements,
981+
* you won't get deferred members (whether they have an overriding concrete member or not)
982+
*
983+
* Thus, findMember requiring DEFERRED flags yields deferred members,
984+
* while `findMember(excludedFlags = 0, requiredFlags = 0).filter(_.isDeferred)` may not (if there's a corresponding concrete member)
985+
*
986+
* Requirements take precedence over exclusions, so requiring and excluding DEFERRED will yield a DEFERRED member (if there is one).
987+
*
988+
*/
977989
def findMembers(excludedFlags: Long, requiredFlags: Long): Scope = {
978990
def findMembersInternal: Scope = {
979991
var members: Scope = null
@@ -983,10 +995,10 @@ trait Types
983995
//Console.println("find member " + name.decode + " in " + this + ":" + this.baseClasses)//DEBUG
984996
var required = requiredFlags
985997
var excluded = excludedFlags | DEFERRED
986-
var continue = true
998+
var retryForDeferred = true
987999
var self: Type = null
988-
while (continue) {
989-
continue = false
1000+
while (retryForDeferred) {
1001+
retryForDeferred = false
9901002
val bcs0 = baseClasses
9911003
var bcs = bcs0
9921004
while (!bcs.isEmpty) {
@@ -1018,7 +1030,7 @@ trait Types
10181030
}
10191031
if (others eq null) members enter sym
10201032
} else if (excl == DEFERRED) {
1021-
continue = true
1033+
retryForDeferred = (excludedFlags & DEFERRED) == 0
10221034
}
10231035
}
10241036
entry = entry.next
@@ -1028,7 +1040,7 @@ trait Types
10281040
} // while (!bcs.isEmpty)
10291041
required |= DEFERRED
10301042
excluded &= ~(DEFERRED.toLong)
1031-
} // while (continue)
1043+
} // while (retryForDeferred)
10321044
if (Statistics.canEnable) Statistics.popTimer(typeOpsStack, start)
10331045
if (members eq null) EmptyScope else members
10341046
}

0 commit comments

Comments
 (0)