Skip to content

Replace ShortcutImplicits #8386

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Mar 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion compiler/src/dotty/tools/dotc/Compiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@ class Compiler {
new ProtectedAccessors, // Add accessors for protected members
new ExtensionMethods, // Expand methods of value classes with extension methods
new CacheAliasImplicits, // Cache RHS of parameterless alias implicits
new ShortcutImplicits, // Allow implicit functions without creating closures
new ByNameClosures, // Expand arguments to by-name parameters to closures
new HoistSuperArgs, // Hoist complex arguments of supercalls to enclosing scope
new ClassOf, // Expand `Predef.classOf` calls.
Expand Down
5 changes: 4 additions & 1 deletion compiler/src/dotty/tools/dotc/ast/tpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,12 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
def seq(stats: List[Tree], expr: Tree)(implicit ctx: Context): Tree =
if (stats.isEmpty) expr
else expr match {
case Block(_, _: Closure) =>
Block(stats, expr) // leave closures in their own block
case Block(estats, eexpr) =>
cpy.Block(expr)(stats ::: estats, eexpr).withType(ta.avoidingType(eexpr, stats))
case _ => Block(stats, expr)
case _ =>
Block(stats, expr)
}

def If(cond: Tree, thenp: Tree, elsep: Tree)(implicit ctx: Context): If =
Expand Down
6 changes: 6 additions & 0 deletions compiler/src/dotty/tools/dotc/config/Config.scala
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,12 @@ object Config {
/** If set, prints a trace of all symbol completions */
final val showCompletions = false

/** If set, method results that are context functions are flattened by adding
* the parameters of the context function results to the methods themselves.
* This is an optimization that reduces closure allocations.
*/
final val flattenContextFunctionResults = true

/** If set, enables tracing */
final val tracingEnabled = false

Expand Down
17 changes: 15 additions & 2 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -783,8 +783,7 @@ class Definitions {
@tu lazy val AnnotationDefaultAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.internal.AnnotationDefault")
@tu lazy val BodyAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.internal.Body")
@tu lazy val ChildAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.internal.Child")
@tu lazy val CovariantBetweenAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.internal.CovariantBetween")
@tu lazy val ContravariantBetweenAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.internal.ContravariantBetween")
@tu lazy val ContextResultCountAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.internal.ContextResultCount")
@tu lazy val DeprecatedAnnot: ClassSymbol = ctx.requiredClass("scala.deprecated")
@tu lazy val ImplicitAmbiguousAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.implicitAmbiguous")
@tu lazy val ImplicitNotFoundAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.implicitNotFound")
Expand Down Expand Up @@ -1268,6 +1267,20 @@ class Definitions {
def isContextFunctionType(tp: Type)(implicit ctx: Context): Boolean =
asContextFunctionType(tp).exists

/** An extractor for context function types `As ?=> B`, possibly with
* dependent refinements. Optionally returns a triple consisting of the argument
* types `As`, the result type `B` and a whether the type is an erased context function.
*/
object ContextFunctionType:
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this object belongs at Types.scala.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

One can debate this. I put it next to isContextFunctionType and asContextFunctionType, since it is related.

def unapply(tp: Type)(using ctx: Context): Option[(List[Type], Type, Boolean)] =
if ctx.erasedTypes then unapply(tp)(using ctx.withPhase(ctx.erasurePhase))
else
val tp1 = tp.dealias
if isContextFunctionClass(tp1.typeSymbol) then
val args = asContextFunctionType(tp).dropDependentRefinement.argInfos
Some((args.init, args.last, tp1.typeSymbol.name.isErasedFunction))
else None

def isErasedFunctionType(tp: Type)(implicit ctx: Context): Boolean =
isFunctionType(tp) && tp.dealias.typeSymbol.name.isErasedFunction

Expand Down
1 change: 0 additions & 1 deletion compiler/src/dotty/tools/dotc/core/NameKinds.scala
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,6 @@ object NameKinds {
val InlineAccessorName: PrefixNameKind = new PrefixNameKind(INLINEACCESSOR, "inline$")

val AvoidClashName: SuffixNameKind = new SuffixNameKind(AVOIDCLASH, "$_avoid_name_clash_$")
val DirectMethodName: SuffixNameKind = new SuffixNameKind(DIRECT, "$direct") { override def definesNewName = true }
val FieldName: SuffixNameKind = new SuffixNameKind(FIELD, "$$local") {
override def mkString(underlying: TermName, info: ThisInfo) = underlying.toString
}
Expand Down
53 changes: 29 additions & 24 deletions compiler/src/dotty/tools/dotc/core/TypeErasure.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ package core
import Symbols._, Types._, Contexts._, Flags._, Names._, StdNames._
import Flags.JavaDefined
import Uniques.unique
import dotc.transform.ExplicitOuter._
import dotc.transform.ValueClasses._
import transform.ExplicitOuter._
import transform.ValueClasses._
import transform.TypeUtils._
import transform.ContextFunctionResults._
import Decorators._
import Definitions.MaxImplementedFunctionArity
import scala.annotation.tailrec
Expand Down Expand Up @@ -129,23 +130,23 @@ object TypeErasure {
erasures(erasureIdx(isJava, semiEraseVCs, isConstructor, wildcardOK))

/** The current context with a phase no later than erasure */
private def erasureCtx(implicit ctx: Context) =
def preErasureCtx(implicit ctx: Context) =
if (ctx.erasedTypes) ctx.withPhase(ctx.erasurePhase) else ctx

/** The standard erasure of a Scala type. Value classes are erased as normal classes.
*
* @param tp The type to erase.
*/
def erasure(tp: Type)(implicit ctx: Context): Type =
erasureFn(isJava = false, semiEraseVCs = false, isConstructor = false, wildcardOK = false)(tp)(erasureCtx)
erasureFn(isJava = false, semiEraseVCs = false, isConstructor = false, wildcardOK = false)(tp)(preErasureCtx)

/** The value class erasure of a Scala type, where value classes are semi-erased to
* ErasedValueType (they will be fully erased in [[ElimErasedValueType]]).
*
* @param tp The type to erase.
*/
def valueErasure(tp: Type)(implicit ctx: Context): Type =
erasureFn(isJava = false, semiEraseVCs = true, isConstructor = false, wildcardOK = false)(tp)(erasureCtx)
erasureFn(isJava = false, semiEraseVCs = true, isConstructor = false, wildcardOK = false)(tp)(preErasureCtx)

/** Like value class erasure, but value classes erase to their underlying type erasure */
def fullErasure(tp: Type)(implicit ctx: Context): Type =
Expand All @@ -156,7 +157,7 @@ object TypeErasure {
def sigName(tp: Type, isJava: Boolean)(implicit ctx: Context): TypeName = {
val normTp = tp.underlyingIfRepeated(isJava)
val erase = erasureFn(isJava, semiEraseVCs = false, isConstructor = false, wildcardOK = true)
erase.sigName(normTp)(erasureCtx)
erase.sigName(normTp)(preErasureCtx)
}

/** The erasure of a top-level reference. Differs from normal erasure in that
Expand Down Expand Up @@ -195,9 +196,9 @@ object TypeErasure {

if (defn.isPolymorphicAfterErasure(sym)) eraseParamBounds(sym.info.asInstanceOf[PolyType])
else if (sym.isAbstractType) TypeAlias(WildcardType)
else if (sym.isConstructor) outer.addParam(sym.owner.asClass, erase(tp)(erasureCtx))
else if (sym.is(Label)) erase.eraseResult(sym.info)(erasureCtx)
else erase.eraseInfo(tp, sym)(erasureCtx) match {
else if (sym.isConstructor) outer.addParam(sym.owner.asClass, erase(tp)(preErasureCtx))
else if (sym.is(Label)) erase.eraseResult(sym.info)(preErasureCtx)
else erase.eraseInfo(tp, sym)(preErasureCtx) match {
case einfo: MethodType =>
if (sym.isGetter && einfo.resultType.isRef(defn.UnitClass))
MethodType(Nil, defn.BoxedUnitClass.typeRef)
Expand Down Expand Up @@ -526,21 +527,25 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
* `PolyType`s are treated. `eraseInfo` maps them them to method types, whereas `apply` maps them
* to the underlying type.
*/
def eraseInfo(tp: Type, sym: Symbol)(implicit ctx: Context): Type = tp match {
case ExprType(rt) =>
if (sym.is(Param)) apply(tp)
// Note that params with ExprTypes are eliminated by ElimByName,
// but potentially re-introduced by ResolveSuper, when we add
// forwarders to mixin methods.
// See doc comment for ElimByName for speculation how we could improve this.
else MethodType(Nil, Nil, eraseResult(sym.info.finalResultType.underlyingIfRepeated(isJava)))
case tp: PolyType =>
eraseResult(tp.resultType) match {
case rt: MethodType => rt
case rt => MethodType(Nil, Nil, rt)
}
case tp => this(tp)
}
def eraseInfo(tp: Type, sym: Symbol)(implicit ctx: Context): Type =
val tp1 = tp match
case tp: MethodicType => integrateContextResults(tp, contextResultCount(sym))
case _ => tp
tp1 match
case ExprType(rt) =>
if sym.is(Param) then apply(tp1)
// Note that params with ExprTypes are eliminated by ElimByName,
// but potentially re-introduced by ResolveSuper, when we add
// forwarders to mixin methods.
// See doc comment for ElimByName for speculation how we could improve this.
else
MethodType(Nil, Nil,
eraseResult(sym.info.finalResultType.underlyingIfRepeated(isJava)))
case tp1: PolyType =>
eraseResult(tp1.resultType) match
case rt: MethodType => rt
case rt => MethodType(Nil, Nil, rt)
case tp1 => this(tp1)

private def eraseDerivedValueClassRef(tref: TypeRef)(implicit ctx: Context): Type = {
val cls = tref.symbol.asClass
Expand Down
27 changes: 7 additions & 20 deletions compiler/src/dotty/tools/dotc/transform/Bridges.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import Symbols._, Types._, Contexts._, Decorators._, Flags._, Scopes._
import DenotTransformers._
import ast.untpd
import collection.{mutable, immutable}
import ShortcutImplicits._
import util.Spans.Span
import util.SourcePosition

Expand All @@ -29,9 +28,7 @@ class Bridges(root: ClassSymbol, thisPhase: DenotTransformer)(implicit ctx: Cont
override def parents = Array(root.superClass)

override def exclude(sym: Symbol) =
!sym.isOneOf(MethodOrModule) ||
isImplicitShortcut(sym) ||
super.exclude(sym)
!sym.isOneOf(MethodOrModule) || super.exclude(sym)
}

//val site = root.thisType
Expand Down Expand Up @@ -102,9 +99,10 @@ class Bridges(root: ClassSymbol, thisPhase: DenotTransformer)(implicit ctx: Cont
}

def bridgeRhs(argss: List[List[Tree]]) = {
assert(argss.tail.isEmpty)
val ref = This(root).select(member)
if (member.info.isParameterless) ref // can happen if `member` is a module
else ref.appliedToArgss(argss)
else Erasure.partialApply(ref, argss.head)
}

bridges += DefDef(bridge, bridgeRhs(_).withSpan(bridge.span))
Expand All @@ -113,24 +111,13 @@ class Bridges(root: ClassSymbol, thisPhase: DenotTransformer)(implicit ctx: Cont
/** Add all necessary bridges to template statements `stats`, and remove at the same
* time deferred methods in `stats` that are replaced by a bridge with the same signature.
*/
def add(stats: List[untpd.Tree]): List[untpd.Tree] = {
def add(stats: List[untpd.Tree]): List[untpd.Tree] =
val opc = new BridgesCursor()(preErasureCtx)
val ectx = ctx.withPhase(thisPhase)
while (opc.hasNext) {
if (!opc.overriding.is(Deferred)) {
while opc.hasNext do
if !opc.overriding.is(Deferred) then
addBridgeIfNeeded(opc.overriding, opc.overridden)

if (needsImplicitShortcut(opc.overriding)(ectx) && needsImplicitShortcut(opc.overridden)(ectx))
// implicit shortcuts do not show up in the Bridges cursor, since they
// are created only when referenced. Therefore we need to generate a bridge
// for them specifically, if one is needed for the original methods.
addBridgeIfNeeded(
shortcutMethod(opc.overriding, thisPhase)(ectx),
shortcutMethod(opc.overridden, thisPhase)(ectx))
}
opc.next()
}
if (bridges.isEmpty) stats
if bridges.isEmpty then stats
else stats.filterNot(stat => toBeRemoved contains stat.symbol) ::: bridges.toList
}
}
145 changes: 145 additions & 0 deletions compiler/src/dotty/tools/dotc/transform/ContextFunctionResults.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
package dotty.tools
package dotc
package transform

import core._
import Contexts._, Symbols._, Types._, Annotations._, Constants._
import StdNames.nme
import ast.untpd
import ast.tpd._
import config.Config

object ContextFunctionResults:

/** Annotate methods that have context function result types directly matched by context
* closures on their right-hand side. Parameters to such closures will be integrated
* as additional method parameters in erasure.
*/
def annotateContextResults(mdef: DefDef)(using Context): Unit =
def contextResultCount(rhs: Tree, tp: Type): Int = tp match
case defn.ContextFunctionType(_, resTpe, _) =>
rhs match
case closureDef(meth) => 1 + contextResultCount(meth.rhs, resTpe)
case _ => 0
case _ => 0

val meth = mdef.symbol

// Disable context result annotations for anonymous functions
// and for implementations of PolyFunction
def disabled =
meth.isAnonymousFunction
|| meth.name == nme.apply
&& meth.owner.isAnonymousClass
&& meth.owner.info.parents.exists(_.isRef(defn.PolyFunctionClass))

val count = contextResultCount(mdef.rhs, mdef.tpt.tpe)

if Config.flattenContextFunctionResults && count != 0 && !disabled then
val countAnnot = Annotation(defn.ContextResultCountAnnot, Literal(Constant(count)))
mdef.symbol.addAnnotation(countAnnot)
end annotateContextResults

/** The argument of a ContextResultCount annotation, or 0 if none exists.
* See PostTyper#annotateContextResults.
*/
def contextResultCount(sym: Symbol)(using Context): Int =
sym.getAnnotation(defn.ContextResultCountAnnot) match
case Some(annot) =>
val ast.Trees.Literal(Constant(crCount: Int)) :: Nil: @unchecked = annot.arguments
crCount
case none => 0

/** Turn the first `crCount` context function types in the result type of `tp`
* into the curried method types.
*/
def integrateContextResults(tp: Type, crCount: Int)(using Context): Type =
if crCount == 0 then tp
else tp match
case ExprType(rt) =>
integrateContextResults(rt, crCount)
case tp: MethodOrPoly =>
tp.derivedLambdaType(resType = integrateContextResults(tp.resType, crCount))
case defn.ContextFunctionType(argTypes, resType, isErased) =>
val methodType: MethodTypeCompanion =
if isErased then ErasedMethodType else MethodType
methodType(argTypes, integrateContextResults(resType, crCount - 1))

/** The total number of parameters of method `sym`, not counting
* erased parameters, but including context result parameters.
*/
def totalParamCount(sym: Symbol)(using Context): Int =

def contextParamCount(tp: Type, crCount: Int): Int =
if crCount == 0 then 0
else
val defn.ContextFunctionType(params, resTpe, isErased): @unchecked = tp
val rest = contextParamCount(resTpe, crCount - 1)
if isErased then rest else params.length + rest

def normalParamCount(tp: Type): Int = tp.widenExpr.stripPoly match
case mt @ MethodType(pnames) =>
val rest = normalParamCount(mt.resType)
if mt.isErasedMethod then rest else pnames.length + rest
case _ => contextParamCount(tp, contextResultCount(sym))

normalParamCount(sym.info)
end totalParamCount

/** The rightmost context function type in the result type of `meth`
* that represents `paramCount` curried, non-erased parameters that
* are included in the `contextResultCount` of `meth`.
* Example:
*
* Say we have `def m(x: A): B ?=> (C1, C2, C3) ?=> D ?=> E ?=> F`,
* paramCount == 4, and the contextResultCount of `m` is 3.
* Then we return the type `(C1, C2, C3) ?=> D ?=> E ?=> F`, since this
* type covers the 4 rightmost parameters C1, C2, C3 and D before the
* contextResultCount runs out at E ?=> F.
* Erased parameters are ignored; they contribute nothing to the
* parameter count.
*/
def contextFunctionResultTypeCovering(meth: Symbol, paramCount: Int)(using ctx: Context) =
given Context = ctx.withPhase(ctx.erasurePhase)

// Recursive instances return pairs of context types and the
// # of parameters they represent.
def missingCR(tp: Type, crCount: Int): (Type, Int) =
if crCount == 0 then (tp, 0)
else
val defn.ContextFunctionType(formals, resTpe, isErased): @unchecked = tp
val result @ (rt, nparams) = missingCR(resTpe, crCount - 1)
assert(nparams <= paramCount)
if nparams == paramCount || isErased then result
else (tp, nparams + formals.length)
missingCR(meth.info.finalResultType, contextResultCount(meth))._1
end contextFunctionResultTypeCovering

/** Should selection `tree` be eliminated since it refers to an `apply`
* node of a context function type whose parameters will end up being
* integrated in the preceding method?
* @param `n` the select nodes seen in previous recursive iterations of this method
*/
def integrateSelect(tree: untpd.Tree, n: Int = 0)(using ctx: Context): Boolean =
if ctx.erasedTypes then
integrateSelect(tree, n)(using ctx.withPhase(ctx.erasurePhase))
else tree match
case Select(qual, name) =>
if name == nme.apply && defn.isContextFunctionClass(tree.symbol.maybeOwner) then
integrateSelect(qual, n + 1)
else
n > 0 && contextResultCount(tree.symbol) >= n
case Ident(name) =>
n > 0 && contextResultCount(tree.symbol) >= n
case Apply(fn, args) =>
integrateSelect(fn, n)
case TypeApply(fn, _) =>
integrateSelect(fn, n)
case Block(_, expr) =>
integrateSelect(expr, n)
case Inlined(_, _, expr) =>
integrateSelect(expr, n)
case _ =>
false

end ContextFunctionResults
Loading