Skip to content

Drop requirement that implicit functions must be non-empty #4549

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jun 6, 2018
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions compiler/src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down
8 changes: 6 additions & 2 deletions compiler/src/dotty/tools/dotc/ast/TreeInfo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/ast/Trees.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 ------------------

Expand Down
6 changes: 4 additions & 2 deletions compiler/src/dotty/tools/dotc/ast/untpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
46 changes: 22 additions & 24 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand All @@ -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
Expand All @@ -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
Expand Down
52 changes: 16 additions & 36 deletions compiler/src/dotty/tools/dotc/core/NameOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
1 change: 0 additions & 1 deletion compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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) = {
Expand Down Expand Up @@ -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
}
Expand Down
20 changes: 0 additions & 20 deletions compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
11 changes: 8 additions & 3 deletions compiler/src/dotty/tools/dotc/transform/ExpandSAMs.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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 =>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cl is never used

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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,14 +73,15 @@ 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.
*/
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)

Expand Down
23 changes: 14 additions & 9 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -761,14 +761,10 @@ 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 tree: untpd.FunctionWithMods => (tree.mods.is(Implicit), tree.mods.is(Erased))
case _ => (false, false)
}
if (isErased && args.isEmpty) ctx.error(em"empty function cannot not be erased", tree.pos)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks inconsistent. We do provide names for ErasedFunction0 and ErasedImplicitFunction0 and add them in scope, but we do not allow them to be used in function types.

  • We could remove the start ErasedFuctionN and ErasedImplicitFuctionN at N = 1
  • Or allow them, as a function type that is equivalent to Funcion0 or ImplicitFunction0. We could even consider making them type aliases and warn the the erased keyword is useless.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nicolasstucki Agree we should start ErasedFunctionN at 1. Can you do the change and push it on top of this PR? Thanks!

val funCls = defn.FunctionClass(args.length, isImplicit, isErased)

/** Typechecks dependent function type with given parameters `params` */
Expand Down Expand Up @@ -803,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
Expand Down Expand Up @@ -921,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)
}
Expand All @@ -947,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}")
Expand Down
Loading