diff --git a/src/dotty/tools/backend/jvm/DottyBackendInterface.scala b/src/dotty/tools/backend/jvm/DottyBackendInterface.scala index a64ce5900fc8..9f897ab9ee14 100644 --- a/src/dotty/tools/backend/jvm/DottyBackendInterface.scala +++ b/src/dotty/tools/backend/jvm/DottyBackendInterface.scala @@ -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) diff --git a/src/dotty/tools/dotc/ast/Positioned.scala b/src/dotty/tools/dotc/ast/Positioned.scala index e7f5de5911c4..c7c0f1f0235d 100644 --- a/src/dotty/tools/dotc/ast/Positioned.scala +++ b/src/dotty/tools/dotc/ast/Positioned.scala @@ -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 = _ diff --git a/src/dotty/tools/dotc/ast/TreeInfo.scala b/src/dotty/tools/dotc/ast/TreeInfo.scala index c1efd0b0b020..8943a9fb8d47 100644 --- a/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -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 } @@ -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 diff --git a/src/dotty/tools/dotc/ast/tpd.scala b/src/dotty/tools/dotc/ast/tpd.scala index 5e5c842a8193..eff05403077e 100644 --- a/src/dotty/tools/dotc/ast/tpd.scala +++ b/src/dotty/tools/dotc/ast/tpd.scala @@ -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, diff --git a/src/dotty/tools/dotc/core/Annotations.scala b/src/dotty/tools/dotc/core/Annotations.scala index 5f96a60e649b..59cefb14f841 100644 --- a/src/dotty/tools/dotc/core/Annotations.scala +++ b/src/dotty/tools/dotc/core/Annotations.scala @@ -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 diff --git a/src/dotty/tools/dotc/core/Constants.scala b/src/dotty/tools/dotc/core/Constants.scala index e13e07f5881b..857bac58d43e 100644 --- a/src/dotty/tools/dotc/core/Constants.scala +++ b/src/dotty/tools/dotc/core/Constants.scala @@ -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 diff --git a/src/dotty/tools/dotc/core/Contexts.scala b/src/dotty/tools/dotc/core/Contexts.scala index bbe8e920c60b..13c1019d3ae7 100644 --- a/src/dotty/tools/dotc/core/Contexts.scala +++ b/src/dotty/tools/dotc/core/Contexts.scala @@ -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 */ diff --git a/src/dotty/tools/dotc/core/Definitions.scala b/src/dotty/tools/dotc/core/Definitions.scala index 9128bd3a5262..39266ec86e59 100644 --- a/src/dotty/tools/dotc/core/Definitions.scala +++ b/src/dotty/tools/dotc/core/Definitions.scala @@ -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 . - private def specialPolyClass(name: TypeName, paramFlags: FlagSet, parentConstrs: => Seq[Type]): ClassSymbol = { + /** A class following the template + * + * package scala + * class [ $T0] extends + * + * where = [constr' | constr <- parentConstrs] + * = constr[$T0] of 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 . val completer = new LazyType { def complete(denot: SymDenotation)(implicit ctx: Context): Unit = { val cls = denot.asClass.classSymbol @@ -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 = @@ -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) + * } + * + * 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, @@ -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) @@ -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") @@ -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") @@ -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) diff --git a/src/dotty/tools/dotc/core/Denotations.scala b/src/dotty/tools/dotc/core/Denotations.scala index 6e7eed3bcdc8..5cd0687b1635 100644 --- a/src/dotty/tools/dotc/core/Denotations.scala +++ b/src/dotty/tools/dotc/core/Denotations.scala @@ -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 diff --git a/src/dotty/tools/dotc/core/Flags.scala b/src/dotty/tools/dotc/core/Flags.scala index f866621f258d..87b652e739ff 100644 --- a/src/dotty/tools/dotc/core/Flags.scala +++ b/src/dotty/tools/dotc/core/Flags.scala @@ -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 */ @@ -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 } diff --git a/src/dotty/tools/dotc/core/Mode.scala b/src/dotty/tools/dotc/core/Mode.scala index 5b3dbc872c1c..2398f061dd4b 100644 --- a/src/dotty/tools/dotc/core/Mode.scala +++ b/src/dotty/tools/dotc/core/Mode.scala @@ -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) diff --git a/src/dotty/tools/dotc/core/Names.scala b/src/dotty/tools/dotc/core/Names.scala index 10eef16c1ae4..af7853abc69a 100644 --- a/src/dotty/tools/dotc/core/Names.scala +++ b/src/dotty/tools/dotc/core/Names.scala @@ -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] { diff --git a/src/dotty/tools/dotc/core/Periods.scala b/src/dotty/tools/dotc/core/Periods.scala index 6efadab7f271..4f3bae769a81 100644 --- a/src/dotty/tools/dotc/core/Periods.scala +++ b/src/dotty/tools/dotc/core/Periods.scala @@ -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) diff --git a/src/dotty/tools/dotc/core/Phases.scala b/src/dotty/tools/dotc/core/Phases.scala index 4b2861452d0c..d1040a6202a5 100644 --- a/src/dotty/tools/dotc/core/Phases.scala +++ b/src/dotty/tools/dotc/core/Phases.scala @@ -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 diff --git a/src/dotty/tools/dotc/core/Signature.scala b/src/dotty/tools/dotc/core/Signature.scala index 54771bae5101..67af25e9c4c2 100644 --- a/src/dotty/tools/dotc/core/Signature.scala +++ b/src/dotty/tools/dotc/core/Signature.scala @@ -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? */ diff --git a/src/dotty/tools/dotc/core/StdNames.scala b/src/dotty/tools/dotc/core/StdNames.scala index e2add1a520a4..d7d6c70d1e94 100644 --- a/src/dotty/tools/dotc/core/StdNames.scala +++ b/src/dotty/tools/dotc/core/StdNames.scala @@ -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" diff --git a/src/dotty/tools/dotc/core/SymDenotations.scala b/src/dotty/tools/dotc/core/SymDenotations.scala index 78acd8f1a23f..345bfc5e5746 100644 --- a/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/src/dotty/tools/dotc/core/SymDenotations.scala @@ -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 } diff --git a/src/dotty/tools/dotc/core/Symbols.scala b/src/dotty/tools/dotc/core/Symbols.scala index d40acdfa797b..198d52a932cd 100644 --- a/src/dotty/tools/dotc/core/Symbols.scala +++ b/src/dotty/tools/dotc/core/Symbols.scala @@ -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 diff --git a/src/dotty/tools/dotc/core/TyperState.scala b/src/dotty/tools/dotc/core/TyperState.scala index 36f0261070cd..93dee71c5811 100644 --- a/src/dotty/tools/dotc/core/TyperState.scala +++ b/src/dotty/tools/dotc/core/TyperState.scala @@ -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 diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index 913339409fd8..e3079344a38d 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -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 ----------------------------------------------------- diff --git a/src/dotty/tools/dotc/printing/PlainPrinter.scala b/src/dotty/tools/dotc/printing/PlainPrinter.scala index 3fb220afe701..1e2ba0b4d19b 100644 --- a/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -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 } diff --git a/src/dotty/tools/dotc/transform/CollectEntryPoints.scala b/src/dotty/tools/dotc/transform/CollectEntryPoints.scala index b85c44647c1e..714255962253 100644 --- a/src/dotty/tools/dotc/transform/CollectEntryPoints.scala +++ b/src/dotty/tools/dotc/transform/CollectEntryPoints.scala @@ -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) diff --git a/src/dotty/tools/dotc/transform/InterceptedMethods.scala b/src/dotty/tools/dotc/transform/InterceptedMethods.scala index ffb4ae756105..dfc4149f3971 100644 --- a/src/dotty/tools/dotc/transform/InterceptedMethods.scala +++ b/src/dotty/tools/dotc/transform/InterceptedMethods.scala @@ -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 } diff --git a/src/dotty/tools/dotc/transform/PostTyper.scala b/src/dotty/tools/dotc/transform/PostTyper.scala index fcde59b24a03..d50a08678806 100644 --- a/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/src/dotty/tools/dotc/transform/PostTyper.scala @@ -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 => diff --git a/src/dotty/tools/dotc/typer/Implicits.scala b/src/dotty/tools/dotc/typer/Implicits.scala index 5b336c2e984f..d71d1956f047 100644 --- a/src/dotty/tools/dotc/typer/Implicits.scala +++ b/src/dotty/tools/dotc/typer/Implicits.scala @@ -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) } } diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index f263d3093d2e..6ac885262afe 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -1469,14 +1469,22 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit } } - def adaptToArgs(wtp: Type, pt: FunProto): Tree = wtp match { - case _: MethodType | _: PolyType => - def isUnary = wtp.firstParamTypes match { + def isUnary(tp: Type): Boolean = tp match { + case tp: MethodicType => + tp.firstParamTypes match { case ptype :: Nil => !ptype.isRepeatedParam case _ => false } - if (pt.args.lengthCompare(1) > 0 && isUnary && ctx.canAutoTuple) - adaptToArgs(wtp, pt.tupled) + case tp: TermRef => + tp.denot.alternatives.forall(alt => isUnary(alt.info)) + case _ => + false + } + + def adaptToArgs(wtp: Type, pt: FunProto): Tree = wtp match { + case _: MethodType | _: PolyType => + if (pt.args.lengthCompare(1) > 0 && isUnary(wtp) && ctx.canAutoTuple) + adaptInterpolated(tree, pt.tupled, original) else tree case _ => tryInsertApplyOrImplicit(tree, pt) { @@ -1532,26 +1540,9 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit tree.withType(wtp.resultType) } val args = (wtp.paramNames, wtp.paramTypes).zipped map { (pname, formal) => - def where = d"parameter $pname of $methodStr" - inferImplicit(formal, EmptyTree, tree.pos.endPos) match { - case SearchSuccess(arg, _, _) => - arg - case ambi: AmbiguousImplicits => - implicitArgError(s"ambiguous implicits: ${ambi.explanation} of $where") - case failure: SearchFailure => - val arg = synthesizedClassTag(formal) - if (!arg.isEmpty) arg - else { - var msg = d"no implicit argument of type $formal found for $where" + failure.postscript - for (notFound <- formal.typeSymbol.getAnnotation(defn.ImplicitNotFoundAnnot); - Literal(Constant(raw: String)) <- notFound.argument(0)) - msg = err.implicitNotFoundString( - raw, - formal.typeSymbol.typeParams.map(_.name.unexpandedName.toString), - formal.argInfos) - implicitArgError(msg) - } - } + def implicitArgError(msg: String => String) = + errors += (() => msg(d"parameter $pname of $methodStr")) + inferImplicitArg(formal, implicitArgError) } if (errors.nonEmpty) { // If there are several arguments, some arguments might already @@ -1601,8 +1592,19 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit else missingArgs case _ => - if (tree.tpe <:< pt) tree - else if (ctx.mode is Mode.Pattern) tree // no subtype check for pattern + if (ctx.mode is Mode.Pattern) { + tree match { + case _: RefTree | _: Literal + if !ctx.isAfterTyper && !isVarPattern(tree) && pt.derivesFrom(defn.EqClassClass) => + def implicitArgError(msg: String => String) = + ctx.error(msg("pattern match"), tree.pos.endPos) + val commonEq = defn.EqType.appliedTo(pt | wtp) + inferImplicitArg(commonEq, implicitArgError)(ctx.retractMode(Mode.Pattern)) + case _ => + } + tree + } + else if (tree.tpe <:< pt) tree else if (wtp.isInstanceOf[MethodType]) missingArgs else { typr.println(i"adapt to subtype ${tree.tpe} !<:< $pt") @@ -1611,6 +1613,39 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit } } + /** Find an implicit argument for parameter `formal`. + * @param error An error handler that gets an error message parameter + * which is itself parameterized by another string, + * indicating where the implicit parameter is needed + */ + def inferImplicitArg(formal: Type, error: (String => String) => Unit)(implicit ctx: Context): Tree = + inferImplicit(formal, EmptyTree, tree.pos.endPos) match { + case SearchSuccess(arg, _, _) => + arg + case ambi: AmbiguousImplicits => + error(where => s"ambiguous implicits: ${ambi.explanation} of $where") + EmptyTree + case failure: SearchFailure => + val arg = synthesizedClassTag(formal) + if (!arg.isEmpty) arg + else { + var msgFn = (where: String) => + d"no implicit argument of type $formal found for $where" + failure.postscript + for { + notFound <- formal.typeSymbol.getAnnotation(defn.ImplicitNotFoundAnnot) + Literal(Constant(raw: String)) <- notFound.argument(0) + } { + msgFn = where => + err.implicitNotFoundString( + raw, + formal.typeSymbol.typeParams.map(_.name.unexpandedName.toString), + formal.argInfos) + } + error(msgFn) + EmptyTree + } + } + /** If `formal` is of the form ClassTag[T], where `T` is a class type, * synthesize a class tag for `T`. */ @@ -1684,7 +1719,13 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit case ErrorType => tree case ref: TermRef => - adaptOverloaded(ref) + pt match { + case pt: FunProto + if pt.args.lengthCompare(1) > 0 && isUnary(ref) && ctx.canAutoTuple => + adaptInterpolated(tree, pt.tupled, original) + case _ => + adaptOverloaded(ref) + } case poly: PolyType => if (pt.isInstanceOf[PolyProto]) tree else { diff --git a/src/dotty/tools/dotc/util/Positions.scala b/src/dotty/tools/dotc/util/Positions.scala index c3890cc9a463..4ee2dac01a57 100644 --- a/src/dotty/tools/dotc/util/Positions.scala +++ b/src/dotty/tools/dotc/util/Positions.scala @@ -30,7 +30,7 @@ object Positions { * is roughly where the ^ would go if an error was diagnosed at that position. * All quantities are encoded opaquely in a Long. */ - class Position(val coords: Long) extends AnyVal { + class Position(val coords: Long) extends AnyVal with EqClass[Position] { /** Is this position different from NoPosition? */ def exists = this != NoPosition diff --git a/src/dotty/tools/dotc/util/SourceFile.scala b/src/dotty/tools/dotc/util/SourceFile.scala index 6b547203e1f9..97dd6bbb174d 100644 --- a/src/dotty/tools/dotc/util/SourceFile.scala +++ b/src/dotty/tools/dotc/util/SourceFile.scala @@ -34,7 +34,7 @@ object ScriptSourceFile { } } -case class SourceFile(file: AbstractFile, content: Array[Char]) extends interfaces.SourceFile { +case class SourceFile(file: AbstractFile, content: Array[Char]) extends interfaces.SourceFile with EqClass[SourceFile] { def this(_file: AbstractFile) = this(_file, _file.toCharArray) def this(sourceName: String, cs: Seq[Char]) = this(new VirtualFile(sourceName), cs.toArray) diff --git a/src/dotty/tools/dotc/util/SourcePosition.scala b/src/dotty/tools/dotc/util/SourcePosition.scala index 0b2b2aa0b306..1212490ec485 100644 --- a/src/dotty/tools/dotc/util/SourcePosition.scala +++ b/src/dotty/tools/dotc/util/SourcePosition.scala @@ -5,7 +5,7 @@ package util import Positions.{Position, NoPosition} /** A source position is comprised of a position in a source file */ -case class SourcePosition(source: SourceFile, pos: Position) extends interfaces.SourcePosition { +case class SourcePosition(source: SourceFile, pos: Position) extends interfaces.SourcePosition with EqClass[SourcePosition] { def exists = pos.exists def lineContent: String = source.lineContent(point) diff --git a/src/scala/Eq.scala b/src/scala/Eq.scala new file mode 100644 index 000000000000..b13e0eb3f61d --- /dev/null +++ b/src/scala/Eq.scala @@ -0,0 +1,17 @@ +package scala + +/** A marker class indicating that values of kind `T` can be compared. */ +class Eq[-T] + +/** Besides being a companion object, this object + * can also be used as a value that's compatible with + * any instance of `Eq`. + */ +object Eq extends Eq[Any] { + + /** An implicit that provides an `Eq` instance for all types `T` that have + * a base type `U` such that `U <: EqClass[U]`. + */ + implicit def eqEq[U, T <: EqClass[U] with U]: Eq[T] = Eq +} + diff --git a/src/scala/EqClass.scala b/src/scala/EqClass.scala new file mode 100644 index 000000000000..dc5c31f20286 --- /dev/null +++ b/src/scala/EqClass.scala @@ -0,0 +1,37 @@ +package scala + +/** A class providing a specialized notion of equality, allowing + * only values in the same equality class to be compared. + * To be used in one of two ways: + * + * 1st way: When defining a class or trait `C`, simply write: + * + * class C extends ... EqClass[C] + * + * This makes `C` the root of a separate equality class. Any subtype + * of `C` can then be compared with any other subtype of `C`, but it cannot + * be compared with types that are not subtypes of `C`. + * + * 2nd way: Define `C` like this + * + * class C extends ... EqClass[_] + * + * or, equivalently: + * + * class C extends ... EqClass[Nothing] + * + * This creates a new equality class for `C`, but makes subtypes of `C` not automatically + * comparable with other subtypes of `C`. Instead, the rules of what is comparable to what can + * be encoded by giving `Eq` implicits in the companion object of `C`. Here is an example + * that makes `Optional` values only be comparable if their element types are comparable: + * + * trait Optional[+T] extends EqClass[_] + * case class Some[+T](x: T) extends Optional[T] + * case object None extends Optional[T] + * + * object Optional { + * def optEq[T](implicit ee: Eq[T]): Eq[Optional[T]] = Eq + * } + */ +trait EqClass[-T] extends Any + diff --git a/tests/neg/EqualityStrawman1.scala b/tests/neg/EqualityStrawman1.scala index b1b6c0380a50..3f9896aa0729 100644 --- a/tests/neg/EqualityStrawman1.scala +++ b/tests/neg/EqualityStrawman1.scala @@ -12,23 +12,23 @@ object EqualityStrawman1 { trait Base { def === (other: Any): Boolean = this.equals(other) - def === [T <: CondEquals](other: T)(implicit ce: Impossible[T]): Boolean = ??? + def === (other: Null): Boolean = this.equals(other) + def === [T <: EqClass](other: T)(implicit ce: Impossible[T]): Boolean = ??? } - trait CondEquals extends Base { - def === [T >: this.type <: CondEquals](other: T)(implicit ce: Eq[T]): Boolean = this.equals(other) + trait EqClass[-U] extends Base { + def === [T >: this.type <: EqClass](other: T)(implicit ce: Eq[T]): Boolean = this.equals(other) def === [T](other: T)(implicit ce: Impossible[T]): Boolean = ??? + def === (other: Null): Boolean = this.equals(other) } - trait Equals[-T] extends CondEquals + case class Str(str: String) extends EqClass[_] - case class Str(str: String) extends CondEquals - - case class Num(x: Int) extends Equals[Num] + case class Num(x: Int) extends EqClass[Num] case class Other(x: Int) extends Base - trait Option[+T] extends CondEquals + trait Option[+T] extends EqClass[_] case class Some[+T](x: T) extends Option[T] case object None extends Option[Nothing] @@ -36,7 +36,7 @@ object EqualityStrawman1 { //implicit def eqNum: Eq[Num] = Eq implicit def eqOption[T: Eq]: Eq[Option[T]] = Eq - implicit def eqEq[T <: Equals[T]]: Eq[T] = Eq + implicit def eqEq[T <: EqClass[T]]: Eq[T] = Eq def main(args: Array[String]): Unit = { val x = Str("abc") @@ -56,6 +56,8 @@ object EqualityStrawman1 { Some(x) === z None === z + Other(3) === null + Str("x") === null def ddistinct[T <: Base: Eq](xs: List[T]): List[T] = xs match { case Nil => Nil diff --git a/tests/neg/equality-posttyper.scala b/tests/neg/equality-posttyper.scala new file mode 100644 index 000000000000..036935087656 --- /dev/null +++ b/tests/neg/equality-posttyper.scala @@ -0,0 +1,77 @@ + +object equality { + + case class Str(str: String) extends EqClass + + case class Num(x: Int) extends EqClass[Num] + + case class Other(x: Int) + + trait Option[+T] extends EqClass + case class Some[+T](x: T) extends Option[T] + case object None extends Option[Nothing] + + implicit def eqStr: Eq[Str] = Eq + implicit def eqOption[T: Eq]: Eq[Option[T]] = Eq + + class PreName extends EqClass + implicit def eqPreName: Eq[PreName] = Eq + + implicit def fromString(str: String): PreName = ??? + val name = "abc" + name == "def" + + + + def main(args: Array[String]): Unit = { + val x = Str("abc") + x == x + + val n = Num(2) + val m = Num(3) + n == m + + Other(1) == Other(2) + + Some(x) == None + Some(x) == Some(Str("")) + val z: Option[Str] = Some(Str("abc")) + z == Some(x) + z == None + Some(x) == z + None == z + + Other(3) == null + Str("x") == null + null == Other(3) + null == Str("x") + null == null + + class Fruit extends EqClass + + implicit def eqFruit: Eq[Fruit] = Eq + class Apple extends Fruit + class Pear extends Fruit + val a = new Apple + val p = new Pear + val f: Fruit = a + a == p + p == a + f == p + p == f + Some(new Apple) == Some(new Pear) + + def ddistinct[T: Eq](xs: List[T]): List[T] = xs match { + case Nil => Nil + case x :: xs => x :: xs.filterNot(x == _) + } + + ddistinct(List(z, z, z)) + + x == Other(1) // error + Other(2) == x // error + Other(1) == z // error + z == Other(1) // error + + } +} diff --git a/tests/neg/equality.scala b/tests/neg/equality.scala new file mode 100644 index 000000000000..c4acd0ff1d07 --- /dev/null +++ b/tests/neg/equality.scala @@ -0,0 +1,84 @@ + +object equality { + + case class Str(str: String) extends EqClass + + case class Num(x: Int) extends EqClass[Num] + + case class Other(x: Int) + + trait Option[+T] extends EqClass + case class Some[+T](x: T) extends Option[T] + case object None extends Option[Nothing] + + implicit def eqStr: Eq[Str] = Eq + implicit def eqOption[T: Eq]: Eq[Option[T]] = Eq + + class PreName extends EqClass + implicit def eqPreName: Eq[PreName] = Eq + + implicit def fromString(str: String): PreName = ??? + val name = "abc" + name == "def" + + + + def main(args: Array[String]): Unit = { + val x = Str("abc") + x == x + + val n = Num(2) + val m = Num(3) + n == m + + Other(1) == Other(2) + + Some(x) == None + Some(x) == Some(Str("")) + val z: Option[Str] = Some(Str("abc")) + z == Some(x) + z == None + Some(x) == z + None == z + + Other(3) == null + Str("x") == null + null == Other(3) + null == Str("x") + null == null + + class Fruit extends EqClass + + implicit def eqFruit: Eq[Fruit] = Eq + class Apple extends Fruit + class Pear extends Fruit + val a = new Apple + val p = new Pear + val f: Fruit = a + a == p + p == a + f == p + p == f + Some(new Apple) == Some(new Pear) + + def ddistinct[T: Eq](xs: List[T]): List[T] = xs match { + case Nil => Nil + case x :: xs => x :: xs.filterNot(x == _) + } + + ddistinct(List(z, z, z)) + + n match { + case None => // error + } + + Some(new Apple) == Some(Str("xx")) // error + x == n // error + n == x // error + z == Some(n) // error + z == n // error + Some(n) == z // error + n == z // error + ddistinct(List(z, n)) // error + } +}