Skip to content

Fix #9011: Make single enum values inherit from Product #9018

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

Merged
merged 12 commits into from
May 22, 2020

Conversation

odersky
Copy link
Contributor

@odersky odersky commented May 21, 2020

No description provided.

odersky added 2 commits May 21, 2020 15:15
This was done just because compiling with a non-empty classpaths leads
to conflicts when main is used as an annotation.
@odersky
Copy link
Contributor Author

odersky commented May 21, 2020

Based on #9017. It needs provablyDisjoint to be fixed first.

@@ -0,0 +1,10 @@
package scala

trait EnumValue extends Product:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For parity with case object this should extend Serializable too I think:

Suggested change
trait EnumValue extends Product:
trait EnumValue extends Product with Serializable:

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually it probably makes more sense to have Product and Serializable added as parents to the enum class itself, that way they never show up when lubbing enum cases.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but I am not sure about what other effects this will have.

@odersky
Copy link
Contributor Author

odersky commented May 21, 2020

The failure comes from EnumValue now leaking into a result type of a union. We could fix it locally in the test cases. But I'd like to try first whether we can come up with a systematic fix.

odersky added 4 commits May 21, 2020 18:26
We now strip EnumValue parents from inferred types, unless
they are required by the bound. This is analogous to widen
unions and singletons. It should be generalized to more
types, not just EnumValue.
so that these traits do not leak into inferred types of unions
of cases.
We now drop EnumValue just when inferring types of enum cases. This can be
done unconditionally since in any case every enum case extends Enum, which
extends Product and Serializable. EnumValue is just an invisible implementation
bundle.

This means we disable for now the more general handling in widenInferred.
It is no longer visible in the types of enum constants, so no need to
keep it in Scala.

Also, update doc pages to match the new scheme.
Comment on lines 318 to 322
def dropProtected(tp: Type): Type = tp.dealias match
case tp @ AndType(tp1, tp2) =>
if isProtected(tp1) then tp2
else if isProtected(tp2) then tp1
else tp.derivedAndType(dropProtected(tp1), dropProtected(tp2))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can drop aliases in situation where it isn't necessary, this can be avoided like this:

Suggested change
def dropProtected(tp: Type): Type = tp.dealias match
case tp @ AndType(tp1, tp2) =>
if isProtected(tp1) then tp2
else if isProtected(tp2) then tp1
else tp.derivedAndType(dropProtected(tp1), dropProtected(tp2))
def dropProtected(tp: Type): Type = tp.dealias match
case tpd @ AndType(tp1, tp2) =>
if isProtected(tp1) then tp2
else if isProtected(tp2) then tp1
else
val tpdw = tp.derivedAndType(dropProtected(tp1), dropProtected(tp2))
if tpdw ne tpd then tpdw else tp

@@ -300,6 +300,8 @@ trait ConstraintHandling[AbstractContext] {
* (i.e. `inst.widenSingletons <:< bound` succeeds with satisfiable constraint)
* 2. If `inst` is a union type, approximate the union type from above by an intersection
* of all common base types, provided the result is a subtype of `bound`.
* 3. (currently not enabled) If `inst` is an intersection with some protected base types, drop
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't use the word protected, unless the plan is to actually use the protected modifier with them. Maybe restricted ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. For while I thought one could use protected for this, but now I think, probably not.

Comment on lines 1446 to 1449
case tp @ AndType(tp1, tp2) =>
if isEnumValue(tp1) then tp2
else if isEnumValue(tp2) then tp1
else tp.derivedAndType(dropEnumValue(tp1), dropEnumValue(tp2))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same suggestion as above about dealiasing:

Suggested change
case tp @ AndType(tp1, tp2) =>
if isEnumValue(tp1) then tp2
else if isEnumValue(tp2) then tp1
else tp.derivedAndType(dropEnumValue(tp1), dropEnumValue(tp2))
case tpd @ AndType(tp1, tp2) =>
if isEnumValue(tp1) then tp2
else if isEnumValue(tp2) then tp1
else
val tpdw = tpd.derivedAndType(dropEnumValue(tp1), dropEnumValue(tp2))
if tpdw ne tpd then tpdw else tp

else /*widenRestricted*/(widenOr(widenSingle(inst)))
// widenRestricted is currently not called since it's special cased in `dropEnumValue`
// in `Namer`. It's left in here in case we want to generalize the scheme to other
// "protected inheritance" classes.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure we should leave dead code like this around without a clear idea of what we want to do with it. Is this something you're planning to experiment with more? I think it would make sense to use this mechanism for getting rid of Serializable and Product so that they don't show up when lubbing case classes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See #9028.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice, thanks! Can you leave a reference to this issue number in the comment?

Copy link
Member

@smarter smarter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Otherwise LGTM.

def check(elem: Eq[_])(x: Any, y: Any): Boolean =
elem.asInstanceOf[Eq[Any]].eqv(x, y)

def iterator[T](p: T) = p.asInstanceOf[Product].productIterator
Copy link
Member

@smarter smarter May 22, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Going back to the original issue (#9011), is this cast actually safe?

The example in the docs suggests that if we have a Mirror.ProductOf[T] then we can safely cast an instance of T to Product (in order to access productIterator etc.). But this is not the case.

If this isn't actually guaranteed, then the documentation needs to be updated to not rely on this cast in an example.

@odersky
Copy link
Contributor Author

odersky commented May 22, 2020 via email

@smarter smarter merged commit 2bcd4f6 into scala:master May 22, 2020
@smarter smarter deleted the fix-#9011b branch May 22, 2020 19:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants