Skip to content

Commit 8234c77

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 2a25799 commit 8234c77

40 files changed

+577
-50
lines changed

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

Lines changed: 75 additions & 26 deletions
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
}
@@ -682,7 +680,7 @@ class Definitions {
682680

683681
object FunctionOf {
684682
def apply(args: List[Type], resultType: Type, isImplicit: Boolean = false)(implicit ctx: Context) =
685-
FunctionType(args.length, isImplicit).appliedTo(args ::: resultType :: Nil)
683+
FunctionType(args, resultType, isImplicit).appliedTo(args ::: resultType :: Nil)
686684
def unapply(ft: Type)(implicit ctx: Context) = {
687685
val tsym = ft.typeSymbol
688686
if (isFunctionClass(tsym)) {
@@ -747,16 +745,38 @@ class Definitions {
747745
lazy val Function0_applyR = ImplementedFunctionType(0).symbol.requiredMethodRef(nme.apply)
748746
def Function0_apply(implicit ctx: Context) = Function0_applyR.symbol
749747

750-
def FunctionType(n: Int, isImplicit: Boolean = false)(implicit ctx: Context): TypeRef =
751-
if (n <= MaxImplementedFunctionArity && (!isImplicit || ctx.erasedTypes)) ImplementedFunctionType(n)
748+
def FunctionType(n: Int, isImplicit: Boolean = false, top: Type = AnyType)(implicit ctx: Context): TypeRef = {
749+
if (top.isPhantom) {
750+
val functionPrefix = if (isImplicit) str.ImplicitFunction else str.Function
751+
val functionName = (functionPrefix + n).toTypeName
752+
val functionType = top.normalizedPrefix.select(functionName)
753+
assert(functionType.classSymbol.exists)
754+
functionType.asInstanceOf[TypeRef]
755+
}
756+
else if (n <= MaxImplementedFunctionArity && (!isImplicit || ctx.erasedTypes)) ImplementedFunctionType(n)
752757
else FunctionClass(n, isImplicit).typeRef
758+
}
759+
760+
def FunctionType(args: List[Type], resultType: Type, isImplicit: Boolean)(implicit ctx: Context): TypeRef =
761+
FunctionType(args.length, isImplicit, topInSameUniverse(args, "function arguments."))
762+
763+
private def topInSameUniverse(types: List[Type], relationship: => String)(implicit ctx: Context): Type = {
764+
types match {
765+
case first :: rest => (first /: rest)(inSameUniverse((t1, _) => t1.topType, _, _, relationship, ctx.owner.pos))
766+
case Nil => defn.AnyType
767+
}
768+
}
753769

754770
private lazy val TupleTypes: Set[TypeRef] = TupleType.toSet
755771

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

776+
/** If `cls` is a class in an object extending scala.Phantom, its name, otherwise EmptyTypeName */
777+
def phantomClassName(cls: Symbol)(implicit ctx: Context): TypeName =
778+
if (cls.isClass && cls.owner.derivesFrom(PhantomClass)) cls.asClass.name else EmptyTypeName
779+
760780
/** If type `ref` refers to a class in the scala package, its name, otherwise EmptyTypeName */
761781
def scalaClassName(ref: Type)(implicit ctx: Context): TypeName = scalaClassName(ref.classSymbol)
762782

@@ -775,24 +795,28 @@ class Definitions {
775795
* - FunctionN for N >= 0
776796
* - ImplicitFunctionN for N >= 0
777797
*/
778-
def isFunctionClass(cls: Symbol) = scalaClassName(cls).isFunction
798+
def isFunctionClass(cls: Symbol) =
799+
scalaClassName(cls).isFunction || phantomClassName(cls).isFunction
779800

780801
/** Is an implicit function class.
781802
* - ImplicitFunctionN for N >= 0
782803
*/
783-
def isImplicitFunctionClass(cls: Symbol) = scalaClassName(cls).isImplicitFunction
804+
def isImplicitFunctionClass(cls: Symbol) =
805+
scalaClassName(cls).isImplicitFunction || phantomClassName(cls).isImplicitFunction
784806

785807
/** Is a class that will be erased to FunctionXXL
786808
* - FunctionN for N >= 22
787809
* - ImplicitFunctionN for N >= 22
788810
*/
789-
def isXXLFunctionClass(cls: Symbol) = scalaClassName(cls).functionArity > MaxImplementedFunctionArity
811+
def isXXLFunctionClass(cls: Symbol) =
812+
scalaClassName(cls).functionArity > MaxImplementedFunctionArity
790813

791814
/** Is a synthetic function class
792815
* - FunctionN for N > 22
793816
* - ImplicitFunctionN for N >= 0
794817
*/
795-
def isSyntheticFunctionClass(cls: Symbol) = scalaClassName(cls).isSyntheticFunction
818+
def isSyntheticFunctionClass(cls: Symbol) =
819+
scalaClassName(cls).isSyntheticScalaFunction || phantomClassName(cls).isFunction
796820

797821
def isAbstractFunctionClass(cls: Symbol) = isVarArityClass(cls, str.AbstractFunction)
798822
def isTupleClass(cls: Symbol) = isVarArityClass(cls, str.Tuple)
@@ -809,6 +833,7 @@ class Definitions {
809833
val arity = scalaClassName(cls).functionArity
810834
if (arity > 22) defn.FunctionXXLClass
811835
else if (arity >= 0) defn.FunctionClass(arity)
836+
else if (phantomClassName(cls).isFunction) defn.FunctionClass(0)
812837
else NoSymbol
813838
}
814839

@@ -823,6 +848,7 @@ class Definitions {
823848
val arity = scalaClassName(cls).functionArity
824849
if (arity > 22) defn.FunctionXXLType
825850
else if (arity >= 0) defn.FunctionType(arity)
851+
else if (phantomClassName(cls).isFunction) defn.FunctionType(0)
826852
else NoType
827853
}
828854

@@ -867,7 +893,10 @@ class Definitions {
867893
* trait gets screwed up. Therefore, it is mandatory that FunctionXXL
868894
* is treated as a NoInit trait.
869895
*/
870-
lazy val NoInitClasses = NotRuntimeClasses + FunctionXXLClass
896+
private lazy val NoInitClasses = NotRuntimeClasses + FunctionXXLClass
897+
898+
def isNoInitClass(cls: Symbol): Boolean =
899+
cls.is(NoInitsTrait) || NoInitClasses.contains(cls) || isFunctionClass(cls)
871900

872901
def isPolymorphicAfterErasure(sym: Symbol) =
873902
(sym eq Any_isInstanceOf) || (sym eq Any_asInstanceOf)
@@ -888,7 +917,11 @@ class Definitions {
888917
def isFunctionType(tp: Type)(implicit ctx: Context) = {
889918
val arity = functionArity(tp)
890919
val sym = tp.dealias.typeSymbol
891-
arity >= 0 && isFunctionClass(sym) && tp.isRef(FunctionType(arity, sym.name.isImplicitFunction).typeSymbol)
920+
def top =
921+
if (!sym.owner.derivesFrom(defn.PhantomClass)) defn.AnyType
922+
else sym.owner.thisType.select(tpnme.Any)
923+
def funType = FunctionType(arity, sym.name.isImplicitFunction, top)
924+
arity >= 0 && isFunctionClass(sym) && tp.isRef(funType.typeSymbol)
892925
}
893926

894927
def functionArity(tp: Type)(implicit ctx: Context) = tp.dealias.argInfos.length - 1
@@ -1004,6 +1037,10 @@ class Definitions {
10041037

10051038
lazy val PhantomClass: ClassSymbol = {
10061039
val cls = completeClass(enterCompleteClassSymbol(ScalaPackageClass, tpnme.Phantom, NoInitsTrait, List(AnyType)))
1040+
cls.unforcedDecls.openForMutations.useSynthesizer { name => ctx =>
1041+
if (name.isTypeName && name.isFunction) newFunctionNTrait(name.asTypeName, cls)
1042+
else NoSymbol
1043+
}
10071044

10081045
val any = enterCompleteClassSymbol(cls, tpnme.Any, Protected | Final | NoInitsTrait, Nil)
10091046
val nothing = enterCompleteClassSymbol(cls, tpnme.Nothing, Protected | Final | NoInitsTrait, List(any.typeRef))
@@ -1024,4 +1061,16 @@ class Definitions {
10241061

10251062
def ErasedPhantom_UNIT(implicit ctx: Context) = ErasedPhantomClass.linkedClass.requiredValue("UNIT")
10261063

1064+
/** Ensure that `tp2`' is in the same universe as `tp1`. If that's the case, return
1065+
* `op` applied to both types.
1066+
* If not, issue an error and return `tp1`'.
1067+
*/
1068+
def inSameUniverse(op: (Type, Type) => Type, tp1: Type, tp2: Type, relationship: => String, pos: Position)(implicit ctx: Context): Type =
1069+
if (tp1.topType == tp2.topType)
1070+
op(tp1, tp2)
1071+
else {
1072+
ctx.error(ex"$tp1 and $tp2 are in different universes. They cannot be combined in $relationship", pos)
1073+
tp1
1074+
}
1075+
10271076
}

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ object NameOps {
154154
}
155155
}
156156

157-
/** Is a synthetic function name
157+
/** Is a function name
158158
* - N for FunctionN
159159
* - N for ImplicitFunctionN
160160
* - (-1) otherwise
@@ -175,14 +175,14 @@ object NameOps {
175175
*/
176176
def isImplicitFunction: Boolean = functionArityFor(str.ImplicitFunction) >= 0
177177

178-
/** Is a synthetic function name
178+
/** Is a synthetic function name (in scala package)
179179
* - FunctionN for N > 22
180180
* - ImplicitFunctionN for N >= 0
181181
* - false otherwise
182182
*/
183-
def isSyntheticFunction: Boolean = {
183+
def isSyntheticScalaFunction: Boolean = {
184184
functionArityFor(str.Function) > MaxImplementedFunctionArity ||
185-
functionArityFor(str.ImplicitFunction) >= 0
185+
functionArityFor(str.ImplicitFunction) >= 0
186186
}
187187

188188
/** Parsed function arity for function with some specific prefix */

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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 4 additions & 16 deletions
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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -687,9 +687,9 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
687687
def typedFunction(tree: untpd.Function, pt: Type)(implicit ctx: Context) = track("typedFunction") {
688688
val untpd.Function(args, body) = tree
689689
if (ctx.mode is Mode.Type) {
690-
val funCls = defn.FunctionClass(args.length, tree.isInstanceOf[untpd.ImplicitFunction])
691-
typed(cpy.AppliedTypeTree(tree)(
692-
untpd.TypeTree(funCls.typeRef), args :+ body), pt)
690+
val argsTypes = args.map(tr => typed(tr).tpe)
691+
val funTpe = defn.FunctionType(argsTypes, typed(body).tpe, tree.isInstanceOf[untpd.ImplicitFunction])
692+
typed(cpy.AppliedTypeTree(tree)(untpd.TypeTree(funTpe), args :+ body), pt)
693693
}
694694
else {
695695
val params = args.asInstanceOf[List[untpd.ValDef]]

library/src/scala/Phantom.scala

Lines changed: 15 additions & 0 deletions
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

Lines changed: 6 additions & 0 deletions
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

Lines changed: 16 additions & 0 deletions
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

Lines changed: 14 additions & 0 deletions
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

Lines changed: 16 additions & 0 deletions
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+
}

tests/run/phantom-Functions-0.check

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Blinky1.apply()
2+
Blinky1.apply()
3+
Blinky1.apply()

0 commit comments

Comments
 (0)