Skip to content

Add implicit function types #1775

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 37 commits into from
Dec 18, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
8450556
Add ImplicitFunctionN classes
odersky Dec 3, 2016
fd2c24c
Add syntax for implicit functions
odersky Dec 3, 2016
ad7edc7
Always insert apply for expressions of implicit function type
odersky Dec 3, 2016
4fb19e4
Refactor function operations in Definitions
odersky Dec 3, 2016
415ff70
Handle erasure of implicit function types
odersky Dec 3, 2016
aa6d4fd
Make implicit functions have implicit function type
odersky Dec 3, 2016
e6da213
Changes for matching and subtyping implicit methods
odersky Dec 3, 2016
63ba924
Cleanup of implicit modifiers scheme
odersky Dec 4, 2016
ee59c23
Generalize syntax for implicit function values
odersky Dec 4, 2016
aecfb37
Add code to disable old implicit closure syntax in blocks
odersky Dec 4, 2016
d5ff7e0
Fix erasure of implicit functions
odersky Dec 4, 2016
0336785
Take nesting into account when ranking implicits
odersky Dec 5, 2016
bcc80ad
Create implicit closures to math expected implicit functions
odersky Dec 5, 2016
b804d91
Don't look at nesting for implicit resolution under Scala2 mode.
odersky Dec 5, 2016
43d69cc
Enrich test case
odersky Dec 5, 2016
4c55d2f
More tests and starting a blog post
odersky Dec 5, 2016
c10a990
Finished blog post
odersky Dec 7, 2016
b78150d
Add conclusion to blog post
odersky Dec 7, 2016
6ce5fb1
Fix link
odersky Dec 7, 2016
c9f666f
Fixes to tests
odersky Dec 13, 2016
cc4c3ac
Fix "wrong number of args" reporting
odersky Dec 13, 2016
71b900f
Ref copier that works for Idents and Selects
odersky Dec 13, 2016
5e2f7d1
initialDenot method for symbols
odersky Dec 13, 2016
6eb1a72
New ShortcutImplicits phase
odersky Dec 13, 2016
df4653c
Fix toString in ImplicitFunction tree
odersky Dec 13, 2016
30faa7b
Fix rebase breakage
odersky Dec 13, 2016
04adb53
Add benchmarks
odersky Dec 14, 2016
86dea77
Make specialization tweakable
odersky Dec 14, 2016
c26a8c8
Fix typos in results.md
odersky Dec 14, 2016
65b48e0
Add linked to code
odersky Dec 14, 2016
5ad63c7
Fix more types, add link
odersky Dec 14, 2016
a6ae1a7
Fix typo
odersky Dec 14, 2016
740bd42
Drop Override flag for non-overriding direct methods
odersky Dec 15, 2016
c18d228
Fix typo in comment
odersky Dec 16, 2016
0742ba8
Fix rebase breakage
odersky Dec 17, 2016
7c0b163
Fix formatting
odersky Dec 17, 2016
2e99511
Fix comment
odersky Dec 17, 2016
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
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ import dotty.tools.dotc.core.Names.TypeName
import scala.annotation.tailrec

class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Map[Symbol, Set[ClassSymbol]])(implicit ctx: Context) extends BackendInterface{
import Symbols.{toDenot, toClassDenot}
// Dotty deviation: Need to (re-)import implicit decorators here because otherwise
// they would be shadowed by the more deeply nested `symHelper` decorator.

type Symbol = Symbols.Symbol
type Type = Types.Type
type Tree = tpd.Tree
Expand Down Expand Up @@ -683,8 +687,6 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma
else sym.enclosingClass(ctx.withPhase(ctx.flattenPhase.prev))
} //todo is handled specially for JavaDefined symbols in scalac



