Skip to content

Commit 1f4c9f4

Browse files
committed
Merge pull request #1246 from dotty-staging/add-multiversal-equals-2
Multiversal Equality
2 parents 83bff19 + 2f3b895 commit 1f4c9f4

22 files changed

+524
-70
lines changed

src/dotty/DottyPredef.scala

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,43 @@ package dotty
33
import scala.reflect.runtime.universe.TypeTag
44
import scala.reflect.ClassTag
55
import scala.Predef.???
6+
import scala.collection.Seq
67

78
/** unimplemented implicit for TypeTag */
89
object DottyPredef {
910
implicit def typeTag[T]: TypeTag[T] = ???
1011

1112
implicit def arrayTag[T](implicit ctag: ClassTag[T]): ClassTag[Array[T]] =
1213
ctag.wrap
14+
15+
/** A fall-back implicit to compare values of any types.
16+
* The compiler will restrict implicit instances of `eqAny`. An instance
17+
* `eqAny[T, U]` is _valid_ if `T <: U` or `U <: T` or both `T` and `U` are
18+
* Eq-free. A type `S` is Eq-free if there is no implicit instance of `Eq[S, S]`.
19+
* An implicit search will fail instead of returning an invalid `eqAny` instance.
20+
*/
21+
implicit def eqAny[L, R]: Eq[L, R] = Eq
22+
23+
implicit def eqNumber : Eq[Number, Number] = Eq
24+
implicit def eqString : Eq[String, String] = Eq
25+
26+
// true asymmetry, modeling the (somewhat problematic) nature of equals on Proxies
27+
implicit def eqProxy : Eq[Proxy, Any] = Eq
28+
29+
implicit def eqSeq[T, U](implicit eq: Eq[T, U]): Eq[Seq[T], Seq[U]] = Eq
30+
31+
implicit def eqByteNum : Eq[Byte, Number] = Eq
32+
implicit def eqNumByte : Eq[Number, Byte] = Eq
33+
implicit def eqCharNum : Eq[Char, Number] = Eq
34+
implicit def eqNumChar : Eq[Number, Char] = Eq
35+
implicit def eqShortNum : Eq[Short, Number] = Eq
36+
implicit def eqNumShort : Eq[Number, Short] = Eq
37+
implicit def eqIntNum : Eq[Int, Number] = Eq
38+
implicit def eqNumInt : Eq[Number, Int] = Eq
39+
implicit def eqLongNum : Eq[Long, Number] = Eq
40+
implicit def eqNumLong : Eq[Number, Long] = Eq
41+
implicit def eqFloatNum : Eq[Float, Number] = Eq
42+
implicit def eqNumFloat : Eq[Number, Float] = Eq
43+
implicit def eqDoubleNum: Eq[Double, Number] = Eq
44+
implicit def eqNumDouble: Eq[Number, Double] = Eq
1345
}

src/dotty/tools/backend/jvm/DottyBackendInterface.scala

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -861,11 +861,11 @@ class DottyBackendInterface(outputDirectory: AbstractFile)(implicit ctx: Context
861861
"If possible, please file a bug on issues.scala-lang.org.")
862862

863863
tp match {
864-
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
865-
case ThisType(sym) => storage.getClassBTypeAndRegisterInnerClass(sym.asInstanceOf[ct.int.Symbol])
866-
// case t: SingletonType => primitiveOrClassToBType(t.classSymbol)
867-
case t: SingletonType => t.underlying.toTypeKind(ct)(storage)
868-
case t: RefinedType => t.parent.toTypeKind(ct)(storage) //parents.map(_.toTypeKind(ct)(storage).asClassBType).reduceLeft((a, b) => a.jvmWiseLUB(b))
864+
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
865+
case tp: ThisType => storage.getClassBTypeAndRegisterInnerClass(tp.cls.asInstanceOf[ct.int.Symbol])
866+
// case t: SingletonType => primitiveOrClassToBType(t.classSymbol)
867+
case t: SingletonType => t.underlying.toTypeKind(ct)(storage)
868+
case t: RefinedType => t.parent.toTypeKind(ct)(storage) //parents.map(_.toTypeKind(ct)(storage).asClassBType).reduceLeft((a, b) => a.jvmWiseLUB(b))
869869
}
870870
}
871871
}

