Skip to content

Commit 9cda65c

Browse files
committed
Pure function types -> and ?->
1. Allow `->` and `?->` and function operators, treated like `=>` and `?=>`. 2. under -Ycc treat `->` and `?->` as immutable function types, whereas `A => B` is an alias of `{*} A -> B` and `A ?=> B` is an alias of `{*} A ?-> B`. Closures are unaffected, we still use `=>` for all closures where they are pure or not. Improve printing of capturing types Avoid explicit retains annotations also outside phase cc Generate "Impure" function aliases For every (possibly erased and/or context) function class XFunctionN, generate an alias ImpureXFunctionN in the Scala package defined as type ImpureXFunctionN[...] = {*} XFunctionN[...] Also: - Fix a bug in TypeComparer: glb has to test subCapture in a frozen state - Harden EventuallyCapturingType extractor to not crash on illegal capture sets - Cleanup transformation of inferred types
1 parent a9419af commit 9cda65c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+595
-448
lines changed

compiler/src/dotty/tools/dotc/ast/untpd.scala

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,13 +70,13 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
7070
case class InterpolatedString(id: TermName, segments: List[Tree])(implicit @constructorOnly src: SourceFile)
7171
extends TermTree
7272

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

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

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

219219
case class Infix()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Infix)
220+
221+
case class Impure()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Impure)
220222
}
221223

222224
/** Modifiers and annotations for definitions

compiler/src/dotty/tools/dotc/cc/CaptureOps.scala

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,13 @@ def retainedElems(tree: Tree)(using Context): List[Tree] = tree match
1717
case Apply(_, Typed(SeqLiteral(elems, _), _) :: Nil) => elems
1818
case _ => Nil
1919

20+
class IllegalCaptureRef(tpe: Type) extends Exception
21+
2022
extension (tree: Tree)
2123

22-
def toCaptureRef(using Context): CaptureRef = tree.tpe.asInstanceOf[CaptureRef]
24+
def toCaptureRef(using Context): CaptureRef = tree.tpe match
25+
case ref: CaptureRef => ref
26+
case tpe => throw IllegalCaptureRef(tpe)
2327

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

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

62-
def canHaveInferredCapture(using Context): Boolean = tp match
63-
case tp: TypeRef if tp.symbol.isClass =>
64-
!tp.symbol.isValueClass && tp.symbol != defn.AnyClass
65-
case _: TypeVar | _: TypeParamRef =>
66-
false
67-
case tp: TypeProxy =>
68-
tp.superType.canHaveInferredCapture
69-
case tp: AndType =>
70-
tp.tp1.canHaveInferredCapture && tp.tp2.canHaveInferredCapture
71-
case tp: OrType =>
72-
tp.tp1.canHaveInferredCapture || tp.tp2.canHaveInferredCapture
73-
case _ =>
74-
false
75-
7666
def stripCapturing(using Context): Type = tp.dealiasKeepAnnots match
7767
case CapturingType(parent, _, _) =>
7868
parent.stripCapturing

compiler/src/dotty/tools/dotc/cc/CaptureSet.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,12 @@ sealed abstract class CaptureSet extends Showable:
5656
assert(v.isConst)
5757
Const(v.elems)
5858

59+
final def isUniversal(using Context) =
60+
elems.exists {
61+
case ref: TermRef => ref.symbol == defn.captureRoot
62+
case _ => false
63+
}
64+
5965
/** Cast to variable. @pre: !isConst */
6066
def asVar: Var =
6167
assert(!isConst)

compiler/src/dotty/tools/dotc/cc/CapturingType.scala

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,22 @@ object CapturingType:
1212
else AnnotatedType(parent, CaptureAnnotation(refs, boxed))
1313

