Skip to content

Commit

Permalink
Implement simple phantom functions
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
nicolasstucki committed May 12, 2017
1 parent 1d0e940 commit 0d0bd51
Show file tree
Hide file tree
Showing 40 changed files with 573 additions and 54 deletions.
114 changes: 87 additions & 27 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,14 @@ class Definitions {
private def enterCompleteClassSymbol(owner: Symbol, name: TypeName, flags: FlagSet, parents: List[TypeRef], decls: Scope = newScope) =
ctx.newCompleteClassSymbol(owner, name, flags | Permanent, parents, decls).entered

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

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

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

// NOTE: Ideally we would write `parentConstrs: => Type*` but SIP-24 is only
// implemented in Dotty and not in Scala 2.
Expand Down Expand Up @@ -108,32 +108,30 @@ class Definitions {
* def apply(implicit $x0: T0, ..., $x{N_1}: T{N-1}): R
* }
*/
def newFunctionNTrait(name: TypeName): ClassSymbol = {
def newFunctionNTrait(name: TypeName, lattice: Symbol): ClassSymbol = {
val completer = new LazyType {
def complete(denot: SymDenotation)(implicit ctx: Context): Unit = {
val cls = denot.asClass.classSymbol
val decls = newScope
val arity = name.functionArity
val top = lattice.thisType.select(tpnme.Any)
val bottom = lattice.thisType.select(tpnme.Nothing)
val argParams =
for (i <- List.range(0, arity)) yield
enterTypeParam(cls, name ++ "$T" ++ i.toString, Contravariant, decls)
val resParam = enterTypeParam(cls, name ++ "$R", Covariant, decls)
enterTypeParam(cls, name ++ "$T" ++ i.toString, Contravariant, decls, TypeBounds(bottom, top)).typeRef
val resParam = enterTypeParam(cls, name ++ "$R", Covariant, decls, TypeBounds.empty).typeRef
val (methodType, parentTraits) =
if (name.firstPart.startsWith(str.ImplicitFunction)) {
val superTrait =
FunctionType(arity).appliedTo(argParams.map(_.typeRef) ::: resParam.typeRef :: Nil)
FunctionType(arity, isImplicit = false, top).appliedTo(argParams ::: resParam :: Nil)
(ImplicitMethodType, ctx.normalizeToClassRefs(superTrait :: Nil, cls, decls))
}
else (MethodType, Nil)
val applyMeth =
decls.enter(
newMethod(cls, nme.apply,
methodType(argParams.map(_.typeRef), resParam.typeRef), Deferred))
denot.info =
ClassInfo(ScalaPackageClass.thisType, cls, ObjectType :: parentTraits, decls)
decls.enter(newMethod(cls, nme.apply, methodType(argParams, resParam), Deferred))
denot.info = ClassInfo(lattice.thisType, cls, ObjectType :: parentTraits, decls)
}
}
newClassSymbol(ScalaPackageClass, name, Trait | NoInits, completer)
newClassSymbol(lattice, name, Trait | NoInits, completer)
}

private def newMethod(cls: ClassSymbol, name: TermName, info: Type, flags: FlagSet = EmptyFlags): TermSymbol =
Expand Down Expand Up @@ -193,7 +191,7 @@ class Definitions {
val cls = ScalaPackageVal.moduleClass.asClass
cls.info.decls.openForMutations.useSynthesizer(
name => ctx =>
if (name.isTypeName && name.isSyntheticFunction) newFunctionNTrait(name.asTypeName)
if (name.isTypeName && name.isSyntheticScalaFunction) newFunctionNTrait(name.asTypeName, cls)
else NoSymbol)
cls
}
Expand Down Expand Up @@ -654,7 +652,7 @@ class Definitions {

object FunctionOf {
def apply(args: List[Type], resultType: Type, isImplicit: Boolean = false)(implicit ctx: Context) =
FunctionType(args.length, isImplicit).appliedTo(args ::: resultType :: Nil)
FunctionType(args, resultType, isImplicit).appliedTo(args ::: resultType :: Nil)
def unapply(ft: Type)(implicit ctx: Context) = {
val tsym = ft.typeSymbol
if (isFunctionClass(tsym)) {
Expand Down Expand Up @@ -719,16 +717,38 @@ class Definitions {
lazy val Function0_applyR = ImplementedFunctionType(0).symbol.requiredMethodRef(nme.apply)
def Function0_apply(implicit ctx: Context) = Function0_applyR.symbol

def FunctionType(n: Int, isImplicit: Boolean = false)(implicit ctx: Context): TypeRef =
if (n <= MaxImplementedFunctionArity && (!isImplicit || ctx.erasedTypes)) ImplementedFunctionType(n)
def FunctionType(n: Int, isImplicit: Boolean = false, top: Type = AnyType)(implicit ctx: Context): TypeRef = {
if (top.isPhantom) {
val functionPrefix = if (isImplicit) str.ImplicitFunction else str.Function
val functionName = (functionPrefix + n).toTypeName
val functionType = top.normalizedPrefix.select(functionName)
assert(functionType.classSymbol.exists)
functionType.asInstanceOf[TypeRef]
}
else if (n <= MaxImplementedFunctionArity && (!isImplicit || ctx.erasedTypes)) ImplementedFunctionType(n)
else FunctionClass(n, isImplicit).typeRef
}

def FunctionType(args: List[Type], resultType: Type, isImplicit: Boolean)(implicit ctx: Context): TypeRef =
FunctionType(args.length, isImplicit, topInSameUniverse(args, "function arguments."))

private def topInSameUniverse(types: List[Type], relationship: => String)(implicit ctx: Context): Type = {
types match {
case first :: rest => (first /: rest)(inSameUniverse((t1, _) => t1.topType, _, _, relationship, ctx.owner.pos))
case Nil => defn.AnyType
}
}

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

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

/** If `cls` is a class in an object extending scala.Phantom, its name, otherwise EmptyTypeName */
def phantomClassName(cls: Symbol)(implicit ctx: Context): TypeName =
if (cls.isClass && cls.owner.derivesFrom(PhantomClass)) cls.asClass.name else EmptyTypeName

/** If type `ref` refers to a class in the scala package, its name, otherwise EmptyTypeName */
def scalaClassName(ref: Type)(implicit ctx: Context): TypeName = scalaClassName(ref.classSymbol)

Expand All @@ -747,24 +767,28 @@ class Definitions {
* - FunctionN for N >= 0
* - ImplicitFunctionN for N >= 0
*/
def isFunctionClass(cls: Symbol) = scalaClassName(cls).isFunction
def isFunctionClass(cls: Symbol) =
scalaClassName(cls).isFunction || phantomClassName(cls).isFunction

/** Is an implicit function class.
* - ImplicitFunctionN for N >= 0
*/
def isImplicitFunctionClass(cls: Symbol) = scalaClassName(cls).isImplicitFunction
def isImplicitFunctionClass(cls: Symbol) =
scalaClassName(cls).isImplicitFunction || phantomClassName(cls).isImplicitFunction

/** Is a class that will be erased to FunctionXXL
* - FunctionN for N >= 22
* - ImplicitFunctionN for N >= 22
*/
def isXXLFunctionClass(cls: Symbol) = scalaClassName(cls).functionArity > MaxImplementedFunctionArity
def isXXLFunctionClass(cls: Symbol) =
scalaClassName(cls).functionArity > MaxImplementedFunctionArity

/** Is a synthetic function class
* - FunctionN for N > 22
* - ImplicitFunctionN for N >= 0
*/
def isSyntheticFunctionClass(cls: Symbol) = scalaClassName(cls).isSyntheticFunction
def isSyntheticFunctionClass(cls: Symbol) =
scalaClassName(cls).isSyntheticScalaFunction || phantomClassName(cls).isFunction

def isAbstractFunctionClass(cls: Symbol) = isVarArityClass(cls, str.AbstractFunction)
def isTupleClass(cls: Symbol) = isVarArityClass(cls, str.Tuple)
Expand All @@ -781,6 +805,7 @@ class Definitions {
val arity = scalaClassName(cls).functionArity
if (arity > 22) defn.FunctionXXLClass
else if (arity >= 0) defn.FunctionClass(arity)
else if (phantomClassName(cls).isFunction) defn.FunctionClass(0)
else NoSymbol
}

Expand All @@ -795,6 +820,7 @@ class Definitions {
val arity = scalaClassName(cls).functionArity
if (arity > 22) defn.FunctionXXLType
else if (arity >= 0) defn.FunctionType(arity)
else if (phantomClassName(cls).isFunction) defn.FunctionType(0)
else NoType
}

Expand Down Expand Up @@ -839,7 +865,10 @@ class Definitions {
* trait gets screwed up. Therefore, it is mandatory that FunctionXXL
* is treated as a NoInit trait.
*/
lazy val NoInitClasses = NotRuntimeClasses + FunctionXXLClass
private lazy val NoInitClasses = NotRuntimeClasses + FunctionXXLClass

def isNoInitClass(cls: Symbol): Boolean =
cls.is(NoInitsTrait) || NoInitClasses.contains(cls) || isFunctionClass(cls)

def isPolymorphicAfterErasure(sym: Symbol) =
(sym eq Any_isInstanceOf) || (sym eq Any_asInstanceOf)
Expand All @@ -860,7 +889,11 @@ class Definitions {
def isFunctionType(tp: Type)(implicit ctx: Context) = {
val arity = functionArity(tp)
val sym = tp.dealias.typeSymbol
arity >= 0 && isFunctionClass(sym) && tp.isRef(FunctionType(arity, sym.name.isImplicitFunction).typeSymbol)
def top =
if (!sym.owner.derivesFrom(defn.PhantomClass)) defn.AnyType
else sym.owner.thisType.select(tpnme.Any)
def funType = FunctionType(arity, sym.name.isImplicitFunction, top)
arity >= 0 && isFunctionClass(sym) && tp.isRef(funType.typeSymbol)
}

def functionArity(tp: Type)(implicit ctx: Context) = tp.dealias.argInfos.length - 1
Expand Down Expand Up @@ -975,12 +1008,27 @@ class Definitions {
// ----- Phantoms ---------------------------------------------------------

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

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)

clsScope.useSynthesizer { name => ctx =>
if (name.isTypeName && name.isFunction) newFunctionNTrait(name.asTypeName, cls)
else NoSymbol
}

if (config.Config.useFingerPrints) {
// FIXME: this should not be required, must create them early to make sure they are
// found in the members of objects extending scala.Phantom
for (i <- 1 to MaxImplementedFunctionArity) {
clsScope.lookup((str.Function + i).toTypeName)
clsScope.lookup((str.ImplicitFunction + i).toTypeName)
}
}

cls
}
lazy val Phantom_AnyClass = PhantomClass.unforcedDecls.find(_.name eq tpnme.Any).asClass
Expand All @@ -990,4 +1038,16 @@ class Definitions {
/** 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)

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

}
8 changes: 4 additions & 4 deletions compiler/src/dotty/tools/dotc/core/NameOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ object NameOps {
}
}

/** Is a synthetic function name
/** Is a function name
* - N for FunctionN
* - N for ImplicitFunctionN
* - (-1) otherwise
Expand All @@ -175,14 +175,14 @@ object NameOps {
*/
def isImplicitFunction: Boolean = functionArityFor(str.ImplicitFunction) >= 0

/** Is a synthetic function name
/** Is a synthetic function name (in scala package)
* - FunctionN for N > 22
* - ImplicitFunctionN for N >= 0
* - false otherwise
*/
def isSyntheticFunction: Boolean = {
def isSyntheticScalaFunction: Boolean = {
functionArityFor(str.Function) > MaxImplementedFunctionArity ||
functionArityFor(str.ImplicitFunction) >= 0
functionArityFor(str.ImplicitFunction) >= 0
}

/** Parsed function arity for function with some specific prefix */
Expand Down
6 changes: 3 additions & 3 deletions compiler/src/dotty/tools/dotc/core/TypeErasure.scala
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,8 @@ 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 if (defn.isPhantomTerminalClass(sym)) PhantomErasure.erasedPhantomType
else if (sym eq defn.PhantomClass) defn.ObjectType
else eraseNormalClassRef(tp)
case tp: RefinedType =>
val parent = tp.parent
Expand Down Expand Up @@ -407,8 +408,7 @@ 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
// 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))
tr1 :: trs1.filterNot(_ isRef defn.ObjectClass)
case nil => nil
}
val erasedDecls = decls.filteredScope(sym => !sym.isType || sym.isClass)
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 @@ -176,7 +176,7 @@ class Mixin extends MiniPhaseTransform with SymTransformer { thisTransform =>
case Some(call) =>
if (defn.NotRuntimeClasses.contains(baseCls)) Nil else call :: Nil
case None =>
if (baseCls.is(NoInitsTrait) || defn.NoInitClasses.contains(baseCls)) Nil
if (defn.isNoInitClass(baseCls)) Nil
else {
//println(i"synth super call ${baseCls.primaryConstructor}: ${baseCls.primaryConstructor.info}")
transformFollowingDeep(superRef(baseCls.primaryConstructor).appliedToNone) :: Nil
Expand Down
20 changes: 4 additions & 16 deletions compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala
Original file line number Diff line number Diff line change
Expand Up @@ -453,10 +453,10 @@ trait TypeAssigner {
tree.withType(ref.tpe)

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

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

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

def assignType(tree: untpd.Bind, sym: Symbol)(implicit ctx: Context) =
tree.withType(NamedType.withFixedSym(NoPrefix, sym))
Expand Down Expand Up @@ -535,21 +535,9 @@ 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 first :: rest => (first.tpe /: rest)((tp, tree) => defn.inSameUniverse(_ | _, tp, tree.tpe, relationship, tree.pos))
case Nil => defn.NothingType
}
}
Expand Down
6 changes: 3 additions & 3 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -671,9 +671,9 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
def typedFunction(tree: untpd.Function, pt: Type)(implicit ctx: Context) = track("typedFunction") {
val untpd.Function(args, body) = tree
if (ctx.mode is Mode.Type) {
val funCls = defn.FunctionClass(args.length, tree.isInstanceOf[untpd.ImplicitFunction])
typed(cpy.AppliedTypeTree(tree)(
untpd.TypeTree(funCls.typeRef), args :+ body), pt)
val argsTypes = args.map(tr => typed(tr).tpe)
val funTpe = defn.FunctionType(argsTypes, typed(body).tpe, tree.isInstanceOf[untpd.ImplicitFunction])
typed(cpy.AppliedTypeTree(tree)(untpd.TypeTree(funTpe), args :+ body), pt)
}
else {
val params = args.asInstanceOf[List[untpd.ValDef]]
Expand Down
15 changes: 15 additions & 0 deletions library/src/scala/Phantom.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,20 @@ trait Phantom {
protected final trait Nothing extends this.Any
protected final def assume: this.Nothing
trait Function1[-T1 <: this.Any, +R] {
def apply(x1: T1): R
}
trait ImplicitFunction1[-T1 <: this.Any, +R] extends Function1[T1, R] {
/*implicit*/ def apply(x1: T1): R
}
...
trait FunctionN[-T1 <: this.Any, ..., -Tn <: this.Any, +R] {
def apply(x1: T1, ..., xn: Tn): R
}
trait ImplicitFunctionN[-T1 <: this.Any, ..., -Tn <: this.Any, +R] extends FunctionN[T1, ..., Tn, R] {
/*implicit*/ def apply(x1: T1, ..., xn: Tn): R
}
}
*/
6 changes: 6 additions & 0 deletions tests/neg/phantom-Functions-1.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@

class PhantomFun1NoApply extends Function1[Boo.Casper, Unit] // error: class PhantomFun1NoApply needs to be abstract, since def apply: (p0: Casper)Unit is not defined

object Boo extends Phantom {
type Casper <: this.Any
}
16 changes: 16 additions & 0 deletions tests/neg/phantom-Functions-2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@

class PhantomFun1 extends Boo.Function2[Boo.Casper, Int, Unit] { // error: Type argument Int does not conform to upper bound Boo.Any
def apply(x1: Boo.Casper, x2: Int): Unit = ()
}

class PhantomFun2 extends Boo.Function2[Int, Boo.Casper, Unit] { // error: Type argument Int does not conform to upper bound Boo.Any
def apply(x1: Boo.Casper, x2: Int): Unit = ()
}

class Fun extends Function2[Int, Int, Unit] {
def apply(x1: Int, x2: Int): Unit = ()
}

object Boo extends Phantom {
type Casper <: this.Any
}
Loading

0 comments on commit 0d0bd51

Please sign in to comment.