Skip to content
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
6 changes: 4 additions & 2 deletions compiler/src/dotty/tools/dotc/ast/untpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,13 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
case class InterpolatedString(id: TermName, segments: List[Tree])(implicit @constructorOnly src: SourceFile)
extends TermTree

/** A function type */
/** A function type or closure */
case class Function(args: List[Tree], body: Tree)(implicit @constructorOnly src: SourceFile) extends Tree {
override def isTerm: Boolean = body.isTerm
override def isType: Boolean = body.isType
}

/** A function type with `implicit`, `erased`, or `given` modifiers */
/** A function type or closure with `implicit`, `erased`, or `given` modifiers */
class FunctionWithMods(args: List[Tree], body: Tree, val mods: Modifiers)(implicit @constructorOnly src: SourceFile)
extends Function(args, body)

Expand Down Expand Up @@ -217,6 +217,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
case class Transparent()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Transparent)

case class Infix()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Infix)

case class Impure()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Impure)
}

/** Modifiers and annotations for definitions
Expand Down
20 changes: 5 additions & 15 deletions compiler/src/dotty/tools/dotc/cc/CaptureOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,13 @@ def retainedElems(tree: Tree)(using Context): List[Tree] = tree match
case Apply(_, Typed(SeqLiteral(elems, _), _) :: Nil) => elems
case _ => Nil

class IllegalCaptureRef(tpe: Type) extends Exception

extension (tree: Tree)

def toCaptureRef(using Context): CaptureRef = tree.tpe.asInstanceOf[CaptureRef]
def toCaptureRef(using Context): CaptureRef = tree.tpe match
case ref: CaptureRef => ref
case tpe => throw IllegalCaptureRef(tpe)

def toCaptureSet(using Context): CaptureSet =
tree.getAttachment(Captures) match
Expand Down Expand Up @@ -59,20 +63,6 @@ extension (tp: Type)

def isBoxedCapturing(using Context) = !tp.boxedCaptured.isAlwaysEmpty

def canHaveInferredCapture(using Context): Boolean = tp match
case tp: TypeRef if tp.symbol.isClass =>
!tp.symbol.isValueClass && tp.symbol != defn.AnyClass
case _: TypeVar | _: TypeParamRef =>
false
case tp: TypeProxy =>
tp.superType.canHaveInferredCapture
case tp: AndType =>
tp.tp1.canHaveInferredCapture && tp.tp2.canHaveInferredCapture
case tp: OrType =>
tp.tp1.canHaveInferredCapture || tp.tp2.canHaveInferredCapture
case _ =>
false

def stripCapturing(using Context): Type = tp.dealiasKeepAnnots match
case CapturingType(parent, _, _) =>
parent.stripCapturing
Expand Down
6 changes: 6 additions & 0 deletions compiler/src/dotty/tools/dotc/cc/CaptureSet.scala
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ sealed abstract class CaptureSet extends Showable:
assert(v.isConst)
Const(v.elems)

final def isUniversal(using Context) =
elems.exists {
case ref: TermRef => ref.symbol == defn.captureRoot
case _ => false
}

/** Cast to variable. @pre: !isConst */
def asVar: Var =
assert(!isConst)
Expand Down
18 changes: 15 additions & 3 deletions compiler/src/dotty/tools/dotc/cc/CapturingType.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,22 @@ object CapturingType:
else AnnotatedType(parent, CaptureAnnotation(refs, boxed))

def unapply(tp: AnnotatedType)(using Context): Option[(Type, CaptureSet, Boolean)] =
if ctx.phase == Phases.checkCapturesPhase && tp.annot.symbol == defn.RetainsAnnot then
if ctx.phase == Phases.checkCapturesPhase then EventuallyCapturingType.unapply(tp)
else None

end CapturingType

object EventuallyCapturingType:

def unapply(tp: AnnotatedType)(using Context): Option[(Type, CaptureSet, Boolean)] =
if tp.annot.symbol == defn.RetainsAnnot then
tp.annot match
case ann: CaptureAnnotation => Some((tp.parent, ann.refs, ann.boxed))
case ann => Some((tp.parent, ann.tree.toCaptureSet, ann.tree.isBoxedCapturing))
case ann =>
try Some((tp.parent, ann.tree.toCaptureSet, ann.tree.isBoxedCapturing))
catch case ex: IllegalCaptureRef => None
else None

end CapturingType
end EventuallyCapturingType


