Skip to content

Commit a391a58

Browse files
authored
Merge pull request #2136 from dotty-staging/implement-phantom-types-part-1
Implement phantom types part 1
2 parents 7b1cdbc + a21404a commit a391a58

File tree

102 files changed

+1655
-32
lines changed

Some content is hidden

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

102 files changed

+1655
-32
lines changed

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

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -670,16 +670,6 @@ object desugar {
670670
tree
671671
}
672672

673-
/** EmptyTree in lower bound ==> Nothing
674-
* EmptyTree in upper bounds ==> Any
675-
*/
676-
def typeBoundsTree(tree: TypeBoundsTree)(implicit ctx: Context): TypeBoundsTree = {
677-
val TypeBoundsTree(lo, hi) = tree
678-
val lo1 = if (lo.isEmpty) untpd.TypeTree(defn.NothingType) else lo
679-
val hi1 = if (hi.isEmpty) untpd.TypeTree(defn.AnyType) else hi
680-
cpy.TypeBoundsTree(tree)(lo1, hi1)
681-
}
682-
683673
/** Make closure corresponding to function.
684674
* params => body
685675
* ==>

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

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -820,7 +820,7 @@ class Definitions {
820820
lazy val UnqualifiedOwnerTypes: Set[NamedType] =
821821
RootImportTypes.toSet[NamedType] ++ RootImportTypes.map(_.symbol.moduleClass.typeRef)
822822

823-
lazy val PhantomClasses = Set[Symbol](AnyClass, AnyValClass, NullClass, NothingClass)
823+
lazy val NotRuntimeClasses = Set[Symbol](AnyClass, AnyValClass, NullClass, NothingClass)
824824

825825
/** Classes that are known not to have an initializer irrespective of
826826
* whether NoInits is set. Note: FunctionXXLClass is in this set
@@ -832,7 +832,7 @@ class Definitions {
832832
* trait gets screwed up. Therefore, it is mandatory that FunctionXXL
833833
* is treated as a NoInit trait.
834834
*/
835-
lazy val NoInitClasses = PhantomClasses + FunctionXXLClass
835+
lazy val NoInitClasses = NotRuntimeClasses + FunctionXXLClass
836836

837837
def isPolymorphicAfterErasure(sym: Symbol) =
838838
(sym eq Any_isInstanceOf) || (sym eq Any_asInstanceOf)
@@ -936,7 +936,8 @@ class Definitions {
936936
NullClass,
937937
NothingClass,
938938
SingletonClass,
939-
EqualsPatternClass)
939+
EqualsPatternClass,
940+
PhantomClass)
940941

