Skip to content

Commit 8951701

Browse files
authored
Merge pull request scala#6475 from adriaanm/eta_cleanup
Do not eta-expand 0-arg methods. Improve eta-expansion for method values.
2 parents c4adc81 + 99ff973 commit 8951701

15 files changed

+151
-124
lines changed

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

+14-9
Original file line numberDiff line numberDiff line change
@@ -667,14 +667,14 @@ trait ContextErrors {
667667
def MissingArgsForMethodTpeError(tree: Tree, meth: Symbol) = {
668668
val f = meth.name.decoded
669669
val paf = s"$f(${ meth.asMethod.paramLists map (_ map (_ => "_") mkString ",") mkString ")(" })"
670-
val advice = s"""
671-
|Unapplied methods are only converted to functions when a function type is expected.
672-
|You can make this conversion explicit by writing `$f _` or `$paf` instead of `$f`.""".stripMargin
670+
val advice =
671+
if (meth.isConstructor || meth.info.params.length > definitions.MaxFunctionArity) ""
672+
else s"""
673+
|Unapplied methods are only converted to functions when a function type is expected.
674+
|You can make this conversion explicit by writing `$f _` or `$paf` instead of `$f`.""".stripMargin
673675
val message =
674676
if (meth.isMacro) MacroTooFewArgumentListsMessage
675-
else s"""missing argument list for ${meth.fullLocationString}${
676-
if (!meth.isConstructor) advice else ""
677-
}"""
677+
else s"""missing argument list for ${meth.fullLocationString}$advice"""
678678
issueNormalTypeError(tree, message)
679679
setError(tree)
680680
}
@@ -730,9 +730,12 @@ trait ContextErrors {
730730
setError(tree)
731731
}
732732

733+
def DependentMethodTpeConversionToFunctionError(tree: Tree, tp: Type): Tree = {
734+
issueNormalTypeError(tree, "method with dependent type " + tp + " cannot be converted to function value")
735+
setError(tree)
736+
}
737+
733738
// cases where we do not necessarily return trees
734-
def DependentMethodTpeConversionToFunctionError(tree: Tree, tp: Type) =
735-
issueNormalTypeError(tree, "method with dependent type "+tp+" cannot be converted to function value")
736739

737740
//checkStarPatOK
738741
def StarPatternWithVarargParametersError(tree: Tree) =
@@ -1375,9 +1378,11 @@ trait ContextErrors {
13751378
)
13761379
}
13771380

1381+
// Note that treeInfo.Applied always matches, it just returns Nil when no application was found...
13781382
def treeTypeArgs(annotatedTree: Tree): List[String] = annotatedTree match {
1379-
case TypeApply(_, args) => args.map(_.toString)
13801383
case Block(_, Function(_, treeInfo.Applied(_, targs, _))) => targs.map(_.toString) // eta expansion, see neg/t9527b.scala
1384+
case Function(_, treeInfo.Applied(_, targs, _)) => targs.map(_.toString) // eta expansion, see neg/t9527b.scala
1385+
case treeInfo.Applied(_, targs, _) => targs.map(_.toString)
13811386
case _ => Nil
13821387
}
13831388

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

+8-4
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import symtab.Flags._
1717
trait EtaExpansion { self: Analyzer =>
1818
import global._
1919

20-
/** Expand partial method application `p.f(es_1)...(es_n)`.
20+
/** Expand partial method application `p.f(es_1)...(es_n)`. Does not support dependent method types (yet).
2121
*
2222
* We expand this to the following block, which evaluates
2323
* the target of the application and its supplied arguments if needed (they are not stable),
@@ -33,8 +33,9 @@ trait EtaExpansion { self: Analyzer =>
3333
* }
3434
* ```
3535
*
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.
36+
* This is called from typedEtaExpansion, which itself is called from
37+
* - instantiateToMethodType (for a naked method reference), or
38+
* - typedEta (when type checking a method value, `m _`).
3839
*
3940
**/
4041
def etaExpand(unit: CompilationUnit, tree: Tree, typer: Typer): Tree = {
@@ -120,6 +121,9 @@ trait EtaExpansion { self: Analyzer =>
120121
}
121122

122123
val tree1 = liftoutPrefix(tree)
123-
atPos(tree.pos)(Block(defs.toList, expand(tree1, tpe)))
124+
val expansion = expand(tree1, tpe)
125+
126+
if (defs.isEmpty) expansion
127+
else atPos(tree.pos)(Block(defs.toList, expansion))
124128
}
125129
}

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

