Skip to content

Commit 6bbd242

Browse files
committed
Implement simple phantom functions
Simple phantom functions are FunctionN/ImplicitFunctionN types where all parameters are of the same phantom universe. They are synthesized in the object defining the phantom universe. It main usage should be through `(MyPhantom) => Int` nothation as it does not require any imports.
1 parent d121ad5 commit 6bbd242

40 files changed

+579
-51
lines changed

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

+78-28
Original file line numberDiff line numberDiff line change
@@ -60,14 +60,14 @@ class Definitions {
6060
private def enterCompleteClassSymbol(owner: Symbol, name: TypeName, flags: FlagSet, parents: List[TypeRef], decls: Scope = newScope) =
6161
ctx.newCompleteClassSymbol(owner, name, flags | Permanent, parents, decls).entered
6262

63-
private def enterTypeField(cls: ClassSymbol, name: TypeName, flags: FlagSet, scope: MutableScope) =
64-
scope.enter(newSymbol(cls, name, flags, TypeBounds.empty))
63+
private def enterTypeField(cls: ClassSymbol, name: TypeName, flags: FlagSet, scope: MutableScope, typeBounds: TypeBounds) =
64+
scope.enter(newSymbol(cls, name, flags, typeBounds))
6565

66-
private def enterTypeParam(cls: ClassSymbol, name: TypeName, flags: FlagSet, scope: MutableScope) =
67-
enterTypeField(cls, name, flags | ClassTypeParamCreationFlags, scope)
66+
private def enterTypeParam(cls: ClassSymbol, name: TypeName, flags: FlagSet, scope: MutableScope, typeBounds: TypeBounds) =
67+
enterTypeField(cls, name, flags | ClassTypeParamCreationFlags, scope, typeBounds)
6868

6969
private def enterSyntheticTypeParam(cls: ClassSymbol, paramFlags: FlagSet, scope: MutableScope, suffix: String = "T0") =
70-
enterTypeParam(cls, suffix.toTypeName.expandedName(cls), paramFlags, scope)
70+
enterTypeParam(cls, suffix.toTypeName.expandedName(cls), paramFlags, scope, TypeBounds.empty)
7171

7272
// NOTE: Ideally we would write `parentConstrs: => Type*` but SIP-24 is only
7373
// implemented in Dotty and not in Scala 2.
@@ -108,33 +108,31 @@ class Definitions {
108108
* def apply(implicit $x0: T0, ..., $x{N_1}: T{N-1}): R
109109
* }
110110
*/
111-
def newFunctionNTrait(name: TypeName): ClassSymbol = {
111+
def newFunctionNTrait(name: TypeName, lattice: Symbol): ClassSymbol = {
112112
val completer = new LazyType {
113113
def complete(denot: SymDenotation)(implicit ctx: Context): Unit = {
114114
val cls = denot.asClass.classSymbol
115115
val decls = newScope
116116
val arity = name.functionArity
117+
val top = lattice.thisType.select(tpnme.Any)
118+
val bottom = lattice.thisType.select(tpnme.Nothing)
117119
val paramNamePrefix = tpnme.scala_ ++ str.NAME_JOIN ++ name ++ str.EXPAND_SEPARATOR
118120
val argParams =
119121
for (i <- List.range(1, arity + 1)) yield
120-
enterTypeParam(cls, paramNamePrefix ++ "T" ++ i.toString, Contravariant, decls)
121-
val resParam = enterTypeParam(cls, paramNamePrefix ++ "R", Covariant, decls)
122+
enterTypeParam(cls, paramNamePrefix ++ "T" ++ i.toString, Contravariant, decls, TypeBounds(bottom, top)).typeRef
123+
val resParam = enterTypeParam(cls, paramNamePrefix ++ "R", Covariant, decls, TypeBounds.empty).typeRef
122124
val (methodType, parentTraits) =
123125
if (name.firstPart.startsWith(str.ImplicitFunction)) {
124126
val superTrait =
125-
FunctionType(arity).appliedTo(argParams.map(_.typeRef) ::: resParam.typeRef :: Nil)
127+
FunctionType(arity, isImplicit = false, top).appliedTo(argParams ::: resParam :: Nil)
126128
(ImplicitMethodType, ctx.normalizeToClassRefs(superTrait :: Nil, cls, decls))
127129
}
128130
else (MethodType, Nil)
129-
val applyMeth =
130-
decls.enter(
131-
newMethod(cls, nme.apply,
132-
methodType(argParams.map(_.typeRef), resParam.typeRef), Deferred))
133-
denot.info =
134-
ClassInfo(ScalaPackageClass.thisType, cls, ObjectType :: parentTraits, decls)
131+
decls.enter(newMethod(cls, nme.apply, methodType(argParams, resParam), Deferred))
132+
denot.info = ClassInfo(lattice.thisType, cls, ObjectType :: parentTraits, decls)
135133
}
136134
}
137-
newClassSymbol(ScalaPackageClass, name, Trait | NoInits, completer)
135+
newClassSymbol(lattice, name, Trait | NoInits, completer)
138136
}
139137

140138
private def newMethod(cls: ClassSymbol, name: TermName, info: Type, flags: FlagSet = EmptyFlags): TermSymbol =
@@ -194,7 +192,7 @@ class Definitions {
194192
val cls = ScalaPackageVal.moduleClass.asClass
195193
cls.info.decls.openForMutations.useSynthesizer(
196194
name => ctx =>
197-
if (name.isTypeName && name.isSyntheticFunction) newFunctionNTrait(name.asTypeName)
195+
if (name.isTypeName && name.isSyntheticScalaFunction) newFunctionNTrait(name.asTypeName, cls)
198196
else NoSymbol)
199197
cls
200198
}
@@ -693,7 +691,7 @@ class Definitions {
693691

694692
object FunctionOf {
695693
def apply(args: List[Type], resultType: Type, isImplicit: Boolean = false)(implicit ctx: Context) =
696-
FunctionType(args.length, isImplicit).appliedTo(args ::: resultType :: Nil)
694+
FunctionType(args, resultType, isImplicit).appliedTo(args ::: resultType :: Nil)
697695
def unapply(ft: Type)(implicit ctx: Context) = {
698696
val tsym = ft.typeSymbol
699697
if (isFunctionClass(tsym)) {
@@ -773,16 +771,39 @@ class Definitions {
773771
lazy val Function0_applyR = ImplementedFunctionType(0).symbol.requiredMethodRef(nme.apply)
774772
def Function0_apply(implicit ctx: Context) = Function0_applyR.symbol
775773

776-
def FunctionType(n: Int, isImplicit: Boolean = false)(implicit ctx: Context): TypeRef =
777-
if (n <= MaxImplementedFunctionArity && (!isImplicit || ctx.erasedTypes)) ImplementedFunctionType(n)
774+
def FunctionType(n: Int, isImplicit: Boolean = false, top: Type = AnyType)(implicit ctx: Context): TypeRef = {
775+
if (top.isPhantom) {
776+
val functionPrefix = if (isImplicit) str.ImplicitFunction else str.Function
777+
val functionName = (functionPrefix + n).toTypeName
778+
val functionType = top.normalizedPrefix.select(functionName)
779+
assert(functionType.classSymbol.exists)
780+
functionType.asInstanceOf[TypeRef]
781+
}
782+
else if (n <= MaxImplementedFunctionArity && (!isImplicit || ctx.erasedTypes)) ImplementedFunctionType(n)
778783
else FunctionClass(n, isImplicit).typeRef
784+
}
785+
786+
def FunctionType(args: List[Type], resultType: Type, isImplicit: Boolean)(implicit ctx: Context): TypeRef =
787+
FunctionType(args.length, isImplicit, topInSameUniverse(args, "function arguments."))
788+
789+
private def topInSameUniverse(types: List[Type], relationship: => String)(implicit ctx: Context): Type = {
790+
types match {
791+
case first :: Nil => first.topType
792+
case first :: rest => (first /: rest)(inSameUniverse((t1, _) => t1.topType, _, _, relationship, ctx.owner.pos))
793+
case Nil => defn.AnyType
794+
}
795+
}
779796

780797
private lazy val TupleTypes: Set[TypeRef] = TupleType.toSet
781798

782799
/** If `cls` is a class in the scala package, its name, otherwise EmptyTypeName */
783800
def scalaClassName(cls: Symbol)(implicit ctx: Context): TypeName =
784801
if (cls.isClass && cls.owner == ScalaPackageClass) cls.asClass.name else EmptyTypeName
785802

803+
/** If `cls` is a class in an object extending scala.Phantom, its name, otherwise EmptyTypeName */
804+
def phantomClassName(cls: Symbol)(implicit ctx: Context): TypeName =
805+
if (cls.isClass && cls.owner.derivesFrom(PhantomClass)) cls.asClass.name else EmptyTypeName
806+
786807
/** If type `ref` refers to a class in the scala package, its name, otherwise EmptyTypeName */
787808
def scalaClassName(ref: Type)(implicit ctx: Context): TypeName = scalaClassName(ref.classSymbol)
788809

@@ -801,24 +822,28 @@ class Definitions {
801822
* - FunctionN for N >= 0
802823
* - ImplicitFunctionN for N > 0
803824
*/
804-
def isFunctionClass(cls: Symbol) = scalaClassName(cls).isFunction
825+
def isFunctionClass(cls: Symbol) =
826+
scalaClassName(cls).isFunction || phantomClassName(cls).isFunction
805827

806828
/** Is an implicit function class.
807829
* - ImplicitFunctionN for N > 0
808830
*/
809-
def isImplicitFunctionClass(cls: Symbol) = scalaClassName(cls).isImplicitFunction
831+
def isImplicitFunctionClass(cls: Symbol) =
832+
scalaClassName(cls).isImplicitFunction || phantomClassName(cls).isImplicitFunction
810833

811834
/** Is a class that will be erased to FunctionXXL
812835
* - FunctionN for N >= 22
813836
* - ImplicitFunctionN for N >= 22
814837
*/
815-
def isXXLFunctionClass(cls: Symbol) = scalaClassName(cls).functionArity > MaxImplementedFunctionArity
838+
def isXXLFunctionClass(cls: Symbol) =
839+
scalaClassName(cls).functionArity > MaxImplementedFunctionArity
816840

817841
/** Is a synthetic function class
818842
* - FunctionN for N > 22
819843
* - ImplicitFunctionN for N > 0
820844
*/
821-
def isSyntheticFunctionClass(cls: Symbol) = scalaClassName(cls).isSyntheticFunction
845+
def isSyntheticFunctionClass(cls: Symbol) =
846+
scalaClassName(cls).isSyntheticScalaFunction || phantomClassName(cls).isFunction
822847

823848
def isAbstractFunctionClass(cls: Symbol) = isVarArityClass(cls, str.AbstractFunction)
824849
def isTupleClass(cls: Symbol) = isVarArityClass(cls, str.Tuple)
@@ -835,6 +860,7 @@ class Definitions {
835860
val arity = scalaClassName(cls).functionArity
836861
if (arity > 22) FunctionXXLClass
837862
else if (arity >= 0) FunctionClass(arity)
863+
else if (phantomClassName(cls).isFunction) FunctionClass(0)
838864
else NoSymbol
839865
}
840866

@@ -847,8 +873,9 @@ class Definitions {
847873
*/
848874
def erasedFunctionType(cls: Symbol): Type = {
849875
val arity = scalaClassName(cls).functionArity
850-
if (arity > 22) defn.FunctionXXLType
851-
else if (arity >= 0) defn.FunctionType(arity)
876+
if (arity > 22) FunctionXXLType
877+
else if (arity >= 0) FunctionType(arity)
878+
else if (phantomClassName(cls).isFunction) FunctionType(0)
852879
else NoType
853880
}
854881

@@ -893,7 +920,10 @@ class Definitions {
893920
* trait gets screwed up. Therefore, it is mandatory that FunctionXXL
894921
* is treated as a NoInit trait.
895922
*/
896-
lazy val NoInitClasses = NotRuntimeClasses + FunctionXXLClass
923+
private lazy val NoInitClasses = NotRuntimeClasses + FunctionXXLClass
924+
925+
def isNoInitClass(cls: Symbol): Boolean =
926+
cls.is(NoInitsTrait) || NoInitClasses.contains(cls) || isFunctionClass(cls)
897927

898928
def isPolymorphicAfterErasure(sym: Symbol) =
899929
(sym eq Any_isInstanceOf) || (sym eq Any_asInstanceOf)
@@ -914,7 +944,11 @@ class Definitions {
914944
def isFunctionType(tp: Type)(implicit ctx: Context) = {
915945
val arity = functionArity(tp)
916946
val sym = tp.dealias.typeSymbol
917-
arity >= 0 && isFunctionClass(sym) && tp.isRef(FunctionType(arity, sym.name.isImplicitFunction).typeSymbol)
947+
def top =
948+
if (!sym.owner.derivesFrom(defn.PhantomClass)) defn.AnyType
949+
else sym.owner.thisType.select(tpnme.Any)
950+
def funType = FunctionType(arity, sym.name.isImplicitFunction, top)
951+
arity >= 0 && isFunctionClass(sym) && tp.isRef(funType.typeSymbol)
918952
}
919953

920954
def functionArity(tp: Type)(implicit ctx: Context) = tp.dealias.argInfos.length - 1
@@ -1030,6 +1064,10 @@ class Definitions {
10301064

10311065
lazy val PhantomClass: ClassSymbol = {
10321066
val cls = completeClass(enterCompleteClassSymbol(ScalaPackageClass, tpnme.Phantom, NoInitsTrait, List(AnyType)))
1067+
cls.unforcedDecls.openForMutations.useSynthesizer { name => ctx =>
1068+
if (name.isTypeName && name.isFunction) newFunctionNTrait(name.asTypeName, cls)
1069+
else NoSymbol
1070+
}
10331071

10341072
val any = enterCompleteClassSymbol(cls, tpnme.Any, Protected | Final | NoInitsTrait, Nil)
10351073
val nothing = enterCompleteClassSymbol(cls, tpnme.Nothing, Protected | Final | NoInitsTrait, List(any.typeRef))
@@ -1050,4 +1088,16 @@ class Definitions {
10501088

10511089
def ErasedPhantom_UNIT(implicit ctx: Context) = ErasedPhantomClass.linkedClass.requiredValue("UNIT")
10521090

1091+
/** Ensure that `tp2`' is in the same universe as `tp1`. If that's the case, return
1092+
* `op` applied to both types.
1093+
* If not, issue an error and return `tp1`'.
1094+
*/
1095+
def inSameUniverse(op: (Type, Type) => Type, tp1: Type, tp2: Type, relationship: => String, pos: Position)(implicit ctx: Context): Type =
1096+
if (tp1.topType == tp2.topType)
1097+
op(tp1, tp2)
1098+
else {
1099+
ctx.error(ex"$tp1 and $tp2 are in different universes. They cannot be combined in $relationship", pos)
1100+
tp1
1101+
}
1102+
10531103
}

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

+3-3
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ object NameOps {
159159
}
160160
}
161161

162-
/** Is a synthetic function name
162+
/** Is a function name
163163
* - N for FunctionN
164164
* - N for ImplicitFunctionN (N >= 1)
165165
* - (-1) otherwise
@@ -183,12 +183,12 @@ object NameOps {
183183
*/
184184
def isImplicitFunction: Boolean = functionArityFor(str.ImplicitFunction) >= 1
185185

186-
/** Is a synthetic function name
186+
/** Is a synthetic function name (in scala package)
187187
* - FunctionN for N > 22
188188
* - ImplicitFunctionN for N >= 1
189189
* - false otherwise
190190
*/
191-
def isSyntheticFunction: Boolean = {
191+
def isSyntheticScalaFunction: Boolean = {
192192
functionArityFor(str.Function) > MaxImplementedFunctionArity ||
193193
functionArityFor(str.ImplicitFunction) >= 1
194194
}

compiler/src/dotty/tools/dotc/transform/Mixin.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ class Mixin extends MiniPhaseTransform with SymTransformer { thisTransform =>
178178
case Some(call) =>
179179
if (defn.NotRuntimeClasses.contains(baseCls)) Nil else call :: Nil
180180
case None =>
181-
if (baseCls.is(NoInitsTrait) || defn.NoInitClasses.contains(baseCls)) Nil
181+
if (defn.isNoInitClass(baseCls)) Nil
182182
else {
183183
//println(i"synth super call ${baseCls.primaryConstructor}: ${baseCls.primaryConstructor.info}")
184184
transformFollowingDeep(superRef(baseCls.primaryConstructor).appliedToNone) :: Nil

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

+4-16
Original file line numberDiff line numberDiff line change
@@ -453,10 +453,10 @@ trait TypeAssigner {
453453
tree.withType(ref.tpe)
454454

455455
def assignType(tree: untpd.AndTypeTree, left: Tree, right: Tree)(implicit ctx: Context) =
456-
tree.withType(inSameUniverse(_ & _, left.tpe, right, "an `&`"))
456+
tree.withType(defn.inSameUniverse(_ & _, left.tpe, right.tpe, "an `&`", right.pos))
457457

458458
def assignType(tree: untpd.OrTypeTree, left: Tree, right: Tree)(implicit ctx: Context) =
459-
tree.withType(inSameUniverse(_ | _, left.tpe, right, "an `|`"))
459+
tree.withType(defn.inSameUniverse(_ | _, left.tpe, right.tpe, "an `|`", right.pos))
460460

461461
/** Assign type of RefinedType.
462462
* Refinements are typed as if they were members of refinement class `refineCls`.
@@ -491,7 +491,7 @@ trait TypeAssigner {
491491
def assignType(tree: untpd.TypeBoundsTree, lo: Tree, hi: Tree)(implicit ctx: Context) =
492492
tree.withType(
493493
if (lo eq hi) TypeAlias(lo.tpe)
494-
else inSameUniverse(TypeBounds(_, _), lo.tpe, hi, "type bounds"))
494+
else defn.inSameUniverse(TypeBounds(_, _), lo.tpe, hi.tpe, "type bounds", hi.pos))
495495

496496
def assignType(tree: untpd.Bind, sym: Symbol)(implicit ctx: Context) =
497497
tree.withType(NamedType.withFixedSym(NoPrefix, sym))
@@ -537,21 +537,9 @@ trait TypeAssigner {
537537
def assignType(tree: untpd.PackageDef, pid: Tree)(implicit ctx: Context) =
538538
tree.withType(pid.symbol.valRef)
539539

540-
/** Ensure that `tree2`'s type is in the same universe as `tree1`. If that's the case, return
541-
* `op` applied to both types.
542-
* If not, issue an error and return `tree1`'s type.
543-
*/
544-
private def inSameUniverse(op: (Type, Type) => Type, tp1: Type, tree2: Tree, relationship: => String)(implicit ctx: Context): Type =
545-
if (tp1.topType == tree2.tpe.topType)
546-
op(tp1, tree2.tpe)
547-
else {
548-
ctx.error(ex"$tp1 and ${tree2.tpe} are in different universes. They cannot be combined in $relationship", tree2.pos)
549-
tp1
550-
}
551-
552540
private def lubInSameUniverse(trees: List[Tree], relationship: => String)(implicit ctx: Context): Type =
553541
trees match {
554-
case first :: rest => (first.tpe /: rest)(inSameUniverse(_ | _, _, _, relationship))
542+
case first :: rest => (first.tpe /: rest)((tp, tree) => defn.inSameUniverse(_ | _, tp, tree.tpe, relationship, tree.pos))
555543
case Nil => defn.NothingType
556544
}
557545
}

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

+3-3
Original file line numberDiff line numberDiff line change
@@ -704,9 +704,9 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
704704
else true
705705
case _ => false
706706
}
707-
val funCls = defn.FunctionClass(args.length, isImplicit)
708-
typed(cpy.AppliedTypeTree(tree)(
709-
untpd.TypeTree(funCls.typeRef), args :+ body), pt)
707+
val argsTypes = args.map(tr => typed(tr).tpe)
708+
val funTpe = defn.FunctionType(argsTypes, typed(body).tpe, isImplicit)
709+
typed(cpy.AppliedTypeTree(tree)(untpd.TypeTree(funTpe), args :+ body), pt)
710710
}
711711
else {
712712
val params = args.asInstanceOf[List[untpd.ValDef]]

library/src/scala/Phantom.scala

+15
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,20 @@ trait Phantom {
88
protected final trait Nothing extends this.Any
99
1010
protected final def assume: this.Nothing
11+
12+
trait Function1[-T1 <: this.Any, +R] {
13+
def apply(x1: T1): R
14+
}
15+
trait ImplicitFunction1[-T1 <: this.Any, +R] extends Function1[T1, R] {
16+
/*implicit*/ def apply(x1: T1): R
17+
}
18+
...
19+
trait FunctionN[-T1 <: this.Any, ..., -Tn <: this.Any, +R] {
20+
def apply(x1: T1, ..., xn: Tn): R
21+
}
22+
trait ImplicitFunctionN[-T1 <: this.Any, ..., -Tn <: this.Any, +R] extends FunctionN[T1, ..., Tn, R] {
23+
/*implicit*/ def apply(x1: T1, ..., xn: Tn): R
24+
}
25+
1126
}
1227
*/

tests/neg/phantom-Functions-1.scala

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
2+
class PhantomFun1NoApply extends Function1[Boo.Casper, Unit] // error: class PhantomFun1NoApply needs to be abstract, since def apply: (p0: Casper)Unit is not defined
3+
4+
object Boo extends Phantom {
5+
type Casper <: this.Any
6+
}

tests/neg/phantom-Functions-2.scala

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
2+
class PhantomFun1 extends Boo.Function2[Boo.Casper, Int, Unit] { // error: Type argument Int does not conform to upper bound Boo.Any
3+
def apply(x1: Boo.Casper, x2: Int): Unit = ()
4+
}
5+
6+
class PhantomFun2 extends Boo.Function2[Int, Boo.Casper, Unit] { // error: Type argument Int does not conform to upper bound Boo.Any
7+
def apply(x1: Boo.Casper, x2: Int): Unit = ()
8+
}
9+
10+
class Fun extends Function2[Int, Int, Unit] {
11+
def apply(x1: Int, x2: Int): Unit = ()
12+
}
13+
14+
object Boo extends Phantom {
15+
type Casper <: this.Any
16+
}

tests/neg/phantom-Functions-3.scala

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
2+
// TODO: Importing an object that exends phantom makes FunctionN refere to the Boo.FunctionN
3+
// We should be carefull with this. Use a waring when importing Boo._ or disallow it.
4+
// Or put funtions in an inner object in the Phantom trait, for example scala.Phantom.Functions
5+
6+
import Boo._
7+
8+
class Fun extends Function1[Int, Unit] { // error: Type argument Int does not conform to upper bound Boo.Any
9+
def apply(x1: Int): Unit = ()
10+
}
11+
12+
object Boo extends Phantom {
13+
type Casper <: this.Any
14+
}

tests/neg/phantom-Lambda.scala

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
2+
class Fun {
3+
def a = (x1: Int, x2: Boo.B) => x1 // error: Int and Boo.B are in different universes. They cannot be combined in function arguments.
4+
def b: (Int, Boo.B) => Unit = (x1, x2) => x1 // error: Int and Boo.B are in different universes. They cannot be combined in function arguments.
5+
6+
def c = (x1: Foo.F, x2: Boo.B) => x1 // error: Foo.F and Boo.B are in different universes. They cannot be combined in function arguments.
7+
def d: (Foo.F, Boo.B) => Unit = (x1, x2) => x1 // error: Foo.F and Boo.B are in different universes. They cannot be combined in function arguments.
8+
}
9+
10+
object Boo extends Phantom {
11+
type B <: this.Any
12+
}
13+
14+
object Foo extends Phantom {
15+
type F <: this.Any
16+
}

0 commit comments

Comments
 (0)