// members
def primaryConstructor: Symbol = toDenot(sym).primaryConstructor

Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/Compiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ class Compiler {
new PatternMatcher, // Compile pattern matches
new ExplicitOuter, // Add accessors to outer classes from nested ones.
new ExplicitSelf, // Make references to non-trivial self types explicit as casts
new ShortcutImplicits, // Allow implicit functions without creating closures
new CrossCastAnd, // Normalize selections involving intersection types.
new Splitter), // Expand selections involving union types into conditionals
List(new VCInlineMethods, // Inlines calls to value class methods
Expand Down
23 changes: 16 additions & 7 deletions compiler/src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,13 @@ object desugar {
else vdef
}

def makeImplicitParameters(tpts: List[Tree], forPrimaryConstructor: Boolean)(implicit ctx: Context) =
for (tpt <- tpts) yield {
val paramFlags: FlagSet = if (forPrimaryConstructor) PrivateLocalParamAccessor else Param
val epname = ctx.freshName(nme.EVIDENCE_PARAM_PREFIX).toTermName
ValDef(epname, tpt, EmptyTree).withFlags(paramFlags | Implicit)
}

/** Expand context bounds to evidence params. E.g.,
*
* def f[T >: L <: H : B](params)
Expand All @@ -143,19 +150,16 @@ object desugar {
val epbuf = new ListBuffer[ValDef]
def desugarContextBounds(rhs: Tree): Tree = rhs match {
case ContextBounds(tbounds, cxbounds) =>
for (cxbound <- cxbounds) {
val paramFlags: FlagSet = if (isPrimaryConstructor) PrivateLocalParamAccessor else Param
val epname = ctx.freshName(nme.EVIDENCE_PARAM_PREFIX).toTermName
epbuf += ValDef(epname, cxbound, EmptyTree).withFlags(paramFlags | Implicit)
}
for (cxbound <- cxbounds)
epbuf ++= makeImplicitParameters(cxbounds, isPrimaryConstructor)
tbounds
case PolyTypeTree(tparams, body) =>
cpy.PolyTypeTree(rhs)(tparams, desugarContextBounds(body))
case _ =>
rhs
}
val tparams1 = tparams mapConserve { tdef =>
cpy.TypeDef(tdef)(rhs = desugarContextBounds(tdef.rhs))
val tparams1 = tparams mapConserve { tparam =>
cpy.TypeDef(tparam)(rhs = desugarContextBounds(tparam.rhs))
}

val meth1 = addEvidenceParams(cpy.DefDef(meth)(tparams = tparams1), epbuf.toList)
Expand Down Expand Up @@ -679,6 +683,11 @@ object desugar {
Function(param :: Nil, Block(vdefs, body))
}

def makeImplicitFunction(formals: List[Type], body: Tree)(implicit ctx: Context): Tree = {
val params = makeImplicitParameters(formals.map(TypeTree), forPrimaryConstructor = false)
new ImplicitFunction(params, body)
}

/** Add annotation with class `cls` to tree:
* tree @cls
*/
Expand Down
12 changes: 11 additions & 1 deletion compiler/src/dotty/tools/dotc/ast/TreeInfo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,16 @@ trait UntypedTreeInfo extends TreeInfo[Untyped] { self: Trees.Instance[Untyped]
case _ => false
}

/** Is `tree` an implicit function or closure, possibly nested in a block? */
def isImplicitClosure(tree: Tree)(implicit ctx: Context): Boolean = unsplice(tree) match {
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 _ => false
}

// todo: fill with other methods from TreeInfo that only apply to untpd.Tree's
}

Expand Down Expand Up @@ -501,7 +511,7 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] =>
*/
object closure {
def unapply(tree: Tree): Option[(List[Tree], Tree, Tree)] = tree match {
case Block(_, Closure(env, meth, tpt)) => Some(env, meth, tpt)
case Block(_, expr) => unapply(expr)
case Closure(env, meth, tpt) => Some(env, meth, tpt)
case _ => None
}
Expand Down
5 changes: 5 additions & 0 deletions compiler/src/dotty/tools/dotc/ast/Trees.scala
Original file line number Diff line number Diff line change
Expand Up @@ -890,6 +890,11 @@ object Trees {
case tree: Select if (qualifier eq tree.qualifier) && (name == tree.name) => tree
case _ => finalize(tree, untpd.Select(qualifier, name))
}
/** Copy Ident or Select trees */
def Ref(tree: RefTree)(name: Name)(implicit ctx: Context) = tree match {
case Ident(_) => Ident(tree)(name)
case Select(qual, _) => Select(tree)(qual, name)
}
def This(tree: Tree)(qual: untpd.Ident): This = tree match {
case tree: This if qual eq tree.qual => tree
case _ => finalize(tree, untpd.This(qual))
Expand Down
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/dotc/ast/tpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -450,7 +450,8 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
} else foldOver(sym, tree)
}

