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 simple phantom functions #2405

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
106 changes: 78 additions & 28 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 @@ -104,33 +104,31 @@ 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 paramNamePrefix = tpnme.scala_ ++ str.NAME_JOIN ++ name ++ str.EXPAND_SEPARATOR
val argParams =
for (i <- List.range(1, arity + 1)) yield
enterTypeParam(cls, paramNamePrefix ++ "T" ++ i.toString, Contravariant, decls)
val resParam = enterTypeParam(cls, paramNamePrefix ++ "R", Covariant, decls)
enterTypeParam(cls, paramNamePrefix ++ "T" ++ i.toString, Contravariant, decls, TypeBounds(bottom, top)).typeRef
val resParam = enterTypeParam(cls, paramNamePrefix ++ "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, superTrait :: Nil)
}
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 @@ -190,7 +188,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 @@ -687,7 +685,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 @@ -767,16 +765,39 @@ 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 :: Nil => first.topType
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 @@ -795,24 +816,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 @@ -829,6 +854,7 @@ class Definitions {
val arity = scalaClassName(cls).functionArity
if (arity > 22) FunctionXXLClass
else if (arity >= 0) FunctionClass(arity)
else if (phantomClassName(cls).isFunction) FunctionClass(0)
else NoSymbol
}

Expand All @@ -841,8 +867,9 @@ class Definitions {
*/
def erasedFunctionType(cls: Symbol): Type = {
val arity = scalaClassName(cls).functionArity
if (arity > 22) defn.FunctionXXLType
else if (arity >= 0) defn.FunctionType(arity)
if (arity > 22) FunctionXXLType
else if (arity >= 0) FunctionType(arity)
else if (phantomClassName(cls).isFunction) FunctionType(0)
else NoType
}

Expand Down Expand Up @@ -887,7 +914,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 @@ -908,7 +938,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 @@ -1024,6 +1058,10 @@ class Definitions {

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

val any = enterCompleteClassSymbol(cls, tpnme.Any, Protected | Final | NoInitsTrait, Nil)
val nothing = enterCompleteClassSymbol(cls, tpnme.Nothing, Protected | Final | NoInitsTrait, List(any.typeRef))
Expand All @@ -1044,4 +1082,16 @@ class Definitions {

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

/** 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
}

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

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

/** Is a synthetic function name
/** Is a synthetic function name (in scala package)
* - FunctionN for N > 22
* - ImplicitFunctionN for N >= 1
* - false otherwise
*/
def isSyntheticFunction: Boolean = {
def isSyntheticScalaFunction: Boolean = {
functionArityFor(str.Function) > MaxImplementedFunctionArity ||
functionArityFor(str.ImplicitFunction) >= 1
}
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 @@ -178,7 +178,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 @@ -465,10 +465,10 @@ trait TypeAssigner {
tree.withType(ref.tpe)

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

def assignType(tree: untpd.OrTypeTree, left: Tree, right: Tree)(implicit ctx: Context) =
tree.withType(inSameUniverse(OrType(_, _), left.tpe, right, "an `|`"))
tree.withType(defn.inSameUniverse(OrType(_, _), 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 @@ -503,7 +503,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(NoPrefix, sym))
Expand Down Expand Up @@ -547,21 +547,9 @@ trait TypeAssigner {
def assignType(tree: untpd.PackageDef, pid: Tree)(implicit ctx: Context) =
tree.withType(pid.symbol.termRef)

/** 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 @@ -702,9 +702,9 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
else true
case _ => false
}
val funCls = defn.FunctionClass(args.length, isImplicit)
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, isImplicit)
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
}
14 changes: 14 additions & 0 deletions tests/neg/phantom-Functions-3.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@

// TODO: Importing an object that exends phantom makes FunctionN refere to the Boo.FunctionN
// We should be carefull with this. Use a waring when importing Boo._ or disallow it.
// Or put funtions in an inner object in the Phantom trait, for example scala.Phantom.Functions

import Boo._

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

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

class Fun {
def a = (x1: Int, x2: Boo.B) => x1 // error: Int and Boo.B are in different universes. They cannot be combined in function arguments.
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.

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.
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.
}

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

object Foo extends Phantom {
type F <: this.Any
}
Loading