From d34b677d88126e3a9ff5d04fa55bddd2f5925151 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 20 May 2018 15:16:37 +0200 Subject: [PATCH 1/5] Drop requirement that implicit/erased functions must be non-empty It's the kind of trivial requirement we should not do. In the case of implicit function types, it turns out that it even blocks something that would be useful! Also, rename NonEmptyFunction to FunctionWithMods. --- .../src/dotty/tools/dotc/ast/Desugar.scala | 2 +- compiler/src/dotty/tools/dotc/ast/untpd.scala | 6 +- .../dotty/tools/dotc/parsing/Parsers.scala | 4 +- .../dotc/reporting/diagnostic/messages.scala | 20 ------- .../src/dotty/tools/dotc/typer/Typer.scala | 9 +-- .../dotc/reporting/ErrorMessagesTests.scala | 56 ------------------- 6 files changed, 9 insertions(+), 88 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 0ae11faf502b..fcb8a9de640a 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -869,7 +869,7 @@ object desugar { def makeImplicitFunction(formals: List[Type], body: Tree)(implicit ctx: Context): Tree = { val params = makeImplicitParameters(formals.map(TypeTree)) - new NonEmptyFunction(params, body, Modifiers(Implicit)) + new FunctionWithMods(params, body, Modifiers(Implicit)) } /** Add annotation to tree: diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index f10670c42345..95049bcea6fd 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -56,13 +56,15 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { override def isType = body.isType } - /** A function type that should have non empty args */ - class NonEmptyFunction(args: List[Tree], body: Tree, val mods: Modifiers) extends Function(args, body) + /** A function type with `implicit` or `erased` modifiers */ + class FunctionWithMods(args: List[Tree], body: Tree, val mods: Modifiers) extends Function(args, body) /** A function created from a wildcard expression * @param placeholderParams a list of definitions of synthetic parameters. * @param body the function body where wildcards are replaced by * references to synthetic parameters. + * This is equivalent to Function, except that forms a special case for the overlapping + * positions tests. */ class WildcardFunction(placeholderParams: List[ValDef], body: Tree) extends Function(placeholderParams, body) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index f3a1218f69aa..bd28ac98c416 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -732,7 +732,7 @@ object Parsers { def functionRest(params: List[Tree]): Tree = atPos(start, accept(ARROW)) { val t = typ() - if (imods.is(Implicit) || imods.is(Erased)) new NonEmptyFunction(params, t, imods) + if (imods.is(Implicit) || imods.is(Erased)) new FunctionWithMods(params, t, imods) else Function(params, t) } def funArgTypesRest(first: Tree, following: () => Tree) = { @@ -800,7 +800,7 @@ object Parsers { case ARROW => functionRest(t :: Nil) case FORSOME => syntaxError(ExistentialTypesNoLongerSupported()); t case _ => - if (imods.is(Implicit) && !t.isInstanceOf[NonEmptyFunction]) + if (imods.is(Implicit) && !t.isInstanceOf[FunctionWithMods]) syntaxError("Types with implicit keyword can only be function types", Position(start, start + nme.IMPLICITkw.asSimpleName.length)) t } diff --git a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala index ef685b0dc0f2..14de70803de6 100644 --- a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala @@ -1722,26 +1722,6 @@ object messages { } } - case class FunctionTypeNeedsNonEmptyParameterList(isImplicit: Boolean, isErased: Boolean)(implicit ctx: Context) - extends Message(FunctionTypeNeedsNonEmptyParameterListID) { - assert(isImplicit || isErased) - val kind = "Syntax" - val mods = ((isErased, "erased") :: (isImplicit, "implicit") :: Nil).collect { case (true, mod) => mod }.mkString(" ") - val msg = mods + " function type needs non-empty parameter list" - val explanation = { - val code1 = s"type Transactional[T] = $mods Transaction => T" - val code2 = "val cl: implicit A => B" - hl"""It is not allowed to leave $mods function parameter list empty. - |Possible ways to define $mods function type: - | - |$code1 - | - |or - | - |$code2""".stripMargin - } - } - case class WrongNumberOfParameters(expected: Int)(implicit ctx: Context) extends Message(WrongNumberOfParametersID) { val kind = "Syntax" diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 24ed19112fb6..997173e9456c 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -761,13 +761,8 @@ class Typer extends Namer def typedFunctionType(tree: untpd.Function, pt: Type)(implicit ctx: Context) = { val untpd.Function(args, body) = tree val (isImplicit, isErased) = tree match { - case tree: untpd.NonEmptyFunction => - if (args.nonEmpty) (tree.mods.is(Implicit), tree.mods.is(Erased)) - else { - ctx.error(FunctionTypeNeedsNonEmptyParameterList(tree.mods.is(Implicit), tree.mods.is(Erased)), tree.pos) - (false, false) - } - case _ => (false, false) + case tree: untpd.FunctionWithMods => (tree.mods.is(Implicit), tree.mods.is(Erased)) + case _ => (false, false) } val funCls = defn.FunctionClass(args.length, isImplicit, isErased) diff --git a/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala b/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala index 101d5664973c..e997b3170555 100644 --- a/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala +++ b/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala @@ -873,20 +873,6 @@ class ErrorMessagesTests extends ErrorMessagesTest { assertEquals(err, WildcardOnTypeArgumentNotAllowedOnNew()) } - @Test def implicitFunctionTypeNeedsNonEmptyParameterList = - checkMessagesAfter(RefChecks.name) { - """abstract class Foo { - | type Contextual[T] = implicit () => T - | val x: implicit () => Int - |}""".stripMargin - } - .expect { (ictx, messages) => - implicit val ctx: Context = ictx - - assertMessageCount(2, messages) - messages.foreach(assertEquals(_, FunctionTypeNeedsNonEmptyParameterList(isImplicit = true, isErased = false))) - } - @Test def wrongNumberOfParameters = checkMessagesAfter(RefChecks.name) { """object NumberOfParams { @@ -1310,48 +1296,6 @@ class ErrorMessagesTests extends ErrorMessagesTest { assertEquals(symbol.name.mangledString, "a") } - @Test def i4127a = - checkMessagesAfter(FrontEnd.name) { - """ - |class Foo { - | val x: implicit () => Int = () => 1 - |} - """.stripMargin - }.expect { (ictx, messages) => - implicit val ctx: Context = ictx - assertMessageCount(1, messages) - val (msg @ FunctionTypeNeedsNonEmptyParameterList(_, _)) :: Nil = messages - assertEquals(msg.mods, "implicit") - } - - @Test def i4127b = - checkMessagesAfter(FrontEnd.name) { - """ - |class Foo { - | val x: erased () => Int = () => 1 - |} - """.stripMargin - }.expect { (ictx, messages) => - implicit val ctx: Context = ictx - assertMessageCount(1, messages) - val (msg @ FunctionTypeNeedsNonEmptyParameterList(_, _)) :: Nil = messages - assertEquals(msg.mods, "erased") - } - - @Test def i4127c = - checkMessagesAfter(FrontEnd.name) { - """ - |class Foo { - | val x: erased implicit () => Int = () => 1 - |} - """.stripMargin - }.expect { (ictx, messages) => - implicit val ctx: Context = ictx - assertMessageCount(1, messages) - val (msg @ FunctionTypeNeedsNonEmptyParameterList(_, _)) :: Nil = messages - assertEquals(msg.mods, "erased implicit") - } - @Test def renameImportTwice = checkMessagesAfter(PostTyper.name) { """ From 23afa6fdb23a1ab4ae768fdb1985caf0dea730e7 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 20 May 2018 20:00:27 +0200 Subject: [PATCH 2/5] 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. --- .../src/dotty/tools/dotc/ast/Desugar.scala | 6 +-- .../src/dotty/tools/dotc/ast/TreeInfo.scala | 8 ++- compiler/src/dotty/tools/dotc/ast/Trees.scala | 1 + .../dotty/tools/dotc/core/Definitions.scala | 46 ++++++++-------- .../src/dotty/tools/dotc/core/NameOps.scala | 52 ++++++------------- .../src/dotty/tools/dotc/core/Types.scala | 1 - .../tools/dotc/transform/ExpandSAMs.scala | 11 ++-- .../dotc/transform/ShortcutImplicits.scala | 3 +- .../src/dotty/tools/dotc/typer/Typer.scala | 18 +++++-- tests/neg/i2642.scala | 10 +++- 10 files changed, 80 insertions(+), 76 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index fcb8a9de640a..22ff9c8a67ec 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -819,12 +819,12 @@ object desugar { * * If `inlineable` is true, tag $anonfun with an @inline annotation. */ - def makeClosure(params: List[ValDef], body: Tree, tpt: Tree = TypeTree(), inlineable: Boolean)(implicit ctx: Context) = { + def makeClosure(params: List[ValDef], body: Tree, tpt: Tree = TypeTree(), isInlineable: Boolean, isImplicit: Boolean)(implicit ctx: Context) = { var mods = synthetic | Artifact - if (inlineable) mods |= Inline + if (isInlineable) mods |= Inline Block( DefDef(nme.ANON_FUN, Nil, params :: Nil, tpt, body).withMods(mods), - Closure(Nil, Ident(nme.ANON_FUN), EmptyTree)) + Closure(Nil, Ident(nme.ANON_FUN), if (isImplicit) ImplicitEmptyTree else EmptyTree)) } /** If `nparams` == 1, expand partial function diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index bd9195f77ab3..b817131c0ce6 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -298,11 +298,15 @@ trait UntypedTreeInfo extends TreeInfo[Untyped] { self: Trees.Instance[Untyped] /** Is `tree` an implicit function or closure, possibly nested in a block? */ def isImplicitClosure(tree: Tree)(implicit ctx: Context): Boolean = unsplice(tree) match { + case tree: FunctionWithMods => tree.mods.is(Implicit) case Function((param: untpd.ValDef) :: _, _) => param.mods.is(Implicit) case Closure(_, meth, _) => true case Block(Nil, expr) => isImplicitClosure(expr) - case Block(DefDef(nme.ANON_FUN, _, (param :: _) :: _, _, _) :: Nil, _: Closure) => - param.mods.is(Implicit) + case Block(DefDef(nme.ANON_FUN, _, params :: _, _, _) :: Nil, cl: Closure) => + params match { + case param :: _ => param.mods.is(Implicit) + case Nil => cl.tpt.eq(untpd.ImplicitEmptyTree) || defn.isImplicitFunctionType(cl.tpt.typeOpt) + } case _ => false } diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index 32235e35e904..12992bc5dfb2 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -882,6 +882,7 @@ object Trees { @sharable val EmptyTree: Thicket = genericEmptyTree @sharable val EmptyValDef: ValDef = genericEmptyValDef + @sharable val ImplicitEmptyTree: Thicket = Thicket(Nil) // an empty tree marking an implicit closure // ----- Auxiliary creation methods ------------------ diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index ef5d0d1f7022..130fa11cf718 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -855,22 +855,17 @@ class Definitions { lazy val TupleType = mkArityArray("scala.Tuple", MaxTupleArity, 2) - def FunctionClass(n: Int, isImplicit: Boolean = false, isErased: Boolean = false)(implicit ctx: Context) = { - if (isImplicit && isErased) { - require(n > 0) + def FunctionClass(n: Int, isImplicit: Boolean = false, isErased: Boolean = false)(implicit ctx: Context) = + if (isImplicit && isErased) ctx.requiredClass("scala.ErasedImplicitFunction" + n.toString) - } - else if (isImplicit) { - require(n > 0) + else if (isImplicit) ctx.requiredClass("scala.ImplicitFunction" + n.toString) - } - else if (isErased) { - require(n > 0) + else if (isErased) ctx.requiredClass("scala.ErasedFunction" + n.toString) - } - else if (n <= MaxImplementedFunctionArity) FunctionClassPerRun()(ctx)(n) - else ctx.requiredClass("scala.Function" + n.toString) - } + else if (n <= MaxImplementedFunctionArity) + FunctionClassPerRun()(ctx)(n) + else + ctx.requiredClass("scala.Function" + n.toString) lazy val Function0_applyR = ImplementedFunctionType(0).symbol.requiredMethodRef(nme.apply) def Function0_apply(implicit ctx: Context) = Function0_applyR.symbol @@ -899,23 +894,26 @@ class Definitions { def isBottomType(tp: Type) = tp.derivesFrom(NothingClass) || tp.derivesFrom(NullClass) - /** Is a function class. - * - FunctionN for N >= 0 - * - ImplicitFunctionN for N > 0 - * - ErasedFunctionN for N > 0 - * - ErasedImplicitFunctionN for N > 0 + /** Is a function class, i.e. on of + * - FunctionN + * - ImplicitFunctionN + * - ErasedFunctionN + * - ErasedImplicitFunctionN + * for N >= 0 */ def isFunctionClass(cls: Symbol) = scalaClassName(cls).isFunction /** Is an implicit function class. - * - ImplicitFunctionN for N > 0 - * - ErasedImplicitFunctionN for N > 0 + * - ImplicitFunctionN + * - ErasedImplicitFunctionN + * for N >= 0 */ def isImplicitFunctionClass(cls: Symbol) = scalaClassName(cls).isImplicitFunction /** Is an erased function class. - * - ErasedFunctionN for N > 0 - * - ErasedImplicitFunctionN for N > 0 + * - ErasedFunctionN + * - ErasedImplicitFunctionN + * for N >= 0 */ def isErasedFunctionClass(cls: Symbol) = scalaClassName(cls).isErasedFunction @@ -942,7 +940,7 @@ class Definitions { * - FunctionN for N > 22 becomes FunctionXXL * - FunctionN for 22 > N >= 0 remains as FunctionN * - ImplicitFunctionN for N > 22 becomes FunctionXXL - * - ImplicitFunctionN for 22 > N >= 0 becomes FunctionN + * - ImplicitFunctionN for N <= 22 becomes FunctionN * - ErasedFunctionN becomes Function0 * - ImplicitErasedFunctionN becomes Function0 * - anything else becomes a NoSymbol @@ -959,7 +957,7 @@ class Definitions { * - FunctionN for N > 22 becomes FunctionXXL * - FunctionN for 22 > N >= 0 remains as FunctionN * - ImplicitFunctionN for N > 22 becomes FunctionXXL - * - ImplicitFunctionN for 22 > N >= 0 becomes FunctionN + * - ImplicitFunctionN for N <= 22 becomes FunctionN * - ErasedFunctionN becomes Function0 * - ImplicitErasedFunctionN becomes Function0 * - anything else becomes a NoType diff --git a/compiler/src/dotty/tools/dotc/core/NameOps.scala b/compiler/src/dotty/tools/dotc/core/NameOps.scala index 584139248959..9caea19f249b 100644 --- a/compiler/src/dotty/tools/dotc/core/NameOps.scala +++ b/compiler/src/dotty/tools/dotc/core/NameOps.scala @@ -167,59 +167,39 @@ object NameOps { } } - /** Is a synthetic function name - * - N for FunctionN - * - N for ImplicitFunctionN (N >= 1) - * - (-1) otherwise - */ def functionArity: Int = - functionArityFor(str.Function) max { - val n = - functionArityFor(str.ImplicitFunction) max - functionArityFor(str.ErasedFunction) max - functionArityFor(str.ErasedImplicitFunction) - if (n == 0) -1 else n - } + functionArityFor(str.Function) max + functionArityFor(str.ImplicitFunction) max + functionArityFor(str.ErasedFunction) max + functionArityFor(str.ErasedImplicitFunction) - /** Is a function name - * - FunctionN for N >= 0 - * - ImplicitFunctionN for N >= 1 - * - ErasedFunctionN for N >= 1 - * - ErasedImplicitFunctionN for N >= 1 - * - false otherwise + /** Is a function name, i.e one of FunctionN, ImplicitFunctionN, ErasedFunctionN, ErasedImplicitFunctionN for N >= 0 */ def isFunction: Boolean = functionArity >= 0 - /** Is a implicit function name - * - ImplicitFunctionN for N >= 1 - * - ErasedImplicitFunctionN for N >= 1 - * - false otherwise + /** Is an implicit function name, i.e one of ImplicitFunctionN, ErasedImplicitFunctionN for N >= 0 */ def isImplicitFunction: Boolean = { - functionArityFor(str.ImplicitFunction) >= 1 || - functionArityFor(str.ErasedImplicitFunction) >= 1 + functionArityFor(str.ImplicitFunction) >= 0 || + functionArityFor(str.ErasedImplicitFunction) >= 0 } - /** Is a implicit function name - * - ErasedFunctionN for N >= 1 - * - ErasedImplicitFunctionN for N >= 1 - * - false otherwise + /** Is an erased function name, i.e. one of ErasedFunctionN, ErasedImplicitFunctionN for N >= 0 */ def isErasedFunction: Boolean = { - functionArityFor(str.ErasedFunction) >= 1 || - functionArityFor(str.ErasedImplicitFunction) >= 1 + functionArityFor(str.ErasedFunction) >= 0 || + functionArityFor(str.ErasedImplicitFunction) >= 0 } - /** Is a synthetic function name + /** Is a synthetic function name, i.e. one of * - FunctionN for N > 22 - * - ImplicitFunctionN for N >= 1 - * - ErasedFunctionN for N >= 1 - * - ErasedImplicitFunctionN for N >= 1 - * - false otherwise + * - ImplicitFunctionN for N >= 0 + * - ErasedFunctionN for N >= 0 + * - ErasedImplicitFunctionN for N >= 0 */ def isSyntheticFunction: Boolean = { functionArityFor(str.Function) > MaxImplementedFunctionArity || - functionArityFor(str.ImplicitFunction) >= 1 || + functionArityFor(str.ImplicitFunction) >= 0 || isErasedFunction } diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 79e4a469b00a..8e36471d9391 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -2892,7 +2892,6 @@ object Types { final override def isImplicitMethod: Boolean = companion.eq(ImplicitMethodType) || companion.eq(ErasedImplicitMethodType) final override def isErasedMethod: Boolean = companion.eq(ErasedMethodType) || companion.eq(ErasedImplicitMethodType) - def computeSignature(implicit ctx: Context): Signature = { val params = if (isErasedMethod) Nil else paramInfos resultSignature.prepend(params, isJavaMethod) diff --git a/compiler/src/dotty/tools/dotc/transform/ExpandSAMs.scala b/compiler/src/dotty/tools/dotc/transform/ExpandSAMs.scala index ca652e157cd5..f1ecdb9b76b2 100644 --- a/compiler/src/dotty/tools/dotc/transform/ExpandSAMs.scala +++ b/compiler/src/dotty/tools/dotc/transform/ExpandSAMs.scala @@ -15,12 +15,14 @@ import dotty.tools.dotc.util.Positions.Position * These fall into five categories * * 1. Partial function closures, we need to generate isDefinedAt and applyOrElse methods for these. - * 2. Closures implementing non-trait classes. + * 2. Closures implementing non-trait classes * 3. Closures implementing classes that inherit from a class other than Object * (a lambda cannot not be a run-time subtype of such a class) * 4. Closures that implement traits which run initialization code. * 5. Closures that get synthesized abstract methods in the transformation pipeline. These methods can be * (1) superaccessors, (2) outer references, (3) accessors for fields. + * + * However, implicit function types do not count as SAM types. */ class ExpandSAMs extends MiniPhase { override def phaseName = "expandSAMs" @@ -32,9 +34,12 @@ class ExpandSAMs extends MiniPhase { ctx.platform.isSam(cls) override def transformBlock(tree: Block)(implicit ctx: Context): Tree = tree match { - case Block(stats @ (fn: DefDef) :: Nil, Closure(_, fnRef, tpt)) if fnRef.symbol == fn.symbol => + case Block(stats @ (fn: DefDef) :: Nil, cl @ Closure(_, fnRef, tpt)) if fnRef.symbol == fn.symbol => tpt.tpe match { - case NoType => tree // it's a plain function + case NoType => + tree // it's a plain function + case tpe if defn.isImplicitFunctionType(tpe) => + tree case tpe @ SAMType(_) if tpe.isRef(defn.PartialFunctionClass) => val tpe1 = checkRefinements(tpe, fn.pos) toPartialFunction(tree, tpe1) diff --git a/compiler/src/dotty/tools/dotc/transform/ShortcutImplicits.scala b/compiler/src/dotty/tools/dotc/transform/ShortcutImplicits.scala index 3cc8b68233d0..eb08329ec3e6 100644 --- a/compiler/src/dotty/tools/dotc/transform/ShortcutImplicits.scala +++ b/compiler/src/dotty/tools/dotc/transform/ShortcutImplicits.scala @@ -73,7 +73,7 @@ class ShortcutImplicits extends MiniPhase with IdentityDenotTransformer { thisPh ctx.fresh.updateStore(DirectMeth, newMutableSymbolMap[Symbol]) /** Should `sym` get a ..$direct companion? - * This is the case if (1) `sym` is a method with an implicit function type as final result type. + * This is the case if `sym` is a method with a non-nullary implicit function type as final result type. * However if `specializeMonoTargets` is false, we exclude symbols that are known * to be only targets of monomorphic calls because they are effectively * final and don't override anything. @@ -81,6 +81,7 @@ class ShortcutImplicits extends MiniPhase with IdentityDenotTransformer { thisPh private def shouldBeSpecialized(sym: Symbol)(implicit ctx: Context) = sym.is(Method, butNot = Accessor) && defn.isImplicitFunctionType(sym.info.finalResultType) && + defn.functionArity(sym.info.finalResultType) > 0 && !sym.isAnonymousFunction && (specializeMonoTargets || !sym.isEffectivelyFinal || sym.allOverriddenSymbols.nonEmpty) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 997173e9456c..5ad53f3229f0 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -762,8 +762,9 @@ class Typer extends Namer val untpd.Function(args, body) = tree val (isImplicit, isErased) = tree match { case tree: untpd.FunctionWithMods => (tree.mods.is(Implicit), tree.mods.is(Erased)) - case _ => (false, false) + case _ => (false, false) } + if (isErased && args.isEmpty) ctx.error(em"empty function cannot not be erased", tree.pos) val funCls = defn.FunctionClass(args.length, isImplicit, isErased) /** Typechecks dependent function type with given parameters `params` */ @@ -798,6 +799,11 @@ class Typer extends Namer def typedFunctionValue(tree: untpd.Function, pt: Type)(implicit ctx: Context) = { val untpd.Function(params: List[untpd.ValDef] @unchecked, body) = tree + val isImplicit = tree match { + case tree: untpd.FunctionWithMods => tree.mods.is(Implicit) + case _ => false + } + pt match { case pt: TypeVar if untpd.isFunctionWithUnknownParamType(tree) => // try to instantiate `pt` if this is possible. If it does not @@ -916,8 +922,8 @@ class Typer extends Namer else cpy.ValDef(param)( tpt = untpd.TypeTree( inferredParamType(param, protoFormal(i)).underlyingIfRepeated(isJava = false))) - val inlineable = pt.hasAnnotation(defn.InlineParamAnnot) - desugar.makeClosure(inferredParams, fnBody, resultTpt, inlineable) + val isInlineable = pt.hasAnnotation(defn.InlineParamAnnot) + desugar.makeClosure(inferredParams, fnBody, resultTpt, isInlineable, isImplicit) } typed(desugared, pt) } @@ -942,7 +948,11 @@ class Typer extends Namer |because it has internal parameter dependencies, |position = ${tree.pos}, raw type = ${mt.toString}""") // !!! DEBUG. Eventually, convert to an error? } - else EmptyTree + else if ((tree.tpt `eq` untpd.ImplicitEmptyTree) && mt.paramNames.isEmpty) + // Note implicitness of function in target type sicne there are no method parameters that indicate it. + TypeTree(defn.FunctionOf(Nil, mt.resType, isImplicit = true, isErased = false)) + else + EmptyTree } case tp => throw new java.lang.Error(i"internal error: closing over non-method $tp, pos = ${tree.pos}") diff --git a/tests/neg/i2642.scala b/tests/neg/i2642.scala index df6e0f599ac1..fce66d13b527 100644 --- a/tests/neg/i2642.scala +++ b/tests/neg/i2642.scala @@ -1,4 +1,10 @@ object Foo { - type X = implicit () => Int // error: implicit function needs parameters - def ff: X = () // error: found: Unit, expected: X + type X = implicit () => Int // now ok, used to be: implicit function needs parameters + def ff: X = () // error: found: Unit, expected: Int + + type Y = erased () => Int // error: empty function may not be erased + def gg: Y = () // error: found: Unit, expected: Y + + type Z = erased implicit () => Int // error: empty function may not be erased + def hh: Z = () // error: found: Unit, expected: Int } From 5f3c3e85a296b448d01482dd08c327cad884bee7 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 20 May 2018 20:03:12 +0200 Subject: [PATCH 3/5] Add run test for empty implicit function types --- tests/run/i2642.scala | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 tests/run/i2642.scala diff --git a/tests/run/i2642.scala b/tests/run/i2642.scala new file mode 100644 index 000000000000..6349f2e10d65 --- /dev/null +++ b/tests/run/i2642.scala @@ -0,0 +1,7 @@ +// Tests nullary implicit function types +object Test extends App { + class I + type X = implicit () => Int + def ff: X = 2 + assert(ff == 2) +} From 46c1ce4d12862611cd860cb8548a752689943665 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 28 May 2018 19:27:56 +0200 Subject: [PATCH 4/5] Cleanup --- compiler/src/dotty/tools/dotc/transform/ExpandSAMs.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/transform/ExpandSAMs.scala b/compiler/src/dotty/tools/dotc/transform/ExpandSAMs.scala index f1ecdb9b76b2..201609078802 100644 --- a/compiler/src/dotty/tools/dotc/transform/ExpandSAMs.scala +++ b/compiler/src/dotty/tools/dotc/transform/ExpandSAMs.scala @@ -34,7 +34,7 @@ class ExpandSAMs extends MiniPhase { ctx.platform.isSam(cls) override def transformBlock(tree: Block)(implicit ctx: Context): Tree = tree match { - case Block(stats @ (fn: DefDef) :: Nil, cl @ Closure(_, fnRef, tpt)) if fnRef.symbol == fn.symbol => + case Block(stats @ (fn: DefDef) :: Nil, Closure(_, fnRef, tpt)) if fnRef.symbol == fn.symbol => tpt.tpe match { case NoType => tree // it's a plain function From c85cee08642755a55fa1b0b9cb768b52e04fec4c Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 28 May 2018 19:40:37 +0200 Subject: [PATCH 5/5] Start ErasedFunctionN and ErasedImplicitFunctionN at N=1 --- .../dotty/tools/dotc/core/Definitions.scala | 21 +++++++--------- .../src/dotty/tools/dotc/core/NameOps.scala | 25 +++++++++++-------- .../src/dotty/tools/dotc/typer/Typer.scala | 11 ++++++-- 3 files changed, 32 insertions(+), 25 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 130fa11cf718..c35cf3eef62d 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -894,26 +894,23 @@ class Definitions { def isBottomType(tp: Type) = tp.derivesFrom(NothingClass) || tp.derivesFrom(NullClass) - /** Is a function class, i.e. on of - * - FunctionN - * - ImplicitFunctionN - * - ErasedFunctionN - * - ErasedImplicitFunctionN - * for N >= 0 + /** Is a function class. + * - FunctionN for N >= 0 + * - ImplicitFunctionN for N >= 0 + * - ErasedFunctionN for N > 0 + * - ErasedImplicitFunctionN for N > 0 */ def isFunctionClass(cls: Symbol) = scalaClassName(cls).isFunction /** Is an implicit function class. - * - ImplicitFunctionN - * - ErasedImplicitFunctionN - * for N >= 0 + * - ImplicitFunctionN for N >= 0 + * - ErasedImplicitFunctionN for N > 0 */ def isImplicitFunctionClass(cls: Symbol) = scalaClassName(cls).isImplicitFunction /** Is an erased function class. - * - ErasedFunctionN - * - ErasedImplicitFunctionN - * for N >= 0 + * - ErasedFunctionN for N > 0 + * - ErasedImplicitFunctionN for N > 0 */ def isErasedFunctionClass(cls: Symbol) = scalaClassName(cls).isErasedFunction diff --git a/compiler/src/dotty/tools/dotc/core/NameOps.scala b/compiler/src/dotty/tools/dotc/core/NameOps.scala index 9caea19f249b..52a0cbb735b5 100644 --- a/compiler/src/dotty/tools/dotc/core/NameOps.scala +++ b/compiler/src/dotty/tools/dotc/core/NameOps.scala @@ -169,33 +169,36 @@ object NameOps { def functionArity: Int = functionArityFor(str.Function) max - functionArityFor(str.ImplicitFunction) max - functionArityFor(str.ErasedFunction) max - functionArityFor(str.ErasedImplicitFunction) + functionArityFor(str.ImplicitFunction) max { + val n = + functionArityFor(str.ErasedFunction) max + functionArityFor(str.ErasedImplicitFunction) + if (n == 0) -1 else n + } - /** Is a function name, i.e one of FunctionN, ImplicitFunctionN, ErasedFunctionN, ErasedImplicitFunctionN for N >= 0 + /** Is a function name, i.e one of FunctionN, ImplicitFunctionN for N >= 0 or ErasedFunctionN, ErasedImplicitFunctionN for N > 0 */ def isFunction: Boolean = functionArity >= 0 - /** Is an implicit function name, i.e one of ImplicitFunctionN, ErasedImplicitFunctionN for N >= 0 + /** Is an implicit function name, i.e one of ImplicitFunctionN for N >= 0 or ErasedImplicitFunctionN for N > 0 */ def isImplicitFunction: Boolean = { functionArityFor(str.ImplicitFunction) >= 0 || - functionArityFor(str.ErasedImplicitFunction) >= 0 + functionArityFor(str.ErasedImplicitFunction) > 0 } - /** Is an erased function name, i.e. one of ErasedFunctionN, ErasedImplicitFunctionN for N >= 0 + /** Is an erased function name, i.e. one of ErasedFunctionN, ErasedImplicitFunctionN for N > 0 */ def isErasedFunction: Boolean = { - functionArityFor(str.ErasedFunction) >= 0 || - functionArityFor(str.ErasedImplicitFunction) >= 0 + functionArityFor(str.ErasedFunction) > 0 || + functionArityFor(str.ErasedImplicitFunction) > 0 } /** Is a synthetic function name, i.e. one of * - FunctionN for N > 22 * - ImplicitFunctionN for N >= 0 - * - ErasedFunctionN for N >= 0 - * - ErasedImplicitFunctionN for N >= 0 + * - ErasedFunctionN for N > 0 + * - ErasedImplicitFunctionN for N > 0 */ def isSyntheticFunction: Boolean = { functionArityFor(str.Function) > MaxImplementedFunctionArity || diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 5ad53f3229f0..60ec32a778e2 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -761,10 +761,17 @@ class Typer extends Namer def typedFunctionType(tree: untpd.Function, pt: Type)(implicit ctx: Context) = { val untpd.Function(args, body) = tree val (isImplicit, isErased) = tree match { - case tree: untpd.FunctionWithMods => (tree.mods.is(Implicit), tree.mods.is(Erased)) + case tree: untpd.FunctionWithMods => + val isImplicit = tree.mods.is(Implicit) + var isErased = tree.mods.is(Erased) + if (isErased && args.isEmpty) { + ctx.error("An empty function cannot not be erased", tree.pos) + isErased = false + } + (isImplicit, isErased) case _ => (false, false) } - if (isErased && args.isEmpty) ctx.error(em"empty function cannot not be erased", tree.pos) + val funCls = defn.FunctionClass(args.length, isImplicit, isErased) /** Typechecks dependent function type with given parameters `params` */