-3
Original file line numberDiff line numberDiff line change
@@ -205,9 +205,6 @@ trait StdAttachments {
205205

206206
def removeStabilizingDefinitions(tree: Tree): Tree =
207207
tree.removeAttachment[StabilizingDefinitions](StabilizingDefinitionsTag)
208-
209-
/** Added to trees that appear in a method value, e.g., to `f(x)` in `f(x) _` */
210-
case object MethodValueAttachment
211208
}
212209

213210

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

+65-86
Original file line numberDiff line numberDiff line change
@@ -348,24 +348,6 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
348348
}
349349
}
350350

351-
def checkParamsConvertible(tree: Tree, tpe0: Type): Unit = {
352-
def checkParamsConvertible0(tpe: Type) =
353-
tpe match {
354-
case MethodType(formals, restpe) =>
355-
/*
356-
if (formals.exists(_.typeSymbol == ByNameParamClass) && formals.length != 1)
357-
error(pos, "methods with `=>`-parameter can be converted to function values only if they take no other parameters")
358-
if (formals exists (isRepeatedParamType(_)))
359-
error(pos, "methods with `*`-parameters cannot be converted to function values");
360-
*/
361-
if (tpe.isDependentMethodType)
362-
DependentMethodTpeConversionToFunctionError(tree, tpe)
363-
checkParamsConvertible(tree, restpe)
364-
case _ =>
365-
}
366-
checkParamsConvertible0(tpe0)
367-
}
368-
369351
/** Check that type of given tree does not contain local or private
370352
* components.
371353
*/
@@ -803,10 +785,10 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
803785
* unless followed by explicit type application.
804786
* (4) Do the following to unapplied methods used as values:
805787
* (4.1) If the method has only implicit parameters pass implicit arguments
806-
* (4.2) otherwise, if `pt` is a function type and method is not a constructor,
807-
* convert to function by eta-expansion,
808-
* (4.3) otherwise, if the method is nullary with a result type compatible to `pt`
788+
* (4.2) otherwise, if the method is nullary with a result type compatible to `pt`
809789
* and it is not a constructor, apply it to ()
790+
* (4.3) otherwise, if `pt` is a function type and method is not a constructor,
791+
* convert to function by eta-expansion,
810792
* otherwise issue an error
811793
* (5) Convert constructors in a pattern as follows:
812794
* (5.1) If constructor refers to a case class factory, set tree's type to the unique
@@ -900,62 +882,42 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
900882
def instantiateToMethodType(mt: MethodType): Tree = {
901883
val meth = tree match {
902884
// a partial named application is a block (see comment in EtaExpansion)
885+
//
886+
// TODO: document why we need to peel off one layer to begin with, and, if we do, why we don't need to recurse??
887+
// (and why we don't need to look at the stats to make sure it's the block created by eta-expansion)
888+
// I don't see how we call etaExpand on a constructor..
889+
//
890+
// I guess we don't need to recurse because the outer block and a nested block will refer to the same method
891+
// after eta-expansion (if that's what generated these blocks), so we can just look at the outer one.
892+
//
893+
// How about user-written blocks? Can they ever have a MethodType?
903894
case Block(_, tree1) => tree1.symbol
904895
case _ => tree.symbol
905896
}
906897

907898
def cantAdapt =
908-
if (context.implicitsEnabled) MissingArgsForMethodTpeError(tree, meth)
909-
else UnstableTreeError(tree)
910-
911-
def emptyApplication: Tree = adapt(typed(Apply(tree, Nil) setPos tree.pos), mode, pt, original)
899+
if (context.implicitsEnabled) MissingArgsForMethodTpeError(tree, meth) else UnstableTreeError(tree)
912900

913901
// constructors do not eta-expand
914902
if (meth.isConstructor) cantAdapt
915-
// (4.2) eta-expand method value when function or sam type is expected
916-
else if (isFunctionType(pt) || (!mt.params.isEmpty && samOf(pt).exists)) {
917-
// scala/bug#9536 `!mt.params.isEmpty &&`: for backwards compatibility with 2.11,
918-
// we don't adapt a zero-arg method value to a SAM
919-
// In 2.13, we won't do any eta-expansion for zero-arg methods, but we should deprecate first
920-
921-
debuglog(s"eta-expanding $tree: ${tree.tpe} to $pt")
922-
checkParamsConvertible(tree, tree.tpe)
923-
924-
// method values (`m _`) are always eta-expanded (this syntax will disappear once we eta-expand regardless of expected type, at least for arity > 0)
925-
// a "naked" method reference (`m`) may or not be eta expanded -- currently, this depends on the expected type and the arity (the conditions for this are in flux)
926-
def isMethodValue = tree.getAndRemoveAttachment[MethodValueAttachment.type].isDefined
927-
val nakedZeroAryMethod = mt.params.isEmpty && !isMethodValue
928-
929-
// scala/bug#7187 eta-expansion of zero-arg method value is deprecated
930-
// 2.13 will switch order of (4.3) and (4.2), always inserting () before attempting eta expansion
931-
// (This effectively disables implicit eta-expansion of 0-ary methods.)
932-
// See mind-bending stuff like scala/bug#9178
933-
if (nakedZeroAryMethod && settings.isScala213) emptyApplication
934-
else {
935-
// eventually, we will deprecate insertion of `()` (except for java-defined methods) -- this is already the case in dotty
936-
// Once that's done, we can more aggressively eta-expand method references, even if they are 0-arity
937-
// 2.13 will already eta-expand non-zero-arity methods regardless of expected type (whereas 2.12 requires a function-equivalent type)
938-
if (nakedZeroAryMethod && settings.isScala212) {
939-
currentRun.reporting.deprecationWarning(tree.pos, NoSymbol,
940-
s"Eta-expansion of zero-argument methods is deprecated. To avoid this warning, write ${Function(Nil, Apply(tree, Nil))}.", "2.12.0")
941-
}
942-
943-
val tree0 = etaExpand(context.unit, tree, this)
903+
// (4.2) apply to empty argument list
904+
else if (mt.params.isEmpty && (settings.isScala213 || !isFunctionType(pt))) {
905+
// Starting with 2.13, always insert `()`, regardless of expected type.
906+
// On older versions, expected type must not be a FunctionN (can be a SAM)
907+
//
908+
// scala/bug#7187 deprecates eta-expansion of zero-arg method values (since 2.12; was never introduced for SAM types: scala/bug#9536).
909+
// The next step is to also deprecate insertion of `()` (except for java-defined methods), as dotty already requires explicitly writing them.
910+
// Once explicit application to () is required, we can more aggressively eta-expand method references, even if they are 0-arity
911+
adapt(typed(Apply(tree, Nil) setPos tree.pos), mode, pt, original)
912+
}
913+
// (4.3) eta-expand method value when function or sam type is expected (for experimentation, always eta-expand under 2.14 source level)
914+
else if (isFunctionType(pt) || samOf(pt).exists || settings.isScala214) { // TODO: decide on `settings.isScala214`
915+
if (settings.isScala212 && mt.params.isEmpty) // implies isFunctionType(pt)
916+
currentRun.reporting.deprecationWarning(tree.pos, NoSymbol, "Eta-expansion of zero-argument methods is deprecated. "+
917+
s"To avoid this warning, write ${Function(Nil, Apply(tree, Nil))}.", "2.12.0")
944918

945-
// #2624: need to infer type arguments for eta expansion of a polymorphic method
946-
// context.undetparams contains clones of meth.typeParams (fresh ones were generated in etaExpand)
947-
// need to run typer on tree0, since etaExpansion sets the tpe's of its subtrees to null
948-
// can't type with the expected type, as we can't recreate the setup in (3) without calling typed
949-
// (note that (3) does not call typed to do the polymorphic type instantiation --
950-
// it is called after the tree has been typed with a polymorphic expected result type)
951-
if (hasUndets)
952-
instantiate(typed(tree0, mode), mode, pt)
953-
else
954-
typed(tree0, mode, pt)
955-
}
919+
typedEtaExpansion(tree, mode, pt)
956920
}
957-
// (4.3) apply to empty argument list
958-
else if (mt.params.isEmpty) emptyApplication
959921
else cantAdapt
960922
}
961923

@@ -1645,7 +1607,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
16451607
map2(preSuperStats, preSuperVals)((ldef, gdef) => gdef.tpt setType ldef.symbol.tpe)
16461608

16471609
if (superCall1 == cunit) EmptyTree
1648-
else cbody2 match {
1610+
else cbody2 match { // ???
16491611
case Block(_, expr) => expr
16501612
case tree => tree
16511613
}
@@ -3003,7 +2965,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
30032965
// This tree is the result from one of:
30042966
// - manual eta-expansion with named arguments (x => f(x));
30052967
// - wildcard-style eta expansion (`m(_, _,)`);
3006-
// - instantiateToMethodType adapting a tree of method type to a function type using etaExpand.
2968+
// - (I don't think it can result from etaExpand, because we know the argument types there.)
30072969
//
30082970
// Note that method values are a separate thing (`m _`): they have the idiosyncratic shape
30092971
// of `Typed(expr, Function(Nil, EmptyTree))`
@@ -3082,6 +3044,23 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
30823044
}
30833045
}
30843046

