-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Implement phantom types part 1 #2136
Conversation
This PR is the first part of #1408. |
newScopeEntry(newFunctionNTrait(name.asTypeName)) | ||
else res | ||
else null |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't that exactly the same logic as before? If yes, I don't see it as an improvement.
} | ||
|
||
def isPhantomAnyClass(sym: Symbol)(implicit ctx: Context): Boolean = | ||
sym.exists && (sym.owner eq PhantomClass) && sym.name == tpnme.Any |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you swap the name and owner tests, you can avoid the additional exists
test. NoSymbol
has a valid name: <none>
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same observation applies to the following methods.
} | ||
|
||
private def isPhantomClass(sym: Symbol)(implicit ctx: Context): Boolean = | ||
sym.isClass && (sym.owner eq defn.PhantomClass) && (sym.name == tpnme.Any || sym.name == tpnme.Nothing) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't the last part, (sym.name == tpnme.Any || sym.name == tpnme.Nothing)
, redundant?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably
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.phantomTopClass == tp2.phantomTopClass) | ||
else false | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@i think isPhantom
is not strong enough. Concretely, the test would return true
for
scala.Nothing <: phantom.X
if X
is an abstract type in phantom
. So I believe you need to follow supertypes to determine isPhantom
, e.g. isPhantomClass(tp2.phantomTopClass)
.
However, we should carefully benchmark the runtime cost of this. Type comparison takes the lion's share of typechecking time. We should get some data, like: how much time takes the dotty
test or the compileStdLib
test, or the whole junit test suite? If we see a slowdown, we can try to mitigate by either finding a faster way to test or caching the result of the test in a type.
@@ -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 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we need the @tailrec
here. @tailrec
is intended to be normative. We should only use it if it would be a problem if the method was not tail recursive. I don't see this being the case here. dominators
will not be called on huge lists of classes, and neither is it very performance sensitive.
// todo: retract mode between Type and Pattern? | ||
|
||
/** Check that the are not mixed Any/Phantom.Any types in `&`, `|` and type bounds, | ||
* this includes Phantom.Any of different universes. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo: the -> there
case TypeBoundsTree(lo, hi) => | ||
checkedTops2(lo, hi, PhantomCrossedMixedBounds(lo, hi), tree.pos) | ||
case untpd.InfixOp(left, op, right) => | ||
checkedTops2(left, right, PhantomCrossedMixedBounds(left, right), tree.pos) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wrong error message. (In general I prefer if one does not immediately generate error messages in new code. It's a premature optimization. Much clearer to write the error directly as a string, then one can check whether it makes sense or not).
case _ => Set(defn.topOf(tree.typeOpt)) | ||
} | ||
} | ||
checkedTops(tree) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's better to reorganize type checking as follows: In TypeAssigner, insert check code in the assignType methods for AndType, OrType, TypeBounds, If, and Match. Here's an example for OrType:
def assignType(tree: untpd.OrTypeTree, left: Tree, right: Tree)(implicit ctx: Context) = {
checkSameUniverse(left, right, "be combined in `|`)
tree.withType(left.tpe | right.tpe)
}
where
checkSameUniverse(tree1: Tree, tree2: Tree, relationship: => String) =
if (topClass(tree1.tpe) != topClass(tree2.tpe))
ctx.error(ex"${tree1.tpe} and ${tree2.tpe} are in different universes. They cannot $relationship")
If we do this, Typer
can be left completely alone.
import dotty.tools.dotc.core.Types._ | ||
import dotty.tools.dotc.transform.TreeTransforms.{MiniPhaseTransform, TransformerInfo} | ||
|
||
class PhantomTypeErasure extends MiniPhaseTransform with InfoTransformer { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need a doc comment here that describes in detail what the phase does.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aren't we supposed to also erase values and parameters of phantom types here? Without a leading doc comment, impossible to tell.
} | ||
|
||
override def transformTypeApply(tree: TypeApply)(implicit ctx: Context, info: TransformerInfo): Tree = | ||
if (defn.isPhantomAssume(tree.fun.symbol)) Literal(Constant(null)).withType(defn.ErasedPhantomType) else tree |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why null
? Looks like a code smell to me.
d4da0b1
to
069bc38
Compare
74a643a
to
8b27beb
Compare
The following table shows the result of a macro benchmark done on my laptop. The time is the time taken to run the standard lib test in the test suite under
Surprisingly the execution times are are slightly faster after the phantom types where added. We would need to run this on a stabler machine if we would wish to have more precise numbers. But at least there is no evident time degradation. |
@odersky all requested changes where taken care of. Additionally, the |
I think CI is a bad indicator of times. We need to get better benchmarks.
Until then, we need to test locally. It's better to do specific tests than
rely on aggregates.
…On Thu, Apr 13, 2017 at 9:50 AM, Nicolas Stucki ***@***.***> wrote:
@odersky <https://github.com/odersky> all requested changes where taken
care of.
Additionally, the PhantomTypeErasure erasure phase was removed and
integrated into Erasure as it should have been from the start.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#2136 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/AAwlVtQ5hTxwtK9wsklFIeA8NCLXIDbMks5rvdOqgaJpZM4MlLH0>
.
{"api_version":"1.0","publisher":{"api_key":"
05dde50f1d1a384dd78767c55493e4bb","name":"GitHub"},"entity":
{"external_key":"github/lampepfl/dotty","title":"
lampepfl/dotty","subtitle":"GitHub repository","main_image_url":"
https://cloud.githubusercontent.com/assets/143418/17495839/a5054eac-5d88-
11e6-95fc-7290892c7bb5.png","avatar_image_url":"https://
cloud.githubusercontent.com/assets/143418/15842166/
7c72db34-2c0b-11e6-9aed-b52498112777.png","action":{"name":"Open in
GitHub","url":"https://github.com/lampepfl/dotty"}},"
***@***.*** in
#2136: @odersky all requested changes where taken care of.
\r\n\r\nAdditionally, the `PhantomTypeErasure` erasure phase was removed
and integrated into `Erasure` as it should have been from the
start."}],"action":{"name":"View Pull Request","url":"https://
github.com/lampepfl/dotty/pull/2136#issuecomment-293818404"}}}
--
Prof. Martin Odersky
LAMP/IC, EPFL
|
I ran the benchmarks locally on my machine, not on the CI. |
0555a33
to
5ee0a9a
Compare
36eaf5e
to
12da43d
Compare
This removes an unnecessary additional phase and fixes siggnatures of erased methods.
ddbce6d
to
40ab53a
Compare
@nicolasstucki I did a pass to polish. Can you review my last commit? If you are happy with it, we can get the PR in. |
Last commit LGTM :) |
|
||
val explanation = | ||
hl"""|""".stripMargin | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All changes in this file should be reverted.
@odersky now it is ready to be merged. |
So, let's merge this at last! |
Finally :) |
230 LOC of code without error messages and tests.
In this PR phantom types are defined and implemented. Only a minimal version of phantom term erasure is included.
Phantom types are in latices outside of the normal
Any
/Nothing
type lattice.A new phantom lattice can be defined by extending an
object
withscala.Phantom
.This trait defines synthetic members
protected final trait Any
where thisAny
does not extendsscala.Any
protected final abstract class Nothing extends this.Any
protected final def assume: this.Nothing
A phantom lattice object can expose any of the phantom members (
Any
,Nothing
,assume
)using an alias but it is not required.
Restriction on lattices:
&
or|
types.Phantom type erasure (minimalistic):
Phantom.Any
andPhantom.Nothing
are erased toBoxedUnit
Phantom.assume
is erased toBoxedUnit.UNIT
Phantom
is removed from the parents of the object inheriting it.Extra tests for Phantoms 1
andExtra tests for Phantoms 2
are tests intended to check the correct erasure of phantom arguments and fields (not in this PR). These tests also pass on this PR as the semantics and runtime result is equivalent with or without that additional erasure.