From 8450556080c5fd8f8553bec4f39ea08fbb05c9d2 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 3 Dec 2016 12:22:44 +0100 Subject: [PATCH 01/37] Add ImplicitFunctionN classes These are always synthetic; generated on demand. --- .../dotty/tools/dotc/core/Definitions.scala | 30 +++++++++++++------ .../src/dotty/tools/dotc/core/NameOps.scala | 13 ++++---- .../src/dotty/tools/dotc/core/StdNames.scala | 1 + 3 files changed, 30 insertions(+), 14 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 3f1f3e294bbc..b90cd597f976 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -87,23 +87,32 @@ class Definitions { } /** The trait FunctionN, for some N */ - private def newFunctionNTrait(n: Int) = { + 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 (implicitFlag, parentTraits) = + if (name.startsWith(tpnme.ImplicitFunction)) { + val superTrait = + FunctionType(arity).appliedTo(argParams.map(_.typeRef) ::: resParam.typeRef :: Nil) + (Implicit, ctx.normalizeToClassRefs(superTrait :: Nil, cls, decls)) + } + else (EmptyFlags, 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 | implicitFlag)) + 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 = @@ -659,6 +668,9 @@ class Definitions { lazy val Function0_applyR = ImplementedFunctionType(0).symbol.requiredMethodRef(nme.apply) def Function0_apply(implicit ctx: Context) = Function0_applyR.symbol + def ImplicitFunctionClass(n: Int)(implicit ctx: Context) = + ctx.requiredClass("scala.ImplicitFunction" + n.toString) + def FunctionType(n: Int)(implicit ctx: Context): TypeRef = if (n < MaxImplementedFunctionArity) ImplementedFunctionType(n) else FunctionClass(n).typeRef @@ -834,8 +846,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 > MaxImplementedFunctionArity) + newScopeEntry(newFunctionNTrait(name.asTypeName)) else res } } diff --git a/compiler/src/dotty/tools/dotc/core/NameOps.scala b/compiler/src/dotty/tools/dotc/core/NameOps.scala index 7a4fc0512fdf..f40915528281 100644 --- a/compiler/src/dotty/tools/dotc/core/NameOps.scala +++ b/compiler/src/dotty/tools/dotc/core/NameOps.scala @@ -229,11 +229,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 { diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index e71893c1ee2c..adcb1524218f 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -181,6 +181,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" From fd2c24c3159cefa583889a176f31d1e2325fe7e6 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 3 Dec 2016 12:53:10 +0100 Subject: [PATCH 02/37] Add syntax for implicit functions --- compiler/src/dotty/tools/dotc/ast/untpd.scala | 4 +++ .../dotty/tools/dotc/parsing/Parsers.scala | 16 ++++++---- .../src/dotty/tools/dotc/typer/Typer.scala | 8 +++-- docs/syntax-summary.txt | 4 +-- tests/pos/implicitFuns.scala | 30 +++++++++++++++++++ 5 files changed, 53 insertions(+), 9 deletions(-) create mode 100644 tests/pos/implicitFuns.scala diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index 6c5210287134..25b69b1f5847 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -53,6 +53,10 @@ 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) + /** 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 diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 704f399ca0d0..dabd8d2b0ea9 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -681,7 +681,7 @@ object Parsers { } } - /** Type ::= FunArgTypes `=>' Type + /** Type ::= [`implicit'] FunArgTypes `=>' Type * | HkTypeParamClause `->' Type * | InfixType * FunArgTypes ::= InfixType @@ -689,20 +689,26 @@ object Parsers { */ def typ(): Tree = { val start = in.offset + val isImplicit = in.token == IMPLICIT + if (isImplicit) in.nextToken() + def functionRest(params: List[Tree]): Tree = + atPos(start, accept(ARROW)) { + val t = typ() + if (isImplicit) new ImplicitFunction(params, t) else Function(params, t) + } val t = if (in.token == LPAREN) { in.nextToken() if (in.token == RPAREN) { in.nextToken() - atPos(start, accept(ARROW)) { Function(Nil, typ()) } + functionRest(Nil) } else { openParens.change(LPAREN, 1) val ts = commaSeparated(funArgType) openParens.change(LPAREN, -1) accept(RPAREN) - if (in.token == ARROW) - atPos(start, in.skipToken()) { Function(ts, typ()) } + if (isImplicit || in.token == ARROW) functionRest(ts) else { for (t <- ts) if (t.isInstanceOf[ByNameTypeTree]) @@ -722,7 +728,7 @@ object Parsers { else infixType() in.token match { - case ARROW => atPos(start, in.skipToken()) { Function(List(t), typ()) } + case ARROW => functionRest(t :: Nil) case FORSOME => syntaxError("existential types no longer supported; use a wildcard type or dependent type instead"); t case _ => t } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index eec3859f9700..c8deda4bc26e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -660,9 +660,13 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit def typedFunction(tree: untpd.Function, pt: Type)(implicit ctx: Context) = track("typedFunction") { val untpd.Function(args, body) = tree - if (ctx.mode is Mode.Type) + if (ctx.mode is Mode.Type) { + val funCls = + if (tree.isInstanceOf[untpd.ImplicitFunction]) defn.ImplicitFunctionClass(args.length) + else defn.FunctionClass(args.length) typed(cpy.AppliedTypeTree(tree)( - untpd.TypeTree(defn.FunctionClass(args.length).typeRef), args :+ body), pt) + untpd.TypeTree(funCls.typeRef), args :+ body), pt) + } else { val params = args.asInstanceOf[List[untpd.ValDef]] diff --git a/docs/syntax-summary.txt b/docs/syntax-summary.txt index 04e149de6f3b..fe0ebc89cc7f 100644 --- a/docs/syntax-summary.txt +++ b/docs/syntax-summary.txt @@ -95,8 +95,8 @@ grammar. | [id '.'] `super' [ClassQualifier] `.' id ClassQualifier ::= `[' id `]' - Type ::= FunArgTypes `=>' Type Function(ts, t) - | HkTypeParamClause `=>' Type TypeLambda(ps, t) + Type ::= [`implicit'] FunArgTypes `=>' Type Function(ts, t) + | HkTypeParamClause `=>' Type TypeLambda(ps, t) | InfixType FunArgTypes ::= InfixType | `(' [ FunArgType {`,' FunArgType } ] `)' diff --git a/tests/pos/implicitFuns.scala b/tests/pos/implicitFuns.scala new file mode 100644 index 000000000000..e62682546754 --- /dev/null +++ b/tests/pos/implicitFuns.scala @@ -0,0 +1,30 @@ +object Test { + + val x: ImplicitFunction1[String, Boolean] = ??? + + val y: String => Boolean = x + + val b = x("hello") + + val b1: Boolean = b + +} +object Test2 { + + val x: implicit String => Boolean = ??? + + val xx: implicit (String, Int) => Int = ??? + + val y: String => Boolean = x + + val yy: (String, Int) => Any = xx + + val b = x("hello") + + val b1: Boolean = b + + val c = xx("hh", 22) + + val c1: Int = c + +} From ad7edc7bd8af963b768afdc50b7038a8daa47ccb Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 3 Dec 2016 13:52:48 +0100 Subject: [PATCH 03/37] Always insert apply for expressions of implicit function type --- .../dotty/tools/dotc/core/Definitions.scala | 29 +++++++++++++++---- .../src/dotty/tools/dotc/typer/Typer.scala | 21 +++++++++----- tests/pos/implicitFuns.scala | 6 ++++ 3 files changed, 43 insertions(+), 13 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index b90cd597f976..f04dac50535f 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -86,7 +86,25 @@ class Definitions { newClassSymbol(ScalaPackageClass, name, EmptyFlags, completer).entered } - /** The trait FunctionN, for some N */ + /** 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 = { @@ -97,17 +115,17 @@ class Definitions { for (i <- List.range(0, arity)) yield enterTypeParam(cls, name ++ "$T" ++ i.toString, Contravariant, decls) val resParam = enterTypeParam(cls, name ++ "$R", Covariant, decls) - val (implicitFlag, parentTraits) = + val (methodType, parentTraits) = if (name.startsWith(tpnme.ImplicitFunction)) { val superTrait = FunctionType(arity).appliedTo(argParams.map(_.typeRef) ::: resParam.typeRef :: Nil) - (Implicit, ctx.normalizeToClassRefs(superTrait :: Nil, cls, decls)) + (ImplicitMethodType, ctx.normalizeToClassRefs(superTrait :: Nil, cls, decls)) } - else (EmptyFlags, Nil) + else (MethodType, Nil) val applyMeth = decls.enter( newMethod(cls, nme.apply, - MethodType(argParams.map(_.typeRef), resParam.typeRef), Deferred | implicitFlag)) + methodType(argParams.map(_.typeRef), resParam.typeRef), Deferred)) denot.info = ClassInfo(ScalaPackageClass.thisType, cls, ObjectType :: parentTraits, decls) } @@ -698,6 +716,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) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index c8deda4bc26e..018a6064bf52 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1616,6 +1616,14 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit } } + /** Is `pt` a prototype of an `apply` selection, or a parameterless function yielding one? */ + def isApplyProto(pt: Type)(implicit ctx: Context): Boolean = pt match { + case pt: SelectionProto => pt.name == nme.apply + case pt: FunProto => pt.args.isEmpty && isApplyProto(pt.resultType) + case pt: IgnoredProto => isApplyProto(pt.ignored) + case _ => false + } + /** Add apply node or implicit conversions. Two strategies are tried, and the first * that is successful is picked. If neither of the strategies are successful, continues with * `fallBack`. @@ -1629,14 +1637,6 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit */ def tryInsertApplyOrImplicit(tree: Tree, pt: ProtoType)(fallBack: => Tree)(implicit ctx: Context): Tree = { - /** Is `pt` a prototype of an `apply` selection, or a parameterless function yielding one? */ - def isApplyProto(pt: Type): Boolean = pt match { - case pt: SelectionProto => pt.name == nme.apply - case pt: FunProto => pt.args.isEmpty && isApplyProto(pt.resultType) - case pt: IgnoredProto => isApplyProto(pt.ignored) - case _ => false - } - def tryApply(implicit ctx: Context) = { val sel = typedSelect(untpd.Select(untpd.TypedSplice(tree), nme.apply), pt) if (sel.tpe.isError) sel else adapt(sel, pt) @@ -1878,6 +1878,11 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit err.typeMismatch(tree, pt) else missingArgs + case wtp: RefinedType + if defn.isImplicitFunctionClass(wtp.underlyingClassRef(refinementOK = false).classSymbol) && + !isApplyProto(pt) => + typr.println(i"insert apply on implicit $tree") + typed(untpd.Select(untpd.TypedSplice(tree), nme.apply), pt) case _ => ctx.typeComparer.GADTused = false if (ctx.mode is Mode.Pattern) { diff --git a/tests/pos/implicitFuns.scala b/tests/pos/implicitFuns.scala index e62682546754..edb2434c959c 100644 --- a/tests/pos/implicitFuns.scala +++ b/tests/pos/implicitFuns.scala @@ -19,10 +19,16 @@ object Test2 { val yy: (String, Int) => Any = xx + implicit val world: String = "world!" + val b = x("hello") val b1: Boolean = b + val bi = x + + val bi1: Boolean = bi + val c = xx("hh", 22) val c1: Int = c From 4fb19e43f696845a18cbe2a7671654674ffce9b7 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 3 Dec 2016 19:32:09 +0100 Subject: [PATCH 04/37] Refactor function operations in Definitions Also: show implicit function types correctly. Also: refine applications of implicit funcitons - don't do it for closure trees - don't do it after typer. --- .../src/dotty/tools/dotc/ast/TreeInfo.scala | 4 ++- .../dotty/tools/dotc/core/Definitions.scala | 32 +++++++++++-------- .../tools/dotc/printing/RefinedPrinter.scala | 7 ++-- .../dotty/tools/dotc/typer/Applications.scala | 4 +-- .../src/dotty/tools/dotc/typer/Typer.scala | 6 ++-- 5 files changed, 32 insertions(+), 21 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index d1e6bd38a83a..ae7c93784d0c 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -501,12 +501,14 @@ 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 } } + def isClosure(tree: Tree) = closure.unapply(tree).isDefined + /** If tree is a closure, its body, otherwise tree itself */ def closureBody(tree: Tree)(implicit ctx: Context): Tree = tree match { case Block((meth @ DefDef(nme.ANON_FUN, _, _, _, _)) :: Nil, Closure(_, _, _)) => meth.rhs diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index f04dac50535f..ff259f1844b8 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -617,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 @@ -689,8 +690,9 @@ class Definitions { def ImplicitFunctionClass(n: Int)(implicit ctx: Context) = ctx.requiredClass("scala.ImplicitFunction" + n.toString) - def FunctionType(n: Int)(implicit ctx: Context): TypeRef = - if (n < MaxImplementedFunctionArity) ImplementedFunctionType(n) + 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 @@ -776,11 +778,15 @@ 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 diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 1ddf3cd6dffa..3085ad8fd0e3 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -113,20 +113,21 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { override def toText(tp: Type): Text = controlled { def toTextTuple(args: List[Type]): Text = "(" ~ Text(args.map(argText), ", ") ~ ")" - def toTextFunction(args: List[Type]): Text = + def toTextFunction(args: List[Type], isImplicit: Boolean): Text = changePrec(GlobalPrec) { val argStr: Text = if (args.length == 2 && !defn.isTupleType(args.head)) atPrec(InfixPrec) { argText(args.head) } else toTextTuple(args.init) - argStr ~ " => " ~ argText(args.last) + ("implicit " provided isImplicit) ~ argStr ~ " => " ~ argText(args.last) } homogenize(tp) match { case AppliedType(tycon, args) => val cls = tycon.typeSymbol if (tycon.isRepeatedParam) return toTextLocal(args.head) ~ "*" - if (defn.isFunctionClass(cls)) return toTextFunction(args) + if (defn.isFunctionClass(cls)) return toTextFunction(args, isImplicit = false) + if (defn.isImplicitFunctionClass(cls)) return toTextFunction(args, isImplicit = true) if (defn.isTupleClass(cls)) return toTextTuple(args) return (toTextLocal(tycon) ~ "[" ~ Text(args map argText, ", ") ~ "]").close case tp: TypeRef => diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 4203ab9b2edc..469d657a9817 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -1294,7 +1294,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => val alts1 = alts filter pt.isMatchedBy resolveOverloaded(alts1, pt1, targs1) - case defn.FunctionOf(args, resultType) => + case defn.FunctionOf(args, resultType, _) => narrowByTypes(alts, args, resultType) case pt => @@ -1345,7 +1345,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => // (p_1_1, ..., p_m_1) => r_1 // ... // (p_1_n, ..., p_m_n) => r_n - val decomposedFormalsForArg: List[Option[(List[Type], Type)]] = + val decomposedFormalsForArg: List[Option[(List[Type], Type, Boolean)]] = formalsForArg.map(defn.FunctionOf.unapply) if (decomposedFormalsForArg.forall(_.isDefined)) { val formalParamTypessForArg: List[List[Type]] = diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 018a6064bf52..e016ba3a6508 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1880,8 +1880,10 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit missingArgs case wtp: RefinedType if defn.isImplicitFunctionClass(wtp.underlyingClassRef(refinementOK = false).classSymbol) && - !isApplyProto(pt) => - typr.println(i"insert apply on implicit $tree") + !isClosure(tree) && + !isApplyProto(pt) && + !ctx.isAfterTyper => + typr.println("insert apply on implicit $tree") typed(untpd.Select(untpd.TypedSplice(tree), nme.apply), pt) case _ => ctx.typeComparer.GADTused = false From 415ff701e277627c5429c283269b757bf9172e07 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 3 Dec 2016 19:37:17 +0100 Subject: [PATCH 05/37] Handle erasure of implicit function types --- compiler/src/dotty/tools/dotc/core/TypeErasure.scala | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala index 82943377a866..bc736b229cd2 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala @@ -2,7 +2,9 @@ package dotty.tools package dotc package core -import Symbols._, Types._, Contexts._, Flags._, Names._, StdNames._, Decorators._, Flags.JavaDefined +import Symbols._, Types._, Contexts._, Flags._, Names._, StdNames._, Decorators._ +import Flags.JavaDefined +import NameOps._ import Uniques.unique import dotc.transform.ExplicitOuter._ import dotc.transform.ValueClasses._ @@ -327,6 +329,7 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean * - For a typeref scala.Any, scala.AnyVal or scala.Singleton: |java.lang.Object| * - For a typeref scala.Unit, |scala.runtime.BoxedUnit|. * - For a typeref scala.FunctionN, where N > MaxImplementedFunctionArity, scala.FunctionXXL + * - For a typeref scala.ImplicitFunctionN, | scala.FunctionN | * - For a typeref P.C where C refers to a class, # C. * - For a typeref P.C where C refers to an alias type, the erasure of C's alias. * - For a typeref P.C where C refers to an abstract type, the erasure of C's upper bound. @@ -356,6 +359,7 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean else if (semiEraseVCs && isDerivedValueClass(sym)) eraseDerivedValueClassRef(tp) else if (sym == defn.ArrayClass) apply(tp.appliedTo(TypeBounds.empty)) // i966 shows that we can hit a raw Array type. else if (defn.isUnimplementedFunctionClass(sym)) defn.FunctionXXLType + else if (defn.isImplicitFunctionClass(sym)) apply(defn.FunctionType(sym.name.functionArity)) else eraseNormalClassRef(tp) case tp: RefinedType => val parent = tp.parent @@ -492,7 +496,10 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean val erasedVCRef = eraseDerivedValueClassRef(tp) if (erasedVCRef.exists) return sigName(erasedVCRef) } - normalizeClass(sym.asClass).fullName.asTypeName + if (defn.isImplicitFunctionClass(sym)) + sigName(defn.FunctionType(sym.name.functionArity)) + else + normalizeClass(sym.asClass).fullName.asTypeName case defn.ArrayOf(elem) => sigName(this(tp)) case JavaArrayType(elem) => From aa6d4fd522d9ce1652a1387d28399d43b5924141 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 3 Dec 2016 19:38:20 +0100 Subject: [PATCH 06/37] Make implicit functions have implicit function type --- compiler/src/dotty/tools/dotc/core/Types.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 4dffc4542541..01a331e86580 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1210,7 +1210,7 @@ object Types { case mt @ MethodType(_, formals) if !mt.isDependent || ctx.mode.is(Mode.AllowDependentFunctions) => val formals1 = if (dropLast == 0) formals else formals dropRight dropLast defn.FunctionOf( - formals1 mapConserve (_.underlyingIfRepeated(mt.isJava)), mt.resultType) + formals1 mapConserve (_.underlyingIfRepeated(mt.isJava)), mt.resultType, mt.isImplicit && !ctx.erasedTypes) } /** The signature of this type. This is by default NotAMethod, From e6da2137f48ca6019b826be501ac64e452e5fe7e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 3 Dec 2016 19:46:24 +0100 Subject: [PATCH 07/37] Changes for matching and subtyping implicit methods Implicitness is ignored for matching (otherwise apply in ImplicitFunction could not shadow apply in Function). And explicit trumps implicit in subtyping comparisons. --- compiler/src/dotty/tools/dotc/core/TypeComparer.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 743220f550a0..ecda3b538381 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -478,7 +478,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { case tp1 @ MethodType(_, formals1) => (tp1.signature consistentParams tp2.signature) && matchingParams(formals1, formals2, tp1.isJava, tp2.isJava) && - tp1.isImplicit == tp2.isImplicit && // needed? + (!tp1.isImplicit || tp2.isImplicit) && // non-implicit functions shadow implicit ones isSubType(tp1.resultType, tp2.resultType.subst(tp2, tp1)) case _ => false @@ -1003,9 +1003,9 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { case tp1: MethodType => tp2.widen match { case tp2: MethodType => - tp1.isImplicit == tp2.isImplicit && - matchingParams(tp1.paramTypes, tp2.paramTypes, tp1.isJava, tp2.isJava) && - matchesType(tp1.resultType, tp2.resultType.subst(tp2, tp1), relaxed) + // implicitness is ignored when matching + matchingParams(tp1.paramTypes, tp2.paramTypes, tp1.isJava, tp2.isJava) && + matchesType(tp1.resultType, tp2.resultType.subst(tp2, tp1), relaxed) case tp2 => relaxed && tp1.paramNames.isEmpty && matchesType(tp1.resultType, tp2, relaxed) From 63ba924a5ad9c01768be22f0b115c9501aa9f23a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 4 Dec 2016 12:44:08 +0100 Subject: [PATCH 08/37] Cleanup of implicit modifiers scheme Implicit modifiers were quite irregular compared to the other ones. This commit does a cleanup. --- compiler/src/dotty/tools/dotc/ast/untpd.scala | 2 +- .../src/dotty/tools/dotc/core/Comments.scala | 2 +- .../dotty/tools/dotc/parsing/Parsers.scala | 25 +++++++++---------- .../dotc/parsing/ModifiersParsingTest.scala | 6 ++--- 4 files changed, 17 insertions(+), 18 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index 25b69b1f5847..9707770d5d8c 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -115,7 +115,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) diff --git a/compiler/src/dotty/tools/dotc/core/Comments.scala b/compiler/src/dotty/tools/dotc/core/Comments.scala index 1e623db4d670..2559209c3b97 100644 --- a/compiler/src/dotty/tools/dotc/core/Comments.scala +++ b/compiler/src/dotty/tools/dotc/core/Comments.scala @@ -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("", code)).localDef(codePos.start, EmptyFlags) + val tree = new Parser(new SourceFile("", code)).localDef(codePos.start) tree match { case tree: untpd.DefDef => diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index dabd8d2b0ea9..5dd0df022cd6 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1067,7 +1067,7 @@ object Parsers { case FOR => forExpr() case IMPLICIT => - implicitClosure(in.skipToken(), location) + implicitClosure(in.offset, location, atPos(in.skipToken()) { Mod.Implicit() }) case _ => expr1Rest(postfixExpr(), location) } @@ -1118,9 +1118,8 @@ object Parsers { /** Expr ::= implicit Id `=>' Expr * BlockResult ::= implicit Id [`:' InfixType] `=>' Block */ - def implicitClosure(start: Int, location: Location.Value, implicitMod: Option[Mod] = None): Tree = { - var mods = atPos(start) { Modifiers(Implicit) } - if (implicitMod.nonEmpty) mods = mods.withAddedMod(implicitMod.get) + def implicitClosure(start: Int, location: Location.Value, implicitMod: Mod): Tree = { + val mods = Modifiers(Implicit).withAddedMod(implicitMod) val id = termIdent() val paramExpr = if (location == Location.InBlock && in.token == COLON) @@ -1489,7 +1488,7 @@ object Parsers { private def modOfToken(tok: Int): Mod = tok match { case ABSTRACT => Mod.Abstract() case FINAL => Mod.Final() - case IMPLICIT => Mod.Implicit(ImplicitCommon) + case IMPLICIT => Mod.Implicit() case INLINE => Mod.Inline() case LAZY => Mod.Lazy() case OVERRIDE => Mod.Override() @@ -1745,7 +1744,7 @@ object Parsers { else { if (in.token == IMPLICIT) { implicitOffset = in.offset - implicitMod = atPos(in.skipToken()) { Mod.Implicit(Implicit) } + implicitMod = atPos(in.skipToken()) { Mod.Implicit() } } commaSeparated(param) } @@ -2218,9 +2217,9 @@ object Parsers { stats.toList } - def localDef(start: Int, implicitFlag: FlagSet, implicitMod: Option[Mod] = None): Tree = { - var mods = addFlag(defAnnotsMods(localModifierTokens), implicitFlag) - if (implicitMod.nonEmpty) mods = mods.withAddedMod(implicitMod.get) + def localDef(start: Int, implicitMod: Option[Mod] = None): Tree = { + var mods = defAnnotsMods(localModifierTokens) + for (imod <- implicitMod) mods = (mods | ImplicitCommon).withAddedMod(imod) defOrDcl(start, mods) } @@ -2243,11 +2242,11 @@ object Parsers { else if (isDefIntro(localModifierTokens)) if (in.token == IMPLICIT) { val start = in.offset - val mod = atPos(in.skipToken()) { Mod.Implicit(ImplicitCommon) } - if (isIdent) stats += implicitClosure(start, Location.InBlock, Some(mod)) - else stats += localDef(start, ImplicitCommon, Some(mod)) + val mod = atPos(in.skipToken()) { Mod.Implicit() } + if (isIdent) stats += implicitClosure(start, Location.InBlock, mod) + else stats += localDef(start, Some(mod)) } else { - stats += localDef(in.offset, EmptyFlags) + stats += localDef(in.offset) } else if (!isStatSep && (in.token != CASE)) { exitOnError = mustStartStat diff --git a/compiler/test/dotty/tools/dotc/parsing/ModifiersParsingTest.scala b/compiler/test/dotty/tools/dotc/parsing/ModifiersParsingTest.scala index e31ef2160682..32f842e9252c 100644 --- a/compiler/test/dotty/tools/dotc/parsing/ModifiersParsingTest.scala +++ b/compiler/test/dotty/tools/dotc/parsing/ModifiersParsingTest.scala @@ -140,12 +140,12 @@ class ModifiersParsingTest { source = "def f(implicit a: Int, b: Int) = ???" println(source.defParam(0).modifiers) - assert(source.defParam(0).modifiers == List(Mod.Implicit(Flags.Implicit))) - assert(source.defParam(1).modifiers == List(Mod.Implicit(Flags.Implicit))) + assert(source.defParam(0).modifiers == List(Mod.Implicit())) + assert(source.defParam(1).modifiers == List(Mod.Implicit())) source = "def f(x: Int, y: Int)(implicit a: Int, b: Int) = ???" assert(source.defParam(0, 0).modifiers == List()) - assert(source.defParam(1, 0).modifiers == List(Mod.Implicit(Flags.Implicit))) + assert(source.defParam(1, 0).modifiers == List(Mod.Implicit())) } @Test def blockDef = { From ee59c23e9033ed775c64583c849bd47cc3f195af Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 4 Dec 2016 14:41:28 +0100 Subject: [PATCH 09/37] Generalize syntax for implicit function values - allow more than one implicit binding - harmonize syntax in expressions and blocks --- .../dotty/tools/dotc/parsing/Parsers.scala | 117 +++++++++++------- .../src/dotty/tools/dotc/parsing/Tokens.scala | 2 + docs/syntax-summary.txt | 12 +- 3 files changed, 81 insertions(+), 50 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 5dd0df022cd6..6fb10fd39a2e 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -146,6 +146,7 @@ object Parsers { def isNumericLit = numericLitTokens contains in.token def isModifier = modifierTokens contains in.token def isExprIntro = canStartExpressionTokens contains in.token + def isBindingIntro = canStartBindingTokens contains in.token def isTemplateIntro = templateIntroTokens contains in.token def isDclIntro = dclIntroTokens contains in.token def isStatSeqEnd = in.token == RBRACE || in.token == EOF @@ -951,14 +952,14 @@ object Parsers { } } - /** Expr ::= FunParams `=>' Expr + /** Expr ::= [`implicit'] FunParams `=>' Expr * | Expr1 * FunParams ::= Bindings - * | [`implicit'] Id + * | Id * | `_' * ExprInParens ::= PostfixExpr `:' Type * | Expr - * BlockResult ::= (FunParams | [`implicit'] Id `:' InfixType) => Block + * BlockResult ::= [`implicit'] FunParams `=>' Block * | Expr1 * Expr1 ::= `if' `(' Expr `)' {nl} Expr [[semi] else Expr] * | `if' Expr `then' Expr [[semi] else Expr] @@ -985,22 +986,27 @@ object Parsers { def expr(): Tree = expr(Location.ElseWhere) def expr(location: Location.Value): Tree = { - val saved = placeholderParams - placeholderParams = Nil - val t = expr1(location) - if (in.token == ARROW) { - placeholderParams = saved - closureRest(startOffset(t), location, convertToParams(t)) - } - else if (isWildcard(t)) { - placeholderParams = placeholderParams ::: saved - t + val start = in.offset + if (in.token == IMPLICIT) + implicitClosure(start, location, implicitMods()) + else { + val saved = placeholderParams + placeholderParams = Nil + val t = expr1(location) + if (in.token == ARROW) { + placeholderParams = saved + closureRest(start, location, convertToParams(t)) + } + else if (isWildcard(t)) { + placeholderParams = placeholderParams ::: saved + t + } + else + try + if (placeholderParams.isEmpty) t + else new WildcardFunction(placeholderParams.reverse, t) + finally placeholderParams = saved } - else - try - if (placeholderParams.isEmpty) t - else new WildcardFunction(placeholderParams.reverse, t) - finally placeholderParams = saved } def expr1(location: Location.Value = Location.ElseWhere): Tree = in.token match { @@ -1066,8 +1072,6 @@ object Parsers { atPos(in.skipToken()) { Return(if (isExprIntro) expr() else EmptyTree, EmptyTree) } case FOR => forExpr() - case IMPLICIT => - implicitClosure(in.offset, location, atPos(in.skipToken()) { Mod.Implicit() }) case _ => expr1Rest(postfixExpr(), location) } @@ -1115,19 +1119,43 @@ object Parsers { } } + /** FunParams ::= Bindings + * | Id + * | `_' + * Bindings ::= `(' [Binding {`,' Binding}] `)' + */ + def funParams(mods: Modifiers, location: Location.Value): List[Tree] = + if (in.token == LPAREN) + inParens(if (in.token == RPAREN) Nil else commaSeparated(() => binding(mods))) + else { + val start = in.offset + val name = bindingName() + val t = + if (in.token == COLON && location == Location.InBlock) { + in.nextToken() + infixType() + } + else TypeTree() + (atPos(start) { makeParameter(name, t, mods) }) :: Nil + } + + /** Binding ::= (Id | `_') [`:' Type] + */ + def binding(mods: Modifiers): Tree = + atPos(in.offset) { makeParameter(bindingName(), typedOpt(), mods) } + + def bindingName(): TermName = + if (in.token == USCORE) { + in.nextToken() + ctx.freshName(nme.USCORE_PARAM_PREFIX).toTermName + } + else ident() + /** Expr ::= implicit Id `=>' Expr - * BlockResult ::= implicit Id [`:' InfixType] `=>' Block - */ - def implicitClosure(start: Int, location: Location.Value, implicitMod: Mod): Tree = { - val mods = Modifiers(Implicit).withAddedMod(implicitMod) - val id = termIdent() - val paramExpr = - if (location == Location.InBlock && in.token == COLON) - atPos(startOffset(id), in.skipToken()) { Typed(id, infixType()) } - else - id - closureRest(start, location, convertToParam(paramExpr, mods) :: Nil) - } + * BlockResult ::= implicit Id [`:' InfixType] `=>' Block // Scala2 only + */ + def implicitClosure(start: Int, location: Location.Value, implicitMods: Modifiers): Tree = + closureRest(start, location, funParams(implicitMods, location)) def closureRest(start: Int, location: Location.Value, params: List[Tree]): Tree = atPos(start, in.offset) { @@ -1581,6 +1609,9 @@ object Parsers { normalize(loop(start)) } + def implicitMods(): Modifiers = + addMod(EmptyModifiers, atPos(accept(IMPLICIT)) { Mod.Implicit() }) + /** Wrap annotation or constructor in New(...). */ def wrapNew(tpt: Tree) = Select(New(tpt), nme.CONSTRUCTOR) @@ -1686,9 +1717,9 @@ object Parsers { * Param ::= id `:' ParamType [`=' Expr] */ def paramClauses(owner: Name, ofCaseClass: Boolean = false): List[List[ValDef]] = { - var implicitMod: Mod = null - var firstClauseOfCaseClass = ofCaseClass + var imods: Modifiers = EmptyModifiers var implicitOffset = -1 // use once + var firstClauseOfCaseClass = ofCaseClass def param(): ValDef = { val start = in.offset var mods = annotsAsMods() @@ -1723,7 +1754,7 @@ object Parsers { if (in.token == ARROW) { if (owner.isTypeName && !(mods is Local)) syntaxError(s"${if (mods is Mutable) "`var'" else "`val'"} parameters may not be call-by-name") - else if (implicitMod != null) + else if (imods.hasFlags) syntaxError("implicit parameters may not be call-by-name") } paramType() @@ -1735,7 +1766,7 @@ object Parsers { mods = mods.withPos(mods.pos.union(Position(implicitOffset, implicitOffset))) implicitOffset = -1 } - if (implicitMod != null) mods = addMod(mods, implicitMod) + for (imod <- imods.mods) mods = addMod(mods, imod) ValDef(name, tpt, default).withMods(mods) } } @@ -1744,7 +1775,7 @@ object Parsers { else { if (in.token == IMPLICIT) { implicitOffset = in.offset - implicitMod = atPos(in.skipToken()) { Mod.Implicit() } + imods = implicitMods() } commaSeparated(param) } @@ -1754,7 +1785,7 @@ object Parsers { if (in.token == LPAREN) paramClause() :: { firstClauseOfCaseClass = false - if (implicitMod == null) clauses() else Nil + if (imods.hasFlags) Nil else clauses() } else Nil } @@ -2217,9 +2248,9 @@ object Parsers { stats.toList } - def localDef(start: Int, implicitMod: Option[Mod] = None): Tree = { + def localDef(start: Int, implicitMods: Modifiers = EmptyModifiers): Tree = { var mods = defAnnotsMods(localModifierTokens) - for (imod <- implicitMod) mods = (mods | ImplicitCommon).withAddedMod(imod) + for (imod <- implicitMods.mods) mods = addMod(mods, imod) defOrDcl(start, mods) } @@ -2242,9 +2273,9 @@ object Parsers { else if (isDefIntro(localModifierTokens)) if (in.token == IMPLICIT) { val start = in.offset - val mod = atPos(in.skipToken()) { Mod.Implicit() } - if (isIdent) stats += implicitClosure(start, Location.InBlock, mod) - else stats += localDef(start, Some(mod)) + val imods = implicitMods() + if (isBindingIntro) stats += implicitClosure(start, Location.InBlock, imods) + else stats += localDef(start, imods) } else { stats += localDef(in.offset) } diff --git a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala index 5324207dbf86..280832ef3292 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala @@ -209,6 +209,8 @@ object Tokens extends TokensCommon { final val canStartTypeTokens = literalTokens | identifierTokens | BitSet( THIS, SUPER, USCORE, LPAREN, AT) + final val canStartBindingTokens = identifierTokens | BitSet(USCORE, LPAREN) + final val templateIntroTokens = BitSet(CLASS, TRAIT, OBJECT, CASECLASS, CASEOBJECT) final val dclIntroTokens = BitSet(DEF, VAL, VAR, TYPE) diff --git a/docs/syntax-summary.txt b/docs/syntax-summary.txt index fe0ebc89cc7f..d382507d3880 100644 --- a/docs/syntax-summary.txt +++ b/docs/syntax-summary.txt @@ -125,13 +125,13 @@ grammar. TypeBounds ::= [`>:' Type] [`<: Type] | INT TypeBoundsTree(lo, hi) TypeParamBounds ::= TypeBounds {`<%' Type} {`:' Type} ContextBounds(typeBounds, tps) - Expr ::= FunParams `=>' Expr Function(args, expr), Function(ValDef([implicit], id, TypeTree(), EmptyTree), expr) + Expr ::= [`implicit'] FunParams `=>' Expr Function(args, expr), Function(ValDef([implicit], id, TypeTree(), EmptyTree), expr) FunParams ::= Bindings - | [`implicit'] id + | id | `_' ExprInParens ::= PostfixExpr `:' Type | Expr - BlockResult ::= (FunParams | [`implicit'] id `:' InfixType) `=>' Block + BlockResult ::= [`implicit'] FunParams `=>' Block | Expr1 Expr1 ::= `if' `(' Expr `)' {nl} Expr [[semi] else Expr] If(Parens(cond), thenp, elsep?) | `if' Expr `then' Expr [[semi] else Expr] If(cond, thenp, elsep?) @@ -178,9 +178,7 @@ grammar. | {Annotation} {LocalModifier} TmplDef | Expr1 | - ResultExpr ::= Expr1 - | (Bindings | ([`implicit'] id | `_') `:' ) `=>' Block - Function(args, block) // block starts at => + ForExpr ::= `for' (`(' Enumerators `)' | `{' Enumerators `}') ForYield(enums, expr) {nl} [`yield'] Expr ForDo(enums, expr) | `for' Enumerators (`do' Expr | `yield' Expr) @@ -241,7 +239,7 @@ grammar. DefParams ::= DefParam {`,' DefParam} DefParam ::= {Annotation} [`inline'] Param ValDef(mods, id, tpe, expr) -- point of mods at id. - Bindings ::= `(' Binding {`,' Binding `)' bindings + Bindings ::= `(' Binding {`,' Binding}] `)' Binding ::= (id | `_') [`:' Type] ValDef(_, id, tpe, EmptyTree) Modifier ::= LocalModifier From aecfb37919291f3d191aa3c04f753cc2df4d962a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 4 Dec 2016 15:44:04 +0100 Subject: [PATCH 10/37] Add code to disable old implicit closure syntax in blocks This will no longer be supported. On the other hand, as long as the alternative is not yet legal in Scala2.x we cannot flag this as an error. So the migration warning/error and patch code is currently disabled. --- compiler/src/dotty/tools/dotc/parsing/Parsers.scala | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 6fb10fd39a2e..26656aae8618 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1132,8 +1132,18 @@ object Parsers { val name = bindingName() val t = if (in.token == COLON && location == Location.InBlock) { + if (false) // Don't error yet, as the alternative syntax "implicit (x: T) => ... " + // is not supported by Scala2.x + migrationWarningOrError(s"This syntax is no longer supported; parameter needs to be enclosed in (...)") + in.nextToken() - infixType() + val t = infixType() + + if (false && in.isScala2Mode) { + patch(source, Position(start), "(") + patch(source, Position(in.lastOffset), ")") + } + t } else TypeTree() (atPos(start) { makeParameter(name, t, mods) }) :: Nil From d5ff7e052f4c321e3089e0543617f81416e4aed4 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 4 Dec 2016 15:55:33 +0100 Subject: [PATCH 11/37] Fix erasure of implicit functions and check at runtime that it works --- .../dotty/tools/dotc/core/TypeErasure.scala | 2 +- .../dotty/tools/dotc/transform/Erasure.scala | 28 ++++++++------- tests/pending/run/implicitFuns.scala | 32 +++++++++++++++++ tests/pos/implicitFuns.scala | 36 ------------------- 4 files changed, 48 insertions(+), 50 deletions(-) create mode 100644 tests/pending/run/implicitFuns.scala delete mode 100644 tests/pos/implicitFuns.scala diff --git a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala index bc736b229cd2..6b682b028129 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala @@ -44,7 +44,7 @@ object TypeErasure { val sym = tp.symbol sym.isClass && sym != defn.AnyClass && sym != defn.ArrayClass && - !defn.isUnimplementedFunctionClass(sym) + !defn.isUnimplementedFunctionClass(sym) && !defn.isImplicitFunctionClass(sym) case _: TermRef => true case JavaArrayType(elem) => diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index 7595e5f2ebb3..71ecb5c65cc7 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -345,21 +345,23 @@ object Erasure extends TypeTestsCasts{ override def typedSelect(tree: untpd.Select, pt: Type)(implicit ctx: Context): Tree = { def mapOwner(sym: Symbol): Symbol = { - val owner = sym.owner - if ((owner eq defn.AnyClass) || (owner eq defn.AnyValClass)) { - assert(sym.isConstructor, s"${sym.showLocated}") - defn.ObjectClass - } - else if (defn.isUnimplementedFunctionClass(owner)) - defn.FunctionXXLClass - else - owner + def recur(owner: Symbol): Symbol = + if ((owner eq defn.AnyClass) || (owner eq defn.AnyValClass)) { + assert(sym.isConstructor, s"${sym.showLocated}") + defn.ObjectClass + } else if (defn.isUnimplementedFunctionClass(owner)) + defn.FunctionXXLClass + else if (defn.isImplicitFunctionClass(owner)) + recur(defn.FunctionClass(owner.name.functionArity)) + else + owner + recur(sym.owner) } - var sym = tree.symbol - val owner = mapOwner(sym) - if (owner ne sym.owner) sym = owner.info.decl(sym.name).symbol - assert(sym.exists, owner) + val origSym = tree.symbol + val owner = mapOwner(origSym) + val sym = if (owner eq origSym.owner) origSym else owner.info.decl(origSym.name).symbol + assert(sym.exists, origSym.showLocated) def select(qual: Tree, sym: Symbol): Tree = { val name = tree.typeOpt match { diff --git a/tests/pending/run/implicitFuns.scala b/tests/pending/run/implicitFuns.scala new file mode 100644 index 000000000000..1b7ca694ac3d --- /dev/null +++ b/tests/pending/run/implicitFuns.scala @@ -0,0 +1,32 @@ +object Test { + def main(args: Array[String]) = { + + implicit val world: String = "world!" + + val i1 = (implicit (s: String) => s.length > 2) + val i2 = {implicit (s: String) => s.length > 2} + + assert(i1) + assert(i2) + + val x: implicit String => Boolean = { implicit (s: String) => s.length > 2 } + + val xx: implicit (String, Int) => Int = implicit (x: String, y: Int) => x.length + y + + val y: String => Boolean = x + + val yy: (String, Int) => Any = xx + + val b = x("hello") + + val b1: Boolean = b + + val bi = x + + val bi1: Boolean = bi + + val c = xx("hh", 22) + + val c1: Int = c + } +} diff --git a/tests/pos/implicitFuns.scala b/tests/pos/implicitFuns.scala deleted file mode 100644 index edb2434c959c..000000000000 --- a/tests/pos/implicitFuns.scala +++ /dev/null @@ -1,36 +0,0 @@ -object Test { - - val x: ImplicitFunction1[String, Boolean] = ??? - - val y: String => Boolean = x - - val b = x("hello") - - val b1: Boolean = b - -} -object Test2 { - - val x: implicit String => Boolean = ??? - - val xx: implicit (String, Int) => Int = ??? - - val y: String => Boolean = x - - val yy: (String, Int) => Any = xx - - implicit val world: String = "world!" - - val b = x("hello") - - val b1: Boolean = b - - val bi = x - - val bi1: Boolean = bi - - val c = xx("hh", 22) - - val c1: Int = c - -} From 0336785a2280a4a1e51e739e9aac3d5015f7c16f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 5 Dec 2016 09:52:28 +0100 Subject: [PATCH 12/37] Take nesting into account when ranking implicits This will need a spec change. It's necessary in order not to confuse synthetic implicits with each other or with explicit ones in the environment. --- .../dotty/tools/dotc/typer/Applications.scala | 20 ++++-- .../dotty/tools/dotc/typer/Implicits.scala | 64 ++++++++++++------- .../dotty/tools/dotc/typer/ImportInfo.scala | 2 +- .../src/dotty/tools/dotc/typer/Typer.scala | 4 +- 4 files changed, 59 insertions(+), 31 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 469d657a9817..1cb656ca3a9f 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -975,9 +975,21 @@ trait Applications extends Compatibility { self: Typer with Dynamic => } /** In a set of overloaded applicable alternatives, is `alt1` at least as good as - * `alt2`? `alt1` and `alt2` are non-overloaded references. + * `alt2`? Also used for implicits disambiguation. + * + * @param alt1, alt2 Non-overloaded references indicating the two choices + * @param level1, level2 If alternatives come from a comparison of two contextual + * implicit candidates, the nesting levels of the candidates. + * In all other cases the nesting levels are both 0. + * + * An alternative A1 is "as good as" an alternative A2 if it wins or draws in a tournament + * that awards one point for each of the following + * + * - A1 is nested more deeply than A2 + * - The nesting levels of A1 and A2 are the same, and A1's owner derives from A2's owner + * - A1's type is more specific than A2's type. */ - def isAsGood(alt1: TermRef, alt2: TermRef)(implicit ctx: Context): Boolean = track("isAsGood") { ctx.traceIndented(i"isAsGood($alt1, $alt2)", overload) { + def isAsGood(alt1: TermRef, alt2: TermRef, nesting1: Int = 0, nesting2: Int = 0)(implicit ctx: Context): Boolean = track("isAsGood") { ctx.traceIndented(i"isAsGood($alt1, $alt2)", overload) { assert(alt1 ne alt2) @@ -1092,9 +1104,9 @@ trait Applications extends Compatibility { self: Typer with Dynamic => val tp1 = stripImplicit(alt1.widen) val tp2 = stripImplicit(alt2.widen) - def winsOwner1 = isDerived(owner1, owner2) + def winsOwner1 = nesting1 > nesting2 || isDerived(owner1, owner2) def winsType1 = isAsSpecific(alt1, tp1, alt2, tp2) - def winsOwner2 = isDerived(owner2, owner1) + def winsOwner2 = nesting2 > nesting1 || isDerived(owner2, owner1) def winsType2 = isAsSpecific(alt2, tp2, alt1, tp1) overload.println(i"isAsGood($alt1, $alt2)? $tp1 $tp2 $winsOwner1 $winsType1 $winsOwner2 $winsType2") diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 1a9a8f64ce89..79881dc5b66e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -35,6 +35,9 @@ import collection.mutable /** Implicit resolution */ object Implicits { + /** An eligible implicit candidate, consisting of an implicit reference and a nesting level */ + case class Candidate(ref: TermRef, level: Int) + /** A common base class of contextual implicits and of-type implicits which * represents a set of implicit references. */ @@ -42,11 +45,14 @@ object Implicits { implicit val ctx: Context = if (initctx == NoContext) initctx else initctx retractMode Mode.ImplicitsEnabled + /** The nesting level of this context. Non-zero only in ContextialImplicits */ + def level: Int = 0 + /** The implicit references */ def refs: List[TermRef] /** Return those references in `refs` that are compatible with type `pt`. */ - protected def filterMatching(pt: Type)(implicit ctx: Context): List[TermRef] = track("filterMatching") { + protected def filterMatching(pt: Type)(implicit ctx: Context): List[Candidate] = track("filterMatching") { def refMatches(ref: TermRef)(implicit ctx: Context) = /*ctx.traceIndented(i"refMatches $ref $pt")*/ { @@ -97,8 +103,9 @@ object Implicits { } } - if (refs.isEmpty) refs - else refs filter (refMatches(_)(ctx.fresh.addMode(Mode.TypevarsMissContext).setExploreTyperState)) // create a defensive copy of ctx to avoid constraint pollution + if (refs.isEmpty) Nil + else refs.filter(refMatches(_)(ctx.fresh.addMode(Mode.TypevarsMissContext).setExploreTyperState)) // create a defensive copy of ctx to avoid constraint pollution + .map(Candidate(_, level)) } } @@ -114,8 +121,8 @@ object Implicits { buf.toList } - /** The implicit references that are eligible for expected type `tp` */ - lazy val eligible: List[TermRef] = + /** The candidates that are eligible for expected type `tp` */ + lazy val eligible: List[Candidate] = /*>|>*/ track("eligible in tpe") /*<|<*/ { /*>|>*/ ctx.traceIndented(i"eligible($tp), companions = ${companionRefs.toList}%, %", implicitsDetailed, show = true) /*<|<*/ { if (refs.nonEmpty && monitored) record(s"check eligible refs in tpe", refs.length) @@ -135,10 +142,15 @@ object Implicits { * @param outerCtx the next outer context that makes visible further implicits */ class ContextualImplicits(val refs: List[TermRef], val outerImplicits: ContextualImplicits)(initctx: Context) extends ImplicitRefs(initctx) { - private val eligibleCache = new mutable.AnyRefMap[Type, List[TermRef]] + private val eligibleCache = new mutable.AnyRefMap[Type, List[Candidate]] + + override val level: Int = + if (outerImplicits == null) 1 + else if (ctx.scope eq outerImplicits.ctx.scope) outerImplicits.level + else outerImplicits.level + 1 /** The implicit references that are eligible for type `tp`. */ - def eligible(tp: Type): List[TermRef] = /*>|>*/ track(s"eligible in ctx") /*<|<*/ { + def eligible(tp: Type): List[Candidate] = /*>|>*/ track(s"eligible in ctx") /*<|<*/ { if (tp.hash == NotCached) computeEligible(tp) else eligibleCache get tp match { case Some(eligibles) => @@ -162,13 +174,13 @@ object Implicits { } } - private def computeEligible(tp: Type): List[TermRef] = /*>|>*/ ctx.traceIndented(i"computeEligible $tp in $refs%, %", implicitsDetailed) /*<|<*/ { + private def computeEligible(tp: Type): List[Candidate] = /*>|>*/ ctx.traceIndented(i"computeEligible $tp in $refs%, %", implicitsDetailed) /*<|<*/ { if (monitored) record(s"check eligible refs in ctx", refs.length) val ownEligible = filterMatching(tp) if (outerImplicits == NoContext.implicits) ownEligible else ownEligible ::: { - val shadowed = (ownEligible map (_.name)).toSet - outerImplicits.eligible(tp) filterNot (ref => shadowed contains ref.name) + val shadowed = ownEligible.map(_.ref.name).toSet + outerImplicits.eligible(tp).filterNot(cand => shadowed.contains(cand.ref.name)) } } @@ -198,7 +210,7 @@ object Implicits { * @param tree The typed tree that needs to be inserted * @param ctx The context after the implicit search */ - case class SearchSuccess(tree: tpd.Tree, ref: TermRef, tstate: TyperState) extends SearchResult { + case class SearchSuccess(tree: tpd.Tree, ref: TermRef, level: Int, tstate: TyperState) extends SearchResult { override def toString = s"SearchSuccess($tree, $ref)" } @@ -478,7 +490,7 @@ trait Implicits { self: Typer => */ def inferImplicitArg(formal: Type, error: (String => String) => Unit, pos: Position)(implicit ctx: Context): Tree = inferImplicit(formal, EmptyTree, pos) match { - case SearchSuccess(arg, _, _) => + case SearchSuccess(arg, _, _, _) => arg case ambi: AmbiguousImplicits => error(where => s"ambiguous implicits: ${ambi.explanation} of $where") @@ -621,12 +633,13 @@ trait Implicits { self: Typer => protected def failedSearch: SearchFailure = NoImplicitMatches /** Search a list of eligible implicit references */ - def searchImplicits(eligible: List[TermRef], contextual: Boolean): SearchResult = { + def searchImplicits(eligible: List[Candidate], contextual: Boolean): SearchResult = { val constr = ctx.typerState.constraint /** Try to typecheck an implicit reference */ - def typedImplicit(ref: TermRef)(implicit ctx: Context): SearchResult = track("typedImplicit") { ctx.traceIndented(i"typed implicit $ref, pt = $pt, implicitsEnabled == ${ctx.mode is ImplicitsEnabled}", implicits, show = true) { + def typedImplicit(cand: Candidate)(implicit ctx: Context): SearchResult = track("typedImplicit") { ctx.traceIndented(i"typed implicit ${cand.ref}, pt = $pt, implicitsEnabled == ${ctx.mode is ImplicitsEnabled}", implicits, show = true) { assert(constr eq ctx.typerState.constraint) + val ref = cand.ref var generated: Tree = tpd.ref(ref).withPos(pos) if (!argument.isEmpty) generated = typedUnadapted( @@ -667,7 +680,7 @@ trait Implicits { self: Typer => if fn.symbol == defn.Predef_eqAny && !validEqAnyArgs(arg1.tpe, arg2.tpe) => nonMatchingImplicit(ref, Nil) case _ => - SearchSuccess(generated1, ref, ctx.typerState) + SearchSuccess(generated1, ref, cand.level, ctx.typerState) } }} @@ -676,19 +689,20 @@ trait Implicits { self: Typer => * @param pending The list of implicit references that remain to be investigated * @param acc An accumulator of successful matches found so far. */ - def rankImplicits(pending: List[TermRef], acc: List[SearchSuccess]): List[SearchSuccess] = pending match { - case ref :: pending1 => + def rankImplicits(pending: List[Candidate], acc: List[SearchSuccess]): List[SearchSuccess] = pending match { + case cand :: pending1 => val history = ctx.searchHistory nest wildProto val result = - if (history eq ctx.searchHistory) divergingImplicit(ref) - else typedImplicit(ref)(nestedContext.setNewTyperState.setSearchHistory(history)) + if (history eq ctx.searchHistory) divergingImplicit(cand.ref) + else typedImplicit(cand)(nestedContext.setNewTyperState.setSearchHistory(history)) result match { case fail: SearchFailure => rankImplicits(pending1, acc) case best: SearchSuccess => if (ctx.mode.is(Mode.ImplicitExploration)) best :: Nil else { - val newPending = pending1 filter (isAsGood(_, best.ref)(nestedContext.setExploreTyperState)) + val newPending = pending1.filter(cand1 => + isAsGood(cand1.ref, best.ref, cand1.level, best.level)(nestedContext.setExploreTyperState)) rankImplicits(newPending, best :: acc) } } @@ -717,7 +731,7 @@ trait Implicits { self: Typer => /** Convert a (possibly empty) list of search successes into a single search result */ def condense(hits: List[SearchSuccess]): SearchResult = hits match { case best :: alts => - alts find (alt => isAsGood(alt.ref, best.ref)(ctx.fresh.setExploreTyperState)) match { + alts find (alt => isAsGood(alt.ref, best.ref, alt.level, best.level)(ctx.fresh.setExploreTyperState)) match { case Some(alt) => /* !!! DEBUG println(i"ambiguous refs: ${hits map (_.ref) map (_.show) mkString ", "}") @@ -735,16 +749,18 @@ trait Implicits { self: Typer => failedSearch } + def ranking(cand: Candidate) = -ctx.runInfo.useCount(cand.ref) + /** Sort list of implicit references according to their popularity * (# of times each was picked in current run). */ - def sort(eligible: List[TermRef]) = eligible match { + def sort(eligible: List[Candidate]) = eligible match { case Nil => eligible case e1 :: Nil => eligible case e1 :: e2 :: Nil => - if (ctx.runInfo.useCount(e1) < ctx.runInfo.useCount(e2)) e2 :: e1 :: Nil + if (ranking(e2) < ranking(e1)) e2 :: e1 :: Nil else eligible - case _ => eligible.sortBy(-ctx.runInfo.useCount(_)) + case _ => eligible.sortBy(ranking) } condense(rankImplicits(sort(eligible), Nil)) diff --git a/compiler/src/dotty/tools/dotc/typer/ImportInfo.scala b/compiler/src/dotty/tools/dotc/typer/ImportInfo.scala index b4ec3390e94c..e44343e70f10 100644 --- a/compiler/src/dotty/tools/dotc/typer/ImportInfo.scala +++ b/compiler/src/dotty/tools/dotc/typer/ImportInfo.scala @@ -98,7 +98,7 @@ class ImportInfo(symf: => Symbol, val selectors: List[untpd.Tree], val isRootImp * * TODO: Once we have fully bootstrapped, I would prefer if we expressed * unimport with an `override` modifier, and generalized it to all imports. - * I believe this would be more transparent than the curren set of conditions. E.g. + * I believe this would be more transparent than the current set of conditions. E.g. * * override import Predef.{any2stringAdd => _, StringAdd => _, _} // disables String + * override import java.lang.{} // disables all imports diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index e016ba3a6508..1967275e9245 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -519,7 +519,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit case tref: TypeRef if !tref.symbol.isClass && !ctx.isAfterTyper => inferImplicit(defn.ClassTagType.appliedTo(tref), EmptyTree, tpt1.pos)(ctx.retractMode(Mode.Pattern)) match { - case SearchSuccess(arg, _, _) => + case SearchSuccess(arg, _, _, _) => return typed(untpd.Apply(untpd.TypedSplice(arg), tree.expr), pt) case _ => } @@ -1960,7 +1960,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit } // try an implicit conversion inferView(tree, pt) match { - case SearchSuccess(inferred, _, _) => + case SearchSuccess(inferred, _, _, _) => adapt(inferred, pt) case failure: SearchFailure => if (pt.isInstanceOf[ProtoType] && !failure.isInstanceOf[AmbiguousImplicits]) tree From bcc80ad1343a3ed01bef55f494d9658cf02226c6 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 5 Dec 2016 10:34:58 +0100 Subject: [PATCH 13/37] Create implicit closures to math expected implicit functions When the expected type is an implicit function, create an implicit closure to match it. --- .../backend/jvm/DottyBackendInterface.scala | 6 ++-- .../src/dotty/tools/dotc/ast/Desugar.scala | 23 +++++++++---- .../src/dotty/tools/dotc/ast/TreeInfo.scala | 12 +++++-- compiler/src/dotty/tools/dotc/ast/untpd.scala | 6 ++-- .../dotty/tools/dotc/core/Definitions.scala | 3 ++ .../dotty/tools/dotc/typer/Implicits.scala | 8 +++-- .../src/dotty/tools/dotc/typer/Typer.scala | 33 +++++++++++++------ tests/{pending => }/run/implicitFuns.scala | 23 +++++++++++++ 8 files changed, 87 insertions(+), 27 deletions(-) rename tests/{pending => }/run/implicitFuns.scala (52%) diff --git a/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala b/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala index ed32d2df95f8..3b1768ef5bc2 100644 --- a/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala +++ b/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala @@ -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 @@ -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 diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 11f8b81eb26c..def2caabf41e 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -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) @@ -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)) } val meth1 = addEvidenceParams(cpy.DefDef(meth)(tparams = tparams1), epbuf.toList) @@ -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 */ diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index ae7c93784d0c..da83d0644dc2 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -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 } @@ -507,8 +517,6 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => } } - def isClosure(tree: Tree) = closure.unapply(tree).isDefined - /** If tree is a closure, its body, otherwise tree itself */ def closureBody(tree: Tree)(implicit ctx: Context): Tree = tree match { case Block((meth @ DefDef(nme.ANON_FUN, _, _, _, _)) :: Nil, Closure(_, _, _)) => meth.rhs diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index 9707770d5d8c..77755da81152 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -55,7 +55,9 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { } /** An implicit function type */ - class ImplicitFunction(args: List[Tree], body: Tree) extends Function(args, body) + 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 @@ -274,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).[Ts](args1)...(args_n) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index ff259f1844b8..814bbf48f96a 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -790,6 +790,9 @@ class Definitions { 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 */ diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 79881dc5b66e..79cab8b740a7 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -29,7 +29,7 @@ import Inferencing.fullyDefinedType import Trees._ import Hashable._ import config.Config -import config.Printers.{implicits, implicitsDetailed} +import config.Printers.{implicits, implicitsDetailed, typr} import collection.mutable /** Implicit resolution */ @@ -146,7 +146,8 @@ object Implicits { override val level: Int = if (outerImplicits == null) 1 - else if (ctx.scope eq outerImplicits.ctx.scope) outerImplicits.level + else if ((ctx.owner eq outerImplicits.ctx.owner) && + (ctx.scope eq outerImplicits.ctx.scope)) outerImplicits.level else outerImplicits.level + 1 /** The implicit references that are eligible for type `tp`. */ @@ -211,7 +212,7 @@ object Implicits { * @param ctx The context after the implicit search */ case class SearchSuccess(tree: tpd.Tree, ref: TermRef, level: Int, tstate: TyperState) extends SearchResult { - override def toString = s"SearchSuccess($tree, $ref)" + override def toString = s"SearchSuccess($tree, $ref, $level)" } /** A failed search */ @@ -733,6 +734,7 @@ trait Implicits { self: Typer => case best :: alts => alts find (alt => isAsGood(alt.ref, best.ref, alt.level, best.level)(ctx.fresh.setExploreTyperState)) match { case Some(alt) => + typr.println(i"ambiguous implicits for $pt: ${best.ref} @ ${best.level}, ${alt.ref} @ ${alt.level}") /* !!! DEBUG println(i"ambiguous refs: ${hits map (_.ref) map (_.show) mkString ", "}") isAsGood(best.ref, alt.ref, explain = true)(ctx.fresh.withExploreTyperState) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 1967275e9245..616c960194b8 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1494,6 +1494,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit case tree: untpd.If => typedIf(tree, pt) case tree: untpd.Function => typedFunction(tree, pt) case tree: untpd.Closure => typedClosure(tree, pt) + case tree: untpd.Import => typedImport(tree, retrieveSym(tree)) case tree: untpd.Match => typedMatch(tree, pt) case tree: untpd.Return => typedReturn(tree) case tree: untpd.Try => typedTry(tree, pt) @@ -1521,9 +1522,13 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit case _ => typedUnadapted(desugar(tree), pt) } - xtree match { + if (defn.isImplicitFunctionType(pt) && + xtree.isTerm && + !untpd.isImplicitClosure(xtree) && + !ctx.isAfterTyper) + makeImplicitFunction(xtree, pt) + else xtree match { case xtree: untpd.NameTree => typedNamed(encodeName(xtree), pt) - case xtree: untpd.Import => typedImport(xtree, retrieveSym(xtree)) case xtree => typedUnnamed(xtree) } } @@ -1532,6 +1537,14 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit protected def encodeName(tree: untpd.NameTree)(implicit ctx: Context): untpd.NameTree = untpd.rename(tree, tree.name.encode) + protected def makeImplicitFunction(tree: untpd.Tree, pt: Type)(implicit ctx: Context): Tree = { + val defn.FunctionOf(formals, resType, true) = pt.dealias + val paramTypes = formals.map(fullyDefinedType(_, "implicit function parameter", tree.pos)) + val ifun = desugar.makeImplicitFunction(paramTypes, tree) + typr.println(i"make implicit function $tree / $pt ---> $ifun") + typedUnadapted(ifun) + } + def typed(tree: untpd.Tree, pt: Type = WildcardType)(implicit ctx: Context): Tree = /*>|>*/ ctx.traceIndented (i"typing $tree", typr, show = true) /*<|<*/ { assertPositioned(tree) try adapt(typedUnadapted(tree, pt), pt, tree) @@ -1878,16 +1891,16 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit err.typeMismatch(tree, pt) else missingArgs - case wtp: RefinedType - if defn.isImplicitFunctionClass(wtp.underlyingClassRef(refinementOK = false).classSymbol) && - !isClosure(tree) && - !isApplyProto(pt) && - !ctx.isAfterTyper => - typr.println("insert apply on implicit $tree") - typed(untpd.Select(untpd.TypedSplice(tree), nme.apply), pt) case _ => ctx.typeComparer.GADTused = false - if (ctx.mode is Mode.Pattern) { + if (defn.isImplicitFunctionClass(wtp.underlyingClassRef(refinementOK = false).classSymbol) && + !untpd.isImplicitClosure(tree) && + !isApplyProto(pt) && + !ctx.isAfterTyper) { + typr.println("insert apply on implicit $tree") + typed(untpd.Select(untpd.TypedSplice(tree), nme.apply), pt) + } + else if (ctx.mode is Mode.Pattern) { tree match { case _: RefTree | _: Literal if !isVarPattern(tree) && diff --git a/tests/pending/run/implicitFuns.scala b/tests/run/implicitFuns.scala similarity index 52% rename from tests/pending/run/implicitFuns.scala rename to tests/run/implicitFuns.scala index 1b7ca694ac3d..79f0632d216f 100644 --- a/tests/pending/run/implicitFuns.scala +++ b/tests/run/implicitFuns.scala @@ -15,8 +15,31 @@ object Test { val y: String => Boolean = x + object nested { + implicit val empty: String = "" + assert(!x) + } + val yy: (String, Int) => Any = xx + val z1: implicit String => Boolean = implicitly[String].length >= 2 + assert(z1) + + type StringlyBool = implicit String => Boolean + + val z2: StringlyBool = implicitly[String].length >= 2 + assert(z2) + + type Stringly[T] = implicit String => T + + val z3: Stringly[Boolean] = implicitly[String].length >= 2 + assert(z3) + + type GenericImplicit[X] = implicit X => Boolean + + val z4: GenericImplicit[String] = implicitly[String].length >= 2 + assert(z4) + val b = x("hello") val b1: Boolean = b From b804d9167a79fc6330ad4a193685da95c6ef9bca Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 5 Dec 2016 11:14:31 +0100 Subject: [PATCH 14/37] Don't look at nesting for implicit resolution under Scala2 mode. --- compiler/src/dotty/tools/dotc/typer/Implicits.scala | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 79cab8b740a7..3315332041e8 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -144,9 +144,14 @@ object Implicits { class ContextualImplicits(val refs: List[TermRef], val outerImplicits: ContextualImplicits)(initctx: Context) extends ImplicitRefs(initctx) { private val eligibleCache = new mutable.AnyRefMap[Type, List[Candidate]] + /** The level increases if current context has a different owner or scope than + * the context of the next-outer ImplicitRefs. This is however disabled under + * Scala2 mode, since we do not want to change the implicit disambiguation then. + */ override val level: Int = if (outerImplicits == null) 1 - else if ((ctx.owner eq outerImplicits.ctx.owner) && + else if (ctx.scala2Mode || + (ctx.owner eq outerImplicits.ctx.owner) && (ctx.scope eq outerImplicits.ctx.scope)) outerImplicits.level else outerImplicits.level + 1 From 43d69ccf3e496c7c02c5dcc40efd2a5e83f6f79c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 5 Dec 2016 15:16:27 +0100 Subject: [PATCH 15/37] Enrich test case Run a typical dotty compiler scenario with implicit contexts. --- tests/run/implicitFuns.scala | 41 ++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/tests/run/implicitFuns.scala b/tests/run/implicitFuns.scala index 79f0632d216f..ded1b0c29a41 100644 --- a/tests/run/implicitFuns.scala +++ b/tests/run/implicitFuns.scala @@ -51,5 +51,46 @@ object Test { val c = xx("hh", 22) val c1: Int = c + + Contextual.main(args) + } +} + +object Contextual { + + class Key[+V] + + class Context(bindings: Map[Key[Any], Any]) { + def binding[V](key: Key[V]): Option[V] = + bindings.get(key).asInstanceOf[Option[V]] + def withBinding[V](key: Key[V], value: V): Context = + new Context(bindings + ((key, value))) + } + + val rootContext = new Context(Map()) + + val Source = new Key[String] + val Options = new Key[List[String]] + + type Ctx[T] = implicit Context => T + + def ctx: Ctx[Context] = implicitly[Context] + + def compile(s: String): Ctx[Boolean] = + runOn(new java.io.File(s))(ctx.withBinding(Source, s)) >= 0 + + def runOn(f: java.io.File): Ctx[Int] = { + val options = List("-verbose", "-explaintypes") + process(f)(ctx.withBinding(Options, options)) + } + + def process(f: java.io.File): Ctx[Int] = + ctx.binding(Source).get.length - ctx.binding(Options).get.length + + def main(args: Array[String]) = { + implicit val context: Context = rootContext + assert(compile("abc")) + assert(compile("ab")) + assert(!compile("a")) } } From 4c55d2fc491f7bab2a23a0dc5a53d3c57ad8d2d4 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 5 Dec 2016 19:07:02 +0100 Subject: [PATCH 16/37] More tests and starting a blog post --- .../2016-12-05-implicit-function-types.md | 170 ++++++++++++++++++ tests/run/implicitFuns.scala | 119 +++++++++++- 2 files changed, 288 insertions(+), 1 deletion(-) create mode 100644 docs/blog/_posts/2016-12-05-implicit-function-types.md diff --git a/docs/blog/_posts/2016-12-05-implicit-function-types.md b/docs/blog/_posts/2016-12-05-implicit-function-types.md new file mode 100644 index 000000000000..3172e46a497b --- /dev/null +++ b/docs/blog/_posts/2016-12-05-implicit-function-types.md @@ -0,0 +1,170 @@ +--- +layout: blog +title: Implicit Function Types +author: Martin Odersky +authorImg: /images/martin.jpg +--- + +I just made the first pull request to add _implicit function types_ to +Scala. I am pretty excited about it, because, citing the explanation +of the pull request "This is the first step to bring comonadic +abstraction to Scala". That's quite a mouthful, so I better explain what I +mean by it. + +Let me try to explain the words in this sentence from right to left. + +*Scala*: I assume everyone who reads this understands that we mean the + programming language, not the opera house. + +*Abstraction*: The ability to name a concept and use just the name afterwards. + +*Comonadic*: In category theory, a _comonad_ is the dual of a +_monad_. Roughly speaking, a monad is a way to wrap the result (or: +outputs) of a computation in some other type. For instance +`Future[T]` means that the result of type `T` will be produced at +some later time on demand, or `Option[T]` indicates that the result +might also be undefined. + +Dually, a _comonad_ allows to transform or +enrich or otherwise manipulate the _inputs_ to a computation. +The inputs are typically what a computation can access in its +environment. Interesting tasks that are by nature comonadic are + + - passing configuration data to the parts of a system that need them, + - managing capabilities for security critical tasks, + - wiring components up with dependency injection, + - defining the meanings of operations with type classes, + - more generally, passing any sort of context to a computation. + +Implicit function types are a suprisingly simple and general way to +make coding patterns solving these tasks abstractable, reducing +boilerplate code and increasing applicability. + +*First Step* My pull request is first implementation. In solves the + problem in principle, but it introduces some run-time overhead. The + next step will be to eliminate the run-time overhead through some + simple optimizations. + + +## Comparison with Monads + +One can use monads for these tasks, and some people do. For instance +the `Reader` monad is used to abstract over accessing one entry in the +environment. But the code for doing so quickly becomes complex and +inefficient, in particular when combining several contextual +accesses. Monads don't compose in general, and therefore even simple +combinations need to be expressed on the level of monad transformers, +at the price of much boilerplate and complexity. Recognizing this, +peaple have recently experimented with free monads, which alleviate +the composibility problem, but at the price of introducing a whole new +level of interpretation. + +## Implicit Parameters + +In a functional setting, the inputs to a computation are most +naturally expressed as _parameters_. One could simply augment +functions to take additional parameters that represent configurations, +capabilities, dictionaries, or whatever contextual data the functions +need. The only downside with this is that often there's a large +distance in the call graph between the definition of a contextual +element and the site where it is used. Conseuqently, it becomes +tedious to define all those intermediate parameters and to pass them +along to where they are eventually consumed. + +Implicit parameters solve one half of the problem. Implicit +parameters do not have to be propagated using boilerplate code; the +compiler takes care of that. This makes them practical in many +scenarios where plain parameters would be too cumbersome. For +instance, type classes would be a lot less popular if one would have +to pass all dictionaries by hand. Implicit parameters are also very +useful as a general context passing mechanism. For instance in the +_dotty_ compiler, almost every function takes an implicit context +parameter which defines all elements relating to the current state of +the compilation. This is in my experience much better than the cake +pattern because it is lightweight and can express context changes in a +purely functional way. + +The main downside of implicit parameters is the verbosity of their +declaration syntax. It's hard to illustrate this with a smallish example, +because it really only becomes a problem at scale, but let's try anyway. + +Let's say we want to write some piece of code that's designed to run +in a transaction. For the sake of illustration here's a simple transaction class: + + class Transaction { + private val log = new ListBuffer[String] + def println(s: String): Unit = log += s + + private var aborted = false + private var committed = false + + def abort(): Unit = { aborted = true } + def isAborted = aborted + + def commit(): Unit = + if (!aborted && !committed) { + Console.println("******* log ********") + log.foreach(Console.println) + committed = true + } + } + +The transaction encapsulates a log, to which one can print messages. +It can be in one of three states: running, committed, or aborted. +If the transaction is committed, it prints the stored log to the console. + +The `transaction` method lets one run some given code `op` inside +a newly created transaction: + + def transaction[T](op: Transaction => T) = { + val trans: Transaction = new Transaction + op(trans) + trans.commit() + } + +The current transaction needs to be passed along a calling chain to all +the places that need to access it. To illustrate this, here are three +functions `f1`, `f2` and `f3` which call each other, and also access +the current transaction. The most convenient way to achieve this is +passing the current transaction as an implicit parameter. + + def f1(x: Int)(implicit thisTransaction: Transaction): Int = { + thisTransaction.println(s"first step: $x") + f2(x + 1) + } + def f2(x: Int)(implicit thisTransaction: Transaction): Int = { + thisTransaction.println(s"second step: $x") + f3(x * x) + } + def f3(x: Int)(implicit thisTransaction: Transaction): Int = { + thisTransaction.println(s"third step: $x") + if (x % 2 != 0) thisTransaction.abort() + x + } + +The main program calls `f1` in a fresh transaction context and prints +its result: + + def main(args: Array[String]) = { + transaction { + implicit thisTransaction => + val res = f1(args.length) + println(if (thisTransaction.isAborted) "aborted" else s"result: $res") + } + } + +Two sample calls of the program (let's call it `TransactionDemo`) are here: + + scala TransactionDemo 1 2 3 + result: 16 + ******* log ******** + first step: 3 + second step: 4 + third step: 16 + + scala TransactionDemo 1 2 3 4 + aborted + + + + diff --git a/tests/run/implicitFuns.scala b/tests/run/implicitFuns.scala index ded1b0c29a41..ee1d37256e33 100644 --- a/tests/run/implicitFuns.scala +++ b/tests/run/implicitFuns.scala @@ -81,7 +81,7 @@ object Contextual { def runOn(f: java.io.File): Ctx[Int] = { val options = List("-verbose", "-explaintypes") - process(f)(ctx.withBinding(Options, options)) + process(f).apply(ctx.withBinding(Options, options)) } def process(f: java.io.File): Ctx[Int] = @@ -94,3 +94,120 @@ object Contextual { assert(!compile("a")) } } + +import collection.mutable.ListBuffer + +class Transaction { + private val log = new ListBuffer[String] + def println(s: String): Unit = log += s + + private var aborted = false + private var committed = false + + def abort(): Unit = { aborted = true } + def isAborted = aborted + + def commit(): Unit = + if (!aborted && !committed) { + Console.println("******* log ********") + log.foreach(Console.println) + committed = true + } +} + +object TransactionalExplicit { + + def transaction[T](op: Transaction => T) = { + val trans: Transaction = new Transaction + op(trans) + trans.commit() + } + + def f1(x: Int)(implicit thisTransaction: Transaction): Int = { + thisTransaction.println(s"first step: $x") + f2(x + 1) + } + def f2(x: Int)(implicit thisTransaction: Transaction): Int = { + thisTransaction.println(s"second step: $x") + f3(x * x) + } + def f3(x: Int)(implicit thisTransaction: Transaction): Int = { + thisTransaction.println(s"third step: $x") + if (x % 2 != 0) thisTransaction.abort() + x + } + + def main(args: Array[String]) = { + transaction { + implicit thisTransaction => + val res = f1(args.length) + println(if (thisTransaction.isAborted) "aborted" else s"result: $res") + } + } +} + +object Transactional { + type Transactional[T] = implicit Transaction => T + + def transaction[T](op: Transactional[T]) = { + implicit val trans: Transaction = new Transaction + op + trans.commit() + } + + def thisTransaction: Transactional[Transaction] = implicitly[Transaction] + + def f1(x: Int): Transactional[Int] = { + thisTransaction.println(s"first step: $x") + f2(x + 1) + } + def f2(x: Int): Transactional[Int] = { + thisTransaction.println(s"second step: $x") + f3(x * x) + } + def f3(x: Int): Transactional[Int] = { + thisTransaction.println(s"third step: $x") + if (x % 2 != 0) thisTransaction.abort() + x + } + + def main(args: Array[String]) = { + transaction { + val res = f1(args.length) + println(if (thisTransaction.isAborted) "aborted" else s"result: $res") + } + } +} + +object TransactionalExpansion { + + def transaction[T](op: Transaction => T) = { + val trans: Transaction = new Transaction + op.apply(trans) + trans.commit() + } + + def thisTransaction = $t: Transaction => $t + + def f1(x: Int) = { $t: Transaction => + thisTransaction.apply($t).println(s"first step: $x") + f2(x + 1).apply($t) + } + def f2(x: Int) = { $t: Transaction => + thisTransaction.apply($t).println(s"second step: $x") + f3(x * x).apply($t) + } + def f3(x: Int) = { $t: Transaction => + thisTransaction.apply($t).println(s"third step: $x") + if (x % 2 != 0) thisTransaction.apply($t).abort() + x + } + + def main(args: Array[String]) = { + transaction { $t => + val res = f1(args.length).apply($t) + println(if (thisTransaction.apply($t).isAborted) "aborted" else s"result: $res") + } + } +} + From c10a9903144d9c6e039528fcf0269c4f7dadf1cb Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 7 Dec 2016 11:32:38 +0100 Subject: [PATCH 17/37] Finished blog post --- .../2016-12-05-implicit-function-types.md | 260 +++++++++++++++--- 1 file changed, 225 insertions(+), 35 deletions(-) diff --git a/docs/blog/_posts/2016-12-05-implicit-function-types.md b/docs/blog/_posts/2016-12-05-implicit-function-types.md index 3172e46a497b..007665564f76 100644 --- a/docs/blog/_posts/2016-12-05-implicit-function-types.md +++ b/docs/blog/_posts/2016-12-05-implicit-function-types.md @@ -7,28 +7,23 @@ authorImg: /images/martin.jpg I just made the first pull request to add _implicit function types_ to Scala. I am pretty excited about it, because, citing the explanation -of the pull request "This is the first step to bring comonadic -abstraction to Scala". That's quite a mouthful, so I better explain what I +of the pull request "_This is the first step to bring contextual +abstraction to Scala_". That's quite a mouthful, so I better explain what I mean by it. Let me try to explain the words in this sentence from right to left. -*Scala*: I assume everyone who reads this understands that we mean the +**Scala**: I assume everyone who reads this understands that we mean the programming language, not the opera house. -*Abstraction*: The ability to name a concept and use just the name afterwards. +**Abstraction**: The ability to name a concept and use just the name afterwards. -*Comonadic*: In category theory, a _comonad_ is the dual of a -_monad_. Roughly speaking, a monad is a way to wrap the result (or: -outputs) of a computation in some other type. For instance -`Future[T]` means that the result of type `T` will be produced at -some later time on demand, or `Option[T]` indicates that the result -might also be undefined. - -Dually, a _comonad_ allows to transform or -enrich or otherwise manipulate the _inputs_ to a computation. -The inputs are typically what a computation can access in its -environment. Interesting tasks that are by nature comonadic are +**Contextual**: A piece of a program produces results or outputs in +some context. Our programming languages are very good in describing +and abstracting what outputs are produced. But there's hardly anything +yet available to abstract over the inputs that programs get from their +context. Many interesting scenarios fall into that category, +including: - passing configuration data to the parts of a system that need them, - managing capabilities for security critical tasks, @@ -36,29 +31,15 @@ environment. Interesting tasks that are by nature comonadic are - defining the meanings of operations with type classes, - more generally, passing any sort of context to a computation. -Implicit function types are a suprisingly simple and general way to +Implicit function types are a surprisingly simple and general way to make coding patterns solving these tasks abstractable, reducing boilerplate code and increasing applicability. -*First Step* My pull request is first implementation. In solves the - problem in principle, but it introduces some run-time overhead. The +**First Step**: My pull request is a first implementation. It solves the + problem in principle, but introduces some run-time overhead. The next step will be to eliminate the run-time overhead through some simple optimizations. - -## Comparison with Monads - -One can use monads for these tasks, and some people do. For instance -the `Reader` monad is used to abstract over accessing one entry in the -environment. But the code for doing so quickly becomes complex and -inefficient, in particular when combining several contextual -accesses. Monads don't compose in general, and therefore even simple -combinations need to be expressed on the level of monad transformers, -at the price of much boilerplate and complexity. Recognizing this, -peaple have recently experimented with free monads, which alleviate -the composibility problem, but at the price of introducing a whole new -level of interpretation. - ## Implicit Parameters In a functional setting, the inputs to a computation are most @@ -67,7 +48,7 @@ functions to take additional parameters that represent configurations, capabilities, dictionaries, or whatever contextual data the functions need. The only downside with this is that often there's a large distance in the call graph between the definition of a contextual -element and the site where it is used. Conseuqently, it becomes +element and the site where it is used. Consequently, it becomes tedious to define all those intermediate parameters and to pass them along to where they are eventually consumed. @@ -122,11 +103,11 @@ a newly created transaction: trans.commit() } -The current transaction needs to be passed along a calling chain to all +The current transaction needs to be passed along a call chain to all the places that need to access it. To illustrate this, here are three functions `f1`, `f2` and `f3` which call each other, and also access the current transaction. The most convenient way to achieve this is -passing the current transaction as an implicit parameter. +by passing the current transaction as an implicit parameter. def f1(x: Int)(implicit thisTransaction: Transaction): Int = { thisTransaction.println(s"first step: $x") @@ -165,6 +146,215 @@ Two sample calls of the program (let's call it `TransactionDemo`) are here: scala TransactionDemo 1 2 3 4 aborted +So far, so good. The code above is quite compact as far as expressions +are concerned. In particular, it's nice that, being implicit +parameters, none of the transaction values had to be passed along +explicitly in a call. But on the definition side, things are less +rosy: Every one of the functions `f1` to `f3` needed an additional +implicit parameter: + + (implicit thisTransaction: Transaction) + +A three-times repetition might not look so bad here, but it certainly +smells of boilerplate. In real-sized projects, this can get much worse. +For instance, the _dotty_ compiler uses implicit abstraction +over contexts for most of its parts. Consequently it ends up with currently +no fewer than 2641 occurrences of the text string + + (implicit ctx: Context) + +It would be nice if we could get rid of them. + +## Implicit Functions + +Let's massage the definition of `f1` a bit by moving the last parameter section to the right of the equals sign: + + def f1(x: Int) = { implicit thisTransaction: Transaction => + thisTransaction.println(s"first step: $x") + f2(x + 1) + } + +The right hand side of this new version of `f1` is now an implicit +function value. What's the type of this value? Previously, it was +`Transaction => Int`, that is, the knowledge that the function has an +implicit parameter got lost in the type. The main extension implemented by +the pull request is to introduce implicit function types that mirror +the implicit function values which we have already. Concretely, the new +type of `f1` is: + + implicit Transaction => Int + +Just like the normal function type syntax `A => B`, desugars to `scala.Function1[A, B]` +the implicit function type syntax `implicit A => B` desugars to `scala.ImplicitFunction1[A, B]`. +The same holds at other function arities. With dotty's [pull request #1758](https://github.com/lampepfl/dotty/pull/1758) +merged there is no longer an upper limit of 22 for such functions. + +The type `ImplicitFunction1` can be thought of being defined as follows: + + trait ImplicitFunction1[-T0, R] extends Function1[T0, R] { + override def apply(implicit x: T0): R + } + +However, you won't find a classfile for this trait because all implicit function traits +get mapped to normal functions during type erasure. + +There are two rules that guide type checking of implicit function types. +The first rule says that an implicit function is applied to implicit arguments +in the same way an implicit method is. More precisely, if `t` is an expression +of an implicit function type + + t: implicit (T1, ..., Tn) => R + +such that `t` is not an implicit closure itself and `t` is not the +prefix of a call `t.apply(...)`, then an `apply` is implicitly +inserted, so `t` becomes `t.apply`. We have already seen that the +definition of `t.apply` is an implicit method as given in the +corresponding implicit function trait. Hence, it will in turn be +applied to a matching sequence of implicit arguments. The end effect is +that references to implicit functions get applied to implicit arguments in the +same way as references to implicit methods. + +The second rule is the dual of the first. If the expected type +of an expression `t` is an implicit function type + + implicit (T1, ..., Tn) => R + +then `t` is converted to an implicit closure, unless it is already one. +More precisely, `t` is mapped to the implicit closure + + implicit ($ev1: T1, ..., $evn: Tn) => t + +The parameter names of this closure are compiler-generated identifiers +which should not be accessed from user code. That is, the only way to +refer to an implicit parameter of a compiler-generated function is via +`implicitly`. + +It is important to note that this second conversion needs to be applied +_before_ the expression `t` is typechecked. This is because the +conversion establishes the necessary context to make type checking `t` +succeed by defining the required implicit parameters. + +There is one final tweak to make this all work: When using implicit parameters +for nested functions it was so far important to give all implicit parameters +of the same type the same name, or else one would get ambiguities. For instance, consider the +following fragment: + + def f(implicit c: C) = { + def g(implicit c: C) = ... implicitly[C] ... + ... + } + +If we had named the inner parameter `d` instead of `c` we would +have gotten an implicit ambiguity at the call of `implicitly` because +both `c` and `d` would be eligible: + + def f(implicit c: C) = { + def g(implicit d: C) = ... implicitly[C] ... // error! + ... + } + +The problem is that parameters in implicit closures now have +compiler-generated names, so the programmer cannot enforce the proper +naming scheme to avoid all ambiguities. We fix the problem by +introducing a new disambiguation rule which makes nested occurrences +of an implicit take precedence over outer ones. This rule, which +applies to all implicit parameters and implicit locals, is conceptually +analogous to the rule that prefers implicits defined in companion +objects of subclasses over those defined in companion objects of +superclass. With that new disambiguation rule the example code above +now compiles. + +That's the complete set of rules needed to deal with implicit function types. + +## How to Remove Boilerplate + +The main advantage of implicit function types is that, being types, +they can be abstracted. That is, one can define a name for an implicit +function type and then use just the name instead of the full type. +Let's revisit our previous example and see how it can be made more +concise using this technique. + +We first define a type `Transactional` for functions that take an implicit parameter of type `Transaction`: + + type Transactional[T] = implicit Transaction => T + +Making the return type of `f1` to `f3` a `Transactional[Int]`, we can +eliminate their implicit parameter sections: + + def f1(x: Int): Transactional[Int] = { + thisTransaction.println(s"first step: $x") + f2(x + 1) + } + def f2(x: Int): Transactional[Int] = { + thisTransaction.println(s"second step: $x") + f3(x * x) + } + def f3(x: Int): Transactional[Int] = { + thisTransaction.println(s"third step: $x") + if (x % 2 != 0) thisTransaction.abort() + x + } + +You might ask, how does `thisTransaction` typecheck, since there is no +longer a parameter with that name? In fact, `thisTransaction` is now a +global definition: + + def thisTransaction: Transactional[Transaction] = implicitly[Transaction] + +You might ask: a `Transactional[Transaction]`, is that not circular? To see more clearly, let's expand +the definition according to the rules given in the last section. `thisTransaction` +is of implicit function type, so the right hand side is expanded to the +implicit closure + + implicit ($ev0: Transaction) => implicitly[Transaction] + +The right hand side of this closure, `implicitly[Transaction]`, needs +an implicit parameter of type `Transaction`, so the closure is further +expanded to + + implicit ($ev0: Transaction) => implicitly[Transaction]($ev0) + +Now, `implicitly` is defined in `scala.Predef` like this: + + def implicitly[T](implicit x: T) = x + +If we plug that definition into the closure above and simplify, we get: + + implicit ($ev0: Transaction) => $ev0 + +So, `thisTransaction` is just the implicit identity function on `transaction`! +In other words, if we use `thisTransaction` in the body of `f1` to `f3`, it will +pick up and return the unnamed implicit parameter that's in scope. + +Finally, here are the `transaction` and `main` method that complete +the example. Since `transactional`'s parameter `op` is now a +`Transactional`, we can eliminate the `Transaction` argument to `op` +and the `Transaction` lambda in `main`; both will be added by the compiler. + + def transaction[T](op: Transactional[T]) = { + implicit val trans: Transaction = new Transaction + op + trans.commit() + } + def main(args: Array[String]) = { + transaction { + val res = f1(args.length) + println(if (thisTransaction.isAborted) "aborted" else s"result: $res") + } + } + +## Categorically Speaking + +There are many interesting connections with category theory to explore +here. On the one hand, implicit functions are used for tasks that are +sometimes covered with monads such as the reader monad. There's an +argument to be made that implicits have better composability than +monads and why that is. +On the other hand, it turns out that implicit functions can also be +given a co-monadic interpretation, and the interplay between monads and +comonads is very interesting in its own right. +But these discussions will have to wait for another time, as +this blog post is already too long. From b78150d0ef7da350b580232f1b80433f34392774 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 7 Dec 2016 14:39:55 +0100 Subject: [PATCH 18/37] Add conclusion to blog post --- docs/blog/_posts/2016-12-05-implicit-function-types.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/blog/_posts/2016-12-05-implicit-function-types.md b/docs/blog/_posts/2016-12-05-implicit-function-types.md index 007665564f76..73e04836984f 100644 --- a/docs/blog/_posts/2016-12-05-implicit-function-types.md +++ b/docs/blog/_posts/2016-12-05-implicit-function-types.md @@ -358,3 +358,13 @@ comonads is very interesting in its own right. But these discussions will have to wait for another time, as this blog post is already too long. +## Conclusion + +Implicit function types are unique way to abstract over the context in +which some piece of code is run. I believe they will deeply influence +the way we write Scala in the future. They are very powerful +abstractions, in the sense that just declaring a type of a function +will inject certain implicit values into the scope of the function's +implementation. Can this be abused, making code more obscure? +Absolutely, like every other powerful abstraction technique. To keep +your code sane, please keep the (Principle of Least Power)[http://www.lihaoyi.com/post/StrategicScalaStylePrincipleofLeastPower.html] in mind. From 6ce5fb1fa658de5b0509eade48c94e83534abab7 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 7 Dec 2016 14:41:53 +0100 Subject: [PATCH 19/37] Fix link --- .../_posts/2016-12-05-implicit-function-types.md | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/docs/blog/_posts/2016-12-05-implicit-function-types.md b/docs/blog/_posts/2016-12-05-implicit-function-types.md index 73e04836984f..670c652ee8a8 100644 --- a/docs/blog/_posts/2016-12-05-implicit-function-types.md +++ b/docs/blog/_posts/2016-12-05-implicit-function-types.md @@ -5,16 +5,10 @@ author: Martin Odersky authorImg: /images/martin.jpg --- -I just made the first pull request to add _implicit function types_ to -Scala. I am pretty excited about it, because, citing the explanation -of the pull request "_This is the first step to bring contextual -abstraction to Scala_". That's quite a mouthful, so I better explain what I -mean by it. - -Let me try to explain the words in this sentence from right to left. - -**Scala**: I assume everyone who reads this understands that we mean the - programming language, not the opera house. +I just made the [first pull request](https://github.com/lampepfl/dotty/pull/1775) to add _implicit function types_ to +Scala. I am pretty excited about it, because - citing the explanation +of the pull request - "_This is the first step to bring contextual +abstraction to Scala_". What do I mean by this? **Abstraction**: The ability to name a concept and use just the name afterwards. @@ -367,4 +361,4 @@ abstractions, in the sense that just declaring a type of a function will inject certain implicit values into the scope of the function's implementation. Can this be abused, making code more obscure? Absolutely, like every other powerful abstraction technique. To keep -your code sane, please keep the (Principle of Least Power)[http://www.lihaoyi.com/post/StrategicScalaStylePrincipleofLeastPower.html] in mind. +your code sane, please keep the [Principle of Least Power](http://www.lihaoyi.com/post/StrategicScalaStylePrincipleofLeastPower.html) in mind. From c9f666f7c21df45bf085ff2d6f6b0fbd7d01c72d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 13 Dec 2016 14:43:27 +0100 Subject: [PATCH 20/37] Fixes to tests 1. I noted java_all was not running(it took 0.01s to complete); fixed by changing the test directory. 2. We suspected tasty_bootstrap was gettng the wrong classpath and had a lot of problems getting it to print the classpatg. Fixed by refactoring the options we pass to tasty_bootstrap (it has to be -verbose in addition to -classpath). For the moment, both a turned off but we have to just swap a false to a true to turn them on together. --- compiler/test/dotc/tests.scala | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/compiler/test/dotc/tests.scala b/compiler/test/dotc/tests.scala index 827e1addd6ee..f858926d5909 100644 --- a/compiler/test/dotc/tests.scala +++ b/compiler/test/dotc/tests.scala @@ -84,6 +84,7 @@ class tests extends CompilerTest { val runDir = testsDir + "run/" val newDir = testsDir + "new/" val replDir = testsDir + "repl/" + val javaDir = testsDir + "pos-java-interop/" val sourceDir = "./src/" val dottyDir = sourceDir + "dotty/" @@ -260,7 +261,6 @@ class tests extends CompilerTest { dotcDir + "config/PathResolver.scala" ), List(/* "-Ylog:frontend", */ "-Xprompt") ++ staleSymbolError ++ twice) - val javaDir = "./tests/pos-java-interop/" @Test def java_all = compileFiles(javaDir, twice) //@Test def dotc_compilercommand = compileFile(dotcDir + "config/", "CompilerCommand") @@ -349,9 +349,10 @@ class tests extends CompilerTest { @Test def tasty_tests = compileDir(testsDir, "tasty", testPickling) @Test def tasty_bootstrap = { - val opt = List("-priorityclasspath", defaultOutputDir, "-Ylog-classpath") + val logging = if (false) List("-Ylog-classpath", "-verbose") else Nil + val opt = List("-priorityclasspath", defaultOutputDir) ++ logging // first compile dotty - compileDir(dottyDir, ".", List("-deep", "-Ycheck-reentrant", "-strict"))(allowDeepSubtypes) + compileDir(dottyDir, ".", List("-deep", "-Ycheck-reentrant", "-strict") ++ logging)(allowDeepSubtypes) compileDir(libDir, "dotty", "-deep" :: opt) compileDir(libDir, "scala", "-deep" :: opt) From cc4c3acf7d0aa9a2e612e228c17dc3f3294f0126 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 13 Dec 2016 14:54:27 +0100 Subject: [PATCH 21/37] Fix "wrong number of args" reporting "Wrong number of args" only works for type arguments but was called also for term arguments. Ideally we should have a WrongNumberOfArgs message that works for both, but this will take some refactoring. --- .../dotty/tools/dotc/reporting/diagnostic/messages.scala | 6 ++++-- compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala | 4 ++-- compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala | 7 ++++--- compiler/src/dotty/tools/dotc/typer/Typer.scala | 2 +- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala index 489165e56215..9dda233bffe6 100644 --- a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala @@ -224,6 +224,8 @@ object messages { extends Message(8) { val kind = "Member Not Found" + //println(i"site = $site, decls = ${site.decls}, source = ${site.widen.typeSymbol.sourceFile}") //DEBUG + val msg = { import core.Flags._ val maxDist = 3 @@ -606,7 +608,7 @@ object messages { |""" } - case class WrongNumberOfArgs(fntpe: Type, argKind: String, expectedArgs: List[TypeParamInfo], actual: List[untpd.Tree])(implicit ctx: Context) + case class WrongNumberOfTypeArgs(fntpe: Type, expectedArgs: List[TypeParamInfo], actual: List[untpd.Tree])(implicit ctx: Context) extends Message(22) { val kind = "Syntax" @@ -628,7 +630,7 @@ object messages { } val msg = - hl"""|${NoColor(msgPrefix)} ${argKind} arguments for $prettyName$expectedArgString + hl"""|${NoColor(msgPrefix)} type arguments for $prettyName$expectedArgString |expected: $expectedArgString |actual: $actualArgString""".stripMargin diff --git a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala index a066fc04a43d..c8d126155bdb 100644 --- a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala +++ b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala @@ -55,8 +55,8 @@ object ErrorReporting { errorMsg(ex.show, ctx) } - def wrongNumberOfArgs(fntpe: Type, kind: String, expectedArgs: List[TypeParamInfo], actual: List[untpd.Tree], pos: Position)(implicit ctx: Context) = - errorType(WrongNumberOfArgs(fntpe, kind, expectedArgs, actual)(ctx), pos) + def wrongNumberOfTypeArgs(fntpe: Type, expectedArgs: List[TypeParamInfo], actual: List[untpd.Tree], pos: Position)(implicit ctx: Context) = + errorType(WrongNumberOfTypeArgs(fntpe, expectedArgs, actual)(ctx), pos) class Errors(implicit ctx: Context) { diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index ee2d682785ba..c0d6fb7bd3f4 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -314,7 +314,8 @@ trait TypeAssigner { val ownType = fn.tpe.widen match { case fntpe @ MethodType(_, ptypes) => if (sameLength(ptypes, args) || ctx.phase.prev.relaxedTyping) fntpe.instantiate(args.tpes) - else wrongNumberOfArgs(fn.tpe, "", fntpe.typeParams, args, tree.pos) + else + errorType(i"wrong number of arguments for $fntpe: ${fn.tpe}, expected: ${ptypes.length}, found: ${args.length}", tree.pos) case t => errorType(i"${err.exprStr(fn)} does not take parameters", tree.pos) } @@ -369,7 +370,7 @@ trait TypeAssigner { else { val argTypes = args.tpes if (sameLength(argTypes, paramNames) || ctx.phase.prev.relaxedTyping) pt.instantiate(argTypes) - else wrongNumberOfArgs(fn.tpe, "type", pt.typeParams, args, tree.pos) + else wrongNumberOfTypeArgs(fn.tpe, pt.typeParams, args, tree.pos) } case _ => errorType(i"${err.exprStr(fn)} does not take type parameters", tree.pos) @@ -462,7 +463,7 @@ trait TypeAssigner { val ownType = if (hasNamedArg(args)) (tycon.tpe /: args)(refineNamed) else if (sameLength(tparams, args)) tycon.tpe.appliedTo(args.tpes) - else wrongNumberOfArgs(tycon.tpe, "type", tparams, args, tree.pos) + else wrongNumberOfTypeArgs(tycon.tpe, tparams, args, tree.pos) tree.withType(ownType) } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 616c960194b8..2ab512150822 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1056,7 +1056,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit if (hasNamedArg(args)) typedNamedArgs(args) else { if (args.length != tparams.length) { - wrongNumberOfArgs(tpt1.tpe, "type", tparams, args, tree.pos) + wrongNumberOfTypeArgs(tpt1.tpe, tparams, args, tree.pos) args = args.take(tparams.length) } def typedArg(arg: untpd.Tree, tparam: TypeParamInfo) = { From 71b900fde4cb5937c1cbb19da6e3397586e2467c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 13 Dec 2016 14:56:00 +0100 Subject: [PATCH 22/37] Ref copier that works for Idents and Selects The Ref copier copies Idents and Selects, changing the name of either. --- compiler/src/dotty/tools/dotc/ast/Trees.scala | 5 +++++ compiler/src/dotty/tools/dotc/ast/tpd.scala | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index 20273eb85c79..5e2fb99bfa5c 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -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)) diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index cd6b3fcf212f..433808e8e6ee 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -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] = From 5e2f7d18d711814cecce62a4bd7315a26a5efb87 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 13 Dec 2016 14:57:18 +0100 Subject: [PATCH 23/37] initialDenot method for symbols This avoids denotation transforms when called at a later phase because it cuts out current. Not needed in final version of ShortcutImplicits, but I thought it was good to have. --- compiler/src/dotty/tools/dotc/core/Symbols.scala | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/compiler/src/dotty/tools/dotc/core/Symbols.scala b/compiler/src/dotty/tools/dotc/core/Symbols.scala index cfd85c49cd0e..86d0c51bda85 100644 --- a/compiler/src/dotty/tools/dotc/core/Symbols.scala +++ b/compiler/src/dotty/tools/dotc/core/Symbols.scala @@ -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 From 6eb1a72bce041d4fd8519713031894dac3192025 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 13 Dec 2016 14:58:26 +0100 Subject: [PATCH 24/37] New ShortcutImplicits phase Optimizes implicit closures by avoiding closure creation where possible. --- compiler/src/dotty/tools/dotc/Compiler.scala | 1 + .../tools/dotc/config/JavaPlatform.scala | 1 + .../src/dotty/tools/dotc/core/NameOps.scala | 2 + .../src/dotty/tools/dotc/core/StdNames.scala | 1 + .../src/dotty/tools/dotc/core/Types.scala | 4 +- .../dotc/transform/ShortcutImplicits.scala | 143 ++++++++++++++++++ tests/run/implicitFuns.scala | 44 ++++++ 7 files changed, 195 insertions(+), 1 deletion(-) create mode 100644 compiler/src/dotty/tools/dotc/transform/ShortcutImplicits.scala diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index ad3249be2427..900d2b0e34c6 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -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 diff --git a/compiler/src/dotty/tools/dotc/config/JavaPlatform.scala b/compiler/src/dotty/tools/dotc/config/JavaPlatform.scala index a695202d33fc..b5bfbb39fdbd 100644 --- a/compiler/src/dotty/tools/dotc/config/JavaPlatform.scala +++ b/compiler/src/dotty/tools/dotc/config/JavaPlatform.scala @@ -18,6 +18,7 @@ class JavaPlatform extends Platform { currentClassPath = Some(new PathResolver().result) val cp = currentClassPath.get //println(cp) + //println("------------------") cp } diff --git a/compiler/src/dotty/tools/dotc/core/NameOps.scala b/compiler/src/dotty/tools/dotc/core/NameOps.scala index f40915528281..c037d1ce7d12 100644 --- a/compiler/src/dotty/tools/dotc/core/NameOps.scala +++ b/compiler/src/dotty/tools/dotc/core/NameOps.scala @@ -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 diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index adcb1524218f..716959648a9e 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -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. diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 01a331e86580..34422729998c 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -2376,7 +2376,9 @@ object Types { protected def computeSignature(implicit ctx: Context): Signature = resultSignature.prepend(paramTypes, isJava) - def derivedMethodType(paramNames: List[TermName], paramTypes: List[Type], resType: Type)(implicit ctx: Context) = + def derivedMethodType(paramNames: List[TermName] = this.paramNames, + paramTypes: List[Type] = this.paramTypes, + resType: Type = this.resType)(implicit ctx: Context) = if ((paramNames eq this.paramNames) && (paramTypes eq this.paramTypes) && (resType eq this.resType)) this else { val resTypeFn = (x: MethodType) => resType.subst(this, x) diff --git a/compiler/src/dotty/tools/dotc/transform/ShortcutImplicits.scala b/compiler/src/dotty/tools/dotc/transform/ShortcutImplicits.scala new file mode 100644 index 000000000000..bf72b89c093c --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/ShortcutImplicits.scala @@ -0,0 +1,143 @@ +package dotty.tools.dotc +package transform + +import TreeTransforms._ +import core.DenotTransformers.IdentityDenotTransformer +import core.Symbols._ +import core.Contexts._ +import core.Types._ +import core.Flags._ +import core.Decorators._ +import core.StdNames.nme +import core.Names._ +import core.NameOps._ +import ast.Trees._ +import ast.tpd +import collection.mutable + +/** This phase optimizes code using implicit function types, by applying two rewrite rules. + * Let IF be the implicit function type + * + * implicit Us => R + * + * (1) A method definition + * + * def m(xs: Ts): IF = implicit (ys: Us) => E + * + * is expanded to two methods: + * + * def m(xs: Ts): IF = implicit (ys: Us) => m$direct(xs)(ys) + * def m$direct(xs: Ts)(ys: Us): R = E + * + * (and equivalently for methods with type parameters or a different number of value parameter lists). + * An abstract method definition + * + * def m(xs: Ts): IF + * + * is expanded to: + * + * def m(xs: Ts): IF + * def m$direct(xs: Ts, ys: Us): R + * + * (2) A reference `qual.apply` where `qual` has implicit function type and + * `qual` refers to a method `m` is rewritten to a reference to `m$direct`, + * keeping the same type and value arguments as they are found in `qual`. + */ +class ShortcutImplicits extends MiniPhase with IdentityDenotTransformer { thisTransform => + import tpd._ + + override def phaseName: String = "shortcutImplicits" + val treeTransform = new Transform + + class Transform extends TreeTransform { + def phase = thisTransform + + override def prepareForUnit(tree: Tree)(implicit ctx: Context) = new Transform + + /** A map to cache mapping local methods to their direct counterparts. + * A fresh map is created for each unit. + */ + private val directMeth = new mutable.HashMap[Symbol, Symbol] + + /** @pre The type's final result type is an implicit function type `implicit Ts => R`. + * @return The type of the `apply` member of `implicit Ts => R`. + */ + private def directInfo(info: Type)(implicit ctx: Context): Type = info match { + case info: PolyType => info.derivedPolyType(resType = directInfo(info.resultType)) + case info: MethodType => info.derivedMethodType(resType = directInfo(info.resultType)) + case info: ExprType => directInfo(info.resultType) + case info => info.member(nme.apply).info + } + + /** A new `m$direct` method to accompany the given method `m` */ + private def newDirectMethod(sym: Symbol)(implicit ctx: Context): Symbol = + sym.copy( + name = sym.name.directName, + flags = sym.flags | Synthetic, + info = directInfo(sym.info)) + + /** The direct method `m$direct` that accompanies the given method `m`. + * Create one if it does not exist already. + */ + private def directMethod(sym: Symbol)(implicit ctx: Context): Symbol = + if (sym.owner.isClass) { + val direct = sym.owner.info.member(sym.name.directName) + .suchThat(_.info matches directInfo(sym.info)).symbol + if (direct.maybeOwner == sym.owner) direct + else newDirectMethod(sym).enteredAfter(thisTransform) + } + else directMeth.getOrElseUpdate(sym, newDirectMethod(sym)) + + + /** Transform `qual.apply` occurrences according to rewrite rule (2) above */ + override def transformSelect(tree: Select)(implicit ctx: Context, info: TransformerInfo) = + if (tree.name == nme.apply && + defn.isImplicitFunctionType(tree.qualifier.tpe.widen) && + tree.qualifier.symbol.is(Method, butNot = Accessor)) { + def directQual(tree: Tree): Tree = tree match { + case Apply(fn, args) => cpy.Apply(tree)(directQual(fn), args) + case TypeApply(fn, args) => cpy.TypeApply(tree)(directQual(fn), args) + case Block(stats, expr) => cpy.Block(tree)(stats, directQual(expr)) + case tree: RefTree => + cpy.Ref(tree)(tree.name.directName) + .withType(directMethod(tree.symbol).termRef) + } + directQual(tree.qualifier) + } else tree + + /** Transform methods with implicit function type result according to rewrite rule (1) above */ + override def transformDefDef(mdef: DefDef)(implicit ctx: Context, info: TransformerInfo): Tree = { + val original = mdef.symbol + if (defn.isImplicitFunctionType(original.info.finalResultType)) { + val direct = directMethod(original) + + def splitClosure(tree: Tree): (List[Type] => List[List[Tree]] => Tree, Tree) = tree match { + case Block(Nil, expr) => splitClosure(expr) + case Block((meth @ DefDef(nme.ANON_FUN, Nil, clparams :: Nil, _, _)) :: Nil, cl: Closure) => + val tparamSyms = mdef.tparams.map(_.symbol) + val vparamSymss = mdef.vparamss.map(_.map(_.symbol)) + val clparamSyms = clparams.map(_.symbol) + val remappedCore = (ts: List[Type]) => (prefss: List[List[Tree]]) => + meth.rhs + .subst(tparamSyms ::: (vparamSymss.flatten ++ clparamSyms), + ts.map(_.typeSymbol) ::: prefss.flatten.map(_.symbol)) + .changeOwnerAfter(original, direct, thisTransform) + .changeOwnerAfter(meth.symbol, direct, thisTransform) + val forwarder = ref(direct) + .appliedToTypeTrees(tparamSyms.map(ref(_))) + .appliedToArgss(vparamSymss.map(_.map(ref(_))) :+ clparamSyms.map(ref(_))) + val fwdClosure = cpy.Block(tree)(cpy.DefDef(meth)(rhs = forwarder) :: Nil, cl) + (remappedCore, fwdClosure) + case EmptyTree => + (_ => _ => EmptyTree, EmptyTree) + } + + val (remappedCore, fwdClosure) = splitClosure(mdef.rhs) + val originalDef = cpy.DefDef(mdef)(rhs = fwdClosure) + val directDef = polyDefDef(direct.asTerm, remappedCore) + Thicket(originalDef, directDef) + } + else mdef + } + } +} diff --git a/tests/run/implicitFuns.scala b/tests/run/implicitFuns.scala index ee1d37256e33..1bc5a2f6b1da 100644 --- a/tests/run/implicitFuns.scala +++ b/tests/run/implicitFuns.scala @@ -211,3 +211,47 @@ object TransactionalExpansion { } } +object TransactionalAbstracted { + type Transactional[T] = implicit Transaction => T + + trait TransOps { + def thisTransaction: Transactional[Transaction] + def f1(x: Int): Transactional[Int] + def f2(x: Int): Transactional[Int] + def f3(x: Int): Transactional[Int] + } + + object TransOpsObj extends TransOps { + + def thisTransaction: Transactional[Transaction] = implicitly[Transaction] + + def f1(x: Int): Transactional[Int] = { + thisTransaction.println(s"first step: $x") + f2(x + 1) + } + def f2(x: Int): Transactional[Int] = { + thisTransaction.println(s"second step: $x") + f3(x * x) + } + def f3(x: Int): Transactional[Int] = { + thisTransaction.println(s"third step: $x") + if (x % 2 != 0) thisTransaction.abort() + x + } + } + + val transOps: TransOps = TransOpsObj + + def transaction[T](op: Transactional[T]) = { + implicit val trans: Transaction = new Transaction + op + trans.commit() + } + + def main(args: Array[String]) = { + transaction { + val res = transOps.f1(args.length) + println(if (transOps.thisTransaction.isAborted) "aborted" else s"result: $res") + } + } +} From df4653c729a374561e591328f43255bc062b81a4 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 13 Dec 2016 15:38:56 +0100 Subject: [PATCH 25/37] Fix toString in ImplicitFunction tree --- compiler/src/dotty/tools/dotc/ast/untpd.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index 77755da81152..f3ffce8f88e1 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -56,7 +56,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { /** An implicit function type */ class ImplicitFunction(args: List[Tree], body: Tree) extends Function(args, body) { - override def toString = s"ImplicitFunction($args, $body" + override def toString = s"ImplicitFunction($args, $body)" } /** A function created from a wildcard expression From 30faa7b5e2dee0e2b80fe2c7696df20537644d74 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 13 Dec 2016 16:22:13 +0100 Subject: [PATCH 26/37] Fix rebase breakage --- compiler/src/dotty/tools/dotc/ast/Desugar.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index def2caabf41e..9924eab45c9d 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -159,7 +159,7 @@ object desugar { rhs } val tparams1 = tparams mapConserve { tparam => - cpy.TypeDef(tparam)(rhs = desugarContextBounds(tparam)) + cpy.TypeDef(tparam)(rhs = desugarContextBounds(tparam.rhs)) } val meth1 = addEvidenceParams(cpy.DefDef(meth)(tparams = tparams1), epbuf.toList) From 04adb53b8d079ea114c5432ca3b3f824c80756a7 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 14 Dec 2016 14:37:13 +0100 Subject: [PATCH 27/37] Add benchmarks Benchmark code to compare compilation schemes in different scenarios. See results.md for explanations. --- tests/bench/transactional/Benchmark.scala | 5 ++ tests/bench/transactional/ImplicitMega.scala | 67 ++++++++++++++ tests/bench/transactional/ImplicitMono.scala | 45 ++++++++++ tests/bench/transactional/ReaderMonadic.scala | 87 +++++++++++++++++++ tests/bench/transactional/Runner.scala | 25 ++++++ tests/bench/transactional/Transaction.scala | 21 +++++ tests/bench/transactional/results.md | 74 ++++++++++++++++ 7 files changed, 324 insertions(+) create mode 100644 tests/bench/transactional/Benchmark.scala create mode 100644 tests/bench/transactional/ImplicitMega.scala create mode 100644 tests/bench/transactional/ImplicitMono.scala create mode 100644 tests/bench/transactional/ReaderMonadic.scala create mode 100644 tests/bench/transactional/Runner.scala create mode 100644 tests/bench/transactional/Transaction.scala create mode 100644 tests/bench/transactional/results.md diff --git a/tests/bench/transactional/Benchmark.scala b/tests/bench/transactional/Benchmark.scala new file mode 100644 index 000000000000..8af7ebb830d5 --- /dev/null +++ b/tests/bench/transactional/Benchmark.scala @@ -0,0 +1,5 @@ +package transactional +abstract class Benchmark { + def run(): Int +} + diff --git a/tests/bench/transactional/ImplicitMega.scala b/tests/bench/transactional/ImplicitMega.scala new file mode 100644 index 000000000000..80e9c4a43b6e --- /dev/null +++ b/tests/bench/transactional/ImplicitMega.scala @@ -0,0 +1,67 @@ +package transactional +object MegaBench extends Benchmark { + type Transactional[T] = implicit Transaction => T + + def transaction[T](op: Transactional[T]): T = { + implicit val trans: Transaction = new Transaction + val res = op + trans.commit() + res + } + + def thisTransaction: Transactional[Transaction] = implicitly[Transaction] + + abstract class Op { + def f(x: Int): Transactional[Int] + } + + class Op0 extends Op { + def f(x: Int): Transactional[Int] = { + thisTransaction.println("0th step") + x + } + } + + class Op1 extends Op { + def f(x: Int): Transactional[Int] = { + thisTransaction.println("first step") + x + 1 + } + } + + class Op2 extends Op { + def f(x: Int): Transactional[Int] = { + thisTransaction.println("second step") + x + 2 + } + } + + class Op3 extends Op { + def f(x: Int): Transactional[Int] = { + thisTransaction.println("third step") + x + 3 + } + } + + val op = Array[Op](new Op0, new Op1, new Op2, new Op3) + + def f(x: Int, n: Int): Transactional[Int] = { + thisTransaction.println("fourth step") + if (n > 0) f(op(n % 4).f(x), n - 1) + else { + if (x % 2 != 0) thisTransaction.abort() + x + } + } + + def run(): Int = { + transaction { + val res = f(7, 10) + assert(!thisTransaction.isAborted) + assert(res == 22) + res + } + } +} + +object ImplicitMega extends Runner("megamorphic", MegaBench, 22) diff --git a/tests/bench/transactional/ImplicitMono.scala b/tests/bench/transactional/ImplicitMono.scala new file mode 100644 index 000000000000..10391f191571 --- /dev/null +++ b/tests/bench/transactional/ImplicitMono.scala @@ -0,0 +1,45 @@ +package transactional +object MonoBench extends Benchmark { + type Transactional[T] = implicit Transaction => T + + def transaction[T](op: Transactional[T]): T = { + implicit val trans: Transaction = new Transaction + val res = op + trans.commit() + res + } + + def thisTransaction: Transactional[Transaction] = implicitly[Transaction] + + def f1(x: Int): Transactional[Int] = { + thisTransaction.println("first step") + f2(x + 1) + } + def f2(x: Int): Transactional[Int] = { + thisTransaction.println("second step") + f3(x * x) + } + def f3(x: Int): Transactional[Int] = { + thisTransaction.println("third step") + f4(x + 1, 7) + } + def f4(x: Int, n: Int): Transactional[Int] = { + thisTransaction.println("fourth step") + if (n > 0) f4(x + 1, n - 1) + else { + if (x % 2 != 0) thisTransaction.abort() + x + } + } + + def run(): Int = { + transaction { + val res = f1(7) + assert(!thisTransaction.isAborted) + assert(res == 72) + res + } + } +} + +object ImplicitMono extends Runner("monomorphic implicits", MonoBench, 72) diff --git a/tests/bench/transactional/ReaderMonadic.scala b/tests/bench/transactional/ReaderMonadic.scala new file mode 100644 index 000000000000..ce69c35ad591 --- /dev/null +++ b/tests/bench/transactional/ReaderMonadic.scala @@ -0,0 +1,87 @@ +package transactional + +case class Reader[R,A](run: R => A) { + def map[B](f: A => B): Reader[R, B] = Reader(r => f(run(r))) + def flatMap[B](f: A => Reader[R, B]): Reader[R, B] = Reader(r => f(run(r)).run(r)) +} + +object Reader { + def ask[R]: Reader[R,R] = Reader(r => r) +} + +object ReaderBench extends Benchmark { + type Transactional[T] = Reader[Transaction, T] + + def transaction[T](op: Transactional[T]): T = { + implicit val trans: Transaction = new Transaction + val res = op.run(trans) + trans.commit() + res + } + + def thisTransaction: Transactional[Transaction] = Reader.ask + + abstract class Op { + def f(x: Int): Transactional[Int] + } + + class Op0 extends Op { + def f(x: Int): Transactional[Int] = + for (trans <- thisTransaction) + yield { trans.println("0th step"); x } + } + + class Op1 extends Op { + def f(x: Int): Transactional[Int] = + for (trans <- thisTransaction) + yield { trans.println("first step"); x + 1 } + } + + class Op2 extends Op { + def f(x: Int): Transactional[Int] = + for (trans <- thisTransaction) + yield { trans.println("second step"); x + 2 } + } + + class Op3 extends Op { + def f(x: Int): Transactional[Int] = + for (trans <- thisTransaction) + yield { trans.println("third step"); x + 3 } + } + + val op = Array[Op](new Op0, new Op1, new Op2, new Op3) + + def f(x: Int, n: Int): Transactional[Int] = { + def rest(trans: Transaction): Transactional[Int] = { + trans.println("fourth step") + if (n > 0) { + for { + y <- op(n % 4).f(x) + z <- f(y: Int, n - 1) + } + yield z + } + else { + if (x % 2 != 0) + for (trans <- thisTransaction) + yield { trans.abort(); () } + Reader(_ => x) + } + } + thisTransaction.flatMap(rest) + } + + def run(): Int = { + transaction { + for (res <- f(7, 10)) + yield { + for (trans <- thisTransaction) + yield { assert(!trans.isAborted); () } + assert(res == 22) + res + } + } + } +} + +object ReaderMonadic extends Runner("reader monadic", ReaderBench, 22) diff --git a/tests/bench/transactional/Runner.scala b/tests/bench/transactional/Runner.scala new file mode 100644 index 000000000000..48da7ff12aad --- /dev/null +++ b/tests/bench/transactional/Runner.scala @@ -0,0 +1,25 @@ +package transactional +import System.nanoTime + +class Runner(name: String, bench: Benchmark, expected: Int) { + + val numIters = 10000000 + val numTests = 5 + + def run(): Unit = { + val start = nanoTime + var cnt = 0 + var i = 0 + while (i < numIters) { + cnt += bench.run() + i += 1 + } + assert(cnt == expected * numIters) + val duration = nanoTime - start + println(s"$name in ${duration / 1000000}ms") + } + + def main(args: Array[String]) = + for (i <- 0 until numTests) + run() +} diff --git a/tests/bench/transactional/Transaction.scala b/tests/bench/transactional/Transaction.scala new file mode 100644 index 000000000000..dbb28d4527a5 --- /dev/null +++ b/tests/bench/transactional/Transaction.scala @@ -0,0 +1,21 @@ +package transactional +import collection.mutable.ListBuffer + +class Transaction { + private val log = new ListBuffer[String] + def println(s: String): Unit = log += s + + private var aborted = false + private var committed = false + + def abort(): Unit = { aborted = true } + def isAborted = aborted + + def commit(): Unit = + if (!aborted && !committed) { + //Console.println("******* log ********") + //log.foreach(Console.println) + committed = true + } +} + diff --git a/tests/bench/transactional/results.md b/tests/bench/transactional/results.md new file mode 100644 index 000000000000..4a58800ec8e0 --- /dev/null +++ b/tests/bench/transactional/results.md @@ -0,0 +1,74 @@ +# Benchmark results for implicit compilation scenarios. + +### Setup + +Three alternatives: + + 1. No implicit shortcuts + 2. Implicit shortcuts only for possible targets of megamorphic dispatch + (`specializeMonoTargets` set to false) + 3. Implicit shortcuts for all methods returning implicit function types + (`specializeMonoTargets` set to true) + +Two benchmarks: + + - `ImplicitMono`: all calls are monomorphic + - `IplicitMega` : about half of the calls are (4-way) megamorphic, + the others are monomorphic. + +### Results + +| Scheme | ImplicitMono | ImplicitMega | +|---------------------|-------------:|-------------:| +| no shortcuts | 1354ms | 3260ms +| | 955ms | 2906ms +| | 908ms | 2899ms +| | 906ms | 2887ms +| | 886ms | 2872ms +| only mega shortcuts | | + | 1243ms | 2472ms +| | 926ms | 2146ms +| | 925ms | 2169ms +| | 902ms | 2136ms +| | 913ms | 2179ms +| all shortcuts | | +| | 1354ms | 1940ms +| | 1039ms | 1563ms +| | 1031ms | 1593ms +| | 1065ms | 1548ms +| | 1016ms | 1579ms + +### Interpretation + +In the fully monomorphic benchmark, specializing +only megamorphic targets has the same performance as +not spezializing at all (not surprising, since there +are no megamorphic targets). Specializing everything +incurs about a 14% performance hit (maybe due to the extra +code generated; it's hard to pin down what it is). + +Note: We compaute relative performance differences by comparing the +second-best test runs of each series with each other. + +In the megamorphic benchmark, it's the other way round. +Specializing only megamorphic callsites leads to a performance +improvement of about 36% compared to no specialization. Specializing +everything leads to another 37% improvement (85% total compared +to no specialization). + +I think we need larger benchmarks to decide whether we should +specicialize mono-morphic call-targets or not. + +### Comparing with the Reader Monad + +Translating `ImplicitMega` to the reader monad, gives the following runtimes: + +| Reader | +| 11563ms | +| 11108ms | +| 11300ms | +| 11098ms | +| 11159ms | + +This translates to a 710% slowdown compared to implicit function types +with full specialization. \ No newline at end of file From 86dea77d3b659b48fec0e9d53495009a7bff335d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 14 Dec 2016 14:38:45 +0100 Subject: [PATCH 28/37] Make specialization tweakable Introduce an option to not specialize monomorphic targets of callsites. --- .../dotc/transform/ShortcutImplicits.scala | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/ShortcutImplicits.scala b/compiler/src/dotty/tools/dotc/transform/ShortcutImplicits.scala index bf72b89c093c..a9fadaa563a5 100644 --- a/compiler/src/dotty/tools/dotc/transform/ShortcutImplicits.scala +++ b/compiler/src/dotty/tools/dotc/transform/ShortcutImplicits.scala @@ -37,7 +37,7 @@ import collection.mutable * is expanded to: * * def m(xs: Ts): IF - * def m$direct(xs: Ts, ys: Us): R + * def m$direct(xs: Ts)(ys: Us): R * * (2) A reference `qual.apply` where `qual` has implicit function type and * `qual` refers to a method `m` is rewritten to a reference to `m$direct`, @@ -49,6 +49,14 @@ class ShortcutImplicits extends MiniPhase with IdentityDenotTransformer { thisTr override def phaseName: String = "shortcutImplicits" val treeTransform = new Transform + /** If this option true, we don't specialize symbols that are known to be only + * targets of monomorphic calls. + * The reason for this option is that benchmarks show that on the JVM for monomorphic dispatch + * scenarios inlining and escape analysis can often remove all calling overhead, so we might as + * well not duplicate the code. We need more experience to decide on the best setting of this option. + */ + final val specializeMonoTargets = true + class Transform extends TreeTransform { def phase = thisTransform @@ -59,6 +67,17 @@ class ShortcutImplicits extends MiniPhase with IdentityDenotTransformer { thisTr */ private val directMeth = new mutable.HashMap[Symbol, 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. + * However if `specializeMonoTargets` is true, 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) && + (specializeMonoTargets || !sym.isEffectivelyFinal || sym.allOverriddenSymbols.nonEmpty) + /** @pre The type's final result type is an implicit function type `implicit Ts => R`. * @return The type of the `apply` member of `implicit Ts => R`. */ @@ -93,7 +112,7 @@ class ShortcutImplicits extends MiniPhase with IdentityDenotTransformer { thisTr override def transformSelect(tree: Select)(implicit ctx: Context, info: TransformerInfo) = if (tree.name == nme.apply && defn.isImplicitFunctionType(tree.qualifier.tpe.widen) && - tree.qualifier.symbol.is(Method, butNot = Accessor)) { + shouldBeSpecialized(tree.qualifier.symbol)) { def directQual(tree: Tree): Tree = tree match { case Apply(fn, args) => cpy.Apply(tree)(directQual(fn), args) case TypeApply(fn, args) => cpy.TypeApply(tree)(directQual(fn), args) @@ -108,7 +127,7 @@ class ShortcutImplicits extends MiniPhase with IdentityDenotTransformer { thisTr /** Transform methods with implicit function type result according to rewrite rule (1) above */ override def transformDefDef(mdef: DefDef)(implicit ctx: Context, info: TransformerInfo): Tree = { val original = mdef.symbol - if (defn.isImplicitFunctionType(original.info.finalResultType)) { + if (shouldBeSpecialized(original)) { val direct = directMethod(original) def splitClosure(tree: Tree): (List[Type] => List[List[Tree]] => Tree, Tree) = tree match { From c26a8c8811e850ebb682e27ac6b4037331028ea2 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 14 Dec 2016 14:43:05 +0100 Subject: [PATCH 29/37] Fix typos in results.md --- tests/bench/transactional/results.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/bench/transactional/results.md b/tests/bench/transactional/results.md index 4a58800ec8e0..7d66ffe60be8 100644 --- a/tests/bench/transactional/results.md +++ b/tests/bench/transactional/results.md @@ -13,7 +13,7 @@ Three alternatives: Two benchmarks: - `ImplicitMono`: all calls are monomorphic - - `IplicitMega` : about half of the calls are (4-way) megamorphic, + - `ImplicitMega` : about half of the calls are (4-way) megamorphic, the others are monomorphic. ### Results @@ -47,7 +47,7 @@ are no megamorphic targets). Specializing everything incurs about a 14% performance hit (maybe due to the extra code generated; it's hard to pin down what it is). -Note: We compaute relative performance differences by comparing the +Note: We compute relative performance differences by comparing the second-best test runs of each series with each other. In the megamorphic benchmark, it's the other way round. @@ -57,13 +57,14 @@ everything leads to another 37% improvement (85% total compared to no specialization). I think we need larger benchmarks to decide whether we should -specicialize mono-morphic call-targets or not. +specicialize monomorphic call-targets or not. ### Comparing with the Reader Monad -Translating `ImplicitMega` to the reader monad, gives the following runtimes: +Translating `ImplicitMega` to the reader monad gives the following runtimes: | Reader | +|---------| | 11563ms | | 11108ms | | 11300ms | From 65b48e03b3f9eeb0c195024f2a94ba0a4cb6535c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 14 Dec 2016 14:47:04 +0100 Subject: [PATCH 30/37] Add linked to code --- tests/bench/transactional/results.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/bench/transactional/results.md b/tests/bench/transactional/results.md index 7d66ffe60be8..0766432c3008 100644 --- a/tests/bench/transactional/results.md +++ b/tests/bench/transactional/results.md @@ -12,9 +12,11 @@ Three alternatives: Two benchmarks: - - `ImplicitMono`: all calls are monomorphic - - `ImplicitMega` : about half of the calls are (4-way) megamorphic, + - `ImplicitMono`: All calls are monomorphic. + Code in [ImplicitMono.scala](./ImplicitMono.scala). + - `ImplicitMega` : About half of the calls are (4-way) megamorphic, the others are monomorphic. + Code in [ImplicitMega.scala](./ImplicitMega.scala). ### Results @@ -61,7 +63,7 @@ specicialize monomorphic call-targets or not. ### Comparing with the Reader Monad -Translating `ImplicitMega` to the reader monad gives the following runtimes: +Translating `ImplicitMega` to the reader monad (code in [ReaderMonadic.scala](./ReaderMonadic.scala)) gives the following runtimes: | Reader | |---------| From 5ad63c794d4b2433962b79461138d7b98f529112 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 14 Dec 2016 14:51:44 +0100 Subject: [PATCH 31/37] Fix more types, add link --- tests/bench/transactional/results.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/bench/transactional/results.md b/tests/bench/transactional/results.md index 0766432c3008..fa243a8e2d03 100644 --- a/tests/bench/transactional/results.md +++ b/tests/bench/transactional/results.md @@ -6,7 +6,7 @@ Three alternatives: 1. No implicit shortcuts 2. Implicit shortcuts only for possible targets of megamorphic dispatch - (`specializeMonoTargets` set to false) + (`specializeMonoTargets` in [ShortcutImplicits.scala](../../../compiler/src/dotty/tools/dotc/transform/ShortcutImplicits.scala) set to false) 3. Implicit shortcuts for all methods returning implicit function types (`specializeMonoTargets` set to true) @@ -44,7 +44,7 @@ Two benchmarks: In the fully monomorphic benchmark, specializing only megamorphic targets has the same performance as -not spezializing at all (not surprising, since there +not specializing at all (not surprising, since there are no megamorphic targets). Specializing everything incurs about a 14% performance hit (maybe due to the extra code generated; it's hard to pin down what it is). @@ -53,13 +53,13 @@ Note: We compute relative performance differences by comparing the second-best test runs of each series with each other. In the megamorphic benchmark, it's the other way round. -Specializing only megamorphic callsites leads to a performance +Specializing only megamorphic call-sites leads to a performance improvement of about 36% compared to no specialization. Specializing everything leads to another 37% improvement (85% total compared to no specialization). I think we need larger benchmarks to decide whether we should -specicialize monomorphic call-targets or not. +specialize monomorphic call-targets or not. ### Comparing with the Reader Monad From a6ae1a7b5b62b1b4e8b702c640305c3ae6d7da13 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 14 Dec 2016 17:16:46 +0100 Subject: [PATCH 32/37] Fix typo --- tests/bench/transactional/results.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/bench/transactional/results.md b/tests/bench/transactional/results.md index fa243a8e2d03..702617ebb1cd 100644 --- a/tests/bench/transactional/results.md +++ b/tests/bench/transactional/results.md @@ -1,4 +1,4 @@ -# Benchmark results for implicit compilation scenarios. +# Benchmark results for implicit compilation scenarios ### Setup From 740bd425d562e5e9d1744361553054413775cb73 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 15 Dec 2016 18:14:31 +0100 Subject: [PATCH 33/37] Drop Override flag for non-overriding direct methods Also, integrate Jason's test case with the conditional. --- .../src/dotty/tools/dotc/transform/ShortcutImplicits.scala | 7 +++++-- tests/run/implicitFuns.scala | 6 +++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/ShortcutImplicits.scala b/compiler/src/dotty/tools/dotc/transform/ShortcutImplicits.scala index a9fadaa563a5..31dfad75796b 100644 --- a/compiler/src/dotty/tools/dotc/transform/ShortcutImplicits.scala +++ b/compiler/src/dotty/tools/dotc/transform/ShortcutImplicits.scala @@ -89,11 +89,14 @@ class ShortcutImplicits extends MiniPhase with IdentityDenotTransformer { thisTr } /** A new `m$direct` method to accompany the given method `m` */ - private def newDirectMethod(sym: Symbol)(implicit ctx: Context): Symbol = - sym.copy( + private def newDirectMethod(sym: Symbol)(implicit ctx: Context): Symbol = { + val direct = sym.copy( name = sym.name.directName, flags = sym.flags | Synthetic, info = directInfo(sym.info)) + if (direct.allOverriddenSymbols.isEmpty) direct.resetFlag(Override) + direct + } /** The direct method `m$direct` that accompanies the given method `m`. * Create one if it does not exist already. diff --git a/tests/run/implicitFuns.scala b/tests/run/implicitFuns.scala index 1bc5a2f6b1da..496fba0d387c 100644 --- a/tests/run/implicitFuns.scala +++ b/tests/run/implicitFuns.scala @@ -1,5 +1,5 @@ object Test { - def main(args: Array[String]) = { + def main(args: Array[String]): Unit = { implicit val world: String = "world!" @@ -53,6 +53,10 @@ object Test { val c1: Int = c Contextual.main(args) + + def foo(s: String): Stringly[Int] = 42 + + (if ("".isEmpty) foo("") else foo("")).apply("") } } From c18d2286e4d21b4a4e7b67ded9c075c8dbb4cda5 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 16 Dec 2016 09:23:32 +0100 Subject: [PATCH 34/37] Fix typo in comment --- compiler/src/dotty/tools/dotc/transform/ShortcutImplicits.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/transform/ShortcutImplicits.scala b/compiler/src/dotty/tools/dotc/transform/ShortcutImplicits.scala index 31dfad75796b..813aa103c726 100644 --- a/compiler/src/dotty/tools/dotc/transform/ShortcutImplicits.scala +++ b/compiler/src/dotty/tools/dotc/transform/ShortcutImplicits.scala @@ -49,7 +49,7 @@ class ShortcutImplicits extends MiniPhase with IdentityDenotTransformer { thisTr override def phaseName: String = "shortcutImplicits" val treeTransform = new Transform - /** If this option true, we don't specialize symbols that are known to be only + /** If this option is true, we don't specialize symbols that are known to be only * targets of monomorphic calls. * The reason for this option is that benchmarks show that on the JVM for monomorphic dispatch * scenarios inlining and escape analysis can often remove all calling overhead, so we might as From 0742ba8e91b098b10529972bf5b630c98a5a882b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 17 Dec 2016 18:59:33 +0100 Subject: [PATCH 35/37] Fix rebase breakage --- compiler/src/dotty/tools/dotc/core/Definitions.scala | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 814bbf48f96a..5162d63a8f45 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -865,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. */ @@ -874,7 +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.isTypeName && name.functionArity > MaxImplementedFunctionArity) + if (res == null && name.isTypeName && + name.functionArity > maxImplemented(name)) newScopeEntry(newFunctionNTrait(name.asTypeName)) else res } From 7c0b16378dccc1c2da17de0c3506fb6fceb3e9d2 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 17 Dec 2016 19:01:25 +0100 Subject: [PATCH 36/37] Fix formatting --- compiler/src/dotty/tools/dotc/core/Definitions.scala | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 5162d63a8f45..45e37eb8bab3 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -877,8 +877,7 @@ 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.isTypeName && - name.functionArity > maxImplemented(name)) + if (res == null && name.isTypeName && name.functionArity > maxImplemented(name)) newScopeEntry(newFunctionNTrait(name.asTypeName)) else res } From 2e99511840915e2805e7a2c07571d3859a3db698 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 17 Dec 2016 21:51:51 +0100 Subject: [PATCH 37/37] Fix comment --- compiler/src/dotty/tools/dotc/transform/ShortcutImplicits.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/transform/ShortcutImplicits.scala b/compiler/src/dotty/tools/dotc/transform/ShortcutImplicits.scala index 813aa103c726..b5469610fa74 100644 --- a/compiler/src/dotty/tools/dotc/transform/ShortcutImplicits.scala +++ b/compiler/src/dotty/tools/dotc/transform/ShortcutImplicits.scala @@ -69,7 +69,7 @@ class ShortcutImplicits extends MiniPhase with IdentityDenotTransformer { thisTr /** 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. - * However if `specializeMonoTargets` is true, we exclude symbols that are known + * 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. */