Skip to content
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

Make by-name types first-class value types #14225

Closed
wants to merge 17 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 3 additions & 4 deletions compiler/src/dotty/tools/dotc/Compiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,13 @@ class Compiler {
new CheckLoopingImplicits, // Check that implicit defs do not call themselves in an infinite loop
new BetaReduce, // Reduce closure applications
new InlineVals, // Check right hand-sides of an `inline val`s
new ExpandSAMs) :: // Expand single abstract method closures to anonymous classes
new ExpandSAMs, // Expand single abstract method closures to anonymous classes
new ElimRepeated) :: // Rewrite vararg parameters and arguments
List(new init.Checker) :: // Check initialization of objects
List(new ElimRepeated, // Rewrite vararg parameters and arguments
List(new ByNameLambda, // Replace by-name applications with closures
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 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 @@ -83,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
8 changes: 8 additions & 0 deletions compiler/src/dotty/tools/dotc/ast/TreeInfo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -726,6 +726,14 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] =>
case _ => tree
}

/** An anonymous function and a closure node referring to it in a block, without any wrappings */
object simpleClosure:
def unapply(tree: Tree)(using Context): Option[(DefDef, Closure)] = tree match
case Block((meth : DefDef) :: Nil, closure: Closure) if meth.symbol == closure.meth.symbol =>
Some((meth, closure))
case _ =>
None

