Skip to content

Commit 8e32d00

Browse files
committed
Keep Function when CBN arg thunk targets a SAM
The body of `def delay[T](v: => T) = (v _): F0[T]` becomes `() => v` during `typedEta`, and then uncurry considers whether to strip the function wrapper since `v` is known to be a `Function0` thunk. Stripping is sound when the expected type is `Function0` for this expression, but that's no longer a given, since we could be expecting any nullary SAM. Also sweep up a bit around `typedEta`. Encapsulate the, erm, creative encoding of `m _` as `Typed(m, Function(Nil, EmptyTree))`.
1 parent 7025be9 commit 8e32d00

File tree

5 files changed

+50
-29
lines changed

5 files changed

+50
-29
lines changed

src/compiler/scala/tools/nsc/ast/parser/Parsers.scala

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1713,9 +1713,7 @@ self =>
17131713
}
17141714
simpleExprRest(app, canApply = true)
17151715
case USCORE =>
1716-
atPos(t.pos.start, in.skipToken()) {
1717-
Typed(stripParens(t), Function(Nil, EmptyTree))
1718-
}
1716+
atPos(t.pos.start, in.skipToken()) { makeMethodValue(stripParens(t)) }
17191717
case _ =>
17201718
t
17211719
}

src/compiler/scala/tools/nsc/ast/parser/TreeBuilder.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ abstract class TreeBuilder {
3535
def repeatedApplication(tpe: Tree): Tree =
3636
AppliedTypeTree(rootScalaDot(tpnme.REPEATED_PARAM_CLASS_NAME), List(tpe))
3737

38+
// represents `expr _`, as specified in Method Values of spec/06-expressions.md
39+
def makeMethodValue(expr: Tree): Tree = Typed(expr, Function(Nil, EmptyTree))
40+
3841
def makeImportSelector(name: Name, nameOffset: Int): ImportSelector =
3942
ImportSelector(name, nameOffset, name, nameOffset)
4043

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -205,8 +205,11 @@ abstract class UnCurry extends InfoTransform
205205
*
206206
*/
207207
def transformFunction(fun: Function): Tree =
208-
// Undo eta expansion for parameterless and nullary methods
209-
if (fun.vparams.isEmpty && isByNameRef(fun.body)) { noApply += fun.body ; fun.body }
208+
// Undo eta expansion for parameterless and nullary methods, EXCEPT if `fun` targets a SAM.
209+
// Normally, we can unwrap `() => cbn` to `cbn` where `cbn` refers to a CBN argument (typically `cbn` is an Ident),
210+
// because we know `cbn` will already be a `Function0` thunk. When we're targeting a SAM,
211+
// the types don't align and we must preserve the function wrapper.
212+
if (fun.vparams.isEmpty && isByNameRef(fun.body) && fun.attachments.get[SAMFunction].isEmpty) { noApply += fun.body ; fun.body }
210213
else if (forceExpandFunction || inConstructorFlag != 0) {
211214
// Expand the function body into an anonymous class
212215
gen.expandFunction(localTyper)(fun, inConstructorFlag)

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

Lines changed: 32 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2879,8 +2879,9 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
28792879
silent(_.typed(fn, mode.forFunMode, pt)) filter (_ => context.undetparams.isEmpty) map { fn1 =>
28802880
// if context.undetparams is not empty, the function was polymorphic,
28812881
// so we need the missing arguments to infer its type. See #871
2882-
//println("typing eta "+fun+":"+fn1.tpe+"/"+context.undetparams)
28832882
val ftpe = normalize(fn1.tpe) baseType FunctionClass(numVparams)
2883+
// println(s"typeUnEtaExpanded $fn : ${fn1.tpe} (unwrapped $fun) --> normalized: $ftpe")
2884+
28842885
if (isFunctionType(ftpe) && isFullyDefined(ftpe)) ftpe
28852886
else NoType
28862887
} orElse { _ => NoType }
@@ -4365,28 +4366,35 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
43654366
case _ => tp
43664367
}
43674368

4368-
def expectingFunctionMatchingFormals(formals: List[Symbol]) =
4369-
isFunctionType(pt) || samMatchesFunctionBasedOnArity(samOf(pt), formals)
4370-
4371-
def typedEta(expr1: Tree): Tree = expr1.tpe match {
4372-
case TypeRef(_, ByNameParamClass, _) =>
4373-
val expr2 = Function(List(), expr1) setPos expr1.pos
4374-
new ChangeOwnerTraverser(context.owner, expr2.symbol).traverse(expr2)
4375-
typed1(expr2, mode, pt)
4376-
case NullaryMethodType(restpe) =>
4377-
val expr2 = Function(List(), expr1) setPos expr1.pos
4378-
new ChangeOwnerTraverser(context.owner, expr2.symbol).traverse(expr2)
4379-
typed1(expr2, mode, pt)
4380-
case PolyType(_, MethodType(formals, _)) =>
4381-
if (expectingFunctionMatchingFormals(formals)) expr1
4382-
else adapt(expr1, mode, checkArity(expr1)(functionTypeWildcard(formals.length)))
4383-
case MethodType(formals, _) =>
4384-
if (expectingFunctionMatchingFormals(formals)) expr1
4385-
else adapt(expr1, mode, checkArity(expr1)(functionTypeWildcard(formals.length)))
4369+
4370+
/** Eta expand an expression like `m _`, where `m` denotes a method or a by-name argument
4371+
*
4372+
* The spec says:
4373+
* The expression `$e$ _` is well-formed if $e$ is of method type or if $e$ is a call-by-name parameter.
4374+
* (1) If $e$ is a method with parameters, `$e$ _` represents $e$ converted to a function type
4375+
* by [eta expansion](#eta-expansion).
4376+
* (2) If $e$ is a parameterless method or call-by-name parameter of type `=>$T$`, `$e$ _` represents
4377+
* the function of type `() => $T$`, which evaluates $e$ when it is applied to the empty parameterlist `()`.
4378+
*/
4379+
def typedEta(methodValue: Tree): Tree = methodValue.tpe match {
4380+
case tp@(MethodType(_, _) | PolyType(_, MethodType(_, _))) => // (1)
4381+
val formals = tp.params
4382+
if (isFunctionType(pt) || samMatchesFunctionBasedOnArity(samOf(pt), formals)) methodValue
4383+
else adapt(methodValue, mode, checkArity(methodValue)(functionTypeWildcard(formals.length)))
4384+
4385+
case TypeRef(_, ByNameParamClass, _) | NullaryMethodType(_) => // (2)
4386+
val pos = methodValue.pos
4387+
// must create it here to change owner (normally done by typed's typedFunction)
4388+
val funSym = context.owner.newAnonymousFunctionValue(pos)
4389+
new ChangeOwnerTraverser(context.owner, funSym) traverse methodValue
4390+
4391+
typed(Function(List(), methodValue) setSymbol funSym setPos pos, mode, pt)
4392+
43864393
case ErrorType =>
4387-
expr1
4394+
methodValue
4395+
43884396
case _ =>
4389-
UnderscoreEtaError(expr1)
4397+
UnderscoreEtaError(methodValue)
43904398
}
43914399

43924400
def tryTypedArgs(args: List[Tree], mode: Mode): Option[List[Tree]] = {
@@ -4430,7 +4438,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
44304438
case Annotated(_, r) => treesInResult(r)
44314439
case If(_, t, e) => treesInResult(t) ++ treesInResult(e)
44324440
case Try(b, catches, _) => treesInResult(b) ++ catches
4433-
case Typed(r, Function(Nil, EmptyTree)) => treesInResult(r)
4441+
case Typed(r, Function(Nil, EmptyTree)) => treesInResult(r) // a method value
44344442
case Select(qual, name) => treesInResult(qual)
44354443
case Apply(fun, args) => treesInResult(fun) ++ args.flatMap(treesInResult)
44364444
case TypeApply(fun, args) => treesInResult(fun) ++ args.flatMap(treesInResult)
@@ -5070,11 +5078,11 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
50705078
// because `expr` might contain nested macro calls (see SI-6673)
50715079
//
50725080
// Note: apparently `Function(Nil, EmptyTree)` is the secret parser marker
5073-
// which means trailing underscore.
5081+
// which means trailing underscore -- denoting a method value. See makeMethodValue in TreeBuilder.
50745082
case Typed(expr, Function(Nil, EmptyTree)) =>
50755083
typed1(suppressMacroExpansion(expr), mode, pt) match {
50765084
case macroDef if treeInfo.isMacroApplication(macroDef) => MacroEtaError(macroDef)
5077-
case exprTyped => typedEta(checkDead(exprTyped))
5085+
case methodValue => typedEta(checkDead(methodValue))
50785086
}
50795087
case Typed(expr, tpt) =>
50805088
val tpt1 = typedType(tpt, mode) // type the ascribed type first

test/files/run/sammy_cbn.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
trait F0[T] { def apply(): T }
2+
3+
object Test extends App {
4+
def delay[T](v: => T) = (v _): F0[T]
5+
6+
// should not fail with ClassCastException: $$Lambda$6279/897871870 cannot be cast to F0
7+
// (also, should not say boe!)
8+
delay(println("boe!"))
9+
}

0 commit comments

Comments
 (0)