941942
lazy val syntheticCoreClasses = syntheticScalaClasses ++ List(
942943
EmptyPackageVal,
@@ -963,4 +964,23 @@ class Definitions {
963964
_isInitialized = true
964965
}
965966
}
967+
968+
// ----- Phantoms ---------------------------------------------------------
969+
970+
lazy val PhantomClass: ClassSymbol = {
971+
val cls = completeClass(enterCompleteClassSymbol(ScalaPackageClass, tpnme.Phantom, NoInitsTrait, List(AnyType)))
972+
973+
val any = enterCompleteClassSymbol(cls, tpnme.Any, Protected | Final | NoInitsTrait, Nil)
974+
val nothing = enterCompleteClassSymbol(cls, tpnme.Nothing, Protected | Final | NoInitsTrait, List(any.typeRef))
975+
enterMethod(cls, nme.assume_, MethodType(Nil, nothing.typeRef), Protected | Final | Method)
976+
977+
cls
978+
}
979+
lazy val Phantom_AnyClass = PhantomClass.unforcedDecls.find(_.name eq tpnme.Any).asClass
980+
lazy val Phantom_NothingClass = PhantomClass.unforcedDecls.find(_.name eq tpnme.Nothing).asClass
981+
lazy val Phantom_assume = PhantomClass.unforcedDecls.find(_.name eq nme.assume_)
982+
983+
/** If the symbol is of the class scala.Phantom.Any or scala.Phantom.Nothing */
984+
def isPhantomTerminalClass(sym: Symbol) = (sym eq Phantom_AnyClass) || (sym eq Phantom_NothingClass)
985+
966986
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package dotty.tools.dotc.core
2+
3+
import dotty.tools.dotc.ast.tpd._
4+
import dotty.tools.dotc.core.Contexts.Context
5+
import dotty.tools.dotc.core.Symbols.defn
6+
import dotty.tools.dotc.core.Types.Type
7+
8+
/** Phantom erasure erases (minimal erasure):
9+
*
10+
* - Parameters/arguments are erased to BoxedUnit. The next step will remove the parameters
11+
* from the method definitions and calls (implemented in branch implement-phantom-types-part-2).
12+
* - Definitions of `def`, `val`, `lazy val` and `var` returning a phantom type to return a BoxedUnit. The next step
13+
* is to erase the fields for phantom types (implemented in branch implement-phantom-types-part-3)
14+
* - Calls to Phantom.assume become calls to BoxedUnit. Intended to be optimized away by local optimizations.
15+
*
16+
* BoxedUnit is used as it fits perfectly and homogeneously in all locations where phantoms can be found.
17+
* Additionally some of the optimizations that can be performed on phantom types can also be directly implemented
18+
* on all boxed units.
19+
*/
20+
object PhantomErasure {
21+
22+
/** Returns the default erased type of a phantom type */
23+
def erasedPhantomType(implicit ctx: Context): Type = defn.BoxedUnitType
24+
25+
/** Returns the default erased tree for a call to Phantom.assume */
26+
def erasedAssume(implicit ctx: Context): Tree = ref(defn.BoxedUnit_UNIT)
27+
28+
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,8 @@ object StdNames {
237237
final val SourceFileATTR: N = "SourceFile"
238238
final val SyntheticATTR: N = "Synthetic"
239239

240+
final val Phantom: N = "Phantom"
241+
240242
// ----- Term names -----------------------------------------
241243

242244
// Compiler-internal

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -483,8 +483,8 @@ object SymDenotations {
483483
def isNumericValueClass(implicit ctx: Context) =
484484
maybeOwner == defn.ScalaPackageClass && defn.ScalaNumericValueClasses().contains(symbol)
485485

486-
/** Is symbol a phantom class for which no runtime representation exists? */
487-
def isPhantomClass(implicit ctx: Context) = defn.PhantomClasses contains symbol
486+
/** Is symbol a class for which no runtime representation exists? */
487+
def isNotRuntimeClass(implicit ctx: Context) = defn.NotRuntimeClasses contains symbol
488488

489489
/** Is this symbol a class representing a refinement? These classes
490490
* are used only temporarily in Typer and Unpickler as an intermediate
@@ -635,7 +635,7 @@ object SymDenotations {
635635

636636
/** Is this symbol a class references to which that are supertypes of null? */
637637
final def isNullableClass(implicit ctx: Context): Boolean =
638-
isClass && !isValueClass && !(this is ModuleClass) && symbol != defn.NothingClass
638+
isClass && !isValueClass && !(this is ModuleClass) && symbol != defn.NothingClass && !defn.isPhantomTerminalClass(symbol)
639639

640640
/** Is this definition accessible as a member of tree with type `pre`?
641641
* @param pre The type of the tree from which the selection is made

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

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
4747
private var myAnyClass: ClassSymbol = null
4848
private var myNothingClass: ClassSymbol = null
4949
private var myNullClass: ClassSymbol = null
50+
private var myPhantomNothingClass: ClassSymbol = null
5051
private var myObjectClass: ClassSymbol = null
5152
private var myAnyType: TypeRef = null
5253
private var myNothingType: TypeRef = null
@@ -63,6 +64,10 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
6364
if (myNullClass == null) myNullClass = defn.NullClass
6465
myNullClass
6566
}
67+
def PhantomNothingClass = {
68+
if (myPhantomNothingClass == null) myPhantomNothingClass = defn.Phantom_NothingClass
69+
myPhantomNothingClass
70+
}
6671
def ObjectClass = {
6772
if (myObjectClass == null) myObjectClass = defn.ObjectClass
6873
myObjectClass
@@ -550,8 +555,10 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
550555
case OrType(tp1, tp2) => isNullable(tp1) || isNullable(tp2)
551556
case _ => false
552557
}
553-
(tp1.symbol eq NothingClass) && tp2.isValueTypeOrLambda ||
554-
(tp1.symbol eq NullClass) && isNullable(tp2)
558+
val sym1 = tp1.symbol
559+
(sym1 eq NothingClass) && tp2.isValueTypeOrLambda && !tp2.isPhantom ||
560+
(sym1 eq NullClass) && isNullable(tp2) ||
561+
(sym1 eq PhantomNothingClass) && tp1.topType == tp2.topType
555562
}
556563
case tp1: SingletonType =>
557564
/** if `tp2 == p.type` and `p: q.type` then try `tp1 <:< q.type` as a last effort.*/

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,7 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
364364
else if (semiEraseVCs && isDerivedValueClass(sym)) eraseDerivedValueClassRef(tp)
365365
else if (sym == defn.ArrayClass) apply(tp.appliedTo(TypeBounds.empty)) // i966 shows that we can hit a raw Array type.
366366
else if (defn.isSyntheticFunctionClass(sym)) defn.erasedFunctionType(sym)
367+
else if (defn.isPhantomTerminalClass(tp.symbol)) PhantomErasure.erasedPhantomType
367368
else eraseNormalClassRef(tp)
368369
case tp: RefinedType =>
369370
val parent = tp.parent
@@ -403,7 +404,8 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
403404
case tr :: trs1 =>
404405
assert(!tr.classSymbol.is(Trait), cls)
405406
val tr1 = if (cls is Trait) defn.ObjectType else tr
406-
tr1 :: trs1.filterNot(_ isRef defn.ObjectClass)
407+
// We remove the Phantom trait to erase the definitions of Phantom.{assume, Any, Nothing}
408+
tr1 :: trs1.filterNot(x => x.isRef(defn.ObjectClass) || x.isRef(defn.PhantomClass))
407409
case nil => nil
408410
}
409411
val erasedDecls = decls.filteredScope(sym => !sym.isType || sym.isClass)
@@ -506,6 +508,8 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
506508
}
507509
if (defn.isSyntheticFunctionClass(sym))
508510
sigName(defn.erasedFunctionType(sym))
511+
else if (defn.isPhantomTerminalClass(tp.symbol))
512+
sigName(PhantomErasure.erasedPhantomType)
509513
else
510514
normalizeClass(sym.asClass).fullName.asTypeName
511515
case defn.ArrayOf(elem) =>

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

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,44 @@ object Types {
172172
case _ =>
173173
false
174174
}
175-
cls == defn.AnyClass || loop(this)
175+
loop(this)
176+
}
177+
178+
/** Returns true if the type is a phantom type
179+
* - true if XYZ extends scala.Phantom and this type is upper bounded XYZ.Any
180+
* - false otherwise
181+
*/
182+
final def isPhantom(implicit ctx: Context): Boolean = phantomLatticeType.exists
183+
184+
/** Returns the top type of the lattice
185+
* - XYX.Any if XYZ extends scala.Phantom and this type is upper bounded XYZ.Any
186+
* - scala.Any otherwise
187+
*/
188+
final def topType(implicit ctx: Context): Type = {
189+
val lattice = phantomLatticeType
190+
if (lattice.exists) lattice.select(tpnme.Any)
191+
else defn.AnyType
192+
}
193+
194+
/** Returns the bottom type of the lattice
195+
* - XYZ.Nothing if XYZ extends scala.Phantom and this type is upper bounded XYZ.Any
196+
* - scala.Nothing otherwise
197+
*/
198+
final def bottomType(implicit ctx: Context): Type = {
199+
val lattice = phantomLatticeType
200+
if (lattice.exists) lattice.select(tpnme.Nothing)
201+
else defn.NothingType
202+
}
203+
204+
/** Returns the type of the phantom lattice (i.e. the prefix of the phantom type)
205+
* - XYZ if XYZ extends scala.Phantom and this type is upper bounded XYZ.Any
206+
* - NoType otherwise
207+
*/
208+
private final def phantomLatticeType(implicit ctx: Context): Type = widen match {
209+
case tp: ClassInfo if defn.isPhantomTerminalClass(tp.classSymbol) => tp.prefix
210+
case tp: TypeProxy if tp.superType ne this => tp.underlying.phantomLatticeType
211+
case tp: AndOrType => tp.tp1.phantomLatticeType
212+
case _ => NoType
176213
}
177214

