diff --git a/src/dotty/DottyPredef.scala b/src/dotty/DottyPredef.scala index 9170da476f97..cd90c4882b77 100644 --- a/src/dotty/DottyPredef.scala +++ b/src/dotty/DottyPredef.scala @@ -3,6 +3,7 @@ package dotty import scala.reflect.runtime.universe.TypeTag import scala.reflect.ClassTag import scala.Predef.??? +import scala.collection.Seq /** unimplemented implicit for TypeTag */ object DottyPredef { @@ -10,4 +11,35 @@ object DottyPredef { implicit def arrayTag[T](implicit ctag: ClassTag[T]): ClassTag[Array[T]] = ctag.wrap + + /** A fall-back implicit to compare values of any types. + * The compiler will restrict implicit instances of `eqAny`. An instance + * `eqAny[T, U]` is _valid_ if `T <: U` or `U <: T` or both `T` and `U` are + * Eq-free. A type `S` is Eq-free if there is no implicit instance of `Eq[S, S]`. + * An implicit search will fail instead of returning an invalid `eqAny` instance. + */ + implicit def eqAny[L, R]: Eq[L, R] = Eq + + implicit def eqNumber : Eq[Number, Number] = Eq + implicit def eqString : Eq[String, String] = Eq + + // true asymmetry, modeling the (somewhat problematic) nature of equals on Proxies + implicit def eqProxy : Eq[Proxy, Any] = Eq + + implicit def eqSeq[T, U](implicit eq: Eq[T, U]): Eq[Seq[T], Seq[U]] = Eq + + implicit def eqByteNum : Eq[Byte, Number] = Eq + implicit def eqNumByte : Eq[Number, Byte] = Eq + implicit def eqCharNum : Eq[Char, Number] = Eq + implicit def eqNumChar : Eq[Number, Char] = Eq + implicit def eqShortNum : Eq[Short, Number] = Eq + implicit def eqNumShort : Eq[Number, Short] = Eq + implicit def eqIntNum : Eq[Int, Number] = Eq + implicit def eqNumInt : Eq[Number, Int] = Eq + implicit def eqLongNum : Eq[Long, Number] = Eq + implicit def eqNumLong : Eq[Number, Long] = Eq + implicit def eqFloatNum : Eq[Float, Number] = Eq + implicit def eqNumFloat : Eq[Number, Float] = Eq + implicit def eqDoubleNum: Eq[Double, Number] = Eq + implicit def eqNumDouble: Eq[Number, Double] = Eq } diff --git a/src/dotty/tools/backend/jvm/DottyBackendInterface.scala b/src/dotty/tools/backend/jvm/DottyBackendInterface.scala index a64ce5900fc8..002a6bf27189 100644 --- a/src/dotty/tools/backend/jvm/DottyBackendInterface.scala +++ b/src/dotty/tools/backend/jvm/DottyBackendInterface.scala @@ -861,11 +861,11 @@ 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 ThisType(sym) => storage.getClassBTypeAndRegisterInnerClass(sym.asInstanceOf[ct.int.Symbol]) - // case t: SingletonType => primitiveOrClassToBType(t.classSymbol) - case t: SingletonType => t.underlying.toTypeKind(ct)(storage) - case t: RefinedType => t.parent.toTypeKind(ct)(storage) //parents.map(_.toTypeKind(ct)(storage).asClassBType).reduceLeft((a, b) => a.jvmWiseLUB(b)) + 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 tp: ThisType => storage.getClassBTypeAndRegisterInnerClass(tp.cls.asInstanceOf[ct.int.Symbol]) + // case t: SingletonType => primitiveOrClassToBType(t.classSymbol) + case t: SingletonType => t.underlying.toTypeKind(ct)(storage) + case t: RefinedType => t.parent.toTypeKind(ct)(storage) //parents.map(_.toTypeKind(ct)(storage).asClassBType).reduceLeft((a, b) => a.jvmWiseLUB(b)) } } } 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/Definitions.scala b/src/dotty/tools/dotc/core/Definitions.scala index 7f59cbed0259..bcc986755a97 100644 --- a/src/dotty/tools/dotc/core/Definitions.scala +++ b/src/dotty/tools/dotc/core/Definitions.scala @@ -244,6 +244,9 @@ class Definitions { lazy val DottyPredefModuleRef = ctx.requiredModuleRef("dotty.DottyPredef") def DottyPredefModule(implicit ctx: Context) = DottyPredefModuleRef.symbol + + def Predef_eqAny(implicit ctx: Context) = DottyPredefModule.requiredMethod(nme.eqAny) + lazy val DottyArraysModuleRef = ctx.requiredModuleRef("dotty.runtime.Arrays") def DottyArraysModule(implicit ctx: Context) = DottyArraysModuleRef.symbol def newGenericArrayMethod(implicit ctx: Context) = DottyArraysModule.requiredMethod("newGenericArray") @@ -424,10 +427,14 @@ class Definitions { lazy val LanguageModuleRef = ctx.requiredModule("dotty.language") def LanguageModuleClass(implicit ctx: Context) = LanguageModuleRef.symbol.moduleClass.asClass lazy val NonLocalReturnControlType: TypeRef = ctx.requiredClassRef("scala.runtime.NonLocalReturnControl") + lazy val ClassTagType = ctx.requiredClassRef("scala.reflect.ClassTag") def ClassTagClass(implicit ctx: Context) = ClassTagType.symbol.asClass def ClassTagModule(implicit ctx: Context) = ClassTagClass.companionModule + lazy val EqType = ctx.requiredClassRef("scala.Eq") + def EqClass(implicit ctx: Context) = EqType.symbol.asClass + // Annotation base classes lazy val AnnotationType = ctx.requiredClassRef("scala.annotation.Annotation") def AnnotationClass(implicit ctx: Context) = AnnotationType.symbol.asClass diff --git a/src/dotty/tools/dotc/core/Denotations.scala b/src/dotty/tools/dotc/core/Denotations.scala index 080e8a39ba58..69624b01ab03 100644 --- a/src/dotty/tools/dotc/core/Denotations.scala +++ b/src/dotty/tools/dotc/core/Denotations.scala @@ -70,6 +70,8 @@ import Decorators.SymbolIteratorDecorator */ object Denotations { + implicit def eqDenotation: Eq[Denotation, Denotation] = Eq + /** A denotation is the result of resolving * a name (either simple identifier or select) during a given period. * diff --git a/src/dotty/tools/dotc/core/Mode.scala b/src/dotty/tools/dotc/core/Mode.scala index 5b3dbc872c1c..0e188ace2b65 100644 --- a/src/dotty/tools/dotc/core/Mode.scala +++ b/src/dotty/tools/dotc/core/Mode.scala @@ -81,5 +81,8 @@ object Mode { /** We are currently unpickling Scala2 info */ val Scala2Unpickling = newMode(13, "Scala2Unpickling") + /** Use Scala2 scheme for overloading and implicit resolution */ + val OldOverloadingResolution = newMode(14, "OldOverloadingResolution") + val PatternOrType = Pattern | Type } diff --git a/src/dotty/tools/dotc/core/Names.scala b/src/dotty/tools/dotc/core/Names.scala index 10eef16c1ae4..223d9504505a 100644 --- a/src/dotty/tools/dotc/core/Names.scala +++ b/src/dotty/tools/dotc/core/Names.scala @@ -26,6 +26,8 @@ object Names { def toTermName: TermName } + implicit def eqName: Eq[Name, Name] = Eq + /** A name is essentially a string, with three differences * 1. Names belong in one of two name spaces: they are type names or term names. * Term names have a sub-category of "local" field names. diff --git a/src/dotty/tools/dotc/core/StdNames.scala b/src/dotty/tools/dotc/core/StdNames.scala index e2add1a520a4..81f6da0e2c57 100644 --- a/src/dotty/tools/dotc/core/StdNames.scala +++ b/src/dotty/tools/dotc/core/StdNames.scala @@ -394,6 +394,7 @@ object StdNames { val equals_ : N = "equals" val error: N = "error" val eval: N = "eval" + val eqAny: N = "eqAny" val ex: N = "ex" val experimental: N = "experimental" val f: N = "f" diff --git a/src/dotty/tools/dotc/core/Symbols.scala b/src/dotty/tools/dotc/core/Symbols.scala index d40acdfa797b..473f107cb3b1 100644 --- a/src/dotty/tools/dotc/core/Symbols.scala +++ b/src/dotty/tools/dotc/core/Symbols.scala @@ -361,6 +361,8 @@ trait Symbols { this: Context => object Symbols { + implicit def eqSymbol: Eq[Symbol, Symbol] = Eq + /** A Symbol represents a Scala definition/declaration or a package. * @param coord The coordinates of the symbol (a position or an index) * @param id A unique identifier of the symbol (unique per ContextBase) diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index 114e6c9082c4..71ea6d2b3e2c 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -38,6 +38,8 @@ object Types { @sharable private var nextId = 0 + implicit def eqType: Eq[Type, Type] = Eq + /** The class of types. * The principal subclasses and sub-objects are as follows: * 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/typer/Applications.scala b/src/dotty/tools/dotc/typer/Applications.scala index 390ecaee97e3..2b0cc4033509 100644 --- a/src/dotty/tools/dotc/typer/Applications.scala +++ b/src/dotty/tools/dotc/typer/Applications.scala @@ -603,7 +603,17 @@ trait Applications extends Compatibility { self: Typer => failedVal } } - else realApply + else { + val app = realApply + app match { + case Apply(fn @ Select(left, _), right :: Nil) if fn.hasType => + val op = fn.symbol + if (op == defn.Any_== || op == defn.Any_!=) + checkCanEqual(left.tpe.widen, right.tpe.widen, app.pos) + case _ => + } + app + } } /** Overridden in ReTyper to handle primitive operations that can be generated after erasure */ @@ -915,13 +925,54 @@ trait Applications extends Compatibility { self: Typer => { implicit val ctx: Context = nestedCtx - isCompatible(tp1, constrained(tp2).resultType) + isAsSpecificValueType(tp1, constrained(tp2).resultType) } case _ => // (3b) - isCompatible(tp1, tp2) + isAsSpecificValueType(tp1, tp2) } }} + /** Test whether value type `tp1` is as specific as value type `tp2`. + * Let's abbreviate this to `tp1 <:s tp2`. + * Previously, `<:s` was the same as `<:`. This behavior is still + * available under mode `Mode.OldOverloadingResolution`. The new behavior + * is different, however. Here, `T <:s U` iff + * + * flip(T) <: flip(U) + * + * where `flip` changes top-level contravariant type aliases to covariant ones. + * Intuitively `<:s` means subtyping `<:`, except that all top-level arguments + * to contravariant parameters are compared as if they were covariant. E.g. given class + * + * class Cmp[-X] + * + * `Cmp[T] <:s Cmp[U]` if `T <: U`. On the other hand, nested occurrences + * of parameters are not affected. + * So `T <: U` would imply `List[Cmp[U]] <:s List[Cmp[T]]`, as usual. + * + * This relation might seem strange, but it models closely what happens for methods. + * Indeed, if we integrate the existing rules for methods into `<:s` we have now that + * + * (T)R <:s (U)R + * + * iff + * + * T => R <:s U => R + */ + def isAsSpecificValueType(tp1: Type, tp2: Type)(implicit ctx: Context) = + if (ctx.mode.is(Mode.OldOverloadingResolution)) + isCompatible(tp1, tp2) + else { + val flip = new TypeMap { + def apply(t: Type) = t match { + case t: TypeAlias if variance > 0 && t.variance < 0 => t.derivedTypeAlias(t.alias, 1) + case t: TypeBounds => t + case _ => mapOver(t) + } + } + isCompatible(flip(tp1), flip(tp2)) + } + /** Drop any implicit parameter section */ def stripImplicit(tp: Type): Type = tp match { case mt: ImplicitMethodType if !mt.isDependent => diff --git a/src/dotty/tools/dotc/typer/ErrorReporting.scala b/src/dotty/tools/dotc/typer/ErrorReporting.scala index 69c12e5f5c9b..d3303628ef69 100644 --- a/src/dotty/tools/dotc/typer/ErrorReporting.scala +++ b/src/dotty/tools/dotc/typer/ErrorReporting.scala @@ -11,6 +11,7 @@ import util.Positions._ import reporting.Diagnostic import printing.Showable import printing.Disambiguation.disambiguated +import java.util.regex.Matcher.quoteReplacement object ErrorReporting { @@ -124,7 +125,7 @@ object ErrorReporting { def implicitNotFoundString(raw: String, paramNames: List[String], args: List[Type]): String = { def translate(name: String): Option[String] = { val idx = paramNames.indexOf(name) - if (idx >= 0) Some(args(idx).show) else None + if (idx >= 0) Some(quoteReplacement(args(idx).show)) else None } """\$\{\w*\}""".r.replaceSomeIn(raw, m => translate(m.matched.drop(2).init)) } diff --git a/src/dotty/tools/dotc/typer/Implicits.scala b/src/dotty/tools/dotc/typer/Implicits.scala index 5b336c2e984f..38ac709bee5f 100644 --- a/src/dotty/tools/dotc/typer/Implicits.scala +++ b/src/dotty/tools/dotc/typer/Implicits.scala @@ -23,6 +23,8 @@ import Constants._ import Applications._ import ProtoTypes._ import ErrorReporting._ +import Inferencing.fullyDefinedType +import Trees._ import Hashable._ import config.Config import config.Printers._ @@ -191,7 +193,7 @@ object Implicits { /** A successful search * @param ref The implicit reference that succeeded - * @param tree The typed tree that can needs to be inserted + * @param tree The typed tree that needs to be inserted * @param ctx The context after the implicit search */ case class SearchSuccess(tree: tpd.Tree, ref: TermRef, tstate: TyperState) extends SearchResult { @@ -361,7 +363,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) } } @@ -415,6 +417,84 @@ trait Implicits { self: Typer => } } + /** 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, pos: Position)(implicit ctx: Context): Tree = + inferImplicit(formal, EmptyTree, pos) 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, pos) + 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) + Trees.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`. + */ + def synthesizedClassTag(formal: Type, pos: Position)(implicit ctx: Context): Tree = { + if (formal.isRef(defn.ClassTagClass)) + formal.argTypes match { + case arg :: Nil => + val tp = fullyDefinedType(arg, "ClassTag argument", pos) + tp.underlyingClassRef(refinementOK = false) match { + case tref: TypeRef => + return ref(defn.ClassTagModule) + .select(nme.apply) + .appliedToType(tp) + .appliedTo(clsOf(tref)) + .withPos(pos) + case _ => + } + case _ => + } + EmptyTree + } + + private def assumedCanEqual(ltp: Type, rtp: Type)(implicit ctx: Context) = { + val lift = new TypeMap { + def apply(t: Type) = t match { + case t: TypeRef => + t.info match { + case TypeBounds(lo, hi) if lo ne hi => hi + case _ => t + } + case _ => + if (variance > 0) mapOver(t) else t + } + } + ltp.isError || rtp.isError || ltp <:< lift(rtp) || rtp <:< lift(ltp) + } + + /** Check that equality tests between types `ltp` and `rtp` make sense */ + def checkCanEqual(ltp: Type, rtp: Type, pos: Position)(implicit ctx: Context): Unit = + if (!ctx.isAfterTyper && !assumedCanEqual(ltp, rtp)) { + val res = inferImplicitArg( + defn.EqType.appliedTo(ltp, rtp), msgFun => ctx.error(msgFun(""), pos), pos) + implicits.println(i"Eq witness found: $res: ${res.tpe}") + } + /** Find an implicit parameter or conversion. * @param pt The expected type of the parameter or conversion. * @param argument If an implicit conversion is searched, the argument to which @@ -439,7 +519,19 @@ trait Implicits { self: Typer => result case result: AmbiguousImplicits => val deepPt = pt.deepenProto - if (deepPt ne pt) inferImplicit(deepPt, argument, pos) else result + if (deepPt ne pt) inferImplicit(deepPt, argument, pos) + else if (ctx.scala2Mode && !ctx.mode.is(Mode.OldOverloadingResolution)) { + inferImplicit(pt, argument, pos)(ctx.addMode(Mode.OldOverloadingResolution)) match { + case altResult: SearchSuccess => + ctx.migrationWarning( + s"According to new implicit resolution rules, this will be ambiguous:\n ${result.explanation}", + pos) + altResult + case _ => + result + } + } + else result case _ => assert(prevConstr eq ctx.typerState.constraint) result @@ -500,6 +592,18 @@ trait Implicits { self: Typer => case _ => false } } + // Does there exist an implicit value of type `Eq[tp, tp]`? + def hasEq(tp: Type): Boolean = + new ImplicitSearch(defn.EqType.appliedTo(tp, tp), EmptyTree, pos).bestImplicit match { + case result: SearchSuccess => result.ref.symbol != defn.Predef_eqAny + case result: AmbiguousImplicits => true + case _ => false + } + def validEqAnyArgs(tp1: Type, tp2: Type) = { + List(tp1, tp2).foreach(fullyDefinedType(_, "eqAny argument", pos)) + assumedCanEqual(tp1, tp2) || !hasEq(tp1) && !hasEq(tp2) || + { implicits.println(i"invalid eqAny[$tp1, $tp2]"); false } + } if (ctx.reporter.hasErrors) nonMatchingImplicit(ref) else if (contextual && !ctx.mode.is(Mode.ImplicitShadowing) && @@ -507,8 +611,13 @@ trait Implicits { self: Typer => implicits.println(i"SHADOWING $ref in ${ref.termSymbol.owner} is shadowed by $shadowing in ${shadowing.symbol.owner}") shadowedImplicit(ref, methPart(shadowing).tpe) } - else - SearchSuccess(generated1, ref, ctx.typerState) + else generated1 match { + case TypeApply(fn, targs @ (arg1 :: arg2 :: Nil)) + if fn.symbol == defn.Predef_eqAny && !validEqAnyArgs(arg1.tpe, arg2.tpe) => + nonMatchingImplicit(ref) + case _ => + SearchSuccess(generated1, ref, ctx.typerState) + } }} /** Given a list of implicit references, produce a list of all implicit search successes, diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index f263d3093d2e..23e7cdb7b3f1 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, tree.pos.endPos) } if (errors.nonEmpty) { // If there are several arguments, some arguments might already @@ -1601,8 +1592,15 @@ 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 !isVarPattern(tree) => + checkCanEqual(pt, wtp, tree.pos)(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") @@ -1610,29 +1608,6 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit adaptToSubType(wtp) } } - - /** If `formal` is of the form ClassTag[T], where `T` is a class type, - * synthesize a class tag for `T`. - */ - def synthesizedClassTag(formal: Type): Tree = { - if (formal.isRef(defn.ClassTagClass)) - formal.argTypes match { - case arg :: Nil => - val tp = fullyDefinedType(arg, "ClassTag argument", tree.pos) - tp.underlyingClassRef(refinementOK = false) match { - case tref: TypeRef => - return ref(defn.ClassTagModule) - .select(nme.apply) - .appliedToType(tp) - .appliedTo(clsOf(tref)) - .withPos(tree.pos.endPos) - case _ => - } - case _ => - } - EmptyTree - } - /** Adapt an expression of constant type to a different constant type `tpe`. */ def adaptConstant(tree: Tree, tpe: ConstantType): Tree = { def lit = Literal(tpe.value).withPos(tree.pos) @@ -1684,7 +1659,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/scala/Eq.scala b/src/scala/Eq.scala new file mode 100644 index 000000000000..d6d617cab7c6 --- /dev/null +++ b/src/scala/Eq.scala @@ -0,0 +1,14 @@ +package scala + +import annotation.implicitNotFound + +/** A marker trait indicating that values of type `L` can be compared to values of type `R`. */ +@implicitNotFound("Values of types ${L} and ${R} cannot be compared with == or !=") +sealed trait Eq[-L, -R] + +/** 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, Any] + diff --git a/test/dotc/tests.scala b/test/dotc/tests.scala index 3528dfa72edf..4ada2d982dc3 100644 --- a/test/dotc/tests.scala +++ b/test/dotc/tests.scala @@ -119,8 +119,8 @@ class tests extends CompilerTest { @Test def neg_typedIdents() = compileDir(negDir, "typedIdents") val negCustomArgs = negDir + "customArgs/" - @Test def neg_typers = compileFile(negCustomArgs, "typers")(allowDoubleBindings) - @Test def neg_overrideClass = compileFile(negCustomArgs, "overrideClass", List("-language:Scala2")) + @Test def neg_typers() = compileFile(negCustomArgs, "typers")(allowDoubleBindings) + @Test def neg_overrideClass = compileFile(negCustomArgs, "overrideClass", scala2mode) @Test def neg_autoTupling = compileFile(negCustomArgs, "autoTuplingTest", args = "-language:noAutoTupling" :: Nil) @Test def neg_i1050 = compileFile(negCustomArgs, "i1050", List("-strict")) @Test def neg_i1240 = compileFile(negCustomArgs, "i1240")(allowDoubleBindings) diff --git a/test/test/CompilerTest.scala b/test/test/CompilerTest.scala index 1ca836133b54..995244986786 100644 --- a/test/test/CompilerTest.scala +++ b/test/test/CompilerTest.scala @@ -238,9 +238,13 @@ abstract class CompilerTest { val nerrors = reporter.errorCount val xerrors = (expectedErrorsPerFile map {_.totalErrors}).sum + def expectedErrorFiles = + expectedErrorsPerFile.collect{ + case er if er.totalErrors > 0 => er.fileName + } assert(nerrors == xerrors, s"""Wrong # of errors. Expected: $xerrors, found: $nerrors - |Files with expected errors: ${expectedErrorsPerFile.collect{ case er if er.totalErrors > 0 => er.fileName} } + |Files with expected errors: $expectedErrorFiles%, % """.stripMargin) // NEG TEST if (xerrors > 0) { diff --git a/tests/neg/equality.scala b/tests/neg/equality.scala new file mode 100644 index 000000000000..680a12725dd0 --- /dev/null +++ b/tests/neg/equality.scala @@ -0,0 +1,109 @@ +object equality { + + case class Str(str: String) + + case class Num(x: Int) + + case class Other(x: Int) + + trait Option[+T] + case class Some[+T](x: T) extends Option[T] + case object None extends Option[Nothing] + + implicit def eqStr: Eq[Str, Str] = Eq + implicit def eqNum: Eq[Num, Num] = Eq + implicit def eqOption[T, U](implicit e: Eq[T, U]): Eq[Option[T], Option[U]] = Eq + + case class PString(a: String) extends Proxy { + def self = a + } + +/* + implicit def eqString: Eq[String, String] = Eq + implicit def eqInt: Eq[Int, Int] = Eq + implicit def eqNumber: Eq[Number, Number] = Eq + implicit def eqIntNumber: Eq[Int, Number] = Eq + implicit def eqNumberInt: Eq[Number, Int] = Eq +*/ + def main(args: Array[String]): Unit = { + Some(Other(3)) == None + + val x = Str("abc") + x == x + + val n = Num(2) + val m = Num(3) + n == m + + val so: Object = "abc" + so == "abc" + "abc" == so + + 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 + None == Some(Other(3)) + + Other(3) == null + Str("x") == null + null == Other(3) + null == Str("x") + null == null + + class Fruit + + implicit def eqFruit: Eq[Fruit, 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) + + val i = 3 + val bi = BigInt(i) + i == i + bi == bi + i == bi + bi == i + + val ps = PString("hello") + ps == "world" + + n match { + case None => // error + } + + x == Other(1) // error + Other(2) == x // error + Other(1) == z // error + z == Other(1) // error + n == 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 + Some(n) == Some(Other(3)) // error + Some(Other(3)) == Some(n) // error + n == z // error + "abc" == 1 // error + 1 == "abc" // error + "abc" == bi // error + bi == "abc" // error + "world" == ps // error + } +} diff --git a/tests/pending/neg/EqualityStrawman2.scala b/tests/pending/neg/EqualityStrawman2.scala new file mode 100644 index 000000000000..e72e675c29ab --- /dev/null +++ b/tests/pending/neg/EqualityStrawman2.scala @@ -0,0 +1,126 @@ +object equality { + + trait Eq[T, U] + def Eq[T, U]: Eq[T, U] = new Eq[T, U]{} + + implicit class EqualsDeco[T](val x: T) extends AnyVal { + def ===[U] (y: U)(implicit ce: Eq[T, U]) = x.equals(y) + } + + type EqEq[T] = Eq[T, T] + + trait EqClass[T] + + implicit def eqAny[T, U]: Eq[T, U] = Eq +/* + implicit def mixedEq1[T, U](implicit ce: Eq[T, U]): Eq[T, Any] = Eq + implicit def mixedEq2[T, U](implicit ce: Eq[T, U]): Eq[Any, T] = Eq + implicit def mixedEq1alt : Eq[Any, EqClass] = Eq + implicit def mixedEq2 : Eq[EqClass, Any] = Eq + implicit def mixedEq2alt : Eq[EqClass, Any] = Eq + implicit def mixedNull1[T]: Eq[T, Null] = Eq + implicit def mixedNull2[T]: Eq[Null, T] = Eq +*/ + implicit def eqString: Eq[String, String] = Eq + implicit def eqInt: Eq[Int, Int] = Eq + implicit def eqOption[T, U](implicit ce: Eq[T, U]): Eq[Option[T], Option[U]] = Eq + +/* + implicit def eqEq[UE, TE <: UE with EqClass[UE]]: Eq[TE, TE] = Eq + + case class Str(str: String) extends EqClass[Str] + 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, Str] = Eq + //implicit def eqNum: Eq[Num, Num] = Eq + + implicit def eqOption[T, U](implicit ce: Eq[T, U]): Eq[Option[T], Option[U]] = Eq +*/ + def some[T](x: T): Option[T] = Some(x) + + def main(args: Array[String]): Unit = { + val x = "abc" + x === x + + val n = 2 + n === n + x === 1 // error + n === x // error + + 1.0 === 1.0 + 1.0 === new Object + 1.0 === n // error + + some(n) === some(n) + some(n) === 1 // error + some(n) === some(1) // error + 1 === some(n) // error + some(1) === some(n) + some(1.0) === some(new Object) + some(1) === some(x) // error + some(x) === some(1) // error + some(x) === some(1.0) + +/* + val n = Num(2) + val m = Num(3) + n === m + + Other(1) === Other(2) + + Some(x) === Some(Str("")) + val z: Option[Str] = Some(Str("abc")) + z === Some(x) + z === None + Some(x) === z + None === z + Some(Other(1)) === None + + Other(3) === null + Str("x") === null +// null === Other(3) +// null === Str("x") +// null === null + + class Fruit extends EqClass + class Apple extends Fruit + class Pear extends Fruit + implicit def eqFruit: Eq[Fruit, Fruit] = Eq + + Some(new Apple) === Some(new Pear) + + + def ddistinct[T: EqEq](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 => + } + + Some(new Apple) === Some(Str("xx")) // error + Some(new Apple) === Some(Other(1)) // error + Some(Other(1)) === Some(new Apple) // error + x === n // error + n === x // error + x === Other(1) // error + Other(2) === x // error + z === Some(n) // error + z === n // error + Some(n) === z // error + n === z // error + Other(1) === z // error + z === Other(1) // error + ddistinct(List(z, n)) // error +*/ + } +} diff --git a/tests/run/t2030.scala b/tests/pos-scala2/t2030.scala similarity index 100% rename from tests/run/t2030.scala rename to tests/pos-scala2/t2030.scala