Skip to content

Commit 23afa6f

Browse files
committed
Various adjustments to allow empty implicit function types
On the other hand, empty erased function types are again disabled, since they will be compatible with no closures at all. Note: I did not re-instantiate the tests for disallowing empty erased functions. As is so often the case, the most trivial properties get the most tests, precisely because they are easiest to test. But in the end, that just contributes to noise and friction if one wants to change things. neg/i2642.scala does test the relevant bits, that should be enough.
1 parent d34b677 commit 23afa6f

File tree

10 files changed

+80
-76
lines changed

10 files changed

+80
-76
lines changed

compiler/src/dotty/tools/dotc/ast/Desugar.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -819,12 +819,12 @@ object desugar {
819819
*
820820
* If `inlineable` is true, tag $anonfun with an @inline annotation.
821821
*/
822-
def makeClosure(params: List[ValDef], body: Tree, tpt: Tree = TypeTree(), inlineable: Boolean)(implicit ctx: Context) = {
822+
def makeClosure(params: List[ValDef], body: Tree, tpt: Tree = TypeTree(), isInlineable: Boolean, isImplicit: Boolean)(implicit ctx: Context) = {
823823
var mods = synthetic | Artifact
824-
if (inlineable) mods |= Inline
824+
if (isInlineable) mods |= Inline
825825
Block(
826826
DefDef(nme.ANON_FUN, Nil, params :: Nil, tpt, body).withMods(mods),
827-
Closure(Nil, Ident(nme.ANON_FUN), EmptyTree))
827+
Closure(Nil, Ident(nme.ANON_FUN), if (isImplicit) ImplicitEmptyTree else EmptyTree))
828828
}
829829

830830
/** If `nparams` == 1, expand partial function

compiler/src/dotty/tools/dotc/ast/TreeInfo.scala

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -298,11 +298,15 @@ trait UntypedTreeInfo extends TreeInfo[Untyped] { self: Trees.Instance[Untyped]
298298

299299
/** Is `tree` an implicit function or closure, possibly nested in a block? */
300300
def isImplicitClosure(tree: Tree)(implicit ctx: Context): Boolean = unsplice(tree) match {
301+
case tree: FunctionWithMods => tree.mods.is(Implicit)
301302
case Function((param: untpd.ValDef) :: _, _) => param.mods.is(Implicit)
302303
case Closure(_, meth, _) => true
303304
case Block(Nil, expr) => isImplicitClosure(expr)
304-
case Block(DefDef(nme.ANON_FUN, _, (param :: _) :: _, _, _) :: Nil, _: Closure) =>
305-
param.mods.is(Implicit)
305+
case Block(DefDef(nme.ANON_FUN, _, params :: _, _, _) :: Nil, cl: Closure) =>
306+
params match {
307+
case param :: _ => param.mods.is(Implicit)
308+
case Nil => cl.tpt.eq(untpd.ImplicitEmptyTree) || defn.isImplicitFunctionType(cl.tpt.typeOpt)
309+
}
306310
case _ => false
307311
}
308312

compiler/src/dotty/tools/dotc/ast/Trees.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -882,6 +882,7 @@ object Trees {
882882

883883
@sharable val EmptyTree: Thicket = genericEmptyTree
884884
@sharable val EmptyValDef: ValDef = genericEmptyValDef
885+
@sharable val ImplicitEmptyTree: Thicket = Thicket(Nil) // an empty tree marking an implicit closure
885886

886887
// ----- Auxiliary creation methods ------------------
887888

compiler/src/dotty/tools/dotc/core/Definitions.scala

Lines changed: 22 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -855,22 +855,17 @@ class Definitions {
855855

856856
lazy val TupleType = mkArityArray("scala.Tuple", MaxTupleArity, 2)
857857

858-
def FunctionClass(n: Int, isImplicit: Boolean = false, isErased: Boolean = false)(implicit ctx: Context) = {
859-
if (isImplicit && isErased) {
860-
require(n > 0)
858+
def FunctionClass(n: Int, isImplicit: Boolean = false, isErased: Boolean = false)(implicit ctx: Context) =
859+
if (isImplicit && isErased)
861860
ctx.requiredClass("scala.ErasedImplicitFunction" + n.toString)
862-
}
863-
else if (isImplicit) {
864-
require(n > 0)
861+
else if (isImplicit)
865862
ctx.requiredClass("scala.ImplicitFunction" + n.toString)
866-
}
867-
else if (isErased) {
868-
require(n > 0)
863+
else if (isErased)
869864
ctx.requiredClass("scala.ErasedFunction" + n.toString)
870-
}
871-
else if (n <= MaxImplementedFunctionArity) FunctionClassPerRun()(ctx)(n)
872-
else ctx.requiredClass("scala.Function" + n.toString)
873-
}
865+
else if (n <= MaxImplementedFunctionArity)
866+
FunctionClassPerRun()(ctx)(n)
867+
else
868+
ctx.requiredClass("scala.Function" + n.toString)
874869

875870
lazy val Function0_applyR = ImplementedFunctionType(0).symbol.requiredMethodRef(nme.apply)
876871
def Function0_apply(implicit ctx: Context) = Function0_applyR.symbol
@@ -899,23 +894,26 @@ class Definitions {
899894
def isBottomType(tp: Type) =
900895
tp.derivesFrom(NothingClass) || tp.derivesFrom(NullClass)
901896

902-
/** Is a function class.
903-
* - FunctionN for N >= 0
904-
* - ImplicitFunctionN for N > 0
905-
* - ErasedFunctionN for N > 0
906-
* - ErasedImplicitFunctionN for N > 0
897+
/** Is a function class, i.e. on of
898+
* - FunctionN
899+
* - ImplicitFunctionN
900+
* - ErasedFunctionN
901+
* - ErasedImplicitFunctionN
902+
* for N >= 0
907903
*/
908904
def isFunctionClass(cls: Symbol) = scalaClassName(cls).isFunction
909905

910906
/** Is an implicit function class.
911-
* - ImplicitFunctionN for N > 0
912-
* - ErasedImplicitFunctionN for N > 0
907+
* - ImplicitFunctionN
908+
* - ErasedImplicitFunctionN
909+
* for N >= 0
913910
*/
914911
def isImplicitFunctionClass(cls: Symbol) = scalaClassName(cls).isImplicitFunction
915912

916913
/** Is an erased function class.
917-
* - ErasedFunctionN for N > 0
918-
* - ErasedImplicitFunctionN for N > 0
914+
* - ErasedFunctionN
915+
* - ErasedImplicitFunctionN
916+
* for N >= 0
919917
*/
920918
def isErasedFunctionClass(cls: Symbol) = scalaClassName(cls).isErasedFunction
921919

@@ -942,7 +940,7 @@ class Definitions {
942940
* - FunctionN for N > 22 becomes FunctionXXL
943941
* - FunctionN for 22 > N >= 0 remains as FunctionN
944942
* - ImplicitFunctionN for N > 22 becomes FunctionXXL
945-
* - ImplicitFunctionN for 22 > N >= 0 becomes FunctionN
943+
* - ImplicitFunctionN for N <= 22 becomes FunctionN
946944
* - ErasedFunctionN becomes Function0
947945
* - ImplicitErasedFunctionN becomes Function0
948946
* - anything else becomes a NoSymbol
@@ -959,7 +957,7 @@ class Definitions {
959957
* - FunctionN for N > 22 becomes FunctionXXL
960958
* - FunctionN for 22 > N >= 0 remains as FunctionN
961959
* - ImplicitFunctionN for N > 22 becomes FunctionXXL
962-
* - ImplicitFunctionN for 22 > N >= 0 becomes FunctionN
960+
* - ImplicitFunctionN for N <= 22 becomes FunctionN
963961
* - ErasedFunctionN becomes Function0
964962
* - ImplicitErasedFunctionN becomes Function0
965963
* - anything else becomes a NoType

compiler/src/dotty/tools/dotc/core/NameOps.scala

Lines changed: 16 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -167,59 +167,39 @@ object NameOps {
167167
}
168168
}
169169

170-
/** Is a synthetic function name
171-
* - N for FunctionN
172-
* - N for ImplicitFunctionN (N >= 1)
173-
* - (-1) otherwise
174-
*/
175170
def functionArity: Int =
176-
functionArityFor(str.Function) max {
177-
val n =
178-
functionArityFor(str.ImplicitFunction) max
179-
functionArityFor(str.ErasedFunction) max
180-
functionArityFor(str.ErasedImplicitFunction)
181-
if (n == 0) -1 else n
182-
}
171+
functionArityFor(str.Function) max
172+
functionArityFor(str.ImplicitFunction) max
173+
functionArityFor(str.ErasedFunction) max
174+
functionArityFor(str.ErasedImplicitFunction)
183175

184-
/** Is a function name
185-
* - FunctionN for N >= 0
186-
* - ImplicitFunctionN for N >= 1
187-
* - ErasedFunctionN for N >= 1
188-
* - ErasedImplicitFunctionN for N >= 1
189-
* - false otherwise
176+
/** Is a function name, i.e one of FunctionN, ImplicitFunctionN, ErasedFunctionN, ErasedImplicitFunctionN for N >= 0
190177
*/
191178
def isFunction: Boolean = functionArity >= 0
192179

193-
/** Is a implicit function name
194-
* - ImplicitFunctionN for N >= 1
195-
* - ErasedImplicitFunctionN for N >= 1
196-
* - false otherwise
180+
/** Is an implicit function name, i.e one of ImplicitFunctionN, ErasedImplicitFunctionN for N >= 0
197181
*/
198182
def isImplicitFunction: Boolean = {
199-
functionArityFor(str.ImplicitFunction) >= 1 ||
200-
functionArityFor(str.ErasedImplicitFunction) >= 1
183+
functionArityFor(str.ImplicitFunction) >= 0 ||
184+
functionArityFor(str.ErasedImplicitFunction) >= 0
201185
}
202186

203-
/** Is a implicit function name
204-
* - ErasedFunctionN for N >= 1
205-
* - ErasedImplicitFunctionN for N >= 1
206-
* - false otherwise
187+
/** Is an erased function name, i.e. one of ErasedFunctionN, ErasedImplicitFunctionN for N >= 0
207188
*/
208189
def isErasedFunction: Boolean = {
209-
functionArityFor(str.ErasedFunction) >= 1 ||
210-
functionArityFor(str.ErasedImplicitFunction) >= 1
190+
functionArityFor(str.ErasedFunction) >= 0 ||
191+
functionArityFor(str.ErasedImplicitFunction) >= 0
211192
}
212193

213-
/** Is a synthetic function name
194+
/** Is a synthetic function name, i.e. one of
214195
* - FunctionN for N > 22
215-
* - ImplicitFunctionN for N >= 1
216-
* - ErasedFunctionN for N >= 1
217-
* - ErasedImplicitFunctionN for N >= 1
218-
* - false otherwise
196+
* - ImplicitFunctionN for N >= 0
197+
* - ErasedFunctionN for N >= 0
198+
* - ErasedImplicitFunctionN for N >= 0
219199
*/
220200
def isSyntheticFunction: Boolean = {
221201
functionArityFor(str.Function) > MaxImplementedFunctionArity ||
222-
functionArityFor(str.ImplicitFunction) >= 1 ||
202+
functionArityFor(str.ImplicitFunction) >= 0 ||
223203
isErasedFunction
224204
}
225205

compiler/src/dotty/tools/dotc/core/Types.scala

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2892,7 +2892,6 @@ object Types {
28922892
final override def isImplicitMethod: Boolean = companion.eq(ImplicitMethodType) || companion.eq(ErasedImplicitMethodType)
28932893
final override def isErasedMethod: Boolean = companion.eq(ErasedMethodType) || companion.eq(ErasedImplicitMethodType)
28942894

2895-
28962895
def computeSignature(implicit ctx: Context): Signature = {
28972896
val params = if (isErasedMethod) Nil else paramInfos
28982897
resultSignature.prepend(params, isJavaMethod)

compiler/src/dotty/tools/dotc/transform/ExpandSAMs.scala

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,14 @@ import dotty.tools.dotc.util.Positions.Position
1515
* These fall into five categories
1616
*
1717
* 1. Partial function closures, we need to generate isDefinedAt and applyOrElse methods for these.
18-
* 2. Closures implementing non-trait classes.
18+
* 2. Closures implementing non-trait classes
1919
* 3. Closures implementing classes that inherit from a class other than Object
2020
* (a lambda cannot not be a run-time subtype of such a class)
2121
* 4. Closures that implement traits which run initialization code.
2222
* 5. Closures that get synthesized abstract methods in the transformation pipeline. These methods can be
2323
* (1) superaccessors, (2) outer references, (3) accessors for fields.
24+
*
25+
* However, implicit function types do not count as SAM types.
2426
*/
2527
class ExpandSAMs extends MiniPhase {
2628
override def phaseName = "expandSAMs"
@@ -32,9 +34,12 @@ class ExpandSAMs extends MiniPhase {
3234
ctx.platform.isSam(cls)
3335

3436
override def transformBlock(tree: Block)(implicit ctx: Context): Tree = tree match {
35-
case Block(stats @ (fn: DefDef) :: Nil, Closure(_, fnRef, tpt)) if fnRef.symbol == fn.symbol =>
37+
case Block(stats @ (fn: DefDef) :: Nil, cl @ Closure(_, fnRef, tpt)) if fnRef.symbol == fn.symbol =>
3638
tpt.tpe match {
37-
case NoType => tree // it's a plain function
39+
case NoType =>
40+
tree // it's a plain function
41+
case tpe if defn.isImplicitFunctionType(tpe) =>
42+
tree
3843
case tpe @ SAMType(_) if tpe.isRef(defn.PartialFunctionClass) =>
3944
val tpe1 = checkRefinements(tpe, fn.pos)
4045
toPartialFunction(tree, tpe1)

compiler/src/dotty/tools/dotc/transform/ShortcutImplicits.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,14 +73,15 @@ class ShortcutImplicits extends MiniPhase with IdentityDenotTransformer { thisPh
7373
ctx.fresh.updateStore(DirectMeth, newMutableSymbolMap[Symbol])
7474

7575
/** Should `sym` get a ..$direct companion?
76-
* This is the case if (1) `sym` is a method with an implicit function type as final result type.
76+
* This is the case if `sym` is a method with a non-nullary implicit function type as final result type.
7777
* However if `specializeMonoTargets` is false, we exclude symbols that are known
7878
* to be only targets of monomorphic calls because they are effectively
7979
* final and don't override anything.
8080
*/
8181
private def shouldBeSpecialized(sym: Symbol)(implicit ctx: Context) =
8282
sym.is(Method, butNot = Accessor) &&
8383
defn.isImplicitFunctionType(sym.info.finalResultType) &&
84+
defn.functionArity(sym.info.finalResultType) > 0 &&
8485
!sym.isAnonymousFunction &&
8586
(specializeMonoTargets || !sym.isEffectivelyFinal || sym.allOverriddenSymbols.nonEmpty)
8687

compiler/src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -762,8 +762,9 @@ class Typer extends Namer
762762
val untpd.Function(args, body) = tree
763763
val (isImplicit, isErased) = tree match {
764764
case tree: untpd.FunctionWithMods => (tree.mods.is(Implicit), tree.mods.is(Erased))
765-
case _ => (false, false)
765+
case _ => (false, false)
766766
}
767+
if (isErased && args.isEmpty) ctx.error(em"empty function cannot not be erased", tree.pos)
767768
val funCls = defn.FunctionClass(args.length, isImplicit, isErased)
768769

769770
/** Typechecks dependent function type with given parameters `params` */
@@ -798,6 +799,11 @@ class Typer extends Namer
798799
def typedFunctionValue(tree: untpd.Function, pt: Type)(implicit ctx: Context) = {
799800
val untpd.Function(params: List[untpd.ValDef] @unchecked, body) = tree
800801

802+
val isImplicit = tree match {
803+
case tree: untpd.FunctionWithMods => tree.mods.is(Implicit)
804+
case _ => false
805+
}
806+
801807
pt match {
802808
case pt: TypeVar if untpd.isFunctionWithUnknownParamType(tree) =>
803809
// try to instantiate `pt` if this is possible. If it does not
@@ -916,8 +922,8 @@ class Typer extends Namer
916922
else cpy.ValDef(param)(
917923
tpt = untpd.TypeTree(
918924
inferredParamType(param, protoFormal(i)).underlyingIfRepeated(isJava = false)))
919-
val inlineable = pt.hasAnnotation(defn.InlineParamAnnot)
920-
desugar.makeClosure(inferredParams, fnBody, resultTpt, inlineable)
925+
val isInlineable = pt.hasAnnotation(defn.InlineParamAnnot)
926+
desugar.makeClosure(inferredParams, fnBody, resultTpt, isInlineable, isImplicit)
921927
}
922928
typed(desugared, pt)
923929
}
@@ -942,7 +948,11 @@ class Typer extends Namer
942948
|because it has internal parameter dependencies,
943949
|position = ${tree.pos}, raw type = ${mt.toString}""") // !!! DEBUG. Eventually, convert to an error?
944950
}
945-
else EmptyTree
951+
else if ((tree.tpt `eq` untpd.ImplicitEmptyTree) && mt.paramNames.isEmpty)
952+
// Note implicitness of function in target type sicne there are no method parameters that indicate it.
953+
TypeTree(defn.FunctionOf(Nil, mt.resType, isImplicit = true, isErased = false))
954+
else
955+
EmptyTree
946956
}
947957
case tp =>
948958
throw new java.lang.Error(i"internal error: closing over non-method $tp, pos = ${tree.pos}")

tests/neg/i2642.scala

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
11
object Foo {
2-
type X = implicit () => Int // error: implicit function needs parameters
3-
def ff: X = () // error: found: Unit, expected: X
2+
type X = implicit () => Int // now ok, used to be: implicit function needs parameters
3+
def ff: X = () // error: found: Unit, expected: Int
4+
5+
type Y = erased () => Int // error: empty function may not be erased
6+
def gg: Y = () // error: found: Unit, expected: Y
7+
8+
type Z = erased implicit () => Int // error: empty function may not be erased
9+
def hh: Z = () // error: found: Unit, expected: Int
410
}

0 commit comments

Comments
 (0)