178215
/** Is this type guaranteed not to have `null` as a value?

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ class ElimRepeated extends MiniPhaseTransform with InfoTransformer with Annotati
8989
case _ =>
9090
val elemType = tree.tpe.elemType
9191
var elemClass = elemType.classSymbol
92-
if (defn.PhantomClasses contains elemClass) elemClass = defn.ObjectClass
92+
if (defn.NotRuntimeClasses contains elemClass) elemClass = defn.ObjectClass
9393
ref(defn.DottyArraysModule)
9494
.select(nme.seqToArray)
9595
.appliedToType(elemType)

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import ValueClasses._
2828
import TypeUtils._
2929
import ExplicitOuter._
3030
import core.Mode
31+
import core.PhantomErasure
3132

3233
class Erasure extends Phase with DenotTransformer { thisTransformer =>
3334

@@ -454,6 +455,8 @@ object Erasure extends TypeTestsCasts{
454455
val Apply(fun, args) = tree
455456
if (fun.symbol == defn.cbnArg)
456457
typedUnadapted(args.head, pt)
458+
else if (fun.symbol eq defn.Phantom_assume)
459+
PhantomErasure.erasedAssume
457460
else typedExpr(fun, FunProto(args, pt, this)) match {
458461
case fun1: Apply => // arguments passed in prototype were already passed
459462
fun1

0 commit comments

Comments
 (0)