1414
def unapply(tp: AnnotatedType)(using Context): Option[(Type, CaptureSet, Boolean)] =
15-
if ctx.phase == Phases.checkCapturesPhase && tp.annot.symbol == defn.RetainsAnnot then
15+
if ctx.phase == Phases.checkCapturesPhase then EventuallyCapturingType.unapply(tp)
16+
else None
17+
18+
end CapturingType
19+
20+
object EventuallyCapturingType:
21+
22+
def unapply(tp: AnnotatedType)(using Context): Option[(Type, CaptureSet, Boolean)] =
23+
if tp.annot.symbol == defn.RetainsAnnot then
1624
tp.annot match
1725
case ann: CaptureAnnotation => Some((tp.parent, ann.refs, ann.boxed))
18-
case ann => Some((tp.parent, ann.tree.toCaptureSet, ann.tree.isBoxedCapturing))
26+
case ann =>
27+
try Some((tp.parent, ann.tree.toCaptureSet, ann.tree.isBoxedCapturing))
28+
catch case ex: IllegalCaptureRef => None
1929
else None
2030

21-
end CapturingType
31+
end EventuallyCapturingType
32+
33+

compiler/src/dotty/tools/dotc/core/Definitions.scala

Lines changed: 83 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import Flags._, Scopes._, Decorators._, NameOps._, Periods._, NullOpsDecorator._
88
import unpickleScala2.Scala2Unpickler.ensureConstructor
99
import scala.collection.mutable
1010
import collection.mutable
11-
import Denotations.SingleDenotation
11+
import Denotations.{SingleDenotation, staticRef}
1212
import util.{SimpleIdentityMap, SourceFile, NoSource}
1313
import typer.ImportInfo.RootRef
1414
import Comments.CommentsContext
@@ -89,7 +89,7 @@ class Definitions {
8989
*
9090
* FunctionN traits follow this template:
9191
*
92-
* trait FunctionN[T0,...T{N-1}, R] extends Object {
92+
* trait FunctionN[-T0,...-T{N-1}, +R] extends Object {
9393
* def apply($x0: T0, ..., $x{N_1}: T{N-1}): R
9494
* }
9595
*
@@ -99,46 +99,65 @@ class Definitions {
9999
*
100100
* ContextFunctionN traits follow this template:
101101
*
102-
* trait ContextFunctionN[T0,...,T{N-1}, R] extends Object {
102+
* trait ContextFunctionN[-T0,...,-T{N-1}, +R] extends Object {
103103
* def apply(using $x0: T0, ..., $x{N_1}: T{N-1}): R
104104
* }
105105
*
106106
* ErasedFunctionN traits follow this template:
107107
*
108-
* trait ErasedFunctionN[T0,...,T{N-1}, R] extends Object {
108+
* trait ErasedFunctionN[-T0,...,-T{N-1}, +R] extends Object {
109109
* def apply(erased $x0: T0, ..., $x{N_1}: T{N-1}): R
110110
* }
111111
*
112112
* ErasedContextFunctionN traits follow this template:
113113
*
114-
* trait ErasedContextFunctionN[T0,...,T{N-1}, R] extends Object {
114+
* trait ErasedContextFunctionN[-T0,...,-T{N-1}, +R] extends Object {
115115
* def apply(using erased $x0: T0, ..., $x{N_1}: T{N-1}): R
116116
* }
117117
*
118118
* ErasedFunctionN and ErasedContextFunctionN erase to Function0.
119+
*
120+
* EffXYZFunctionN afollow this template:
121+
*
122+
* type EffXYZFunctionN[-T0,...,-T{N-1}, +R] = {*} XYZFunctionN[T0,...,T{N-1}, R]
119123
*/
120-
def newFunctionNTrait(name: TypeName): ClassSymbol = {
124+
private def newFunctionNType(name: TypeName): Symbol = {
125+
val impure = name.startsWith("Impure")
121126
val completer = new LazyType {
122127
def complete(denot: SymDenotation)(using Context): Unit = {
123-
val cls = denot.asClass.classSymbol
124-
val decls = newScope
125128
val arity = name.functionArity
126-
val paramNamePrefix = tpnme.scala ++ str.NAME_JOIN ++ name ++ str.EXPAND_SEPARATOR
127-
val argParamRefs = List.tabulate(arity) { i =>
128-
enterTypeParam(cls, paramNamePrefix ++ "T" ++ (i + 1).toString, Contravariant, decls).typeRef
129-
}
130-
val resParamRef = enterTypeParam(cls, paramNamePrefix ++ "R", Covariant, decls).typeRef
131-
val methodType = MethodType.companion(
132-
isContextual = name.isContextFunction,
133-
isImplicit = false,
134-
isErased = name.isErasedFunction)
135-
decls.enter(newMethod(cls, nme.apply, methodType(argParamRefs, resParamRef), Deferred))
136-
denot.info =
137-
ClassInfo(ScalaPackageClass.thisType, cls, ObjectType :: Nil, decls)
129+
if impure then
130+
val argParamNames = List.tabulate(arity)(tpnme.syntheticTypeParamName)
131+
val argVariances = List.fill(arity)(Contravariant)
132+
val underlyingName = name.asSimpleName.drop(6)
133+
val underlyingClass = ScalaPackageVal.requiredClass(underlyingName)
134+
denot.info = TypeAlias(
135+
HKTypeLambda(argParamNames :+ "R".toTypeName, argVariances :+ Covariant)(
136+
tl => List.fill(arity + 1)(TypeBounds.empty),
137+
tl => CapturingType(underlyingClass.typeRef.appliedTo(tl.paramRefs),
138+
CaptureSet.universal, boxed = false)
139+
))
140+
else
141+
val cls = denot.asClass.classSymbol
142+
val decls = newScope
143+
val paramNamePrefix = tpnme.scala ++ str.NAME_JOIN ++ name ++ str.EXPAND_SEPARATOR
144+
val argParamRefs = List.tabulate(arity) { i =>
145+
enterTypeParam(cls, paramNamePrefix ++ "T" ++ (i + 1).toString, Contravariant, decls).typeRef
146+
}
147+
val resParamRef = enterTypeParam(cls, paramNamePrefix ++ "R", Covariant, decls).typeRef
148+
val methodType = MethodType.companion(
149+
isContextual = name.isContextFunction,
150+
isImplicit = false,
151+
isErased = name.isErasedFunction)
152+
decls.enter(newMethod(cls, nme.apply, methodType(argParamRefs, resParamRef), Deferred))
153+
denot.info =
154+
ClassInfo(ScalaPackageClass.thisType, cls, ObjectType :: Nil, decls)
138155
}
139156
}
140-
val flags = Trait | NoInits
141-
newPermanentClassSymbol(ScalaPackageClass, name, flags, completer)
157+
if impure then
158+
newPermanentSymbol(ScalaPackageClass, name, EmptyFlags, completer)
159+
else
160+
newPermanentClassSymbol(ScalaPackageClass, name, Trait | NoInits, completer)
142161
}
143162

144163
private def newMethod(cls: ClassSymbol, name: TermName, info: Type, flags: FlagSet = EmptyFlags): TermSymbol =
@@ -212,7 +231,7 @@ class Definitions {
212231
val cls = ScalaPackageVal.moduleClass.asClass
213232
cls.info.decls.openForMutations.useSynthesizer(
214233
name =>
215-
if (name.isTypeName && name.isSyntheticFunction) newFunctionNTrait(name.asTypeName)
234+
if (name.isTypeName && name.isSyntheticFunction) newFunctionNType(name.asTypeName)
216235
else NoSymbol)
217236
cls
218237
}
@@ -1289,39 +1308,55 @@ class Definitions {
12891308

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

1311+
/** Cached function types of arbitary arities.
1312+
* Function types are created on demand with newFunctionNTrait, which is
1313+
* called from a synthesizer installed in ScalaPackageClass.
1314+
*/
12921315
private class FunType(prefix: String):
12931316
private var classRefs: Array[TypeRef] = new Array(22)
1317+
12941318
def apply(n: Int): TypeRef =
12951319
while n >= classRefs.length do
12961320
val classRefs1 = new Array[TypeRef](classRefs.length * 2)
12971321
Array.copy(classRefs, 0, classRefs1, 0, classRefs.length)
12981322
classRefs = classRefs1
1323+
val funName = s"scala.$prefix$n"
12991324
if classRefs(n) == null then
1300-
classRefs(n) = requiredClassRef(prefix + n.toString)
1325+
classRefs(n) =
1326+
if prefix.startsWith("Impure")
1327+
then staticRef(funName.toTypeName).symbol.typeRef
1328+
else requiredClassRef(funName)
13011329
classRefs(n)
1302-
1303-
private val erasedContextFunType = FunType("scala.ErasedContextFunction")
1304-
private val contextFunType = FunType("scala.ContextFunction")
1305-
private val erasedFunType = FunType("scala.ErasedFunction")
1306-
private val funType = FunType("scala.Function")
1307-
1308-
def FunctionClass(n: Int, isContextual: Boolean = false, isErased: Boolean = false)(using Context): Symbol =
1309-
( if isContextual && isErased then erasedContextFunType(n)
1310-
else if isContextual then contextFunType(n)
1311-
else if isErased then erasedFunType(n)
1312-
else funType(n)
1313-
).symbol.asClass
1330+
end FunType
1331+
1332+
private def funTypeIdx(isContextual: Boolean, isErased: Boolean, isImpure: Boolean): Int =
1333+
(if isContextual then 1 else 0)
1334+
+ (if isErased then 2 else 0)
1335+
+ (if isImpure then 4 else 0)
1336+
1337+
private val funTypeArray: IArray[FunType] =
1338+
val arr = Array.ofDim[FunType](8)
1339+
val choices = List(false, true)
1340+
for contxt <- choices; erasd <- choices; impure <- choices do
1341+
var str = "Function"
1342+
if contxt then str = "Context" + str
1343+
if erasd then str = "Erased" + str
1344+
if impure then str = "Impure" + str
1345+
arr(funTypeIdx(contxt, erasd, impure)) = FunType(str)
1346+
IArray.unsafeFromArray(arr)
1347+
1348+
def FunctionSymbol(n: Int, isContextual: Boolean = false, isErased: Boolean = false, isImpure: Boolean = false)(using Context): Symbol =
1349+
funTypeArray(funTypeIdx(isContextual, isErased, isImpure))(n).symbol
13141350

13151351
@tu lazy val Function0_apply: Symbol = Function0.requiredMethod(nme.apply)
1316-
@tu lazy val ContextFunction0_apply: Symbol = ContextFunction0.requiredMethod(nme.apply)
13171352

1318-
@tu lazy val Function0: Symbol = FunctionClass(0)
1319-
@tu lazy val Function1: Symbol = FunctionClass(1)
1320-
@tu lazy val Function2: Symbol = FunctionClass(2)
1321-
@tu lazy val ContextFunction0: Symbol = FunctionClass(0, isContextual = true)
1353+
@tu lazy val Function0: Symbol = FunctionSymbol(0)
1354+
@tu lazy val Function1: Symbol = FunctionSymbol(1)
1355+
@tu lazy val Function2: Symbol = FunctionSymbol(2)
1356+
@tu lazy val ContextFunction0: Symbol = FunctionSymbol(0, isContextual = true)
13221357

1323-
def FunctionType(n: Int, isContextual: Boolean = false, isErased: Boolean = false)(using Context): TypeRef =
1324-
FunctionClass(n, isContextual && !ctx.erasedTypes, isErased).typeRef
1358+
def FunctionType(n: Int, isContextual: Boolean = false, isErased: Boolean = false, isImpure: Boolean = false)(using Context): TypeRef =
1359+
FunctionSymbol(n, isContextual && !ctx.erasedTypes, isErased, isImpure).typeRef
13251360

13261361
lazy val PolyFunctionClass = requiredClass("scala.PolyFunction")
13271362
def PolyFunctionType = PolyFunctionClass.typeRef
@@ -1363,6 +1398,10 @@ class Definitions {
13631398
*/
13641399
def isFunctionClass(cls: Symbol): Boolean = scalaClassName(cls).isFunction
13651400

1401+
/** Is a function class, or an impure function type alias */
1402+
def isFunctionSymbol(sym: Symbol): Boolean =
1403+
sym.isType && (sym.owner eq ScalaPackageClass) && sym.name.isFunction
1404+
13661405
/** Is a function class where
13671406
* - FunctionN for N >= 0 and N != XXL
13681407
*/
@@ -1569,7 +1608,7 @@ class Definitions {
15691608

15701609
def isSpecializableFunction(cls: ClassSymbol, paramTypes: List[Type], retType: Type)(using Context): Boolean =
15711610
paramTypes.length <= 2
1572-
&& (cls.derivesFrom(FunctionClass(paramTypes.length)) || isByNameFunctionClass(cls))
1611+
&& (cls.derivesFrom(FunctionSymbol(paramTypes.length)) || isByNameFunctionClass(cls))
15731612
&& isSpecializableFunctionSAM(paramTypes, retType)
15741613

15751614
/** If the Single Abstract Method of a Function class has this type, is it specializable? */

compiler/src/dotty/tools/dotc/core/Flags.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -314,8 +314,8 @@ object Flags {
314314
/** A Scala 2x super accessor / an unpickled Scala 2.x class */
315315
val (SuperParamAliasOrScala2x @ _, SuperParamAlias @ _, Scala2x @ _) = newFlags(26, "<super-param-alias>", "<scala-2.x>")
316316

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

320320
/** An extension method, or a collective extension instance */
321321
val (Extension @ _, ExtensionMethod @ _, _) = newFlags(28, "<extension>")

compiler/src/dotty/tools/dotc/core/NameOps.scala

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -197,20 +197,25 @@ object NameOps {
197197
else collectDigits(acc * 10 + d, idx + 1)
198198
collectDigits(0, suffixStart + 8)
199199

200-
/** name[0..suffixStart) == `str` */
201-
private def isPreceded(str: String, suffixStart: Int) =
202-
str.length == suffixStart && name.firstPart.startsWith(str)
200+
private def isFunctionPrefix(suffixStart: Int, mustHave: String = ""): Boolean =
201+
suffixStart >= 0
202+
&& {
203+
val first = name.firstPart
204+
var found = mustHave.isEmpty
205+
def skip(idx: Int, str: String) =
206+
if first.startsWith(str, idx) then
207+
if str == mustHave then found = true
208+
idx + str.length
209+
else idx
210+
skip(skip(skip(0, "Impure"), "Erased"), "Context") == suffixStart
211+
&& found
212+
}
203213

204214
/** Same as `funArity`, except that it returns -1 if the prefix
205215
* is not one of "", "Context", "Erased", "ErasedContext"
206216
*/
207217
private def checkedFunArity(suffixStart: Int): Int =
208-
if suffixStart == 0
209-
|| isPreceded("Context", suffixStart)
210-
|| isPreceded("Erased", suffixStart)
211-
|| isPreceded("ErasedContext", suffixStart)
212-
then funArity(suffixStart)
213-
else -1
218+
if isFunctionPrefix(suffixStart) then funArity(suffixStart) else -1
214219

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

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

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

239239
/** Is a synthetic function name, i.e. one of
240240
* - FunctionN for N > 22

compiler/src/dotty/tools/dotc/core/StdNames.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -739,6 +739,8 @@ object StdNames {
739739
val XOR : N = "^"
740740
val ZAND : N = "&&"
741741
val ZOR : N = "||"
742+
val PUREARROW: N = "->"
743+
val PURECTXARROW: N = "?->"
742744

743745
// unary operators
744746
val UNARY_PREFIX: N = "unary_"

compiler/src/dotty/tools/dotc/core/TypeComparer.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2401,7 +2401,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
24012401
case tp1: TypeVar if tp1.isInstantiated =>
24022402
tp1.underlying & tp2
24032403
case CapturingType(parent1, refs1, _) =>
2404-
if subCaptures(tp2.captureSet, refs1, frozenConstraint).isOK then
2404+
if subCaptures(tp2.captureSet, refs1, frozen = true).isOK then
24052405
parent1 & tp2
24062406
else
24072407
tp1.derivedCapturingType(parent1 & tp2, refs1)

0 commit comments

Comments
 (0)