Skip to content

Commit 5e5ab18

Browse files
committed
Clarify how/when typedFunction unrolls eta-expansion
Jason points out the recursion will be okay if type checking the function inside the eta-expansion provides fully determined argument types, as the result type is not relevant for this phase of typedFunction.
1 parent 5d7d644 commit 5e5ab18

File tree

2 files changed

+41
-21
lines changed

2 files changed

+41
-21
lines changed

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

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,22 +17,27 @@ import symtab.Flags._
1717
trait EtaExpansion { self: Analyzer =>
1818
import global._
1919

20-
/** <p>
21-
* Expand partial function applications of type `type`.
22-
* </p><pre>
23-
* p.f(es_1)...(es_n)
24-
* ==> {
25-
* <b>private synthetic val</b> eta$f = p.f // if p is not stable
26-
* ...
27-
* <b>private synthetic val</b> eta$e_i = e_i // if e_i is not stable
28-
* ...
29-
* (ps_1 => ... => ps_m => eta$f([es_1])...([es_m])(ps_1)...(ps_m))
30-
* }</pre>
31-
* <p>
32-
* tree is already attributed
33-
* </p>
34-
*/
35-
def etaExpand(unit : CompilationUnit, tree: Tree, typer: Typer): Tree = {
20+
/** Expand partial method application `p.f(es_1)...(es_n)`.
21+
*
22+
* We expand this to the following block, which evaluates
23+
* the target of the application and its supplied arguments if needed (they are not stable),
24+
* and then wraps a Function that abstracts over the missing arguments.
25+
*
26+
* ```
27+
* {
28+
* private synthetic val eta$f = p.f // if p is not stable
29+
* ...
30+
* private synthetic val eta$e_i = e_i // if e_i is not stable
31+
* ...
32+
* (ps_1 => ... => ps_m => eta$f([es_1])...([es_m])(ps_1)...(ps_m))
33+
* }
34+
* ```
35+
*
36+
* This is called from instantiateToMethodType after type checking `tree`,
37+
* and we realize we have a method type, where a function type (builtin or SAM) is expected.
38+
*
39+
**/
40+
def etaExpand(unit: CompilationUnit, tree: Tree, typer: Typer): Tree = {
3641
val tpe = tree.tpe
3742
var cnt = 0 // for NoPosition
3843
def freshName() = {

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

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,10 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
244244
var context = context0
245245
def context1 = context
246246

247+
// for use with silent type checking to when we can't have results with undetermined type params
248+
// note that this captures the context var
249+
val isMonoContext = (_: Any) => context.undetparams.isEmpty
250+
247251
def dropExistential(tp: Type): Type = tp match {
248252
case ExistentialType(tparams, tpe) =>
249253
new SubstWildcardMap(tparams).apply(tp)
@@ -2888,20 +2892,31 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
28882892

28892893
// If we're typing `(a1: T1, ..., aN: TN) => m(a1,..., aN)`, where some Ti are not fully defined,
28902894
// type `m` directly (undoing eta-expansion of method m) to determine the argument types.
2895+
// This tree is the result from one of:
2896+
// - manual eta-expansion with named arguments (x => f(x));
2897+
// - wildcard-style eta expansion (`m(_, _,)`);
2898+
// - instantiateToMethodType adapting a tree of method type to a function type using etaExpand.
2899+
//
2900+
// Note that method values are a separate thing (`m _`): they have the idiosyncratic shape
2901+
// of `Typed(expr, Function(Nil, EmptyTree))`
28912902
val ptUnrollingEtaExpansion =
28922903
if (paramsMissingType.nonEmpty && pt != ErrorType) fun.body match {
2904+
// we can compare arguments and parameters by name because there cannot be a binder between
2905+
// the function's valdefs and the Apply's arguments
28932906
case Apply(meth, args) if (vparams corresponds args) { case (p, Ident(name)) => p.name == name case _ => false } =>
2907+
// We're looking for a method (as indicated by FUNmode in the silent typed below),
2908+
// so let's make sure our expected type is a MethodType
28942909
val methArgs = NoSymbol.newSyntheticValueParams(argpts map { case NoType => WildcardType case tp => tp })
2895-
// we're looking for a method (as indicated by FUNmode), so let's make sure our expected type is a MethodType
2896-
val methPt = MethodType(methArgs, respt)
2897-
2898-
silent(_.typed(meth, mode.forFunMode, methPt)) filter (_ => context.undetparams.isEmpty) map { methTyped =>
2910+
silent(_.typed(meth, mode.forFunMode, MethodType(methArgs, respt))) filter (isMonoContext) map { methTyped =>
28992911
// if context.undetparams is not empty, the method was polymorphic,
29002912
// so we need the missing arguments to infer its type. See #871
29012913
val funPt = normalize(methTyped.tpe) baseType FunctionClass(numVparams)
29022914
// println(s"typeUnEtaExpanded $meth : ${methTyped.tpe} --> normalized: $funPt")
29032915

2904-
if (isFunctionType(funPt) && isFullyDefined(funPt)) funPt
2916+
// If we are sure this function type provides all the necesarry info, so that we won't have
2917+
// any undetermined argument types, go ahead an recurse below (`typedFunction(fun, mode, ptUnrollingEtaExpansion)`)
2918+
// and rest assured we won't end up right back here (and keep recursing)
2919+
if (isFunctionType(funPt) && funPt.typeArgs.iterator.take(numVparams).forall(isFullyDefined)) funPt
29052920
else null
29062921
} orElse { _ => null }
29072922
case _ => null

0 commit comments

Comments
 (0)