Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement phantom types part 1 #2136

Merged
merged 32 commits into from
May 8, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
f067b8e
Rename old PhantomClasses to avoid name clashes with phantom types.
nicolasstucki Jul 23, 2016
ac57124
Implement phantom types.
nicolasstucki Jul 19, 2016
84931e3
Extra tests for Phantoms 1.
nicolasstucki Mar 22, 2017
a23ef92
Extra tests for Phantoms 2.
nicolasstucki Mar 22, 2017
7ccdbf3
Quick cleanup
nicolasstucki Apr 6, 2017
01063d3
Optimizations
nicolasstucki Apr 6, 2017
5fb733c
Remove unecessary code
nicolasstucki Apr 6, 2017
e8590f6
Add restriction on phantom latice definition.
nicolasstucki Apr 6, 2017
621a67b
Fixes after rebase.
nicolasstucki Apr 6, 2017
ee44731
Replace phantomTop by general top type.
nicolasstucki Apr 6, 2017
0f4f863
Fix error on if/match with phantoms of different lattices.
nicolasstucki Apr 6, 2017
efae3a3
Inline error messages.
nicolasstucki Apr 8, 2017
510c0cb
Fix phantom Nothing.
nicolasstucki Apr 9, 2017
f782288
Fix cyclic computation of topType.
nicolasstucki Apr 9, 2017
341cdf1
Move phantom universe checks from Typer to TypeAssigner.
nicolasstucki Apr 9, 2017
a50328f
Remove PhantomTypeErasure and integrate into Erasure.
nicolasstucki Apr 10, 2017
9502cb2
Slighly faster phantom erasure checks.
nicolasstucki Apr 11, 2017
6f03972
Simplify the definition of Phantom.assume.
nicolasstucki Apr 17, 2017
621610f
Cache PhantomNothing, PhantomAny and PhantomAssume symbols.
nicolasstucki Apr 20, 2017
ef62884
Shortcircuit slow path typing Nothing <:< XYZ for non phantoms.
nicolasstucki Apr 20, 2017
49e88fe
Faster bottomType and phantomness checks.
nicolasstucki Apr 21, 2017
e062df3
Improve names of phantom classes in defn.
nicolasstucki Apr 24, 2017
3ff903e
Remove unnecessary changes.
nicolasstucki Apr 24, 2017
f0383fe
Remove ErasedPhantom class and use Unit.
nicolasstucki Apr 26, 2017
91558c3
Set flags of scala.Phantom to be NoInitsTrait.
nicolasstucki Apr 27, 2017
ec13270
Rename Phantoms in defn and add Type.isPhantom.
nicolasstucki May 2, 2017
62b7732
Add documentation.
nicolasstucki May 2, 2017
4202b98
Fix neg/cycles.scala infinite loop on phantomLatticeType.
nicolasstucki May 2, 2017
c6fe778
Code cleanup.
nicolasstucki May 2, 2017
07c02c8
Fix after rebase, if/match missmatch on phantoms.
nicolasstucki May 2, 2017
40ab53a
Polishings of Typer and TypeAssigner
odersky May 8, 2017
a21404a
Remove unused error messages.
nicolasstucki May 8, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 0 additions & 10 deletions compiler/src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -670,16 +670,6 @@ object desugar {
tree
}

/** EmptyTree in lower bound ==> Nothing
* EmptyTree in upper bounds ==> Any
*/
def typeBoundsTree(tree: TypeBoundsTree)(implicit ctx: Context): TypeBoundsTree = {
val TypeBoundsTree(lo, hi) = tree
val lo1 = if (lo.isEmpty) untpd.TypeTree(defn.NothingType) else lo
val hi1 = if (hi.isEmpty) untpd.TypeTree(defn.AnyType) else hi
cpy.TypeBoundsTree(tree)(lo1, hi1)
}