src/dotty/tools/dotc/ast/tpd.scala

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -847,7 +847,13 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
847847
assert(denot.exists, i"no member $receiver . $method, members = ${receiver.tpe.decls}")
848848
val selected =
849849
if (denot.isOverloaded) {
850-
val allAlts = denot.alternatives.map(_.termRef)
850+
def typeParamCount(tp: Type) = tp.widen match {
851+
case tp: PolyType => tp.paramBounds.length
852+
case _ => 0
853+
}
854+
var allAlts = denot.alternatives
855+
.map(_.termRef).filter(tr => typeParamCount(tr) == targs.length)
856+
if (targs.isEmpty) allAlts = allAlts.filterNot(_.widen.isInstanceOf[PolyType])
851857
val alternatives =
852858
ctx.typer.resolveOverloaded(allAlts, proto, Nil)
853859
assert(alternatives.size == 1,

src/dotty/tools/dotc/core/Definitions.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,9 @@ class Definitions {
244244

245245
lazy val DottyPredefModuleRef = ctx.requiredModuleRef("dotty.DottyPredef")
246246
def DottyPredefModule(implicit ctx: Context) = DottyPredefModuleRef.symbol
247+
248+
def Predef_eqAny(implicit ctx: Context) = DottyPredefModule.requiredMethod(nme.eqAny)
249+
247250
lazy val DottyArraysModuleRef = ctx.requiredModuleRef("dotty.runtime.Arrays")
248251
def DottyArraysModule(implicit ctx: Context) = DottyArraysModuleRef.symbol
249252
def newGenericArrayMethod(implicit ctx: Context) = DottyArraysModule.requiredMethod("newGenericArray")
@@ -424,10 +427,14 @@ class Definitions {
424427
lazy val LanguageModuleRef = ctx.requiredModule("dotty.language")
425428
def LanguageModuleClass(implicit ctx: Context) = LanguageModuleRef.symbol.moduleClass.asClass
426429
lazy val NonLocalReturnControlType: TypeRef = ctx.requiredClassRef("scala.runtime.NonLocalReturnControl")
430+
427431
lazy val ClassTagType = ctx.requiredClassRef("scala.reflect.ClassTag")
428432
def ClassTagClass(implicit ctx: Context) = ClassTagType.symbol.asClass
429433
def ClassTagModule(implicit ctx: Context) = ClassTagClass.companionModule
430434

435+
lazy val EqType = ctx.requiredClassRef("scala.Eq")
436+
def EqClass(implicit ctx: Context) = EqType.symbol.asClass
437+
431438
// Annotation base classes
432439
lazy val AnnotationType = ctx.requiredClassRef("scala.annotation.Annotation")
433440
def AnnotationClass(implicit ctx: Context) = AnnotationType.symbol.asClass

src/dotty/tools/dotc/core/Denotations.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ import Decorators.SymbolIteratorDecorator
7070
*/
7171
object Denotations {
7272

73+
implicit def eqDenotation: Eq[Denotation, Denotation] = Eq
74+
7375
/** A denotation is the result of resolving
7476
* a name (either simple identifier or select) during a given period.
7577
*

src/dotty/tools/dotc/core/Mode.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,5 +81,8 @@ object Mode {
8181
/** We are currently unpickling Scala2 info */
8282
val Scala2Unpickling = newMode(13, "Scala2Unpickling")
8383

84+
/** Use Scala2 scheme for overloading and implicit resolution */
85+
val OldOverloadingResolution = newMode(14, "OldOverloadingResolution")
86+
8487
val PatternOrType = Pattern | Type
8588
}

src/dotty/tools/dotc/core/Names.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ object Names {
2626
def toTermName: TermName
2727
}
2828

29+
implicit def eqName: Eq[Name, Name] = Eq
30+
2931
/** A name is essentially a string, with three differences
3032
* 1. Names belong in one of two name spaces: they are type names or term names.
3133
* Term names have a sub-category of "local" field names.

src/dotty/tools/dotc/core/StdNames.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,7 @@ object StdNames {
394394
val equals_ : N = "equals"
395395
val error: N = "error"
396396
val eval: N = "eval"
397+
val eqAny: N = "eqAny"
397398
val ex: N = "ex"
398399
val experimental: N = "experimental"
399400
val f: N = "f"

src/dotty/tools/dotc/core/Symbols.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,8 @@ trait Symbols { this: Context =>
361361

362362
object Symbols {
363363

364+
implicit def eqSymbol: Eq[Symbol, Symbol] = Eq
365+
364366
/** A Symbol represents a Scala definition/declaration or a package.
365367
* @param coord The coordinates of the symbol (a position or an index)
366368
* @param id A unique identifier of the symbol (unique per ContextBase)

src/dotty/tools/dotc/core/Types.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ object Types {
3838

3939
@sharable private var nextId = 0
4040

41+
implicit def eqType: Eq[Type, Type] = Eq
42+
4143
/** The class of types.
4244
* The principal subclasses and sub-objects are as follows:
4345
*

src/dotty/tools/dotc/printing/PlainPrinter.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ class PlainPrinter(_ctx: Context) extends Printer {
5151
case tp @ TypeRef(_, tpnme.hkApply) =>
5252
val tp1 = tp.reduceProjection
5353
if (tp1 eq tp) tp else homogenize(tp1)
54+
case tp: LazyRef =>
55+
homogenize(tp.ref)
5456
case _ =>
5557
tp
5658
}

src/dotty/tools/dotc/transform/CollectEntryPoints.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ class CollectEntryPoints extends MiniPhaseTransform {
7575
val javaPlatform = ctx.platform.asInstanceOf[JavaPlatform]
7676
if (javaPlatform.hasJavaMainMethod(companion))
7777
failNoForwarder("companion contains its own main method")
78-
else if (companion != NoSymbol && companion.info.member(nme.main) != NoSymbol)
78+
else if (companion.exists && companion.info.member(nme.main).exists)
7979
// this is only because forwarders aren't smart enough yet
8080
failNoForwarder("companion contains its own main method (implementation restriction: no main is allowed, regardless of signature)")
8181
else if (companion.flags is Flags.Trait)

src/dotty/tools/dotc/typer/Applications.scala

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -603,7 +603,17 @@ trait Applications extends Compatibility { self: Typer =>
603603
failedVal
604604
}
605605
}
606-
else realApply
606+
else {
607+
val app = realApply
608+
app match {
609+
case Apply(fn @ Select(left, _), right :: Nil) if fn.hasType =>
610+
val op = fn.symbol
611+
if (op == defn.Any_== || op == defn.Any_!=)
612+
checkCanEqual(left.tpe.widen, right.tpe.widen, app.pos)
613+
case _ =>
614+
}
615+
app
616+
}
607617
}
608618

609619
/** Overridden in ReTyper to handle primitive operations that can be generated after erasure */
@@ -915,13 +925,54 @@ trait Applications extends Compatibility { self: Typer =>
915925

916926
{
917927
implicit val ctx: Context = nestedCtx
918-
isCompatible(tp1, constrained(tp2).resultType)
928+
isAsSpecificValueType(tp1, constrained(tp2).resultType)
919929
}
920930
case _ => // (3b)
921-
isCompatible(tp1, tp2)
931+
isAsSpecificValueType(tp1, tp2)
922932
}
923933
}}
924934

935+
/** Test whether value type `tp1` is as specific as value type `tp2`.
936+
* Let's abbreviate this to `tp1 <:s tp2`.
937+
* Previously, `<:s` was the same as `<:`. This behavior is still
938+
* available under mode `Mode.OldOverloadingResolution`. The new behavior
939+
* is different, however. Here, `T <:s U` iff
940+
*
941+
* flip(T) <: flip(U)
942+
*
943+
* where `flip` changes top-level contravariant type aliases to covariant ones.
944+
* Intuitively `<:s` means subtyping `<:`, except that all top-level arguments
945+
* to contravariant parameters are compared as if they were covariant. E.g. given class
946+
*
947+
* class Cmp[-X]
948+
*
949+
* `Cmp[T] <:s Cmp[U]` if `T <: U`. On the other hand, nested occurrences
950+
* of parameters are not affected.
951+
* So `T <: U` would imply `List[Cmp[U]] <:s List[Cmp[T]]`, as usual.
952+
*
953+
* This relation might seem strange, but it models closely what happens for methods.
954+
* Indeed, if we integrate the existing rules for methods into `<:s` we have now that
955+
*
956+
* (T)R <:s (U)R
957+
*
958+
* iff
959+
*
960+
* T => R <:s U => R
961+
*/
962+
def isAsSpecificValueType(tp1: Type, tp2: Type)(implicit ctx: Context) =
963+
if (ctx.mode.is(Mode.OldOverloadingResolution))
964+
isCompatible(tp1, tp2)
965+
else {
966+
val flip = new TypeMap {
967+
def apply(t: Type) = t match {
968+
case t: TypeAlias if variance > 0 && t.variance < 0 => t.derivedTypeAlias(t.alias, 1)
969+
case t: TypeBounds => t
970+
case _ => mapOver(t)
971+
}
972+
}
973+
isCompatible(flip(tp1), flip(tp2))
974+
}
975+
925976
/** Drop any implicit parameter section */
926977
def stripImplicit(tp: Type): Type = tp match {
927978
case mt: ImplicitMethodType if !mt.isDependent =>

src/dotty/tools/dotc/typer/ErrorReporting.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import util.Positions._
1111
import reporting.Diagnostic
1212
import printing.Showable
1313
import printing.Disambiguation.disambiguated
14+
import java.util.regex.Matcher.quoteReplacement
1415

1516
object ErrorReporting {
1617

@@ -124,7 +125,7 @@ object ErrorReporting {
124125
def implicitNotFoundString(raw: String, paramNames: List[String], args: List[Type]): String = {
125126
def translate(name: String): Option[String] = {
126127
val idx = paramNames.indexOf(name)
127-
if (idx >= 0) Some(args(idx).show) else None
128+
if (idx >= 0) Some(quoteReplacement(args(idx).show)) else None
128129
}
129130
"""\$\{\w*\}""".r.replaceSomeIn(raw, m => translate(m.matched.drop(2).init))
130131
}

0 commit comments

Comments
 (0)