/** The variables defined by a pattern, in reverse order of their appearance. */
def patVars(tree: Tree)(using Context): List[Symbol] = {
val acc = new TreeAccumulator[List[Symbol]] {
Expand Down
15 changes: 15 additions & 0 deletions compiler/src/dotty/tools/dotc/ast/Trees.scala
Original file line number Diff line number Diff line change
Expand Up @@ -623,6 +623,12 @@ object Trees {
def name: Name = bind.name
}

/** By-name wrapper; created by Typer and TreUnpickler, eliminated in TreePickler and ByNameLambda */
case class ByName[-T >: Untyped] private[ast] (expr: Tree[T])(implicit @constructorOnly src: SourceFile)
extends TermTree[T] {
type ThisTree[-T >: Untyped] = ByName[T]
}

/** return expr
* where `from` refers to the method or label from which the return takes place
* After program transformations this is not necessarily the enclosing method, because
Expand Down Expand Up @@ -1075,6 +1081,7 @@ object Trees {
type InlineMatch = Trees.InlineMatch[T]
type CaseDef = Trees.CaseDef[T]
type Labeled = Trees.Labeled[T]
type ByName = Trees.ByName[T]
type Return = Trees.Return[T]
type WhileDo = Trees.WhileDo[T]
type Try = Trees.Try[T]
Expand Down Expand Up @@ -1228,6 +1235,10 @@ object Trees {
case tree: Labeled if (bind eq tree.bind) && (expr eq tree.expr) => tree
case _ => finalize(tree, untpd.Labeled(bind, expr)(sourceFile(tree)))
}
def ByName(tree: Tree)(expr: Tree)(using Context): ByName = tree match {
case tree: ByName if expr eq tree.expr => tree
case _ => finalize(tree, untpd.ByName(expr)(sourceFile(tree)))
}
def Return(tree: Tree)(expr: Tree, from: Tree)(using Context): Return = tree match {
case tree: Return if (expr eq tree.expr) && (from eq tree.from) => tree
case _ => finalize(tree, untpd.Return(expr, from)(sourceFile(tree)))
Expand Down Expand Up @@ -1411,6 +1422,8 @@ object Trees {
cpy.CaseDef(tree)(transform(pat), transform(guard), transform(body))
case Labeled(bind, expr) =>
cpy.Labeled(tree)(transformSub(bind), transform(expr))
case ByName(expr) =>
cpy.ByName(tree)(transform(expr))
case Return(expr, from) =>
cpy.Return(tree)(transform(expr), transformSub(from))
case WhileDo(cond, body) =>
Expand Down Expand Up @@ -1556,6 +1569,8 @@ object Trees {
this(this(this(x, pat), guard), body)
case Labeled(bind, expr) =>
this(this(x, bind), expr)
case ByName(expr) =>
this(x, expr)
case Return(expr, from) =>
this(this(x, expr), from)
case WhileDo(cond, body) =>
Expand Down
23 changes: 22 additions & 1 deletion compiler/src/dotty/tools/dotc/ast/tpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,9 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
def Labeled(sym: TermSymbol, expr: Tree)(using Context): Labeled =
Labeled(Bind(sym, EmptyTree), expr)

def ByName(expr: Tree)(using Context): ByName =
ta.assignType(untpd.ByName(expr), expr)

def Return(expr: Tree, from: Tree)(using Context): Return =
ta.assignType(untpd.Return(expr, from))

Expand Down Expand Up @@ -698,6 +701,9 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
override def Labeled(tree: Tree)(bind: Bind, expr: Tree)(using Context): Labeled =
ta.assignType(untpdCpy.Labeled(tree)(bind, expr))

override def ByName(tree: Tree)(expr: Tree)(using Context): ByName =
ta.assignType(untpdCpy.ByName(tree)(expr), expr)

override def Return(tree: Tree)(expr: Tree, from: Tree)(using Context): Return =
ta.assignType(untpdCpy.Return(tree)(expr, from))

Expand Down Expand Up @@ -955,6 +961,21 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
def ensureApplied(using Context): Tree =
if (tree.tpe.widen.isParameterless) tree else tree.appliedToNone

/** If tree is a by-name application `<byname>(arg)` return `arg`, otherwise the original tree */
def dropByName(using Context): Tree = tree match
case ByName(body) => body
case _ => tree

/** Wrap tree in a by-name application unless it is already one */
def wrapByName(using Context): Tree = tree match
case ByName(_) => tree
case _ => ByName(tree)

/** Make sure tree is by-name application if `formal` is a by-name parameter type */
def alignByName(formal: Type)(using Context) = formal match
case ByNameType(_) if !tree.tpe.widen.isByName => ByName(tree)
case _ => tree

/** `tree == that` */
def equal(that: Tree)(using Context): Tree =
if (that.tpe.widen.isRef(defn.NothingClass))
Expand Down Expand Up @@ -1108,7 +1129,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {

def etaExpandCFT(using Context): Tree =
def expand(target: Tree, tp: Type)(using Context): Tree = tp match
case defn.ContextFunctionType(argTypes, resType, isErased) =>
case defn.ContextFunctionType(argTypes, resType, isErased) if argTypes.nonEmpty =>
val anonFun = newAnonFun(
ctx.owner,
MethodType.companion(isContextual = true, isErased = isErased)(argTypes, resType),
Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/ast/untpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
def InlineMatch(selector: Tree, cases: List[CaseDef])(implicit src: SourceFile): Match = new InlineMatch(selector, cases)
def CaseDef(pat: Tree, guard: Tree, body: Tree)(implicit src: SourceFile): CaseDef = new CaseDef(pat, guard, body)
def Labeled(bind: Bind, expr: Tree)(implicit src: SourceFile): Labeled = new Labeled(bind, expr)
def ByName(expr: Tree)(implicit src: SourceFile): ByName = new ByName(expr)
def Return(expr: Tree, from: Tree)(implicit src: SourceFile): Return = new Return(expr, from)
def WhileDo(cond: Tree, body: Tree)(implicit src: SourceFile): WhileDo = new WhileDo(cond, body)
def Try(expr: Tree, cases: List[CaseDef], finalizer: Tree)(implicit src: SourceFile): Try = new Try(expr, cases, finalizer)
Expand Down
13 changes: 6 additions & 7 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 Expand Up @@ -1081,6 +1075,9 @@ class Definitions {
}
}

final def isByNameClass(sym: Symbol): Boolean =
sym eq ContextFunction0

final def isCompiletime_S(sym: Symbol)(using Context): Boolean =
sym.name == tpnme.S && sym.owner == CompiletimeOpsIntModuleClass

Expand Down Expand Up @@ -1298,6 +1295,7 @@ class Definitions {
@tu lazy val Function0: Symbol = FunctionClass(0)
@tu lazy val Function1: Symbol = FunctionClass(1)
@tu lazy val Function2: Symbol = FunctionClass(2)
@tu lazy val ContextFunction0: Symbol = FunctionClass(0, isContextual = true)

def FunctionType(n: Int, isContextual: Boolean = false, isErased: Boolean = false)(using Context): TypeRef =
FunctionClass(n, isContextual && !ctx.erasedTypes, isErased).typeRef
Expand Down Expand Up @@ -1544,7 +1542,8 @@ class Definitions {
new PerRun(Function2SpecializedReturnTypes.map(_.symbol))

def isSpecializableFunction(cls: ClassSymbol, paramTypes: List[Type], retType: Type)(using Context): Boolean =
paramTypes.length <= 2 && cls.derivesFrom(FunctionClass(paramTypes.length))
paramTypes.length <= 2
&& (cls.derivesFrom(FunctionClass(paramTypes.length)) || isByNameClass(cls))
&& isSpecializableFunctionSAM(paramTypes, retType)

/** If the Single Abstract Method of a Function class has this type, is it specializable? */
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/JavaNullInterop.scala
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ object JavaNullInterop {
// then its Scala signature will be `def setNames(names: (String|Null)*): Unit`.
// This is because `setNames(null)` passes as argument a single-element array containing the value `null`,
// and not a `null` array.
!tp.isRef(defn.RepeatedParamClass)
!tp.isRepeatedParam
case _ => true
})

Expand Down
1 change: 0 additions & 1 deletion compiler/src/dotty/tools/dotc/core/StdNames.scala
Original file line number Diff line number Diff line change
Expand Up @@ -445,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
4 changes: 3 additions & 1 deletion compiler/src/dotty/tools/dotc/core/TypeApplications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -412,11 +412,13 @@ class TypeApplications(val self: Type) extends AnyVal {

/** Translate a type of the form From[T] to either To[T] or To[? <: T] (if `wildcardArg` is set). Keep other types as they are.
* `from` and `to` must be static classes, both with one type parameter, and the same variance.
* Do the same for by name types => From[T] and => To[T]
* Do the same for ExprTypes and by-name types => From[T] and => To[T].
*/
def translateParameterized(from: ClassSymbol, to: ClassSymbol, wildcardArg: Boolean = false)(using Context): Type = self match {
case self @ ExprType(tp) =>
self.derivedExprType(tp.translateParameterized(from, to))
case self @ ByNameType(tp) =>
self.derivedByNameType(tp.translateParameterized(from, to))
case _ =>
if (self.derivesFrom(from)) {
def elemType(tp: Type): Type = tp.widenDealias match
Expand Down
14 changes: 0 additions & 14 deletions compiler/src/dotty/tools/dotc/core/TypeComparer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2335,13 +2335,6 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
}
case tp1: RecType =>
tp1.rebind(distributeAnd(tp1.parent, tp2))
case ExprType(rt1) =>
tp2 match {
case ExprType(rt2) =>
ExprType(rt1 & rt2)
case _ =>
NoType
}
case tp1: TypeVar if tp1.isInstantiated =>
tp1.underlying & tp2
case tp1: AnnotatedType if !tp1.isRefining =>
Expand All @@ -2359,13 +2352,6 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
* The rhs is a proper supertype of the lhs.
*/
private def distributeOr(tp1: Type, tp2: Type, isSoft: Boolean = true): Type = tp1 match {
case ExprType(rt1) =>
tp2 match {
case ExprType(rt2) =>
ExprType(lub(rt1, rt2, isSoft = isSoft))
case _ =>
NoType
}
case tp1: TypeVar if tp1.isInstantiated =>
lub(tp1.underlying, tp2, isSoft = isSoft)
case tp1: AnnotatedType if !tp1.isRefining =>
Expand Down
17 changes: 4 additions & 13 deletions compiler/src/dotty/tools/dotc/core/TypeErasure.scala
Original file line number Diff line number Diff line change
Expand Up @@ -603,8 +603,6 @@ class TypeErasure(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConst
this(tp.widen)
case SuperType(thistpe, supertpe) =>
SuperType(this(thistpe), this(supertpe))
case ExprType(rt) =>
defn.FunctionType(0)
case RefinedType(parent, nme.apply, refinedInfo) if parent.typeSymbol eq defn.PolyFunctionClass =>
erasePolyFunctionApply(refinedInfo)
case tp: TypeProxy =>
Expand Down Expand Up @@ -692,7 +690,7 @@ class TypeErasure(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConst
else defn.TupleXXLClass.typeRef
}

/** The erasure of a symbol's info. This is different from `apply` in the way `ExprType`s and
/** The erasure of a symbol's info. This is different from `apply` in the way
* `PolyType`s are treated. `eraseInfo` maps them them to method types, whereas `apply` maps them
* to the underlying type.
*/
Expand All @@ -702,14 +700,9 @@ class TypeErasure(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConst
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(rt.translateFromRepeated(toArray = sourceLanguage.isJava)))
assert(!sym.is(Param))
MethodType(Nil, Nil,
eraseResult(rt.translateFromRepeated(toArray = sourceLanguage.isJava)))
case tp1: PolyType =>
eraseResult(tp1.resultType) match
case rt: MethodType => rt
Expand Down Expand Up @@ -820,8 +813,6 @@ class TypeErasure(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConst
sigName(elem) ++ "[]"
case tp: TermRef =>
sigName(tp.widen)
case ExprType(rt) =>
sigName(defn.FunctionOf(Nil, rt))
case tp: TypeVar =>
val inst = tp.instanceOpt
if (inst.exists) sigName(inst) else tpnme.Uninstantiated
Expand Down
4 changes: 4 additions & 0 deletions compiler/src/dotty/tools/dotc/core/TypeErrors.scala
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ class MissingType(pre: Type, name: Name) extends TypeError {
}
}

class InvalidPrefix(pre: Type, desig: Designator) extends TypeError:
override def produceMessage(using Context): Message =
i"malformed type: $pre is not a legal prefix for $desig"

class RecursionOverflow(val op: String, details: => String, val previous: Throwable, val weight: Int) extends TypeError {

def explanation: String = s"$op $details"
Expand Down