/** Make closure corresponding to function.
* params => body
* ==>
Expand Down
26 changes: 23 additions & 3 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -820,7 +820,7 @@ class Definitions {
lazy val UnqualifiedOwnerTypes: Set[NamedType] =
RootImportTypes.toSet[NamedType] ++ RootImportTypes.map(_.symbol.moduleClass.typeRef)

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

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

def isPolymorphicAfterErasure(sym: Symbol) =
(sym eq Any_isInstanceOf) || (sym eq Any_asInstanceOf)
Expand Down Expand Up @@ -936,7 +936,8 @@ class Definitions {
NullClass,
NothingClass,
SingletonClass,
EqualsPatternClass)
EqualsPatternClass,
PhantomClass)

lazy val syntheticCoreClasses = syntheticScalaClasses ++ List(
EmptyPackageVal,
Expand All @@ -963,4 +964,23 @@ class Definitions {
_isInitialized = true
}
}

// ----- Phantoms ---------------------------------------------------------

lazy val PhantomClass: ClassSymbol = {
val cls = completeClass(enterCompleteClassSymbol(ScalaPackageClass, tpnme.Phantom, NoInitsTrait, List(AnyType)))

val any = enterCompleteClassSymbol(cls, tpnme.Any, Protected | Final | NoInitsTrait, Nil)
val nothing = enterCompleteClassSymbol(cls, tpnme.Nothing, Protected | Final | NoInitsTrait, List(any.typeRef))
enterMethod(cls, nme.assume_, MethodType(Nil, nothing.typeRef), Protected | Final | Method)

cls
}
lazy val Phantom_AnyClass = PhantomClass.unforcedDecls.find(_.name eq tpnme.Any).asClass
lazy val Phantom_NothingClass = PhantomClass.unforcedDecls.find(_.name eq tpnme.Nothing).asClass
lazy val Phantom_assume = PhantomClass.unforcedDecls.find(_.name eq nme.assume_)

/** If the symbol is of the class scala.Phantom.Any or scala.Phantom.Nothing */
def isPhantomTerminalClass(sym: Symbol) = (sym eq Phantom_AnyClass) || (sym eq Phantom_NothingClass)

}
28 changes: 28 additions & 0 deletions compiler/src/dotty/tools/dotc/core/PhantomErasure.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package dotty.tools.dotc.core

import dotty.tools.dotc.ast.tpd._
import dotty.tools.dotc.core.Contexts.Context
import dotty.tools.dotc.core.Symbols.defn
import dotty.tools.dotc.core.Types.Type

/** Phantom erasure erases (minimal erasure):
*
* - Parameters/arguments are erased to BoxedUnit. The next step will remove the parameters
* from the method definitions and calls (implemented in branch implement-phantom-types-part-2).
* - Definitions of `def`, `val`, `lazy val` and `var` returning a phantom type to return a BoxedUnit. The next step
* is to erase the fields for phantom types (implemented in branch implement-phantom-types-part-3)
* - Calls to Phantom.assume become calls to BoxedUnit. Intended to be optimized away by local optimizations.
*
* BoxedUnit is used as it fits perfectly and homogeneously in all locations where phantoms can be found.
* Additionally some of the optimizations that can be performed on phantom types can also be directly implemented
* on all boxed units.
*/
object PhantomErasure {

/** Returns the default erased type of a phantom type */
def erasedPhantomType(implicit ctx: Context): Type = defn.BoxedUnitType

/** Returns the default erased tree for a call to Phantom.assume */
def erasedAssume(implicit ctx: Context): Tree = ref(defn.BoxedUnit_UNIT)

}
2 changes: 2 additions & 0 deletions compiler/src/dotty/tools/dotc/core/StdNames.scala
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,8 @@ object StdNames {
final val SourceFileATTR: N = "SourceFile"
final val SyntheticATTR: N = "Synthetic"

final val Phantom: N = "Phantom"

// ----- Term names -----------------------------------------

// Compiler-internal
Expand Down
6 changes: 3 additions & 3 deletions compiler/src/dotty/tools/dotc/core/SymDenotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -483,8 +483,8 @@ object SymDenotations {
def isNumericValueClass(implicit ctx: Context) =
maybeOwner == defn.ScalaPackageClass && defn.ScalaNumericValueClasses().contains(symbol)

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

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

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

/** Is this definition accessible as a member of tree with type `pre`?
* @param pre The type of the tree from which the selection is made
Expand Down
11 changes: 9 additions & 2 deletions compiler/src/dotty/tools/dotc/core/TypeComparer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
private var myAnyClass: ClassSymbol = null
private var myNothingClass: ClassSymbol = null
private var myNullClass: ClassSymbol = null
private var myPhantomNothingClass: ClassSymbol = null
private var myObjectClass: ClassSymbol = null
private var myAnyType: TypeRef = null
private var myNothingType: TypeRef = null
Expand All @@ -63,6 +64,10 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
if (myNullClass == null) myNullClass = defn.NullClass
myNullClass
}
def PhantomNothingClass = {
if (myPhantomNothingClass == null) myPhantomNothingClass = defn.Phantom_NothingClass
myPhantomNothingClass
}
def ObjectClass = {
if (myObjectClass == null) myObjectClass = defn.ObjectClass
myObjectClass
Expand Down Expand Up @@ -550,8 +555,10 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
case OrType(tp1, tp2) => isNullable(tp1) || isNullable(tp2)
case _ => false
}
(tp1.symbol eq NothingClass) && tp2.isValueTypeOrLambda ||
(tp1.symbol eq NullClass) && isNullable(tp2)
val sym1 = tp1.symbol
(sym1 eq NothingClass) && tp2.isValueTypeOrLambda && !tp2.isPhantom ||
(sym1 eq NullClass) && isNullable(tp2) ||
(sym1 eq PhantomNothingClass) && tp1.topType == tp2.topType
}
case tp1: SingletonType =>
/** if `tp2 == p.type` and `p: q.type` then try `tp1 <:< q.type` as a last effort.*/
Expand Down
6 changes: 5 additions & 1 deletion compiler/src/dotty/tools/dotc/core/TypeErasure.scala
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,7 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
else if (semiEraseVCs && isDerivedValueClass(sym)) eraseDerivedValueClassRef(tp)
else if (sym == defn.ArrayClass) apply(tp.appliedTo(TypeBounds.empty)) // i966 shows that we can hit a raw Array type.
else if (defn.isSyntheticFunctionClass(sym)) defn.erasedFunctionType(sym)
else if (defn.isPhantomTerminalClass(tp.symbol)) PhantomErasure.erasedPhantomType
else eraseNormalClassRef(tp)
case tp: RefinedType =>
val parent = tp.parent
Expand Down Expand Up @@ -403,7 +404,8 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
case tr :: trs1 =>
assert(!tr.classSymbol.is(Trait), cls)
val tr1 = if (cls is Trait) defn.ObjectType else tr
tr1 :: trs1.filterNot(_ isRef defn.ObjectClass)
// We remove the Phantom trait to erase the definitions of Phantom.{assume, Any, Nothing}
tr1 :: trs1.filterNot(x => x.isRef(defn.ObjectClass) || x.isRef(defn.PhantomClass))
case nil => nil
}
val erasedDecls = decls.filteredScope(sym => !sym.isType || sym.isClass)
Expand Down Expand Up @@ -506,6 +508,8 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
}
if (defn.isSyntheticFunctionClass(sym))
sigName(defn.erasedFunctionType(sym))
else if (defn.isPhantomTerminalClass(tp.symbol))
sigName(PhantomErasure.erasedPhantomType)
else
normalizeClass(sym.asClass).fullName.asTypeName
case defn.ArrayOf(elem) =>
Expand Down
39 changes: 38 additions & 1 deletion compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,44 @@ object Types {
case _ =>
false
}
cls == defn.AnyClass || loop(this)
loop(this)
}

