Skip to content

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

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

Closed
wants to merge 17 commits into from
Closed
Show file tree
Hide file tree
Changes from 6 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
3 changes: 1 addition & 2 deletions compiler/src/dotty/tools/dotc/Compiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,10 @@ class Compiler {
new ExpandSAMs) :: // Expand single abstract method closures to anonymous classes
List(new init.Checker) :: // Check initialization of objects
List(new ElimRepeated, // Rewrite vararg parameters and arguments
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 anonyous function and a closure node referring to it in a block, without any wrappigs */

Choose a reason for hiding this comment

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

typo:
anonyous -> anonymous
wrappigs -> 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
30 changes: 29 additions & 1 deletion compiler/src/dotty/tools/dotc/ast/tpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,14 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
Closure(meth, tss => rhsFn(tss.head).changeOwner(ctx.owner, meth))
}

/** A <byname>(...) application */
object ByName:
def apply(tree: Tree)(using Context): Apply =
Apply(ref(defn.byNameMethod), tree :: Nil)
def unapply(tree: Apply)(using Context): Option[Tree] =
if tree.fun.symbol == defn.byNameMethod then Some(tree.args.head)
else None

def CaseDef(pat: Tree, guard: Tree, body: Tree)(using Context): CaseDef =
ta.assignType(untpd.CaseDef(pat, guard, body), pat, body)

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

/** Is tree a by-name application `<byname>(arg)`? */
def isByName(using Context): Boolean = tree match
case Apply(fun, _) => fun.symbol == defn.byNameMethod
case _ => false

/** 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(underlying) => wrapByName
case _ => tree

/** `tree == that` */
def equal(that: Tree)(using Context): Tree =
if (that.tpe.widen.isRef(defn.NothingClass))
Expand Down Expand Up @@ -1108,7 +1136,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
19 changes: 11 additions & 8 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -447,16 +447,14 @@ 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))

/** Method wrapping by-name arguments; created by Typer, eliminated in ByNameLambda */
@tu lazy val byNameMethod: TermSymbol = enterMethod(OpsPackageClass, nme.BYNAME,
MethodType(List(AnyType))(mt => FunctionOf(Nil, mt.paramRefs(0), isContextual = true)))

@tu lazy val NothingClass: ClassSymbol = enterCompleteClassSymbol(
ScalaPackageClass, tpnme.Nothing, AbstractFinal, List(AnyType))
def NothingType: TypeRef = NothingClass.typeRef
Expand Down Expand Up @@ -1081,6 +1079,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 +1299,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 +1546,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 Expand Up @@ -1809,7 +1812,7 @@ class Definitions {

/** Lists core methods that don't have underlying bytecode, but are synthesized on-the-fly in every reflection universe */
@tu lazy val syntheticCoreMethods: List[TermSymbol] =
AnyMethods ++ ObjectMethods ++ List(String_+, throwMethod)
AnyMethods ++ ObjectMethods ++ List(String_+, throwMethod, byNameMethod)

@tu lazy val reservedScalaClassNames: Set[Name] = syntheticScalaClasses.map(_.name).toSet

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
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/StdNames.scala
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ object StdNames {
val BITMAP_TRANSIENT: N = s"${BITMAP_PREFIX}trans$$" // initialization bitmap for transient lazy vals
val BITMAP_CHECKINIT: N = s"${BITMAP_PREFIX}init$$" // initialization bitmap for checkinit values
val BITMAP_CHECKINIT_TRANSIENT: N = s"${BITMAP_PREFIX}inittrans$$" // initialization bitmap for transient checkinit values
val BYNAME: N = "<byname>"
val DEFAULT_GETTER: N = str.DEFAULT_GETTER
val DEFAULT_GETTER_INIT: N = "$lessinit$greater"
val DO_WHILE_PREFIX: N = "doWhile$"
Expand Down Expand Up @@ -445,7 +446,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
Loading