Skip to content

Fix #10415: Use CanEqual instead of Eql for multiversal equality #10449

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 7 commits into from
Nov 24, 2020
Merged
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
6 changes: 4 additions & 2 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -840,8 +840,10 @@ class Definitions {

@tu lazy val TastyReflectionClass: ClassSymbol = requiredClass("scala.tasty.Reflection")

@tu lazy val EqlClass: ClassSymbol = requiredClass("scala.Eql")
def Eql_eqlAny(using Context): TermSymbol = EqlClass.companionModule.requiredMethod(nme.eqlAny)
@tu lazy val CanEqualClass: ClassSymbol = getClassIfDefined("scala.Eql").orElse(requiredClass("scala.CanEqual")).asClass
def CanEqual_canEqualAny(using Context): TermSymbol =
val methodName = if CanEqualClass.name == tpnme.Eql then nme.eqlAny else nme.canEqualAny
CanEqualClass.companionModule.requiredMethod(methodName)

@tu lazy val TypeBoxClass: ClassSymbol = requiredClass("scala.runtime.TypeBox")
@tu lazy val TypeBox_CAP: TypeSymbol = TypeBoxClass.requiredType(tpnme.CAP)
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/Denotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ import collection.mutable.ListBuffer
*/
object Denotations {

implicit def eqDenotation: Eql[Denotation, Denotation] = Eql.derived
implicit def eqDenotation: CanEqual[Denotation, Denotation] = CanEqual.derived

/** A PreDenotation represents a group of single denotations or a single multi-denotation
* It is used as an optimization to avoid forming MultiDenotations too eagerly.
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/Names.scala
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ object Names {
* in a name table. A derived term name adds a tag, and possibly a number
* or a further simple name to some other name.
*/
abstract class Name extends Designator, Showable derives Eql {
abstract class Name extends Designator, Showable derives CanEqual {

/** A type for names of the same kind as this name */
type ThisName <: Name
Expand Down
2 changes: 2 additions & 0 deletions compiler/src/dotty/tools/dotc/core/StdNames.scala
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,7 @@ object StdNames {
val CAP: N = "CAP"
val Constant: N = "Constant"
val ConstantType: N = "ConstantType"
val Eql: N = "Eql"
val EnumValue: N = "EnumValue"
val ExistentialTypeTree: N = "ExistentialTypeTree"
val Flag : N = "Flag"
Expand Down Expand Up @@ -433,6 +434,7 @@ object StdNames {
val bundle: N = "bundle"
val bytes: N = "bytes"
val canEqual_ : N = "canEqual"
val canEqualAny : N = "canEqualAny"
val cbnArg: N = "<cbn-arg>"
val checkInitialized: N = "checkInitialized"
val ClassManifestFactory: N = "ClassManifestFactory"
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/Symbols.scala
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import config.Printers.typr

object Symbols {

implicit def eqSymbol: Eql[Symbol, Symbol] = Eql.derived
implicit def eqSymbol: CanEqual[Symbol, Symbol] = CanEqual.derived

/** Tree attachment containing the identifiers in a tree as a sorted array */
val Ids: Property.Key[Array[String]] = new Property.Key
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ object Types {

@sharable private var nextId = 0

implicit def eqType: Eql[Type, Type] = Eql.derived
implicit def eqType: CanEqual[Type, Type] = CanEqual.derived

/** Main class representing types.
*
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/semanticdb/Language.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package dotty.tools.dotc.semanticdb

import dotty.tools.dotc.semanticdb.internal._

sealed trait Language(val value: Int) extends SemanticdbEnum derives Eql
sealed trait Language(val value: Int) extends SemanticdbEnum derives CanEqual

object Language {

Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/semanticdb/Range.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ final case class Range(
startCharacter: Int,
endLine: Int,
endCharacter: Int
) extends SemanticdbMessage[Range] derives Eql {
) extends SemanticdbMessage[Range] derives CanEqual {
@sharable
private var __serializedSizeCachedValue: Int = 0
private def __computeSerializedValue(): Int = {
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/semanticdb/Scala3.scala
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ object Scala3:

private val WILDCARDTypeName = nme.WILDCARD.toTypeName

enum SymbolKind derives Eql:
enum SymbolKind derives CanEqual:
kind =>

case Val, Var, Setter, Abstract
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/semanticdb/Schema.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package dotty.tools.dotc.semanticdb

import dotty.tools.dotc.semanticdb.internal._

sealed trait Schema(val value: Int) extends SemanticdbEnum derives Eql
sealed trait Schema(val value: Int) extends SemanticdbEnum derives CanEqual

object Schema {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ object SymbolInformation {

val defaultInstance = SymbolInformation("", Language.UNKNOWN_LANGUAGE, SymbolInformation.Kind.UNKNOWN_KIND, 0, "")

sealed trait Kind(val value: Int) extends SemanticdbEnum derives Eql {
sealed trait Kind(val value: Int) extends SemanticdbEnum derives CanEqual {
def isUnknownKind: Boolean = this == Kind.UNKNOWN_KIND
def isLocal: Boolean = this == Kind.LOCAL
def isField: Boolean = this == Kind.FIELD
Expand Down Expand Up @@ -67,7 +67,7 @@ object SymbolInformation {
}
}

sealed trait Property(val value: Int) extends SemanticdbEnum derives Eql {
sealed trait Property(val value: Int) extends SemanticdbEnum derives CanEqual {
def isUnknownProperty: Boolean = this == Property.UNKNOWN_PROPERTY
def isAbstract: Boolean = this == Property.ABSTRACT
def isFinal: Boolean = this == Property.FINAL
Expand Down Expand Up @@ -131,7 +131,7 @@ final case class SymbolInformation(
kind: SymbolInformation.Kind,
properties: Int,
displayName: String
) extends SemanticdbMessage[SymbolInformation] derives Eql {
) extends SemanticdbMessage[SymbolInformation] derives CanEqual {
@sharable
private var __serializedSizeCachedValue: Int = 0
private def __computeSerializedValue(): Int = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import scala.annotation.internal.sharable

object SymbolOccurrence {

sealed trait Role(val value: Int) extends SemanticdbEnum derives Eql {
sealed trait Role(val value: Int) extends SemanticdbEnum derives CanEqual {
def isDefinition: Boolean = this == Role.DEFINITION
def isReference: Boolean = this == Role.REFERENCE
}
Expand Down Expand Up @@ -33,7 +33,7 @@ final case class SymbolOccurrence(
symbol: String,
range: Option[Range],
role: SymbolOccurrence.Role
) extends SemanticdbMessage[SymbolOccurrence] derives Eql {
) extends SemanticdbMessage[SymbolOccurrence] derives CanEqual {
@sharable
private var __serializedSizeCachedValue: Int = 0
private def __computeSerializedValue(): Int = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ final case class TextDocument(
md5: String,
symbols: Seq[SymbolInformation],
occurrences: Seq[SymbolOccurrence]
) extends SemanticdbMessage[TextDocument] derives Eql {
) extends SemanticdbMessage[TextDocument] derives CanEqual {
@sharable
private var __serializedSizeCachedValue: Int = 0
private def __computeSerializedValue(): Int = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ object TextDocuments {
}
val defaultInstance: TextDocuments = TextDocuments(Nil)
}
final case class TextDocuments(documents: Seq[TextDocument]) extends SemanticdbMessage[TextDocuments] derives Eql {
final case class TextDocuments(documents: Seq[TextDocument]) extends SemanticdbMessage[TextDocuments] derives CanEqual {
@sharable
private var __serializedSizeCachedValue: Int = 0
private def __computeSerializedValue(): Int = {
Expand Down
28 changes: 14 additions & 14 deletions compiler/src/dotty/tools/dotc/typer/Deriving.scala
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,9 @@ trait Deriving {
* the deriving ADT
* (b) a single parameter type class with a parameter of kind * and an ADT with
* one or more type parameter of kind *
* (c) the Eql type class
* (c) the CanEqual type class
*
* See detailed descriptions in deriveSingleParameter and deriveEql below.
* See detailed descriptions in deriveSingleParameter and deriveCanEqual below.
*
* If it passes the checks, enter a type class instance for it in the current scope.
*
Expand Down Expand Up @@ -145,7 +145,7 @@ trait Deriving {
//
// given derived$TC[a, b, c] given TC[a], TC[b], TC[c]: TC[a, b, c]
//
// This, like the derivation for Eql, is a special case of the
// This, like the derivation for CanEqual, is a special case of the
// earlier more general multi-parameter type class model for which
// the heuristic is typically a good one.

Expand Down Expand Up @@ -185,8 +185,8 @@ trait Deriving {
cannotBeUnified
}

def deriveEql: Unit = {
// Specific derives rules for the Eql type class ... (c) above
def deriveCanEqual: Unit = {
// Specific derives rules for the CanEqual type class ... (c) above
//
// This has been extracted from the earlier more general multi-parameter
// type class model. Modulo the assumptions below, the implied semantics
Expand All @@ -196,26 +196,26 @@ trait Deriving {
// 1. Type params of the deriving class correspond to all and only
// elements of the deriving class which are relevant to equality (but:
// type params could be phantom, or the deriving class might have an
// element of a non-Eql type non-parametrically).
// element of a non-CanEqual type non-parametrically).
//
// 2. Type params of kinds other than * can be assumed to be irrelevant to
// the derivation (but: eg. Foo[F[_]](fi: F[Int])).
//
// Are they reasonable? They cover some important cases (eg. Tuples of all
// arities). derives Eql is opt-in, so if the semantics don't match those
// arities). derives CanEqual is opt-in, so if the semantics don't match those
// appropriate for the deriving class the author of that class can provide
// their own instance in the normal way. That being so, the question turns
// on whether there are enough types which fit these semantics for the
// feature to pay its way.

// Procedure:
// We construct a two column matrix of the deriving class type parameters
// and the Eql type class parameters.
// and the CanEqual type class parameters.
//
// Rows: parameters of the deriving class
// Columns: parameters of the Eql type class (L/R)
// Columns: parameters of the CanEqual type class (L/R)
//
// Running example: type class: class Eql[L, R], deriving class: class A[T, U, V]
// Running example: type class: class CanEqual[L, R], deriving class: class A[T, U, V]
// clsParamss =
// T_L T_R
// U_L U_R
Expand All @@ -225,15 +225,15 @@ trait Deriving {
tparam.copy(name = s"${tparam.name}_$$_${tcparam.name}".toTypeName)
.asInstanceOf[TypeSymbol])
}
// Retain only rows with L/R params of kind * which Eql can be applied to.
// Retain only rows with L/R params of kind * which CanEqual can be applied to.
// No pairwise evidence will be required for params of other kinds.
val firstKindedParamss = clsParamss.filter {
case param :: _ => !param.info.isLambdaSub
case _ => false
}

// The types of the required evidence parameters. In the running example:
// Eql[T_L, T_R], Eql[U_L, U_R], Eql[V_L, V_R]
// CanEqual[T_L, T_R], CanEqual[U_L, U_R], CanEqual[V_L, V_R]
val evidenceParamInfos =
for (row <- firstKindedParamss)
yield row.map(_.typeRef)
Expand All @@ -244,12 +244,12 @@ trait Deriving {
for (n <- List.range(0, typeClassArity))
yield cls.typeRef.appliedTo(clsParamss.map(row => row(n).typeRef))

// Eql[A[T_L, U_L, V_L], A[T_R, U_R, V_R]]
// CanEqual[A[T_L, U_L, V_L], A[T_R, U_R, V_R]]
addInstance(clsParamss.flatten, evidenceParamInfos, instanceTypes)
}

if (typeClassArity == 1) deriveSingleParameter
else if (typeClass == defn.EqlClass) deriveEql
else if (typeClass == defn.CanEqualClass) deriveCanEqual
else if (typeClassArity == 0)
report.error(i"type ${typeClass.name} in derives clause of ${cls.name} has no type parameters", derived.srcPos)
else
Expand Down
8 changes: 4 additions & 4 deletions compiler/src/dotty/tools/dotc/typer/Implicits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -883,7 +883,7 @@ trait Implicits:
else s"parameter $paramName" } of $methodStr"
}

/** An Eql[T, U] instance is assumed
/** A CanEqual[T, U] instance is assumed
* - if one of T, U is an error type, or
* - if one of T, U is a subtype of the lifted version of the other,
* unless strict equality is set.
Expand Down Expand Up @@ -913,8 +913,8 @@ trait Implicits:
/** Check that equality tests between types `ltp` and `rtp` make sense */
def checkCanEqual(ltp: Type, rtp: Type, span: Span)(using Context): Unit =
if (!ctx.isAfterTyper && !assumedCanEqual(ltp, rtp)) {
val res = implicitArgTree(defn.EqlClass.typeRef.appliedTo(ltp, rtp), span)
implicits.println(i"Eql witness found for $ltp / $rtp: $res: ${res.tpe}")
val res = implicitArgTree(defn.CanEqualClass.typeRef.appliedTo(ltp, rtp), span)
implicits.println(i"CanEqual witness found for $ltp / $rtp: $res: ${res.tpe}")
}

/** Find an implicit parameter or conversion.
Expand Down Expand Up @@ -1047,7 +1047,7 @@ trait Implicits:
private def nestedContext() =
ctx.fresh.setMode(ctx.mode &~ Mode.ImplicitsEnabled)

private def isCoherent = pt.isRef(defn.EqlClass)
private def isCoherent = pt.isRef(defn.CanEqualClass)

val wideProto = pt.widenExpr

Expand Down
26 changes: 13 additions & 13 deletions compiler/src/dotty/tools/dotc/typer/Synthesizer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -77,27 +77,27 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
}
end synthesizedTypeTest

/** If `formal` is of the form Eql[T, U], try to synthesize an
* `Eql.eqlAny[T, U]` as solution.
/** If `formal` is of the form CanEqual[T, U], try to synthesize an
* `CanEqual.canEqualAny[T, U]` as solution.
*/
val synthesizedEql: SpecialHandler = (formal, span) =>
val synthesizedCanEqual: SpecialHandler = (formal, span) =>

/** Is there an `Eql[T, T]` instance, assuming -strictEquality? */
/** Is there an `CanEqual[T, T]` instance, assuming -strictEquality? */
def hasEq(tp: Type)(using Context): Boolean =
val inst = typer.inferImplicitArg(defn.EqlClass.typeRef.appliedTo(tp, tp), span)
val inst = typer.inferImplicitArg(defn.CanEqualClass.typeRef.appliedTo(tp, tp), span)
!inst.isEmpty && !inst.tpe.isError

/** Can we assume the eqlAny instance for `tp1`, `tp2`?
/** Can we assume the canEqualAny instance for `tp1`, `tp2`?
* This is the case if assumedCanEqual(tp1, tp2), or
* one of `tp1`, `tp2` has a reflexive `Eql` instance.
* one of `tp1`, `tp2` has a reflexive `CanEqual` instance.
*/
def validEqAnyArgs(tp1: Type, tp2: Type)(using Context) =
typer.assumedCanEqual(tp1, tp2)
|| withMode(Mode.StrictEquality) {
!hasEq(tp1) && !hasEq(tp2)
}

/** Is an `Eql[cls1, cls2]` instance assumed for predefined classes `cls1`, cls2`? */
/** Is an `CanEqual[cls1, cls2]` instance assumed for predefined classes `cls1`, cls2`? */
def canComparePredefinedClasses(cls1: ClassSymbol, cls2: ClassSymbol): Boolean =

def cmpWithBoxed(cls1: ClassSymbol, cls2: ClassSymbol) =
Expand Down Expand Up @@ -129,8 +129,8 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
false
end canComparePredefinedClasses

/** Some simulated `Eql` instances for predefined types. It's more efficient
* to do this directly instead of setting up a lot of `Eql` instances to
/** Some simulated `CanEqual` instances for predefined types. It's more efficient
* to do this directly instead of setting up a lot of `CanEqual` instances to
* interpret.
*/
def canComparePredefined(tp1: Type, tp2: Type) =
Expand All @@ -143,10 +143,10 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
List(arg1, arg2).foreach(fullyDefinedType(_, "eq argument", span))
if canComparePredefined(arg1, arg2)
|| !Implicits.strictEquality && explore(validEqAnyArgs(arg1, arg2))
then ref(defn.Eql_eqlAny).appliedToTypes(args).withSpan(span)
then ref(defn.CanEqual_canEqualAny).appliedToTypes(args).withSpan(span)
else EmptyTree
case _ => EmptyTree
end synthesizedEql
end synthesizedCanEqual

/** Creates a tree that will produce a ValueOf instance for the requested type.
* An EmptyTree is returned if materialization fails.
Expand Down Expand Up @@ -363,7 +363,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
val specialHandlers = List(
defn.ClassTagClass -> synthesizedClassTag,
defn.TypeTestClass -> synthesizedTypeTest,
defn.EqlClass -> synthesizedEql,
defn.CanEqualClass -> synthesizedCanEqual,
defn.ValueOfClass -> synthesizedValueOf,
defn.Mirror_ProductClass -> synthesizedProductMirror,
defn.Mirror_SumClass -> synthesizedSumMirror,
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/util/SourceFile.scala
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ class SourceFile(val file: AbstractFile, computeContent: => Array[Char]) extends
override def toString: String = file.toString
}
object SourceFile {
implicit def eqSource: Eql[SourceFile, SourceFile] = Eql.derived
implicit def eqSource: CanEqual[SourceFile, SourceFile] = CanEqual.derived

implicit def fromContext(using Context): SourceFile = ctx.source

Expand Down
4 changes: 2 additions & 2 deletions compiler/test-resources/repl/i4184
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ scala> object foo { class Foo }
// defined object foo
scala> object bar { class Foo }
// defined object bar
scala> implicit def eqFoo: Eql[foo.Foo, foo.Foo] = Eql.derived
def eqFoo: Eql[foo.Foo, foo.Foo]
scala> implicit def eqFoo: CanEqual[foo.Foo, foo.Foo] = CanEqual.derived
def eqFoo: CanEqual[foo.Foo, foo.Foo]
scala> object Bar { new foo.Foo == new bar.Foo }
1 | object Bar { new foo.Foo == new bar.Foo }
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand Down
Loading