124 changes: 82 additions & 42 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import Flags._, Scopes._, Decorators._, NameOps._, Periods._, NullOpsDecorator._
import unpickleScala2.Scala2Unpickler.ensureConstructor
import scala.collection.mutable
import collection.mutable
import Denotations.SingleDenotation
import Denotations.{SingleDenotation, staticRef}
import util.{SimpleIdentityMap, SourceFile, NoSource}
import typer.ImportInfo.RootRef
import Comments.CommentsContext
Expand Down Expand Up @@ -86,7 +86,7 @@ class Definitions {
*
* FunctionN traits follow this template:
*
* trait FunctionN[T0,...T{N-1}, R] extends Object {
* trait FunctionN[-T0,...-T{N-1}, +R] extends Object {
* def apply($x0: T0, ..., $x{N_1}: T{N-1}): R
* }
*
Expand All @@ -96,46 +96,65 @@ class Definitions {
*
* ContextFunctionN traits follow this template:
*
* trait ContextFunctionN[T0,...,T{N-1}, R] extends Object {
* trait ContextFunctionN[-T0,...,-T{N-1}, +R] extends Object {
* def apply(using $x0: T0, ..., $x{N_1}: T{N-1}): R
* }
*
* ErasedFunctionN traits follow this template:
*
* trait ErasedFunctionN[T0,...,T{N-1}, R] extends Object {
* trait ErasedFunctionN[-T0,...,-T{N-1}, +R] extends Object {
* def apply(erased $x0: T0, ..., $x{N_1}: T{N-1}): R
* }
*
* ErasedContextFunctionN traits follow this template:
*
* trait ErasedContextFunctionN[T0,...,T{N-1}, R] extends Object {
* trait ErasedContextFunctionN[-T0,...,-T{N-1}, +R] extends Object {
* def apply(using erased $x0: T0, ..., $x{N_1}: T{N-1}): R
* }
*
* ErasedFunctionN and ErasedContextFunctionN erase to Function0.
*
* EffXYZFunctionN afollow this template:
*
* type EffXYZFunctionN[-T0,...,-T{N-1}, +R] = {*} XYZFunctionN[T0,...,T{N-1}, R]
*/
def newFunctionNTrait(name: TypeName): ClassSymbol = {
private def newFunctionNType(name: TypeName): Symbol = {
val impure = name.startsWith("Impure")
val completer = new LazyType {
def complete(denot: SymDenotation)(using Context): Unit = {
val cls = denot.asClass.classSymbol
val decls = newScope
val arity = name.functionArity
val paramNamePrefix = tpnme.scala ++ str.NAME_JOIN ++ name ++ str.EXPAND_SEPARATOR
val argParamRefs = List.tabulate(arity) { i =>
enterTypeParam(cls, paramNamePrefix ++ "T" ++ (i + 1).toString, Contravariant, decls).typeRef
}
val resParamRef = enterTypeParam(cls, paramNamePrefix ++ "R", Covariant, decls).typeRef
val methodType = MethodType.companion(
isContextual = name.isContextFunction,
isImplicit = false,
isErased = name.isErasedFunction)
decls.enter(newMethod(cls, nme.apply, methodType(argParamRefs, resParamRef), Deferred))
denot.info =
ClassInfo(ScalaPackageClass.thisType, cls, ObjectType :: Nil, decls)
if impure then
val argParamNames = List.tabulate(arity)(tpnme.syntheticTypeParamName)
val argVariances = List.fill(arity)(Contravariant)
val underlyingName = name.asSimpleName.drop(6)
val underlyingClass = ScalaPackageVal.requiredClass(underlyingName)
denot.info = TypeAlias(
HKTypeLambda(argParamNames :+ "R".toTypeName, argVariances :+ Covariant)(
tl => List.fill(arity + 1)(TypeBounds.empty),
tl => CapturingType(underlyingClass.typeRef.appliedTo(tl.paramRefs),
CaptureSet.universal, boxed = false)
))
else
val cls = denot.asClass.classSymbol
val decls = newScope
val paramNamePrefix = tpnme.scala ++ str.NAME_JOIN ++ name ++ str.EXPAND_SEPARATOR
val argParamRefs = List.tabulate(arity) { i =>
enterTypeParam(cls, paramNamePrefix ++ "T" ++ (i + 1).toString, Contravariant, decls).typeRef
}
val resParamRef = enterTypeParam(cls, paramNamePrefix ++ "R", Covariant, decls).typeRef
val methodType = MethodType.companion(
isContextual = name.isContextFunction,
isImplicit = false,
isErased = name.isErasedFunction)
decls.enter(newMethod(cls, nme.apply, methodType(argParamRefs, resParamRef), Deferred))
denot.info =
ClassInfo(ScalaPackageClass.thisType, cls, ObjectType :: Nil, decls)
}
}
val flags = Trait | NoInits
newPermanentClassSymbol(ScalaPackageClass, name, flags, completer)
if impure then
newPermanentSymbol(ScalaPackageClass, name, EmptyFlags, completer)
else
newPermanentClassSymbol(ScalaPackageClass, name, Trait | NoInits, completer)
}

private def newMethod(cls: ClassSymbol, name: TermName, info: Type, flags: FlagSet = EmptyFlags): TermSymbol =
Expand Down Expand Up @@ -209,7 +228,7 @@ class Definitions {
val cls = ScalaPackageVal.moduleClass.asClass
cls.info.decls.openForMutations.useSynthesizer(
name =>
if (name.isTypeName && name.isSyntheticFunction) newFunctionNTrait(name.asTypeName)
if (name.isTypeName && name.isSyntheticFunction) newFunctionNType(name.asTypeName)
else NoSymbol)
cls
}
Expand Down Expand Up @@ -1273,37 +1292,54 @@ class Definitions {

@tu lazy val TupleType: Array[TypeRef] = mkArityArray("scala.Tuple", MaxTupleArity, 1)

/** Cached function types of arbitary arities.
* Function types are created on demand with newFunctionNTrait, which is
* called from a synthesizer installed in ScalaPackageClass.
*/
private class FunType(prefix: String):
private var classRefs: Array[TypeRef] = new Array(22)

def apply(n: Int): TypeRef =
while n >= classRefs.length do
val classRefs1 = new Array[TypeRef](classRefs.length * 2)
Array.copy(classRefs, 0, classRefs1, 0, classRefs.length)
classRefs = classRefs1
val funName = s"scala.$prefix$n"
if classRefs(n) == null then
classRefs(n) = requiredClassRef(prefix + n.toString)
classRefs(n) =
if prefix.startsWith("Impure")
then staticRef(funName.toTypeName).symbol.typeRef
else requiredClassRef(funName)
classRefs(n)

private val erasedContextFunType = FunType("scala.ErasedContextFunction")
private val contextFunType = FunType("scala.ContextFunction")
private val erasedFunType = FunType("scala.ErasedFunction")
private val funType = FunType("scala.Function")

def FunctionClass(n: Int, isContextual: Boolean = false, isErased: Boolean = false)(using Context): Symbol =
( if isContextual && isErased then erasedContextFunType(n)
else if isContextual then contextFunType(n)
else if isErased then erasedFunType(n)
else funType(n)
).symbol.asClass
end FunType

private def funTypeIdx(isContextual: Boolean, isErased: Boolean, isImpure: Boolean): Int =
(if isContextual then 1 else 0)
+ (if isErased then 2 else 0)
+ (if isImpure then 4 else 0)

private val funTypeArray: IArray[FunType] =
val arr = Array.ofDim[FunType](8)
val choices = List(false, true)
for contxt <- choices; erasd <- choices; impure <- choices do
var str = "Function"
if contxt then str = "Context" + str
if erasd then str = "Erased" + str
if impure then str = "Impure" + str
arr(funTypeIdx(contxt, erasd, impure)) = FunType(str)
IArray.unsafeFromArray(arr)

def FunctionSymbol(n: Int, isContextual: Boolean = false, isErased: Boolean = false, isImpure: Boolean = false)(using Context): Symbol =
funTypeArray(funTypeIdx(isContextual, isErased, isImpure))(n).symbol

@tu lazy val Function0_apply: Symbol = Function0.requiredMethod(nme.apply)

@tu lazy val Function0: Symbol = FunctionClass(0)
@tu lazy val Function1: Symbol = FunctionClass(1)
@tu lazy val Function2: Symbol = FunctionClass(2)
@tu lazy val Function0: Symbol = FunctionSymbol(0)
@tu lazy val Function1: Symbol = FunctionSymbol(1)
@tu lazy val Function2: Symbol = FunctionSymbol(2)

def FunctionType(n: Int, isContextual: Boolean = false, isErased: Boolean = false)(using Context): TypeRef =
FunctionClass(n, isContextual && !ctx.erasedTypes, isErased).typeRef
def FunctionType(n: Int, isContextual: Boolean = false, isErased: Boolean = false, isImpure: Boolean = false)(using Context): TypeRef =
FunctionSymbol(n, isContextual && !ctx.erasedTypes, isErased, isImpure).typeRef

lazy val PolyFunctionClass = requiredClass("scala.PolyFunction")
def PolyFunctionType = PolyFunctionClass.typeRef
Expand Down Expand Up @@ -1345,6 +1381,10 @@ class Definitions {
*/
def isFunctionClass(cls: Symbol): Boolean = scalaClassName(cls).isFunction

/** Is a function class, or an impure function type alias */
def isFunctionSymbol(sym: Symbol): Boolean =
sym.isType && (sym.owner eq ScalaPackageClass) && sym.name.isFunction

/** Is a function class where
* - FunctionN for N >= 0 and N != XXL
*/
Expand Down Expand Up @@ -1550,7 +1590,7 @@ 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(FunctionSymbol(paramTypes.length))
&& isSpecializableFunctionSAM(paramTypes, retType)

/** If the Single Abstract Method of a Function class has this type, is it specializable? */
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/core/Flags.scala
Original file line number Diff line number Diff line change
Expand Up @@ -314,8 +314,8 @@ object Flags {
/** A Scala 2x super accessor / an unpickled Scala 2.x class */
val (SuperParamAliasOrScala2x @ _, SuperParamAlias @ _, Scala2x @ _) = newFlags(26, "<super-param-alias>", "<scala-2.x>")

/** A parameter with a default value */
val (_, HasDefault @ _, _) = newFlags(27, "<hasdefault>")
/** A parameter with a default value / an impure untpd.Function type */
val (_, HasDefault @ _, Impure @ _) = newFlags(27, "<hasdefault>", "<{*}>")

/** An extension method, or a collective extension instance */
val (Extension @ _, ExtensionMethod @ _, _) = newFlags(28, "<extension>")
Expand Down
40 changes: 20 additions & 20 deletions compiler/src/dotty/tools/dotc/core/NameOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -197,20 +197,25 @@ object NameOps {
else collectDigits(acc * 10 + d, idx + 1)
collectDigits(0, suffixStart + 8)

/** name[0..suffixStart) == `str` */
private def isPreceded(str: String, suffixStart: Int) =
str.length == suffixStart && name.firstPart.startsWith(str)
private def isFunctionPrefix(suffixStart: Int, mustHave: String = ""): Boolean =
suffixStart >= 0
&& {
val first = name.firstPart
var found = mustHave.isEmpty
def skip(idx: Int, str: String) =
if first.startsWith(str, idx) then
if str == mustHave then found = true
idx + str.length
else idx
skip(skip(skip(0, "Impure"), "Erased"), "Context") == suffixStart
&& found
}

/** Same as `funArity`, except that it returns -1 if the prefix
* is not one of "", "Context", "Erased", "ErasedContext"
*/
private def checkedFunArity(suffixStart: Int): Int =
if suffixStart == 0
|| isPreceded("Context", suffixStart)
|| isPreceded("Erased", suffixStart)
|| isPreceded("ErasedContext", suffixStart)
then funArity(suffixStart)
else -1
if isFunctionPrefix(suffixStart) then funArity(suffixStart) else -1

/** Is a function name, i.e one of FunctionXXL, FunctionN, ContextFunctionN, ErasedFunctionN, ErasedContextFunctionN for N >= 0
*/
Expand All @@ -222,19 +227,14 @@ object NameOps {
*/
def isPlainFunction: Boolean = functionArity >= 0

/** Is an context function name, i.e one of ContextFunctionN or ErasedContextFunctionN for N >= 0
*/
def isContextFunction: Boolean =
/** Is a function name that contains `mustHave` as a substring */
private def isSpecificFunction(mustHave: String): Boolean =
val suffixStart = functionSuffixStart
(isPreceded("Context", suffixStart) || isPreceded("ErasedContext", suffixStart))
&& funArity(suffixStart) >= 0
isFunctionPrefix(suffixStart, mustHave) && funArity(suffixStart) >= 0

/** Is an erased function name, i.e. one of ErasedFunctionN, ErasedContextFunctionN for N >= 0
*/
def isErasedFunction: Boolean =
val suffixStart = functionSuffixStart
(isPreceded("Erased", suffixStart) || isPreceded("ErasedContext", suffixStart))
&& funArity(suffixStart) >= 0
def isContextFunction: Boolean = isSpecificFunction("Context")
def isErasedFunction: Boolean = isSpecificFunction("Erased")
def isImpureFunction: Boolean = isSpecificFunction("Impure")

/** Is a synthetic function name, i.e. one of
* - FunctionN for N > 22
Expand Down
2 changes: 2 additions & 0 deletions compiler/src/dotty/tools/dotc/core/StdNames.scala
Original file line number Diff line number Diff line change
Expand Up @@ -741,6 +741,8 @@ object StdNames {
val XOR : N = "^"
val ZAND : N = "&&"
val ZOR : N = "||"
val PUREARROW: N = "->"
val PURECTXARROW: N = "?->"

// unary operators
val UNARY_PREFIX: N = "unary_"
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/TypeComparer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2413,7 +2413,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
case tp1: TypeVar if tp1.isInstantiated =>
tp1.underlying & tp2
case CapturingType(parent1, refs1, _) =>
if subCaptures(tp2.captureSet, refs1, frozenConstraint).isOK then
if subCaptures(tp2.captureSet, refs1, frozen = true).isOK then
parent1 & tp2
else
tp1.derivedCapturingType(parent1 & tp2, refs1)
Expand Down
Loading