Skip to content

Commit 971f5bc

Browse files
committed
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[...]
1 parent d2bb1b6 commit 971f5bc

File tree

6 files changed

+101
-57
lines changed

6 files changed

+101
-57
lines changed

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

Lines changed: 77 additions & 40 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
@@ -86,7 +86,7 @@ class Definitions {
8686
*
8787
* FunctionN traits follow this template:
8888
*
89-
* trait FunctionN[T0,...T{N-1}, R] extends Object {
89+
* trait FunctionN[-T0,...-T{N-1}, +R] extends Object {
9090
* def apply($x0: T0, ..., $x{N_1}: T{N-1}): R
9191
* }
9292
*
@@ -96,46 +96,65 @@ class Definitions {
9696
*
9797
* ContextFunctionN traits follow this template:
9898
*
99-
* trait ContextFunctionN[T0,...,T{N-1}, R] extends Object {
99+
* trait ContextFunctionN[-T0,...,-T{N-1}, +R] extends Object {
100100
* def apply(using $x0: T0, ..., $x{N_1}: T{N-1}): R
101101
* }
102102
*
103103
* ErasedFunctionN traits follow this template:
104104
*
105-
* trait ErasedFunctionN[T0,...,T{N-1}, R] extends Object {
105+
* trait ErasedFunctionN[-T0,...,-T{N-1}, +R] extends Object {
106106
* def apply(erased $x0: T0, ..., $x{N_1}: T{N-1}): R
107107
* }
108108
*
109109
* ErasedContextFunctionN traits follow this template:
110110
*
111-
* trait ErasedContextFunctionN[T0,...,T{N-1}, R] extends Object {
111+
* trait ErasedContextFunctionN[-T0,...,-T{N-1}, +R] extends Object {
112112
* def apply(using erased $x0: T0, ..., $x{N_1}: T{N-1}): R
113113
* }
114114
*
115115
* ErasedFunctionN and ErasedContextFunctionN erase to Function0.
116+
*
117+
* EffXYZFunctionN afollow this template:
118+
*
119+
* type EffXYZFunctionN[-T0,...,-T{N-1}, +R] = {*} XYZFunctionN[T0,...,T{N-1}, R]
116120
*/
117-
def newFunctionNTrait(name: TypeName): ClassSymbol = {
121+
private def newFunctionNType(name: TypeName): Symbol = {
122+
val impure = name.startsWith("Impure")
118123
val completer = new LazyType {
119124
def complete(denot: SymDenotation)(using Context): Unit = {
120-
val cls = denot.asClass.classSymbol
121-
val decls = newScope
122125
val arity = name.functionArity
123-
val paramNamePrefix = tpnme.scala ++ str.NAME_JOIN ++ name ++ str.EXPAND_SEPARATOR
124-
val argParamRefs = List.tabulate(arity) { i =>
125-
enterTypeParam(cls, paramNamePrefix ++ "T" ++ (i + 1).toString, Contravariant, decls).typeRef
126-
}
127-
val resParamRef = enterTypeParam(cls, paramNamePrefix ++ "R", Covariant, decls).typeRef
128-
val methodType = MethodType.companion(
129-
isContextual = name.isContextFunction,
130-
isImplicit = false,
131-
isErased = name.isErasedFunction)
132-
decls.enter(newMethod(cls, nme.apply, methodType(argParamRefs, resParamRef), Deferred))
133-
denot.info =
134-
ClassInfo(ScalaPackageClass.thisType, cls, ObjectType :: Nil, decls)
126+
if impure then
127+
val argParamNames = List.tabulate(arity)(tpnme.syntheticTypeParamName)
128+
val argVariances = List.fill(arity)(Contravariant)
129+
val underlyingName = name.asSimpleName.drop(6)
130+
val underlyingClass = ScalaPackageVal.requiredClass(underlyingName)
131+
denot.info = TypeAlias(
132+
HKTypeLambda(argParamNames :+ "R".toTypeName, argVariances :+ Covariant)(
133+
tl => List.fill(arity + 1)(TypeBounds.empty),
134+
tl => CapturingType(underlyingClass.typeRef.appliedTo(tl.paramRefs),
135+
CaptureSet.universal, boxed = false)
136+
))
137+
else
138+
val cls = denot.asClass.classSymbol
139+
val decls = newScope
140+
val paramNamePrefix = tpnme.scala ++ str.NAME_JOIN ++ name ++ str.EXPAND_SEPARATOR
141+
val argParamRefs = List.tabulate(arity) { i =>
142+
enterTypeParam(cls, paramNamePrefix ++ "T" ++ (i + 1).toString, Contravariant, decls).typeRef
143+
}
144+
val resParamRef = enterTypeParam(cls, paramNamePrefix ++ "R", Covariant, decls).typeRef
145+
val methodType = MethodType.companion(
146+
isContextual = name.isContextFunction,
147+
isImplicit = false,
148+
isErased = name.isErasedFunction)
149+
decls.enter(newMethod(cls, nme.apply, methodType(argParamRefs, resParamRef), Deferred))
150+
denot.info =
151+
ClassInfo(ScalaPackageClass.thisType, cls, ObjectType :: Nil, decls)
135152
}
136153
}
137-
val flags = Trait | NoInits
138-
newPermanentClassSymbol(ScalaPackageClass, name, flags, completer)
154+
if impure then
155+
newPermanentSymbol(ScalaPackageClass, name, EmptyFlags, completer)
156+
else
157+
newPermanentClassSymbol(ScalaPackageClass, name, Trait | NoInits, completer)
139158
}
140159

141160
private def newMethod(cls: ClassSymbol, name: TermName, info: Type, flags: FlagSet = EmptyFlags): TermSymbol =
@@ -209,7 +228,7 @@ class Definitions {
209228
val cls = ScalaPackageVal.moduleClass.asClass
210229
cls.info.decls.openForMutations.useSynthesizer(
211230
name =>
212-
if (name.isTypeName && name.isSyntheticFunction) newFunctionNTrait(name.asTypeName)
231+
if (name.isTypeName && name.isSyntheticFunction) newFunctionNType(name.asTypeName)
213232
else NoSymbol)
214233
cls
215234
}
@@ -1273,37 +1292,55 @@ class Definitions {
12731292

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

1295+
/** Cached function types of arbitary arities.
1296+
* Function types are created on demand with newFunctionNTrait, which is
1297+
* called from a synthesizer installed in ScalaPackageClass.
1298+
*/
12761299
private class FunType(prefix: String):
12771300
private var classRefs: Array[TypeRef] = new Array(22)
1301+
12781302
def apply(n: Int): TypeRef =
12791303
while n >= classRefs.length do
12801304
val classRefs1 = new Array[TypeRef](classRefs.length * 2)
12811305
Array.copy(classRefs, 0, classRefs1, 0, classRefs.length)
12821306
classRefs = classRefs1
1307+
val funName = prefix + n.toString
12831308
if classRefs(n) == null then
1284-
classRefs(n) = requiredClassRef(prefix + n.toString)
1309+
classRefs(n) =
1310+
if prefix.startsWith("Impure")
1311+
then staticRef(funName.toTypeName).symbol.typeRef
1312+
else requiredClassRef(funName)
12851313
classRefs(n)
1314+
end FunType
1315+
1316+
private val (funTypeCache, funTypeArray) =
1317+
val funTypeCache = mutable.Map[String, FunType]()
1318+
val funTypeArray = Array.ofDim[FunType](2, 2, 2)
1319+
1320+
for contxt <- 0 to 1; erasd <- 0 to 1; impure <- 0 to 1 do
1321+
var str = "Function"
1322+
if contxt == 1 then str = "Context" + str
1323+
if erasd == 1 then str = "Erased" + str
1324+
if impure == 1 then str = "Impure" + str
1325+
val funType = FunType("scala." + str)
1326+
funTypeCache(str) = funType
1327+
funTypeArray(contxt)(erasd)(impure) = funType
12861328

1287-
private val erasedContextFunType = FunType("scala.ErasedContextFunction")
1288-
private val contextFunType = FunType("scala.ContextFunction")
1289-
private val erasedFunType = FunType("scala.ErasedFunction")
1290-
private val funType = FunType("scala.Function")
1329+
(funTypeCache: collection.Map[String, FunType], IArray.unsafeFromArray(funTypeArray))
1330+
end val
12911331

1292-
def FunctionClass(n: Int, isContextual: Boolean = false, isErased: Boolean = false)(using Context): Symbol =
1293-
( if isContextual && isErased then erasedContextFunType(n)
1294-
else if isContextual then contextFunType(n)
1295-
else if isErased then erasedFunType(n)
1296-
else funType(n)
1297-
).symbol.asClass
1332+
def FunctionSymbol(n: Int, isContextual: Boolean = false, isErased: Boolean = false, isEffectful: Boolean = false)(using Context): Symbol =
1333+
def ord(b: Boolean): Int = if b then 1 else 0
1334+
funTypeArray(ord(isContextual))(ord(isErased))(ord(isEffectful))(n).symbol
12981335

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

1301-
@tu lazy val Function0: Symbol = FunctionClass(0)
1302-
@tu lazy val Function1: Symbol = FunctionClass(1)
1303-
@tu lazy val Function2: Symbol = FunctionClass(2)
1338+
@tu lazy val Function0: Symbol = FunctionSymbol(0)
1339+
@tu lazy val Function1: Symbol = FunctionSymbol(1)
1340+
@tu lazy val Function2: Symbol = FunctionSymbol(2)
13041341

1305-
def FunctionType(n: Int, isContextual: Boolean = false, isErased: Boolean = false)(using Context): TypeRef =
1306-
FunctionClass(n, isContextual && !ctx.erasedTypes, isErased).typeRef
1342+
def FunctionType(n: Int, isContextual: Boolean = false, isErased: Boolean = false, isEffectful: Boolean = false)(using Context): TypeRef =
1343+
FunctionSymbol(n, isContextual && !ctx.erasedTypes, isErased, isEffectful).typeRef
13071344

13081345
lazy val PolyFunctionClass = requiredClass("scala.PolyFunction")
13091346
def PolyFunctionType = PolyFunctionClass.typeRef
@@ -1550,7 +1587,7 @@ class Definitions {
15501587
new PerRun(Function2SpecializedReturnTypes.map(_.symbol))
15511588

15521589
def isSpecializableFunction(cls: ClassSymbol, paramTypes: List[Type], retType: Type)(using Context): Boolean =
1553-
paramTypes.length <= 2 && cls.derivesFrom(FunctionClass(paramTypes.length))
1590+
paramTypes.length <= 2 && cls.derivesFrom(FunctionSymbol(paramTypes.length))
15541591
&& isSpecializableFunctionSAM(paramTypes, retType)
15551592

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

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

Lines changed: 16 additions & 13 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
*/
@@ -226,15 +231,13 @@ object NameOps {
226231
*/
227232
def isContextFunction: Boolean =
228233
val suffixStart = functionSuffixStart
229-
(isPreceded("Context", suffixStart) || isPreceded("ErasedContext", suffixStart))
230-
&& funArity(suffixStart) >= 0
234+
isFunctionPrefix(suffixStart, mustHave = "Context") && funArity(suffixStart) >= 0
231235

232236
/** Is an erased function name, i.e. one of ErasedFunctionN, ErasedContextFunctionN for N >= 0
233237
*/
234238
def isErasedFunction: Boolean =
235239
val suffixStart = functionSuffixStart
236-
(isPreceded("Erased", suffixStart) || isPreceded("ErasedContext", suffixStart))
237-
&& funArity(suffixStart) >= 0
240+
isFunctionPrefix(suffixStart, mustHave = "Erased") && funArity(suffixStart) >= 0
238241

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

compiler/src/dotty/tools/dotc/typer/Implicits.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -815,7 +815,7 @@ trait Implicits:
815815
def isOldStyleFunctionConversion(tpe: Type): Boolean =
816816
tpe match {
817817
case PolyType(_, resType) => isOldStyleFunctionConversion(resType)
818-
case _ => tpe.derivesFrom(defn.FunctionClass(1)) && !tpe.derivesFrom(defn.ConversionClass) && !tpe.derivesFrom(defn.SubTypeClass)
818+
case _ => tpe.derivesFrom(defn.FunctionSymbol(1)) && !tpe.derivesFrom(defn.ConversionClass) && !tpe.derivesFrom(defn.SubTypeClass)
819819
}
820820

821821
try

compiler/src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1238,7 +1238,7 @@ class Typer extends Namer
12381238
val numArgs = args.length
12391239
val isContextual = funFlags.is(Given)
12401240
val isErased = funFlags.is(Erased)
1241-
val funCls = defn.FunctionClass(numArgs, isContextual, isErased)
1241+
val funCls = defn.FunctionSymbol(numArgs, isContextual, isErased)
12421242

12431243
/** If `app` is a function type with arguments that are all erased classes,
12441244
* turn it into an erased function type.
@@ -1248,7 +1248,7 @@ class Typer extends Namer
12481248
if !isErased
12491249
&& numArgs > 0
12501250
&& args.indexWhere(!_.tpe.isErasedClass) == numArgs =>
1251-
val tycon1 = TypeTree(defn.FunctionClass(numArgs, isContextual, isErased = true).typeRef)
1251+
val tycon1 = TypeTree(defn.FunctionSymbol(numArgs, isContextual, isErased = true).typeRef)
12521252
.withSpan(tycon.span)
12531253
assignType(cpy.AppliedTypeTree(app)(tycon1, args), tycon1, args)
12541254
case _ =>

compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2692,7 +2692,7 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler
26922692
def SomeModule: Symbol = dotc.core.Symbols.defn.SomeClass.companionModule
26932693
def ProductClass: Symbol = dotc.core.Symbols.defn.ProductClass
26942694
def FunctionClass(arity: Int, isImplicit: Boolean = false, isErased: Boolean = false): Symbol =
2695-
dotc.core.Symbols.defn.FunctionClass(arity, isImplicit, isErased)
2695+
dotc.core.Symbols.defn.FunctionSymbol(arity, isImplicit, isErased)
26962696
def TupleClass(arity: Int): Symbol =
26972697
dotc.core.Symbols.defn.TupleType(arity).classSymbol.asClass
26982698
def isTupleClass(sym: Symbol): Boolean =

tests/pos/impurefun.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
object Test:
2+
3+
val f: ImpureFunction1[Int, Int] = (x: Int) => x + 1
4+

0 commit comments

Comments
 (0)