-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Add refining annotations #4626
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
Add refining annotations #4626
Conversation
The idea is that such annotations create some proper subtype of the type they annotate. Hence, they cannot be stripped away like normal annotations are.
- some keep their annotations - some only keep refining annotations - most of them still strip all annotations
@gsps I recommend to use this status as a basis for further work. Next step could be to define an annotation like library/src/scala/annotation/internal |
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.
Should ConstantType
and TermRef
be refactored into AnnotatedType
with RefiningAnnotation
?
* Refining annotations are more "sticky" than normal ones. They are conceptually kept | ||
* around when normal refinements would also not be stripped away. | ||
*/ | ||
trait RefiningAnnotation extends StaticAnnotation |
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 expose that in the stdlib, isn't this compiler internal only?
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.
No, you might want to define your own annotations (say units of measure). Maybe they need a compiler plugin to order to be checked, or maybe you can do it in the future with annotation macros. RefiningAnnotation
just makes sure they propagate correctly.
In principle, we could consider this. But these two are a little bit different since they are also the main carriers of singleton types. Also the terms they capture are not very general, so it's not clear what we would win. |
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.
This looks nice! At this point, I'd actually be quite tempted to also add DistributiveRefiningAnnotation
along with the appropriate cases to distributiveOr
and distributiveAnd
;-)
In any case, I'll give it a shot and try to rebase our WIP transparent
PR on this.
@@ -694,6 +694,8 @@ class Definitions { | |||
def ClassfileAnnotationClass(implicit ctx: Context) = ClassfileAnnotationType.symbol.asClass | |||
lazy val StaticAnnotationType = ctx.requiredClassRef("scala.annotation.StaticAnnotation") | |||
def StaticAnnotationClass(implicit ctx: Context) = StaticAnnotationType.symbol.asClass | |||
lazy val RefiningAnnotationType = ctx.requiredClassRef("scala.annotation.RefiningAnnotation") |
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.
(Misaligned =
)
@@ -283,6 +283,15 @@ object Types { | |||
case _ => false | |||
} | |||
|
|||
/** Does this type have a supertype with an annotation satisfying given predicate `p`? */ | |||
def derivesAnnotWith(p: Annotation => Boolean)(implicit ctx: Context): Boolean = this match { | |||
case tp: AnnotatedType => p(tp.annot) || tp.tpe.derivesAnnotWith(p) |
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.
Consider renaming AnnotatedType#tpe
to #parent
or #info
? (I find tp.tpe
slightly confusing.)
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.
Good idea.
@@ -8,6 +8,7 @@ import core.Types.WildcardType | |||
object common { | |||
|
|||
val alwaysTrue = Function.const(true) _ | |||
val alwaysFalse = Function.const(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.
Is this used anywhere?
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.
No. I used it in an intermediate version. I kept it for duality.
@@ -700,7 +705,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { | |||
} | |||
case tycon1: TypeVar => | |||
isMatchingApply(tycon1.underlying) | |||
case tycon1: AnnotatedType => | |||
case tycon1: AnnotatedType if !tycon1.isRefining => |
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'm not sure when a type constructor would have a RefiningAnnotation
, but wouldn't it in principle be okay to widen here, regardless of whether the annotation is refining?
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.
Yes, well spotted. Narrowing the lower bound is always safe, and it is the only thing we can do in this case.
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.
Looks like this PR was merged without addressing this comment?
a4745fd
to
81a5cf9
Compare
LGTM. |
@@ -1546,7 +1552,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { | |||
} | |||
case tp1: TypeVar if tp1.isInstantiated => | |||
tp1.underlying & tp2 | |||
case tp1: AnnotatedType => | |||
case tp1: AnnotatedType if !tp1.isRefining => | |||
tp1.underlying & tp2 |
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 not rewrap the annotation around the intersection if it's a refining annotation?
|
||
override def stripAnnots(implicit ctx: Context): Type = tpe.stripAnnots | ||
def isRefining(implicit ctx: Context) = { | ||
if (!isRefiningKnown) { |
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.
Shouldn't the cache be invalidated at the start of a new run?
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 was thinking about that too, but decided that it's probably fine given the internal nature of such annotations. Then again, we do expose RefiningAnnotation in the public scala.annotation
package.
This PR would really really benefits from having tests. |
Adds a new kind of annotation that conceptually refines the annotated type. That is, refining annotations yield subtypes. They are more "sticky" than normal annotations. In a number of situations they are not stripped away where normal annotations are.
It is intended that this kind of annotation is a good basis to define
TypeOf
types.