/** Returns true if the type is a phantom type
* - true if XYZ extends scala.Phantom and this type is upper bounded XYZ.Any
* - false otherwise
*/
final def isPhantom(implicit ctx: Context): Boolean = phantomLatticeType.exists

/** Returns the top type of the lattice
* - XYX.Any if XYZ extends scala.Phantom and this type is upper bounded XYZ.Any
* - scala.Any otherwise
*/
final def topType(implicit ctx: Context): Type = {
val lattice = phantomLatticeType
if (lattice.exists) lattice.select(tpnme.Any)
else defn.AnyType
}

/** Returns the bottom type of the lattice
* - XYZ.Nothing if XYZ extends scala.Phantom and this type is upper bounded XYZ.Any
* - scala.Nothing otherwise
*/
final def bottomType(implicit ctx: Context): Type = {
val lattice = phantomLatticeType
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could be: topType.prefix.select(tpnme.Nothing) or something close.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It could. The advantage of this version is that computing a bottom type that would result in scala.Nothing is more efficient.

if (lattice.exists) lattice.select(tpnme.Nothing)
else defn.NothingType
}

/** Returns the type of the phantom lattice (i.e. the prefix of the phantom type)
* - XYZ if XYZ extends scala.Phantom and this type is upper bounded XYZ.Any
* - NoType otherwise
*/
private final def phantomLatticeType(implicit ctx: Context): Type = widen match {
case tp: ClassInfo if defn.isPhantomTerminalClass(tp.classSymbol) => tp.prefix
case tp: TypeProxy if tp.superType ne this => tp.underlying.phantomLatticeType
case tp: AndOrType => tp.tp1.phantomLatticeType
case _ => NoType
}

