Skip to content

Add erasable phantom classes (old) #1408

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

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 6 additions & 1 deletion compiler/src/dotty/tools/dotc/Compiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import typer.{FrontEnd, Typer, ImportInfo, RefChecks}
import reporting.{Reporter, ConsoleReporter}
import Phases.Phase
import transform._
import transform.phantom._
import util.FreshNameCreator
import transform.TreeTransforms.{TreeTransform, TreeTransformer}
import core.DenotTransformers.DenotTransformer
Expand Down Expand Up @@ -73,7 +74,10 @@ class Compiler {
new AugmentScala2Traits, // Expand traits defined in Scala 2.11 to simulate old-style rewritings
new ResolveSuper, // Implement super accessors and add forwarders to trait methods
new PrimitiveForwarders, // Add forwarders to trait methods that have a mismatch between generic and primitives
new ArrayConstructors), // Intercept creation of (non-generic) arrays and intrinsify.
new ArrayConstructors, // Intercept creation of (non-generic) arrays and intrinsify.
new PhantomTermEval), // Extracts the evaluation of phantom arguments placing them before the call.
List(new PhantomTermErasure), // Erases phantom parameters and arguments
List(new PhantomTypeErasure), // Erases phantom types to ErasedPhantom
List(new Erasure), // Rewrite types to JVM model, erasing all type parameters, abstract types and refinements.
List(new ElimErasedValueType, // Expand erased value types to their underlying implmementation types
new VCElideAllocations, // Peep-hole optimization to eliminate unnecessary value class allocations
Expand All @@ -90,6 +94,7 @@ class Compiler {
List(new LambdaLift, // Lifts out nested functions to class scope, storing free variables in environments
// Note: in this mini-phase block scopes are incorrect. No phases that rely on scopes should be here
new ElimStaticThis, // Replace `this` references to static objects by global identifiers
new PhantomVals, // Remove ValDefs of phantom type
new Flatten, // Lift all inner classes to package scope
new RestoreScopes), // Repair scopes rendered invalid by moving definitions in prior phases of the group
List(new MoveStatics, // Move static methods to companion classes
Expand Down
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 @@ -626,16 +626,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
192 changes: 146 additions & 46 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala

Large diffs are not rendered by default.

52 changes: 43 additions & 9 deletions compiler/src/dotty/tools/dotc/core/NameOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -232,35 +232,63 @@ object NameOps {
}
}

/** Is a synthetic function name
/** Return the function arity
* - N for FunctionN
* - N for ImplicitFunctionN
* - (-1) otherwise
* - N for PhantomFunctionM where N is the length of M
* - N for ImplicitPhantomFunctionM where N is the length of M
* - (-1) otherwise
*/
def functionArity: Int =
functionArityFor(tpnme.Function) max functionArityFor(tpnme.ImplicitFunction)
def functionArity(implicit ctx: Context): Int = phantomicity.arity

/** Checks and returns the phantomicity of the function */
def phantomicity(implicit ctx: Context): Phantomicity = {
val arity = functionArityFor(tpnme.Function) max functionArityFor(tpnme.ImplicitFunction)
if (arity >= 0) Phantomicity.noPhantoms(arity)
else {
val phantomicity = phantomFunctionPhantomicity(tpnme.PhantomFunction)
if (phantomicity.isValid) phantomicity
else phantomFunctionPhantomicity(tpnme.ImplicitPhantomFunction)
}
}

/** Is a function name
* - FunctionN for N >= 0
* - ImplicitFunctionN for N >= 0
* - PhantomFunctionM for a valid M
* - ImplicitPhantomFunctionM for a valid M
* - false otherwise
*/
def isFunction: Boolean = functionArity >= 0
def isFunction(implicit ctx: Context): Boolean = functionArity >= 0

/** Is a implicit function name
* - ImplicitFunctionN for N >= 0
* - ImplicitPhantomFunctionM for a valid M
* - false otherwise
*/
def isImplicitFunction: Boolean = {
functionArityFor(tpnme.ImplicitFunction) >= 0 ||
phantomFunctionPhantomicity(tpnme.ImplicitPhantomFunction).isValid
}

/** Is a phantom function name
* - PhantomFunctionM for a valid M
* - ImplicitPhantomFunctionM for a valid M
* - false otherwise
*/
def isImplicitFunction: Boolean = functionArityFor(tpnme.ImplicitFunction) >= 0
def isPhantomFunction(implicit ctx: Context): Boolean = phantomicity.hasPhantoms

/** Is a synthetic function name
* - FunctionN for N > 22
* - ImplicitFunctionN for N >= 0
* - PhantomFunctionM for a valid M
* - ImplicitPhantomFunctionM for a valid M
* - false otherwise
*/
def isSyntheticFunction: Boolean = {
functionArityFor(tpnme.Function) > MaxImplementedFunctionArity ||
functionArityFor(tpnme.ImplicitFunction) >= 0
def isSyntheticFunction(implicit ctx: Context): Boolean = {
val p = phantomicity
if (name.startsWith(tpnme.Function)) p.arity > MaxImplementedFunctionArity
else p.isValid
}

/** Parsed function arity for function with some specific prefix */
Expand All @@ -271,6 +299,12 @@ object NameOps {
else -1
}

/** Parsed function phantomicity for function with some specific prefix */
private def phantomFunctionPhantomicity(prefix: Name): Phantomicity = {
lazy val p = Phantomicity.from(name.toString.substring(prefix.length))
if (name.startsWith(prefix) && p.isValid) p
else Phantomicity.invalid
}

/** The number of hops specified in an outer-select name */
def outerSelectHops: Int = {
Expand Down
77 changes: 77 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Phantomicity.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package dotty.tools.dotc.core

import dotty.tools.dotc.core.Contexts.Context
import dotty.tools.dotc.core.Decorators._
import dotty.tools.dotc.core.Names._
import dotty.tools.dotc.core.StdNames._
import dotty.tools.dotc.core.Symbols._
import dotty.tools.dotc.core.Types._

/** Suffix encoding M of `PhantomFunctionM` and `ImplicitPhantomFunctionM`
*
* `FunctionN` and `ImplicitFunctionN` can also be encoded as
* `PhantomFunctionM` and `ImplicitPhantomFunctionM` where `M` has `N + 1` `O`s
*/
class Phantomicity private (val encodedNames: Array[TermName]) extends AnyVal {
import Phantomicity._

/** If has phantom parameters or return type */
def hasPhantoms: Boolean = encodedNames.exists(_ != scalaLattice)

/** If all parameters and return type are phantoms */
def allPhantoms: Boolean = encodedNames.forall(_ != scalaLattice)

/** If the return type is a phantom */
def returnsPhantom: Boolean = encodedNames.last != scalaLattice

/** Arity of this function. Including phantom parameters. */
def arity: Int = if (isValid) encodedNames.length - 1 else -1

/** Erased arity of this function. Without phantom parameters. */
def erasedArity: Int =
if (isValid) encodedNames.init.count(_ == scalaLattice)
else -1

def tParamBounds(i: Int)(implicit ctx: Context): TypeBounds = {
val latticeName = encodedNames(i).decode
if (latticeName == scalaLattice) {
TypeBounds.empty
} else {
val lattice = staticRefOf(latticeName).info
TypeBounds(lattice.select(tpnme.Nothing), lattice.select(tpnme.Any))
}
}

/** Is a valid phantomicity */
def isValid: Boolean = encodedNames ne invalid.encodedNames

/** Encoded suffix of the function name */
def encodedString: String = "$$" + encodedNames.mkString(separator.toString)

}

object Phantomicity {
private val separator = '_'
private val scalaLattice = "scala".toTermName

lazy val invalid: Phantomicity = new Phantomicity(Array.empty)

def apply(types: List[Type])(implicit ctx: Context): Phantomicity = {
def typeToString(tp: Type) = {
val lattice = tp.phantomLatticeClass
if (lattice.exists) lattice.termSymbol.fullName.encode.asTermName
else scalaLattice
}
val encodedStrings = types.iterator.map(typeToString).toArray
new Phantomicity(encodedStrings)
}

def from(str: String): Phantomicity =
new Phantomicity(str.substring(2).split(separator).map(_.toTermName))

def noPhantoms(arity: Int)(implicit ctx: Context): Phantomicity = apply(List.fill(arity + 1)(defn.AnyType))

private def staticRefOf(name: Name)(implicit ctx: Context) =
if (name.contains('.')) ctx.base.staticRef(name)
else defn.EmptyPackageClass.info.decl(name)
}
4 changes: 4 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Phases.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import config.Printers.config
import scala.collection.mutable.{ListBuffer, ArrayBuffer}
import dotty.tools.dotc.transform.TreeTransforms.{TreeTransformer, MiniPhase, TreeTransform}
import dotty.tools.dotc.transform._
import dotty.tools.dotc.transform.phantom._
import Periods._
import typer.{FrontEnd, RefChecks}
import ast.tpd
Expand Down Expand Up @@ -309,6 +310,7 @@ object Phases {

private var myPeriod: Period = Periods.InvalidPeriod
private var myBase: ContextBase = null
private var myErasedPhantomTerms = false
private var myErasedTypes = false
private var myFlatClasses = false
private var myRefChecked = false
Expand All @@ -326,6 +328,7 @@ object Phases {
def start = myPeriod.firstPhaseId
def end = myPeriod.lastPhaseId

final def erasedPhantomTerms = myErasedPhantomTerms // Phase is after phantom terms
final def erasedTypes = myErasedTypes // Phase is after erasure
final def flatClasses = myFlatClasses // Phase is after flatten
final def refChecked = myRefChecked // Phase is after RefChecks
Expand All @@ -337,6 +340,7 @@ object Phases {
assert(myPeriod == Periods.InvalidPeriod, s"phase $this has already been used once; cannot be reused")
myBase = base
myPeriod = Period(NoRunId, start, end)
myErasedPhantomTerms = prev.getClass == classOf[PhantomTermErasure] || prev.erasedPhantomTerms
myErasedTypes = prev.getClass == classOf[Erasure] || prev.erasedTypes
myFlatClasses = prev.getClass == classOf[Flatten] || prev.flatClasses
myRefChecked = prev.getClass == classOf[RefChecks] || prev.refChecked
Expand Down
4 changes: 4 additions & 0 deletions compiler/src/dotty/tools/dotc/core/StdNames.scala
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,8 @@ object StdNames {
final val ExprApi: N = "ExprApi"
final val Function: N = "Function"
final val ImplicitFunction: N = "ImplicitFunction"
final val PhantomFunction: N = "PhantomFunction"
final val ImplicitPhantomFunction: N = "ImplicitPhantomFunction"
final val Mirror: N = "Mirror"
final val Nothing: N = "Nothing"
final val Null: N = "Null"
Expand Down Expand Up @@ -230,6 +232,8 @@ object StdNames {
final val SourceFileATTR: N = "SourceFile"
final val SyntheticATTR: N = "Synthetic"

final val Phantom: N = "Phantom"

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

// Compiler-internal
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/core/SymDenotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -488,8 +488,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
13 changes: 11 additions & 2 deletions compiler/src/dotty/tools/dotc/core/TypeComparer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import config.Printers.{typr, constr, subtyping, noPrinter}
import TypeErasure.{erasedLub, erasedGlb}
import TypeApplications._
import scala.util.control.NonFatal
import scala.annotation.tailrec

/** Provides methods to compare types.
*/
Expand Down Expand Up @@ -550,8 +551,16 @@ 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)
def isPhantom(tp: Type): Boolean = tp.widenDealias match {
case tp: TypeRef => defn.isPhantomAnyClass(tp.symbol)
case tp: RefinedOrRecType => isPhantom(tp.parent)
case tp: AndOrType => isPhantom(tp.tp1)
case _ => false
}
if (tp1.symbol eq NothingClass) tp2.isValueTypeOrLambda && !isPhantom(tp2)
else if (tp1.symbol eq NullClass) isNullable(tp2) && !isPhantom(tp2)
else if (defn.isPhantomNothingClass(tp1.symbol)) tp2.isValueTypeOrLambda && (tp1.phantomLatticeClass == tp2.phantomLatticeClass)
else false
}
case tp1: SingletonType =>
/** if `tp2 == p.type` and `p: q.type` then try `tp1 <:< q.type` as a last effort.*/
Expand Down
7 changes: 5 additions & 2 deletions compiler/src/dotty/tools/dotc/core/TypeOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import util.{SimpleMap, Property}
import collection.mutable
import ast.tpd._

import scala.annotation.tailrec

trait TypeOps { this: Context => // TODO: Make standalone object.

/** The type `tp` as seen from prefix `pre` and owner `cls`. See the spec
Expand Down Expand Up @@ -199,7 +201,7 @@ trait TypeOps { this: Context => // TODO: Make standalone object.
}

/** The minimal set of classes in `cs` which derive all other classes in `cs` */
def dominators(cs: List[ClassSymbol], accu: List[ClassSymbol]): List[ClassSymbol] = (cs: @unchecked) match {
@tailrec def dominators(cs: List[ClassSymbol], accu: List[ClassSymbol]): List[ClassSymbol] = cs match {
case c :: rest =>
val accu1 = if (accu exists (_ derivesFrom c)) accu else c :: accu
if (cs == c.baseClasses) accu1 else dominators(rest, accu1)
Expand Down Expand Up @@ -255,7 +257,8 @@ trait TypeOps { this: Context => // TODO: Make standalone object.
else tp.baseTypeWithArgs(cls)
base.mapReduceOr(identity)(mergeRefined)
}
doms.map(baseTp).reduceLeft(AndType.apply)
if (doms.isEmpty) new ErrorType("no parents in common") // This can happen in the union of Any with PhantomAny
else doms.map(baseTp).reduceLeft(AndType.apply)
}
}
}
Expand Down
11 changes: 10 additions & 1 deletion compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,16 @@ object Types {
case _ =>
false
}
cls == defn.AnyClass || loop(this)
loop(this)
}

final def isPhantom(implicit ctx: Context): Boolean = phantomLatticeClass.exists

final def phantomLatticeClass(implicit ctx: Context): Type = this match {
case tp: ClassInfo if tp.classSymbol.owner eq defn.PhantomClass => tp.prefix
case tp: TypeProxy => tp.superType.phantomLatticeClass
case tp: AndOrType => tp.tp1.phantomLatticeClass
case _ => NoType
}

/** Is this type guaranteed not to have `null` as a value?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,14 @@ public enum ErrorMessageID {
CyclicReferenceInvolvingID,
CyclicReferenceInvolvingImplicitID,
SuperQualMustBeParentID,
ErasedPhantomsSignatureCollisionID,
PhantomInheritanceID,
PhantomMixedBoundsID,
PhantomCrossedMixedBoundsID,
MatchPhantomID,
MatchOnPhantomID,
IfElsePhantomID,
PhantomIsInObjectID,
;

public int errorNumber() {
Expand Down
Loading