override val cpy = new TypedTreeCopier
override val cpy: TypedTreeCopier = // Type ascription needed to pick up any new members in TreeCopier (currently there are none)
new TypedTreeCopier

class TypedTreeCopier extends TreeCopier {
def postProcess(tree: Tree, copied: untpd.Tree): copied.ThisTree[Type] =
Expand Down
10 changes: 7 additions & 3 deletions compiler/src/dotty/tools/dotc/ast/untpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
override def isTerm = body.isTerm
override def isType = body.isType
}

/** An implicit function type */
class ImplicitFunction(args: List[Tree], body: Tree) extends Function(args, body) {
override def toString = s"ImplicitFunction($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
Expand Down Expand Up @@ -111,7 +117,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {

case class Var() extends Mod(Flags.Mutable)

case class Implicit(flag: FlagSet = Flags.ImplicitCommon) extends Mod(flag)
case class Implicit() extends Mod(Flags.ImplicitCommon)

case class Final() extends Mod(Flags.Final)

Expand Down Expand Up @@ -270,8 +276,6 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {

// ------ Additional creation methods for untyped only -----------------

// def TypeTree(tpe: Type): TypeTree = TypeTree().withType(tpe) todo: move to untpd/tpd

/** new pre.C[Ts](args1)...(args_n)
* ==>
* (new pre.C).<init>[Ts](args1)...(args_n)
Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/config/JavaPlatform.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class JavaPlatform extends Platform {
currentClassPath = Some(new PathResolver().result)
val cp = currentClassPath.get
//println(cp)
//println("------------------")
cp
}

Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/Comments.scala
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ object Comments {
def apply(comment: Comment, code: String, codePos: Position)(implicit ctx: Context) =
new UseCase(comment, code, codePos) {
val untpdCode = {
val tree = new Parser(new SourceFile("<usecase>", code)).localDef(codePos.start, EmptyFlags)
val tree = new Parser(new SourceFile("<usecase>", code)).localDef(codePos.start)

tree match {
case tree: untpd.DefDef =>
Expand Down
89 changes: 66 additions & 23 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -86,24 +86,51 @@ class Definitions {
newClassSymbol(ScalaPackageClass, name, EmptyFlags, completer).entered
}

/** The trait FunctionN, for some N */
private def newFunctionNTrait(n: Int) = {
/** The trait FunctionN or ImplicitFunctionN, for some N
* @param name The name of the trait to be created
*
* FunctionN traits follow this template:
*
* trait FunctionN[T0,...T{N-1}, R] extends Object {
* def apply($x0: T0, ..., $x{N_1}: T{N-1}): R
* }
*
* That is, they follow the template given for Function2..Function22 in the
* standard library, but without `tupled` and `curried` methods and without
* a `toString`.
*
* ImplicitFunctionN traits follow this template:
*
* trait ImplicitFunctionN[T0,...,T{N-1}, R] extends Object with FunctionN[T0,...,T{N-1}, R] {
* def apply(implicit $x0: T0, ..., $x{N_1}: T{N-1}): R
* }
*/
private def newFunctionNTrait(name: TypeName) = {
val completer = new LazyType {
def complete(denot: SymDenotation)(implicit ctx: Context): Unit = {
val cls = denot.asClass.classSymbol
val decls = newScope
val arity = name.functionArity
val argParams =
for (i <- List.range(0, n)) yield
enterTypeParam(cls, s"T$i".toTypeName, Contravariant, decls)
val resParam = enterTypeParam(cls, s"R".toTypeName, Covariant, decls)
for (i <- List.range(0, arity)) yield
enterTypeParam(cls, name ++ "$T" ++ i.toString, Contravariant, decls)
val resParam = enterTypeParam(cls, name ++ "$R", Covariant, decls)
val (methodType, parentTraits) =
if (name.startsWith(tpnme.ImplicitFunction)) {
val superTrait =
FunctionType(arity).appliedTo(argParams.map(_.typeRef) ::: resParam.typeRef :: Nil)
(ImplicitMethodType, ctx.normalizeToClassRefs(superTrait :: Nil, cls, decls))
}
else (MethodType, Nil)
val applyMeth =
decls.enter(
newMethod(cls, nme.apply,
MethodType(argParams.map(_.typeRef), resParam.typeRef), Deferred))
denot.info = ClassInfo(ScalaPackageClass.thisType, cls, ObjectType :: Nil, decls)
methodType(argParams.map(_.typeRef), resParam.typeRef), Deferred))
denot.info =
ClassInfo(ScalaPackageClass.thisType, cls, ObjectType :: parentTraits, decls)
}
}
newClassSymbol(ScalaPackageClass, s"Function$n".toTypeName, Trait, completer)
newClassSymbol(ScalaPackageClass, name, Trait, completer)
}

private def newMethod(cls: ClassSymbol, name: TermName, info: Type, flags: FlagSet = EmptyFlags): TermSymbol =
Expand Down Expand Up @@ -590,15 +617,16 @@ class Definitions {
sym.owner.linkedClass.typeRef

object FunctionOf {
def apply(args: List[Type], resultType: Type)(implicit ctx: Context) =
FunctionType(args.length).appliedTo(args ::: resultType :: Nil)
def apply(args: List[Type], resultType: Type, isImplicit: Boolean = false)(implicit ctx: Context) =
FunctionType(args.length, isImplicit).appliedTo(args ::: resultType :: Nil)
def unapply(ft: Type)(implicit ctx: Context) = {
val tsym = ft.typeSymbol
if (isFunctionClass(tsym)) {
lazy val targs = ft.argInfos
val isImplicitFun = isImplicitFunctionClass(tsym)
if (isImplicitFun || isFunctionClass(tsym)) {
val targs = ft.argInfos
val numArgs = targs.length - 1
if (numArgs >= 0 && FunctionType(numArgs).symbol == tsym)
Some(targs.init, targs.last)
if (numArgs >= 0 && FunctionType(numArgs, isImplicitFun).symbol == tsym)
Some(targs.init, targs.last, isImplicitFun)
else None
}
else None
Expand Down Expand Up @@ -659,8 +687,12 @@ class Definitions {
lazy val Function0_applyR = ImplementedFunctionType(0).symbol.requiredMethodRef(nme.apply)
def Function0_apply(implicit ctx: Context) = Function0_applyR.symbol

def FunctionType(n: Int)(implicit ctx: Context): TypeRef =
if (n < MaxImplementedFunctionArity) ImplementedFunctionType(n)
def ImplicitFunctionClass(n: Int)(implicit ctx: Context) =
ctx.requiredClass("scala.ImplicitFunction" + n.toString)

def FunctionType(n: Int, isImplicit: Boolean = false)(implicit ctx: Context): TypeRef =
if (isImplicit && !ctx.erasedTypes) ImplicitFunctionClass(n).typeRef
else if (n < MaxImplementedFunctionArity) ImplementedFunctionType(n)
else FunctionClass(n).typeRef

private lazy val TupleTypes: Set[TypeRef] = TupleType.toSet
Expand All @@ -686,6 +718,7 @@ class Definitions {
tp.derivesFrom(NothingClass) || tp.derivesFrom(NullClass)

def isFunctionClass(cls: Symbol) = isVarArityClass(cls, tpnme.Function)
def isImplicitFunctionClass(cls: Symbol) = isVarArityClass(cls, tpnme.ImplicitFunction)
def isUnimplementedFunctionClass(cls: Symbol) =
isFunctionClass(cls) && cls.name.functionArity > MaxImplementedFunctionArity
def isAbstractFunctionClass(cls: Symbol) = isVarArityClass(cls, tpnme.AbstractFunction)
Expand Down Expand Up @@ -745,14 +778,21 @@ class Definitions {
}
else -1

def isFunctionType(tp: Type)(implicit ctx: Context) =
isFunctionClass(tp.dealias.typeSymbol) && {
val arity = functionArity(tp)
arity >= 0 && tp.isRef(FunctionType(functionArity(tp)).typeSymbol)
}
/** Is `tp` (an alias) of either a scala.FunctionN or a scala.ImplicitFunctionN ? */
def isFunctionType(tp: Type)(implicit ctx: Context) = {
val arity = functionArity(tp)
val sym = tp.dealias.typeSymbol
arity >= 0 && (
isFunctionClass(sym) && tp.isRef(FunctionType(arity, isImplicit = false).typeSymbol) ||
isImplicitFunctionClass(sym) && tp.isRef(FunctionType(arity, isImplicit = true).typeSymbol)
)
}

def functionArity(tp: Type)(implicit ctx: Context) = tp.dealias.argInfos.length - 1

def isImplicitFunctionType(tp: Type)(implicit ctx: Context) =
isFunctionType(tp) && tp.dealias.typeSymbol.name.startsWith(tpnme.ImplicitFunction)

// ----- primitive value class machinery ------------------------------------------

/** This class would also be obviated by the implicit function type design */
Expand Down Expand Up @@ -825,6 +865,9 @@ class Definitions {

// ----- Initialization ---------------------------------------------------

private def maxImplemented(name: Name) =
if (name `startsWith` tpnme.Function) MaxImplementedFunctionArity else 0

/** Give the scala package a scope where a FunctionN trait is automatically
* added when someone looks for it.
*/
Expand All @@ -834,8 +877,8 @@ class Definitions {
val newDecls = new MutableScope(oldDecls) {
override def lookupEntry(name: Name)(implicit ctx: Context): ScopeEntry = {
val res = super.lookupEntry(name)
if (res == null && name.functionArity > MaxImplementedFunctionArity)
newScopeEntry(newFunctionNTrait(name.functionArity))
if (res == null && name.isTypeName && name.functionArity > maxImplemented(name))
newScopeEntry(newFunctionNTrait(name.asTypeName))
else res
}
}
Expand Down
15 changes: 10 additions & 5 deletions compiler/src/dotty/tools/dotc/core/NameOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,8 @@ object NameOps {

def errorName: N = likeTyped(name ++ nme.ERROR)

def directName: N = likeTyped(name ++ DIRECT_SUFFIX)

def freshened(implicit ctx: Context): N =
likeTyped(
if (name.isModuleClassName) name.stripModuleClassSuffix.freshened.moduleClassName
Expand Down Expand Up @@ -229,11 +231,14 @@ object NameOps {
}
}

def functionArity: Int =
if (name.startsWith(tpnme.Function))
try name.drop(tpnme.Function.length).toString.toInt
catch { case ex: NumberFormatException => -1 }
else -1
def functionArity: Int = {
def test(prefix: Name): Int =
if (name.startsWith(prefix))
try name.drop(prefix.length).toString.toInt
catch { case ex: NumberFormatException => -1 }
else -1
test(tpnme.Function) max test(tpnme.ImplicitFunction)
}

/** The name of the generic runtime operation corresponding to an array operation */
def genericArrayOp: TermName = name match {
Expand Down
2 changes: 2 additions & 0 deletions compiler/src/dotty/tools/dotc/core/StdNames.scala
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ object StdNames {
val COMPANION_MODULE_METHOD: N = "companion$module"
val COMPANION_CLASS_METHOD: N = "companion$class"
val TRAIT_SETTER_SEPARATOR: N = "$_setter_$"
val DIRECT_SUFFIX: N = "$direct"

// value types (and AnyRef) are all used as terms as well
// as (at least) arguments to the @specialize annotation.
Expand Down Expand Up @@ -181,6 +182,7 @@ object StdNames {
final val AnyVal: N = "AnyVal"
final val ExprApi: N = "ExprApi"
final val Function: N = "Function"
final val ImplicitFunction: N = "ImplicitFunction"
final val Mirror: N = "Mirror"
final val Nothing: N = "Nothing"
final val Null: N = "Null"
Expand Down
4 changes: 4 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Symbols.scala
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,10 @@ object Symbols {
denot
}

/** The initial denotation of this symbol, without going through `current` */
final def initialDenot(implicit ctx: Context): SymDenotation =
lastDenot.initial

private[core] def defRunId: RunId =
if (lastDenot == null) NoRunId else lastDenot.validFor.runId

Expand Down
Loading