/** Is this type guaranteed not to have `null` as a value?
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ class ElimRepeated extends MiniPhaseTransform with InfoTransformer with Annotati
case _ =>
val elemType = tree.tpe.elemType
var elemClass = elemType.classSymbol
if (defn.PhantomClasses contains elemClass) elemClass = defn.ObjectClass
if (defn.NotRuntimeClasses contains elemClass) elemClass = defn.ObjectClass
ref(defn.DottyArraysModule)
.select(nme.seqToArray)
.appliedToType(elemType)
Expand Down
3 changes: 3 additions & 0 deletions compiler/src/dotty/tools/dotc/transform/Erasure.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import ValueClasses._
import TypeUtils._
import ExplicitOuter._
import core.Mode
import core.PhantomErasure

class Erasure extends Phase with DenotTransformer { thisTransformer =>

Expand Down Expand Up @@ -454,6 +455,8 @@ object Erasure extends TypeTestsCasts{
val Apply(fun, args) = tree
if (fun.symbol == defn.cbnArg)
typedUnadapted(args.head, pt)
else if (fun.symbol eq defn.Phantom_assume)
PhantomErasure.erasedAssume
else typedExpr(fun, FunProto(args, pt, this)) match {
case fun1: Apply => // arguments passed in prototype were already passed
fun1
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/transform/Mixin.scala
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ class Mixin extends MiniPhaseTransform with SymTransformer { thisTransform =>

def superCallOpt(baseCls: Symbol): List[Tree] = superCalls.get(baseCls) match {
case Some(call) =>
if (defn.PhantomClasses.contains(baseCls)) Nil else call :: Nil
if (defn.NotRuntimeClasses.contains(baseCls)) Nil else call :: Nil
case None =>
if (baseCls.is(NoInitsTrait) || defn.NoInitClasses.contains(baseCls)) Nil
else {
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/transform/TreeGen.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ object TreeGen {
def wrapArrayMethodName(elemtp: Type)(implicit ctx: Context): TermName = {
val elemCls = elemtp.classSymbol
if (elemCls.isPrimitiveValueClass) nme.wrapXArray(elemCls.name)
else if (elemCls.derivesFrom(defn.ObjectClass) && !elemCls.isPhantomClass) nme.wrapRefArray
else if (elemCls.derivesFrom(defn.ObjectClass) && !elemCls.isNotRuntimeClass) nme.wrapRefArray
else nme.genericWrapArray
}

Expand Down
37 changes: 30 additions & 7 deletions compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala
Original file line number Diff line number Diff line change
Expand Up @@ -418,7 +418,7 @@ trait TypeAssigner {
tree.withType(avoidingType(expansion, bindings))

def assignType(tree: untpd.If, thenp: Tree, elsep: Tree)(implicit ctx: Context) =
tree.withType(thenp.tpe | elsep.tpe)
tree.withType(lubInSameUniverse(thenp :: elsep :: Nil, "branches of an if/else"))

def assignType(tree: untpd.Closure, meth: Tree, target: Tree)(implicit ctx: Context) =
tree.withType(
Expand All @@ -428,15 +428,18 @@ trait TypeAssigner {
def assignType(tree: untpd.CaseDef, body: Tree)(implicit ctx: Context) =
tree.withType(body.tpe)

def assignType(tree: untpd.Match, cases: List[CaseDef])(implicit ctx: Context) =
tree.withType(ctx.typeComparer.lub(cases.tpes))
def assignType(tree: untpd.Match, cases: List[CaseDef])(implicit ctx: Context) = {
if (tree.selector.typeOpt.isPhantom)
ctx.error("cannot pattern match on values of a phantom type", tree.selector.pos)
tree.withType(lubInSameUniverse(cases, "branches of a match"))
}

def assignType(tree: untpd.Return)(implicit ctx: Context) =
tree.withType(defn.NothingType)

def assignType(tree: untpd.Try, expr: Tree, cases: List[CaseDef])(implicit ctx: Context) =
if (cases.isEmpty) tree.withType(expr.tpe)
else tree.withType(ctx.typeComparer.lub(expr.tpe :: cases.tpes))
else tree.withType(lubInSameUniverse(expr :: cases, "branches of a try"))

def assignType(tree: untpd.SeqLiteral, elems: List[Tree], elemtpt: Tree)(implicit ctx: Context) = {
val ownType = tree match {
Expand All @@ -450,10 +453,10 @@ trait TypeAssigner {
tree.withType(ref.tpe)

def assignType(tree: untpd.AndTypeTree, left: Tree, right: Tree)(implicit ctx: Context) =
tree.withType(left.tpe & right.tpe)
tree.withType(inSameUniverse(_ & _, left.tpe, right, "an `&`"))

def assignType(tree: untpd.OrTypeTree, left: Tree, right: Tree)(implicit ctx: Context) =
tree.withType(left.tpe | right.tpe)
tree.withType(inSameUniverse(_ | _, left.tpe, right, "an `|`"))

/** Assign type of RefinedType.
* Refinements are typed as if they were members of refinement class `refineCls`.
Expand Down Expand Up @@ -484,7 +487,9 @@ trait TypeAssigner {
tree.withType(ExprType(result.tpe))

def assignType(tree: untpd.TypeBoundsTree, lo: Tree, hi: Tree)(implicit ctx: Context) =
tree.withType(if (lo eq hi) TypeAlias(lo.tpe) else TypeBounds(lo.tpe, hi.tpe))
tree.withType(
if (lo eq hi) TypeAlias(lo.tpe)
else inSameUniverse(TypeBounds(_, _), lo.tpe, hi, "type bounds"))

def assignType(tree: untpd.Bind, sym: Symbol)(implicit ctx: Context) =
tree.withType(NamedType.withFixedSym(NoPrefix, sym))
Expand Down Expand Up @@ -529,6 +534,24 @@ trait TypeAssigner {

def assignType(tree: untpd.PackageDef, pid: Tree)(implicit ctx: Context) =
tree.withType(pid.symbol.valRef)

/** Ensure that `tree2`'s type is in the same universe as `tree1`. If that's the case, return
* `op` applied to both types.
* If not, issue an error and return `tree1`'s type.
*/
private def inSameUniverse(op: (Type, Type) => Type, tp1: Type, tree2: Tree, relationship: => String)(implicit ctx: Context): Type =
if (tp1.topType == tree2.tpe.topType)
op(tp1, tree2.tpe)
else {
ctx.error(ex"$tp1 and ${tree2.tpe} are in different universes. They cannot be combined in $relationship", tree2.pos)
tp1
}

private def lubInSameUniverse(trees: List[Tree], relationship: => String)(implicit ctx: Context): Type =
trees match {
case first :: rest => (first.tpe /: rest)(inSameUniverse(_ | _, _, _, relationship))
case Nil => defn.NothingType
}
}

object TypeAssigner extends TypeAssigner
Expand Down
12 changes: 10 additions & 2 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1108,10 +1108,14 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
}

def typedTypeBoundsTree(tree: untpd.TypeBoundsTree)(implicit ctx: Context): TypeBoundsTree = track("typedTypeBoundsTree") {
val TypeBoundsTree(lo, hi) = desugar.typeBoundsTree(tree)
val TypeBoundsTree(lo, hi) = tree
val lo1 = typed(lo)
val hi1 = typed(hi)
val tree1 = assignType(cpy.TypeBoundsTree(tree)(lo1, hi1), lo1, hi1)

val lo2 = if (lo1.isEmpty) typed(untpd.TypeTree(hi1.typeOpt.bottomType)) else lo1
val hi2 = if (hi1.isEmpty) typed(untpd.TypeTree(lo1.typeOpt.topType)) else hi1

val tree1 = assignType(cpy.TypeBoundsTree(tree)(lo2, hi2), lo2, hi2)
if (ctx.mode.is(Mode.Pattern)) {
// Associate a pattern-bound type symbol with the wildcard.
// The bounds of the type symbol can be constrained when comparing a pattern type
Expand Down Expand Up @@ -1330,6 +1334,10 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
cls, isRequired, cdef.pos)
}

// Check that phantom lattices are defined in a static object
if (cls.classParents.exists(_.classSymbol eq defn.PhantomClass) && !cls.isStaticOwner)
ctx.error("only static objects can extend scala.Phantom", cdef.pos)

// check value class constraints
checkDerivedValueClass(cls, body1)

Expand Down
1 change: 1 addition & 0 deletions compiler/test/dotty/tools/dotc/CompilationTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ class CompilationTests extends ParallelTesting {
compileFile("../tests/neg/customArgs/noimports2.scala", defaultOptions.and("-Yno-imports")) +
compileFile("../tests/neg/customArgs/overloadsOnAbstractTypes.scala", allowDoubleBindings) +
compileFile("../tests/neg/customArgs/xfatalWarnings.scala", defaultOptions.and("-Xfatal-warnings")) +
compileFile("../tests/neg/customArgs/phantom-overload.scala", allowDoubleBindings) +
compileFile("../tests/neg/tailcall/t1672b.scala", defaultOptions) +
compileFile("../tests/neg/tailcall/t3275.scala", defaultOptions) +
compileFile("../tests/neg/tailcall/t6574.scala", defaultOptions) +
Expand Down
Loading