Skip to content

Commit

Permalink
Install the new ElimByName in place of ByNameClosures
Browse files Browse the repository at this point in the history
... and drop the old ElimByName.
  • Loading branch information
odersky committed Jan 20, 2022
1 parent 81cbe32 commit bf937ed
Show file tree
Hide file tree
Showing 11 changed files with 161 additions and 345 deletions.
4 changes: 1 addition & 3 deletions compiler/src/dotty/tools/dotc/Compiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,11 @@ class Compiler {
new InlineVals, // Check right hand-sides of an `inline val`s
new ExpandSAMs, // Expand single abstract method closures to anonymous classes
new ElimRepeated) :: // Rewrite vararg parameters and arguments
List(new ElimByNameParams) ::
List(new init.Checker) :: // Check initialization of objects
List(new ProtectedAccessors, // Add accessors for protected members
new ExtensionMethods, // Expand methods of value classes with extension methods
new UncacheGivenAliases, // Avoid caching RHS of simple parameterless given aliases
new ByNameClosures, // Expand arguments to by-name parameters to closures
new ElimByName, // Map by-name parameters to functions
new HoistSuperArgs, // Hoist complex arguments of supercalls to enclosing scope
new SpecializeApplyMethods, // Adds specialized methods to FunctionN
new RefChecks, // Various checks mostly related to abstract members and overriding
Expand All @@ -84,7 +83,6 @@ class Compiler {
new sjs.ExplicitJSClasses, // Make all JS classes explicit (Scala.js only)
new ExplicitOuter, // Add accessors to outer classes from nested ones.
new ExplicitSelf, // Make references to non-trivial self types explicit as casts
new ElimByName, // Expand by-name parameter references
new StringInterpolatorOpt) :: // Optimizes raw and s string interpolators by rewriting them to string concatenations
List(new PruneErasedDefs, // Drop erased definitions from scopes and simplify erased expressions
new UninitializedDefs, // Replaces `compiletime.uninitialized` by `_`
Expand Down
6 changes: 0 additions & 6 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -447,12 +447,6 @@ class Definitions {
@tu lazy val andType: TypeSymbol = enterBinaryAlias(tpnme.AND, AndType(_, _))
@tu lazy val orType: TypeSymbol = enterBinaryAlias(tpnme.OR, OrType(_, _, soft = false))

/** Marker method to indicate an argument to a call-by-name parameter.
* Created by byNameClosures and elimByName, eliminated by Erasure,
*/
@tu lazy val cbnArg: TermSymbol = enterPolyMethod(OpsPackageClass, nme.cbnArg, 1,
pt => MethodType(List(FunctionOf(Nil, pt.paramRefs(0))), pt.paramRefs(0)))

/** Method representing a throw */
@tu lazy val throwMethod: TermSymbol = enterMethod(OpsPackageClass, nme.THROWkw,
MethodType(List(ThrowableType), NothingType))
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/Phases.scala
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ object Phases {
myCollectNullableFieldsPhase = phaseOfClass(classOf[CollectNullableFields])
myRefChecksPhase = phaseOfClass(classOf[RefChecks])
myElimRepeatedPhase = phaseOfClass(classOf[ElimRepeated])
myElimByNamePhase = phaseOfClass(classOf[ElimByNameParams])
myElimByNamePhase = phaseOfClass(classOf[ElimByName])
myExtensionMethodsPhase = phaseOfClass(classOf[ExtensionMethods])
myErasurePhase = phaseOfClass(classOf[Erasure])
myElimErasedValueTypePhase = phaseOfClass(classOf[ElimErasedValueType])
Expand Down
2 changes: 0 additions & 2 deletions compiler/src/dotty/tools/dotc/core/StdNames.scala
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,6 @@ object StdNames {
// ----- Type names -----------------------------------------

final val BYNAME_PARAM_CLASS: N = "<byname>"
final val BYNAME_PARAM_FUN: N = "<function0-byname>"
final val EQUALS_PATTERN: N = "<equals>"
final val LOCAL_CHILD: N = "<local child>"
final val REPEATED_PARAM_CLASS: N = "<repeated>"
Expand Down Expand Up @@ -446,7 +445,6 @@ object StdNames {
val bytes: N = "bytes"
val canEqual_ : N = "canEqual"
val canEqualAny : N = "canEqualAny"
val cbnArg: N = "<cbn-arg>"
val checkInitialized: N = "checkInitialized"
val ClassManifestFactory: N = "ClassManifestFactory"
val classOf: N = "classOf"
Expand Down
6 changes: 3 additions & 3 deletions compiler/src/dotty/tools/dotc/core/TypeComparer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ package core

import Types._, Contexts._, Symbols._, Flags._, Names._, NameOps._, Denotations._
import Decorators._
import Phases.gettersPhase
import Phases.{gettersPhase, elimByNamePhase}
import StdNames.nme
import TypeOps.refineUsingParent
import collection.mutable
Expand Down Expand Up @@ -1511,10 +1511,10 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
}
arg2.contains(arg1norm)
case ExprType(arg2res)
if ctx.phaseId > ctx.base.elimByNamePhase.id && !ctx.erasedTypes
if ctx.phaseId > elimByNamePhase.id && !ctx.erasedTypes
&& defn.isByNameFunction(arg1) =>
// ElimByName maps `=> T` to `()? => T`, but only in method parameters. It leaves
// embedded `=> T` alone. This clause needs to compensate for that.
// embedded `=> T` arguments alone. This clause needs to compensate for that.
isSubArg(arg1.argInfos.head, arg2res)
case _ =>
arg1 match {
Expand Down
40 changes: 0 additions & 40 deletions compiler/src/dotty/tools/dotc/transform/ByNameClosures.scala

This file was deleted.

155 changes: 115 additions & 40 deletions compiler/src/dotty/tools/dotc/transform/ElimByName.scala
Original file line number Diff line number Diff line change
@@ -1,80 +1,155 @@
package dotty.tools.dotc
package dotty.tools
package dotc
package transform

import core._
import DenotTransformers.InfoTransformer
import Symbols._
import Contexts._
import Symbols._
import Types._
import Flags._
import SymDenotations.*
import DenotTransformers.InfoTransformer
import NameKinds.SuperArgName
import core.StdNames.nme
import ast.Trees._
import MegaPhase.*
import Decorators.*
import reporting.trace

/** This phase eliminates ExprTypes `=> T` as types of method parameter references, and replaces them b
* nullary function types. More precisely:
/** This phase implements the following transformations:
*
* For the types of parameter symbols:
* 1. For types of method and class parameters:
*
* => T ==> () => T
* => T becomes () ?=> T
*
* For cbn parameter values
* 2. For references to cbn-parameters:
*
* x ==> x()
* x becomes x.apply()
*
* Note: This scheme to have inconsistent types between method types (whose formal types are still
* ExprTypes and parameter valdefs (which are now FunctionTypes) is not pretty. There are two
* other options which have been abandoned or not yet pursued.
* 3. For arguments to cbn parameters
*
* Option 1: Transform => T to () => T also in method and function types. The problem with this is
* that is that it requires to look at every type, and this forces too much, causing
* Cyclic Reference errors. Abandoned for this reason.
* e becomes () ?=> e
*
* Option 2: Merge ElimByName with erasure, or have it run immediately before. This has not been
* tried yet.
* An opimization is applied: If the argument `e` to a cbn parameter is already
* of type `() ?=> T` and is a pure expression, we avoid (2) and (3), i.e. we
* pass `e` directly instead of `() ?=> e.apply()`.
*
* Note that `() ?=> T` cannot be written in source since user-defined context functions
* must have at least one parameter. We use the type here as a convenient marker
* of something that will erase to Function0, and where we know that it came from
* a by-name parameter.
*
* Note also that the transformation applies only to types of parameters, not to other
* occurrences of ExprTypes. In particular, embedded occurrences in function types
* such as `(=> T) => U` are left as-is. Trying to convert these as well would mean
* traversing all the types, and that leads to cyclic reference errors in many cases.
* This can cause problems in that we might have sometimes a `() ?=> T` where a
* `=> T` is expected. To compensate, there is a new clause in TypeComparer#subArg that
* declares `() ?=> T` to be a subtype of `T` for arguments of type applications,
* after this phase and up to erasure.
*/
class ElimByName extends TransformByNameApply with InfoTransformer {
class ElimByName extends MiniPhase, InfoTransformer:
thisPhase =>

import ast.tpd._

override def phaseName: String = ElimByName.name

override def changesParents: Boolean = true // Only true for by-names
override def runsAfterGroupsOf: Set[String] = Set(ExpandSAMs.name, ElimRepeated.name)
// - ExpanSAMs applied to partial functions creates methods that need
// to be fully defined before converting. Test case is pos/i9391.scala.
// - ByNameLambda needs to run in a group after ElimRepeated since ElimRepeated
// works on simple arguments but not converted closures, and it sees the arguments
// after transformations by subsequent miniphases in the same group.

override def changesParents: Boolean = true
// Expr types in parent type arguments are changed to function types.

/** If denotation had an ExprType before, it now gets a function type */
private def exprBecomesFunction(symd: SymDenotation)(using Context): Boolean =
symd.is(Param) || symd.is(ParamAccessor, butNot = Method)

def transformInfo(tp: Type, sym: Symbol)(using Context): Type = tp match {
case ExprType(rt) if exprBecomesFunction(sym) =>
defn.ByNameFunction(rt)
case tp: MethodType =>
def exprToFun(tp: Type) = tp match
case ExprType(rt) => defn.ByNameFunction(rt)
case tp => tp
tp.derivedLambdaType(
paramInfos = tp.paramInfos.mapConserve(exprToFun),
resType = transformInfo(tp.resType, sym))
case tp: PolyType =>
tp.derivedLambdaType(resType = transformInfo(tp.resType, sym))
case _ => tp
}

override def infoMayChange(sym: Symbol)(using Context): Boolean =
sym.is(Method) || exprBecomesFunction(sym)

def byNameClosure(arg: Tree, argType: Type)(using Context): Tree =
val meth = newAnonFun(ctx.owner, MethodType(Nil, argType), coord = arg.span)
Closure(meth,
_ => arg.changeOwnerAfter(ctx.owner, meth, thisPhase),
targetType = defn.ByNameFunction(argType)
).withSpan(arg.span)

/** Map `tree` to `tree.apply()` is `ftree` was of ExprType and becomes now a function */
private def applyIfFunction(tree: Tree, ftree: Tree)(using Context) =
if (isByNameRef(ftree)) {
private def isByNameRef(tree: Tree)(using Context): Boolean =
defn.isByNameFunction(tree.tpe.widen)

/** Map `tree` to `tree.apply()` is `tree` is of type `() ?=> T` */
private def applyIfFunction(tree: Tree)(using Context) =
if isByNameRef(tree) then
val tree0 = transformFollowing(tree)
atPhase(next) { tree0.select(defn.Function0_apply).appliedToNone }
}
atPhase(next) { tree0.select(defn.ContextFunction0_apply).appliedToNone }
else tree

override def transformIdent(tree: Ident)(using Context): Tree =
applyIfFunction(tree, tree)
applyIfFunction(tree)

override def transformSelect(tree: Select)(using Context): Tree =
applyIfFunction(tree, tree)
applyIfFunction(tree)

override def transformTypeApply(tree: TypeApply)(using Context): Tree = tree match {
case TypeApply(Select(_, nme.asInstanceOf_), arg :: Nil) =>
// tree might be of form e.asInstanceOf[x.type] where x becomes a function.
// See pos/t296.scala
applyIfFunction(tree, arg)
applyIfFunction(tree)
case _ => tree
}

override def transformApply(tree: Apply)(using Context): Tree =
trace(s"transforming ${tree.show} at phase ${ctx.phase}", show = true) {

def transformArg(arg: Tree, formal: Type): Tree = formal match
case defn.ByNameFunction(formalResult) =>
def stripTyped(t: Tree): Tree = t match
case Typed(expr, _) => stripTyped(expr)
case _ => t
stripTyped(arg) match
case Apply(Select(qual, nme.apply), Nil)
if isByNameRef(qual) && (isPureExpr(qual) || qual.symbol.isAllOf(InlineParam)) =>
qual
case _ =>
if isByNameRef(arg) || arg.symbol.name.is(SuperArgName)
then arg
else
var argType = arg.tpe.widenIfUnstable
if argType.isBottomType then argType = formalResult
byNameClosure(arg, argType)
case _ =>
arg

val mt @ MethodType(_) = tree.fun.tpe.widen
val args1 = tree.args.zipWithConserve(mt.paramInfos)(transformArg)
cpy.Apply(tree)(tree.fun, args1)
}

override def transformValDef(tree: ValDef)(using Context): Tree =
atPhase(next) {
if (exprBecomesFunction(tree.symbol))
if exprBecomesFunction(tree.symbol) then
cpy.ValDef(tree)(tpt = tree.tpt.withType(tree.symbol.info))
else tree
}

def transformInfo(tp: Type, sym: Symbol)(using Context): Type = tp match {
case ExprType(rt) => defn.FunctionOf(Nil, rt)
case _ => tp
}

override def infoMayChange(sym: Symbol)(using Context): Boolean = sym.isTerm && exprBecomesFunction(sym)
}

object ElimByName {
val name: String = "elimByName"
}
object ElimByName:
val name: String = "elimByName"
Loading

0 comments on commit bf937ed

Please sign in to comment.