Skip to content

Add multiversal equals #1234

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

Closed
wants to merge 17 commits into from
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
2 changes: 1 addition & 1 deletion src/dotty/tools/backend/jvm/DottyBackendInterface.scala
Original file line number Diff line number Diff line change
Expand Up @@ -861,7 +861,7 @@ class DottyBackendInterface(outputDirectory: AbstractFile)(implicit ctx: Context
"If possible, please file a bug on issues.scala-lang.org.")

tp match {
case ThisType(ArrayClass) => ObjectReference.asInstanceOf[ct.bTypes.ClassBType] // was introduced in 9b17332f11 to fix SI-999, but this code is not reached in its test, or any other test
case tp: ThisType if tp.cls == ArrayClass => ObjectReference.asInstanceOf[ct.bTypes.ClassBType] // was introduced in 9b17332f11 to fix SI-999, but this code is not reached in its test, or any other test
case ThisType(sym) => storage.getClassBTypeAndRegisterInnerClass(sym.asInstanceOf[ct.int.Symbol])
// case t: SingletonType => primitiveOrClassToBType(t.classSymbol)
case t: SingletonType => t.underlying.toTypeKind(ct)(storage)
Expand Down
2 changes: 1 addition & 1 deletion src/dotty/tools/dotc/ast/Positioned.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import util.DotClass

/** A base class for things that have positions (currently: modifiers and trees)
*/
abstract class Positioned extends DotClass with Product {
abstract class Positioned extends DotClass with Product with EqClass[Positioned] {

private[this] var curPos: Position = _

Expand Down
6 changes: 3 additions & 3 deletions src/dotty/tools/dotc/ast/TreeInfo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ trait TreeInfo[T >: Untyped <: Type] { self: Trees.Instance[T] =>
def defKind(tree: Tree): FlagSet = unsplice(tree) match {
case EmptyTree | _: Import => NoInitsInterface
case tree: TypeDef => if (tree.isClassDef) NoInits else NoInitsInterface
case tree: DefDef => if (tree.unforcedRhs == EmptyTree) NoInitsInterface else NoInits
case tree: ValDef => if (tree.unforcedRhs == EmptyTree) NoInitsInterface else EmptyFlags
case tree: DefDef => if (tree.unforcedRhs eq EmptyTree) NoInitsInterface else NoInits
case tree: ValDef => if (tree.unforcedRhs eq EmptyTree) NoInitsInterface else EmptyFlags
case _ => EmptyFlags
}

Expand Down Expand Up @@ -254,7 +254,7 @@ trait TreeInfo[T >: Untyped <: Type] { self: Trees.Instance[T] =>
*/
def lacksDefinition(mdef: MemberDef)(implicit ctx: Context) = mdef match {
case mdef: ValOrDefDef =>
mdef.unforcedRhs == EmptyTree && !mdef.name.isConstructorName && !mdef.mods.is(ParamAccessor)
(mdef.unforcedRhs eq EmptyTree) && !mdef.name.isConstructorName && !mdef.mods.is(ParamAccessor)
case mdef: TypeDef =>
mdef.rhs.isEmpty || mdef.rhs.isInstanceOf[TypeBoundsTree]
case _ => false
Expand Down
8 changes: 7 additions & 1 deletion src/dotty/tools/dotc/ast/tpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -847,7 +847,13 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
assert(denot.exists, i"no member $receiver . $method, members = ${receiver.tpe.decls}")
val selected =
if (denot.isOverloaded) {
val allAlts = denot.alternatives.map(_.termRef)
def typeParamCount(tp: Type) = tp.widen match {
case tp: PolyType => tp.paramBounds.length
case _ => 0
}
var allAlts = denot.alternatives
.map(_.termRef).filter(tr => typeParamCount(tr) == targs.length)
if (targs.isEmpty) allAlts = allAlts.filterNot(_.widen.isInstanceOf[PolyType])
val alternatives =
ctx.typer.resolveOverloaded(allAlts, proto, Nil)
assert(alternatives.size == 1,
Expand Down
2 changes: 1 addition & 1 deletion src/dotty/tools/dotc/core/Annotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import dotty.tools.dotc.ast.{tpd, untpd}

object Annotations {

abstract class Annotation {
abstract class Annotation extends EqClass[Annotation] {
def tree(implicit ctx: Context): Tree
def symbol(implicit ctx: Context): Symbol =
if (tree.symbol.isConstructor) tree.symbol.owner
Expand Down
2 changes: 1 addition & 1 deletion src/dotty/tools/dotc/core/Constants.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ object Constants {
// For supporting java enumerations inside java annotations (see ClassfileParser)
final val EnumTag = 13

case class Constant(value: Any) extends printing.Showable {
case class Constant(value: Any) extends printing.Showable with EqClass[Constant] {
import java.lang.Double.doubleToRawLongBits
import java.lang.Float.floatToRawIntBits

Expand Down
3 changes: 2 additions & 1 deletion src/dotty/tools/dotc/core/Contexts.scala
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ object Contexts {
with SymDenotations
with Reporting
with NamerContextOps
with Cloneable { thiscontext =>
with Cloneable
with EqClass[Context] { thiscontext =>
implicit def ctx: Context = this

/** The context base at the root */
Expand Down
63 changes: 53 additions & 10 deletions src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,19 @@ class Definitions {
private def newSyntheticTypeParam(cls: ClassSymbol, scope: MutableScope, paramFlags: FlagSet, suffix: String = "T0") =
newTypeParam(cls, suffix.toTypeName.expandedName(cls), ExpandedName | paramFlags, scope)

// NOTE: Ideally we would write `parentConstrs: => Type*` but SIP-24 is only
// implemented in Dotty and not in Scala 2.
// See <http://docs.scala-lang.org/sips/pending/repeated-byname.html>.
private def specialPolyClass(name: TypeName, paramFlags: FlagSet, parentConstrs: => Seq[Type]): ClassSymbol = {
/** A class following the template
*
* package scala
* class <name>[<paramFlags> $T0] extends <parents>
*
* where <parents> = [constr' | constr <- parentConstrs]
* <constr'> = constr[$T0] of <constr> has a type parameter
* = constr otherwise
*/
private def specialPolyClass(name: TypeName, classFlags: FlagSet, paramFlags: FlagSet, parentConstrs: => Seq[Type]): ClassSymbol = {
// NOTE: Ideally we would write `parentConstrs: => Type*` but SIP-24 is only
// implemented in Dotty and not in Scala 2.
// See <http://docs.scala-lang.org/sips/pending/repeated-byname.html>.
val completer = new LazyType {
def complete(denot: SymDenotation)(implicit ctx: Context): Unit = {
val cls = denot.asClass.classSymbol
Expand All @@ -73,7 +82,7 @@ class Definitions {
denot.info = ClassInfo(ScalaPackageClass.thisType, cls, parentRefs, paramDecls)
}
}
newClassSymbol(ScalaPackageClass, name, EmptyFlags, completer)
newClassSymbol(ScalaPackageClass, name, classFlags, completer)
}

private def newMethod(cls: ClassSymbol, name: TermName, info: Type, flags: FlagSet = EmptyFlags): TermSymbol =
Expand Down Expand Up @@ -200,6 +209,38 @@ class Definitions {
def ObjectMethods = List(Object_eq, Object_ne, Object_synchronized, Object_clone,
Object_finalize, Object_notify, Object_notifyAll, Object_wait, Object_waitL, Object_waitLI)

/** A trait with the following signature:
*
* trait EqClass[-U] {
* /** Comparison operations between values in the same equality class */
* final def == [T >: this.type <: EqClass[_](other: T)(implicit ce: Eq[T]): Boolean = this.equals(other)
* final def != [T >: this.type <: EqClass[_](other: T)(implicit ce: Eq[T]): Boolean = this.equals(other)
* }
*
Copy link
Contributor

Choose a reason for hiding this comment

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

A minor typo here: missing closing bracket for type parameters.

This seems to me a clever design that I feel a little difficult to appreciate fully the intricacies, e.g. what would happen:

  • if we change trait EqClass[-U] to trait EqClass, U is not explicitly used in the body?
  • if we use [T >: this.type <: EqClass[T] instead [T >: this.type <: EqClass[_]?

* The reason we define this here rather than as a source file is that these definitions
* throughly confuse scalac. When inheriting from EqClass and typechecking == it dies
* with errors like this (and no stacktrace):
*
* Exception in thread "main" scala.reflect.internal.Types$NoCommonType: lub/glb of incompatible types: => core.this.Names.TypeName and scala.this.Nothing
*/
lazy val EqClassClass: ClassSymbol = {
val ecc = specialPolyClass(tpnme.EqClass, PureInterfaceCreationFlags, Contravariant, List(AnyClass.typeRef))
val completer = new LazyType {
override def complete(denot: SymDenotation)(implicit ctx: Context): Unit = {
denot.info =
PolyType(tpnme.syntheticTypeParamNames(1))(
pt => List(TypeBounds(ecc.thisType, ecc.typeRef.appliedTo(TypeBounds.empty))),
pt => MethodType(List(PolyParam(pt, 0)),
ImplicitMethodType(List(EqType.appliedTo(PolyParam(pt, 0))),
BooleanType)))
}
}
newMethod(ecc, nme.EQ, completer, Final)
newMethod(ecc, nme.NE, completer, Final)
ecc
}
def EqClassType = EqClassClass.typeRef

/** Dummy method needed by elimByName */
lazy val dummyApply = newPolyMethod(
OpsPackageClass, nme.dummyApply, 1,
Expand Down Expand Up @@ -282,7 +323,6 @@ class Definitions {
lazy val ArrayModuleType = ctx.requiredModuleRef("scala.Array")
def ArrayModule(implicit ctx: Context) = ArrayModuleType.symbol.moduleClass.asClass


lazy val UnitType: TypeRef = valueTypeRef("scala.Unit", BoxedUnitType, java.lang.Void.TYPE, UnitEnc)
def UnitClass(implicit ctx: Context) = UnitType.symbol.asClass
lazy val BooleanType = valueTypeRef("scala.Boolean", BoxedBooleanType, java.lang.Boolean.TYPE, BooleanEnc)
Expand Down Expand Up @@ -361,10 +401,10 @@ class Definitions {
lazy val BoxedDoubleModule = ctx.requiredModule("java.lang.Double")
lazy val BoxedUnitModule = ctx.requiredModule("java.lang.Void")

lazy val ByNameParamClass2x = specialPolyClass(tpnme.BYNAME_PARAM_CLASS, Covariant, Seq(AnyType))
lazy val EqualsPatternClass = specialPolyClass(tpnme.EQUALS_PATTERN, EmptyFlags, Seq(AnyType))
lazy val ByNameParamClass2x = specialPolyClass(tpnme.BYNAME_PARAM_CLASS, EmptyFlags, Covariant, Seq(AnyType))
lazy val EqualsPatternClass = specialPolyClass(tpnme.EQUALS_PATTERN, EmptyFlags, EmptyFlags, Seq(AnyType))

lazy val RepeatedParamClass = specialPolyClass(tpnme.REPEATED_PARAM_CLASS, Covariant, Seq(ObjectType, SeqType))
lazy val RepeatedParamClass = specialPolyClass(tpnme.REPEATED_PARAM_CLASS, EmptyFlags, Covariant, Seq(ObjectType, SeqType))

// fundamental classes
lazy val StringClass = ctx.requiredClass("java.lang.String")
Expand Down Expand Up @@ -401,6 +441,8 @@ class Definitions {
lazy val StringAdd_plusR = StringAddClass.requiredMethodRef(nme.raw.PLUS)
def StringAdd_+(implicit ctx: Context) = StringAdd_plusR.symbol

lazy val EqType: TypeRef = ctx.requiredClassRef("scala.Eq")
def EqClass(implicit ctx: Context) = EqType.symbol.asClass
lazy val PairType: TypeRef = ctx.requiredClassRef("dotty.Pair")
def PairClass(implicit ctx: Context) = PairType.symbol.asClass
lazy val PartialFunctionType: TypeRef = ctx.requiredClassRef("scala.PartialFunction")
Expand Down Expand Up @@ -797,7 +839,8 @@ class Definitions {
SingletonClass,
EqualsPatternClass,
EmptyPackageVal,
OpsPackageClass)
OpsPackageClass,
EqClassClass)

/** Lists core methods that don't have underlying bytecode, but are synthesized on-the-fly in every reflection universe */
lazy val syntheticCoreMethods = AnyMethods ++ ObjectMethods ++ List(String_+, throwMethod)
Expand Down
2 changes: 1 addition & 1 deletion src/dotty/tools/dotc/core/Denotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ object Denotations {
*
* @param symbol The referencing symbol, or NoSymbol is none exists
*/
abstract class Denotation(val symbol: Symbol) extends util.DotClass with printing.Showable {
abstract class Denotation(val symbol: Symbol) extends util.DotClass with printing.Showable with EqClass[Denotation] {

/** The type info of the denotation, exists only for non-overloaded denotations */
def info(implicit ctx: Context): Type
Expand Down
4 changes: 2 additions & 2 deletions src/dotty/tools/dotc/core/Flags.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ object Flags {
* that has the intersection of the applicability to terms/types
* of the two flag sets. It is checked that the intersection is not empty.
*/
case class FlagSet(val bits: Long) extends AnyVal {
case class FlagSet(val bits: Long) extends AnyVal with EqClass[FlagSet] {

/** The union of this flag set and the given flag set
*/
Expand Down Expand Up @@ -121,7 +121,7 @@ object Flags {
* conjunctively. I.e. for a flag conjunction `fc`,
* `x is fc` tests whether `x` contains all flags in `fc`.
*/
case class FlagConjunction(bits: Long) {
case class FlagConjunction(bits: Long) extends EqClass[FlagConjunction] {
override def toString = FlagSet(bits).toString
}

Expand Down
2 changes: 1 addition & 1 deletion src/dotty/tools/dotc/core/Mode.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package dotty.tools.dotc.core

/** A collection of mode bits that are part of a context */
case class Mode(val bits: Int) extends AnyVal {
case class Mode(val bits: Int) extends AnyVal with EqClass[Mode] {
import Mode._
def | (that: Mode) = Mode(bits | that.bits)
def & (that: Mode) = Mode(bits & that.bits)
Expand Down
2 changes: 1 addition & 1 deletion src/dotty/tools/dotc/core/Names.scala
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ object Names {
* 3. Names are intended to be encoded strings. @see dotc.util.NameTransformer.
* The encoding will be applied when converting a string to a name.
*/
abstract class Name extends DotClass
abstract class Name extends DotClass with EqClass[Name]
with PreName
with collection.immutable.Seq[Char]
with IndexedSeqOptimized[Char, Name] {
Expand Down
2 changes: 1 addition & 1 deletion src/dotty/tools/dotc/core/Periods.scala
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ object Periods {
*
* // Dmitry: sign == 0 isn't actually always true, in some cases phaseId == -1 is used for shifts, that easily creates code < 0
*/
class Period(val code: Int) extends AnyVal {
class Period(val code: Int) extends AnyVal with EqClass[Period] {

/** The run identifier of this period. */
def runId: RunId = code >>> (PhaseWidth * 2)
Expand Down
2 changes: 1 addition & 1 deletion src/dotty/tools/dotc/core/Phases.scala
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ object Phases {
def isAfterTyper(phase: Phase): Boolean = phase.id > typerPhase.id
}

trait Phase extends DotClass {
trait Phase extends DotClass with EqClass[Phase] {

def phaseName: String

Expand Down
2 changes: 1 addition & 1 deletion src/dotty/tools/dotc/core/Signature.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import TypeErasure.sigName
*
* The signatures of non-method types are always `NotAMethod`.
*/
case class Signature(paramsSig: List[TypeName], resSig: TypeName) {
case class Signature(paramsSig: List[TypeName], resSig: TypeName) extends EqClass[Signature] {
import Signature._

/** Does this signature coincide with that signature on their parameter parts? */
Expand Down
1 change: 1 addition & 0 deletions src/dotty/tools/dotc/core/StdNames.scala
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,7 @@ object StdNames {
val emptyValDef: N = "emptyValDef"
val ensureAccessible : N = "ensureAccessible"
val eq: N = "eq"
val EqClass: N = "EqClass"
val equalsNumChar : N = "equalsNumChar"
val equalsNumNum : N = "equalsNumNum"
val equalsNumObject : N = "equalsNumObject"
Expand Down
2 changes: 1 addition & 1 deletion src/dotty/tools/dotc/core/SymDenotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -509,7 +509,7 @@ object SymDenotations {
*/
final def isSelfSym(implicit ctx: Context) = owner.infoOrCompleter match {
case ClassInfo(_, _, _, _, selfInfo) =>
selfInfo == symbol ||
(selfInfo eq symbol) ||
selfInfo.isInstanceOf[Type] && name == nme.WILDCARD
case _ => false
}
Expand Down
2 changes: 1 addition & 1 deletion src/dotty/tools/dotc/core/Symbols.scala
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,7 @@ object Symbols {
* @param coord The coordinates of the symbol (a position or an index)
* @param id A unique identifier of the symbol (unique per ContextBase)
*/
class Symbol private[Symbols] (val coord: Coord, val id: Int) extends DotClass with printing.Showable {
class Symbol private[Symbols] (val coord: Coord, val id: Int) extends DotClass with printing.Showable with EqClass[Symbol] {

type ThisName <: Name

Expand Down
2 changes: 1 addition & 1 deletion src/dotty/tools/dotc/core/TyperState.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import printing.Texts._
import config.Config
import collection.mutable

class TyperState(r: Reporter) extends DotClass with Showable {
class TyperState(r: Reporter) extends DotClass with Showable with EqClass[TyperState] {

/** The current reporter */
def reporter = r
Expand Down
2 changes: 1 addition & 1 deletion src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ object Types {
* +- ErrorType
* +- WildcardType
*/
abstract class Type extends DotClass with Hashable with printing.Showable {
abstract class Type extends DotClass with Hashable with printing.Showable with EqClass[Type] {

// ----- Tests -----------------------------------------------------

Expand Down
2 changes: 2 additions & 0 deletions src/dotty/tools/dotc/printing/PlainPrinter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ class PlainPrinter(_ctx: Context) extends Printer {
case tp @ TypeRef(_, tpnme.hkApply) =>
val tp1 = tp.reduceProjection
if (tp1 eq tp) tp else homogenize(tp1)
case tp: LazyRef =>
homogenize(tp.ref)
case _ =>
tp
}
Expand Down
2 changes: 1 addition & 1 deletion src/dotty/tools/dotc/transform/CollectEntryPoints.scala
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ class CollectEntryPoints extends MiniPhaseTransform {
val javaPlatform = ctx.platform.asInstanceOf[JavaPlatform]
if (javaPlatform.hasJavaMainMethod(companion))
failNoForwarder("companion contains its own main method")
else if (companion != NoSymbol && companion.info.member(nme.main) != NoSymbol)
else if (companion.exists && companion.info.member(nme.main).exists)
// this is only because forwarders aren't smart enough yet
failNoForwarder("companion contains its own main method (implementation restriction: no main is allowed, regardless of signature)")
else if (companion.flags is Flags.Trait)
Expand Down
7 changes: 7 additions & 0 deletions src/dotty/tools/dotc/transform/InterceptedMethods.scala
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,13 @@ class InterceptedMethods extends MiniPhaseTransform {
// so we need replace that method name with Object_getClass to get correct behavior.
// See SI-5568.
qual.selectWithSig(defn.Any_getClass).appliedToNone
case t if t.owner == defn.EqClassClass =>
tree match {
case Apply(Apply(Select(qual, _), args), implicitArgs) =>
qual.select(defn.Any_==).appliedToArgs(args)
case _ =>
tree
}
case _ =>
tree
}
Expand Down
5 changes: 5 additions & 0 deletions src/dotty/tools/dotc/transform/PostTyper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,11 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisTran
case _ =>
super.transform(tree1)
}
case tree @ Apply(Select(lhs, nme.EQ), rhs :: Nil) if tree.symbol == defn.Any_== =>
if (lhs.tpe.derivesFrom(defn.EqClassClass) ||
rhs.tpe.derivesFrom(defn.EqClassClass) && !lhs.tpe.isRef(defn.NullClass))
ctx.error(d"values of type ${lhs.tpe} and ${rhs.tpe} cannot be compared", tree.pos)
super.transform(tree)
case tree @ Assign(sel: Select, _) =>
superAcc.transformAssign(super.transform(tree))
case tree: Template =>
Expand Down
2 changes: 1 addition & 1 deletion src/dotty/tools/dotc/typer/Implicits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,7 @@ trait ImplicitRunInfo { self: RunInfo =>
computeIScope(cacheResult = false)
else implicitScopeCache get tp match {
case Some(is) => is
case None => computeIScope(cacheResult = true)
case None => computeIScope(cacheResult = seen.isEmpty)
}
}

Expand Down
Loading