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
33 changes: 26 additions & 7 deletions compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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. If `inst` is an intersection with some protected base types, drop
* the protected base types from the intersection, provided the result is a subtype of `bound`.
*
* Don't do these widenings if `bound` is a subtype of `scala.Singleton`.
* Also, if the result of these widenings is a TypeRef to a module class,
Expand All @@ -309,26 +311,43 @@ trait ConstraintHandling[AbstractContext] {
* At this point we also drop the @Repeated annotation to avoid inferring type arguments with it,
* as those could leak the annotation to users (see run/inferred-repeated-result).
*/
def widenInferred(inst: Type, bound: Type)(implicit actx: AbstractContext): Type = {
def widenOr(tp: Type) = {
def widenInferred(inst: Type, bound: Type)(implicit actx: AbstractContext): Type =

def isProtected(tp: Type) = tp.typeSymbol == defn.EnumValueClass // for now, to be generalized later

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

case _ =>
tp

def widenProtected(tp: Type) =
val tpw = dropProtected(tp)
if (tpw ne tp) && (tpw <:< bound) then tpw else tp

def widenOr(tp: Type) =
val tpw = tp.widenUnion
if (tpw ne tp) && (tpw <:< bound) then tpw else tp
}
def widenSingle(tp: Type) = {

def widenSingle(tp: Type) =
val tpw = tp.widenSingletons
if (tpw ne tp) && (tpw <:< bound) then tpw else tp
}

def isSingleton(tp: Type): Boolean = tp match
case WildcardType(optBounds) => optBounds.exists && isSingleton(optBounds.bounds.hi)
case _ => isSubTypeWhenFrozen(tp, defn.SingletonType)

val wideInst =
if isSingleton(bound) then inst else widenOr(widenSingle(inst))
if isSingleton(bound) then inst
else widenProtected(widenOr(widenSingle(inst)))
wideInst match
case wideInst: TypeRef if wideInst.symbol.is(Module) =>
TermRef(wideInst.prefix, wideInst.symbol.sourceModule)
case _ =>
wideInst.dropRepeatedAnnot
}
end widenInferred

/** The instance type of `param` in the current constraint (which contains `param`).
* If `fromBelow` is true, the instance type is the lub of the parameter's
Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -639,6 +639,7 @@ class Definitions {
@tu lazy val EnumClass: ClassSymbol = ctx.requiredClass("scala.Enum")
@tu lazy val Enum_ordinal: Symbol = EnumClass.requiredMethod(nme.ordinal)

@tu lazy val EnumValueClass: ClassSymbol = ctx.requiredClass("scala.EnumValue")
@tu lazy val EnumValuesClass: ClassSymbol = ctx.requiredClass("scala.runtime.EnumValues")
@tu lazy val ProductClass: ClassSymbol = ctx.requiredClass("scala.Product")
@tu lazy val Product_canEqual : Symbol = ProductClass.requiredMethod(nme.canEqual_)
Expand Down
11 changes: 11 additions & 0 deletions tests/neg/enumvalues.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
enum Color:
case Red, Green, Blue

@main def Test(c: Boolean) =
// These currently give errors. But maybe we should make the actual
// enum values carry the `EnumValue` trait, and only strip it from
// user-defined vals and defs?
val x: EnumValue = if c then Color.Red else Color.Blue // error // error
val y: EnumValue = Color.Green // error