3047+
// #2624: need to infer type arguments for eta expansion of a polymorphic method
3048+
// context.undetparams contains clones of meth.typeParams (fresh ones were generated in etaExpand)
3049+
// need to run typer on tree0, since etaExpansion sets the tpe's of its subtrees to null
3050+
// can't type with the expected type, as we can't recreate the setup in (3) without calling typed
3051+
// (note that (3) does not call typed to do the polymorphic type instantiation --
3052+
// it is called after the tree has been typed with a polymorphic expected result type)
3053+
def typedEtaExpansion(tree: Tree, mode: Mode, pt: Type): Tree = {
3054+
debuglog(s"eta-expanding $tree: ${tree.tpe} to $pt")
3055+
3056+
if (tree.tpe.isDependentMethodType) DependentMethodTpeConversionToFunctionError(tree, tree.tpe) // TODO: support this
3057+
else {
3058+
val expansion = etaExpand(context.unit, tree, this)
3059+
if (context.undetparams.isEmpty) typed(expansion, mode, pt)
3060+
else instantiate(typed(expansion, mode), mode, pt)
3061+
}
3062+
}
3063+
30853064
def typedRefinement(templ: Template): Unit = {
30863065
val stats = templ.body
30873066
namer.enterSyms(stats)
@@ -4613,14 +4592,6 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
46134592
treeCopy.New(tree, tpt1).setType(tp)
46144593
}
46154594

4616-
def functionTypeWildcard(arity: Int): Type =
4617-
functionType(List.fill(arity)(WildcardType), WildcardType)
4618-
4619-
def checkArity(tree: Tree)(tp: Type): tp.type = tp match {
4620-
case NoType => MaxFunctionArityError(tree); tp
4621-
case _ => tp
4622-
}
4623-
46244595

46254596
/** Eta expand an expression like `m _`, where `m` denotes a method or a by-name argument
46264597
*
@@ -4633,9 +4604,12 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
46334604
*/
46344605
def typedEta(methodValue: Tree): Tree = methodValue.tpe match {
46354606
case tp@(MethodType(_, _) | PolyType(_, MethodType(_, _))) => // (1)
4636-
val formals = tp.params
4637-
if (isFunctionType(pt) || samMatchesFunctionBasedOnArity(samOf(pt), formals)) methodValue
4638-
else adapt(methodValue, mode, checkArity(methodValue)(functionTypeWildcard(formals.length)))
4607+
val etaPt =
4608+
if (pt ne WildcardType) pt
4609+
else functionType(tp.params.map(_ => WildcardType), WildcardType) orElse WildcardType // arity overflow --> NoType
4610+
4611+
// We know syntactically methodValue can't refer to a constructor because you can't write `this _` for that (right???)
4612+
typedEtaExpansion(methodValue, mode, etaPt)
46394613

46404614
case TypeRef(_, ByNameParamClass, _) | NullaryMethodType(_) => // (2)
46414615
val pos = methodValue.pos
@@ -5420,14 +5394,19 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
54205394
// that typecheck must not trigger macro expansions, so we explicitly prohibit them
54215395
// however we cannot do `context.withMacrosDisabled`
54225396
// because `expr` might contain nested macro calls (see scala/bug#6673).
5423-
// Otherwise, eta-expand, passing the original tree, which is required in adapt
5424-
// for trees of the form `f() _`: if the method type takes implicits, the fallback
5425-
// strategy will use `f()`; else if not, original is used to distinguish an explicit
5426-
// method value from eta-expansion driven by an expected function type.
5397+
// Otherwise, (check for dead code, and) eta-expand.
54275398
case MethodValue(expr) =>
5428-
typed1(suppressMacroExpansion(expr), mode, pt) match {
5399+
// Need to type in FUNmode so that we accept a method type (which also means we can't use our pt),
5400+
// this does mean no overloading is performed. The main reason to ignore pt and move to FUNmode is that
5401+
// the `m` in `m _` could involve an implicit conversion, which will go through adapt after converting,
5402+
// which will run afoul of the restriction that a method-typed tree is only allowed when a function type is expected.
5403+
// We peeled off the `_` marker for the typed1 call, so we don't know that the user has requested eta-expansion.
5404+
// See scala/bug#8299.
5405+
val funTyped = typed1(suppressMacroExpansion(expr), mode | FUNmode, WildcardType)
5406+
if (funTyped.tpe.isInstanceOf[OverloadedType]) inferExprAlternative(funTyped, pt)
5407+
funTyped match {
54295408
case macroDef if treeInfo.isMacroApplication(macroDef) => MacroEtaError(macroDef)
5430-
case methodValue => typedEta(checkDead(methodValue).updateAttachment(MethodValueAttachment))
5409+
case methodValue => typedEta(checkDead(methodValue))
54315410
}
54325411
case Typed(expr, tpt) =>
54335412
val tpt1 = typedType(tpt, mode) // type the ascribed type first

test/files/neg/t7187-2.13.check

-6
This file was deleted.

test/files/neg/t7187-2.13.flags

-1
This file was deleted.

test/files/neg/t7187-2.13.scala

-6
This file was deleted.

test/files/neg/t7187-2.14.check

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
t7187-2.14.scala:6: error: type mismatch;
2+
found : Int
3+
required: () => Any
4+
val t1: () => Any = m1 // error
5+
^
6+
t7187-2.14.scala:7: error: type mismatch;
7+
found : Int
8+
required: () => Any
9+
val t2: () => Any = m2 // error, no eta-expansion of zero-args methods
10+
^
11+
two errors found

test/files/neg/t7187-2.14.flags

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
-Xsource:2.14

test/files/neg/t7187-2.14.scala

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
class EtaExpand214 {
2+
def m1 = 1
3+
def m2() = 1
4+
def m3(x: Int) = x
5+
6+
val t1: () => Any = m1 // error
7+
val t2: () => Any = m2 // error, no eta-expansion of zero-args methods
8+
val t3: Int => Any = m3 // ok
9+
10+
val t4 = m1 // apply
11+
val t5 = m2 // apply, ()-insertion
12+
val t6 = m3 // eta-expansion in 2.14
13+
14+
val t4a: Int = t4 // ok
15+
val t5a: Int = t5 // ok
16+
val t6a: Int => Any = t6 // ok
17+
18+
val t7 = m1 _
19+
val t8 = m2 _
20+
val t9 = m3 _
21+
22+
val t7a: () => Any = t7 // ok
23+
val t8a: () => Any = t8 // ok
24+
val t9a: Int => Any = t9 // ok
25+
}

test/files/neg/t7187.check

+6-1
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,9 @@ t7187.scala:25: error: type mismatch;
3232
required: () => Any
3333
val t4b: () => Any = zap() // ditto
3434
^
35-
8 errors found
35+
t7187.scala:30: error: missing argument list for method zup in class EtaExpandZeroArg
36+
Unapplied methods are only converted to functions when a function type is expected.
37+
You can make this conversion explicit by writing `zup _` or `zup(_)` instead of `zup`.
38+
val t5a = zup // error in 2.13, eta-expansion in 2.14
39+
^
40+
9 errors found

0 commit comments

Comments
 (0)