diff options
-rw-r--r-- | src/dotty/tools/dotc/core/Definitions.scala | 7 | ||||
-rw-r--r-- | src/dotty/tools/dotc/core/StdNames.scala | 1 | ||||
-rw-r--r-- | src/dotty/tools/dotc/typer/Applications.scala | 12 | ||||
-rw-r--r-- | src/dotty/tools/dotc/typer/Implicits.scala | 64 | ||||
-rw-r--r-- | src/dotty/tools/dotc/typer/Typer.scala | 56 | ||||
-rw-r--r-- | tests/neg/EqualityStrawman2.scala | 96 | ||||
-rw-r--r-- | tests/pending/neg/EqualityStrawman2.scala | 126 |
7 files changed, 220 insertions, 142 deletions
diff --git a/src/dotty/tools/dotc/core/Definitions.scala b/src/dotty/tools/dotc/core/Definitions.scala index 7f59cbed0..b99d0716b 100644 --- a/src/dotty/tools/dotc/core/Definitions.scala +++ b/src/dotty/tools/dotc/core/Definitions.scala @@ -424,10 +424,17 @@ 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 + def EqModule(implicit ctx: Context) = EqClass.companionModule + + def Eq_eqAny(implicit ctx: Context) = EqModule.requiredMethodRef(nme.eqAny) + // 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/StdNames.scala b/src/dotty/tools/dotc/core/StdNames.scala index e2add1a52..81f6da0e2 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/typer/Applications.scala b/src/dotty/tools/dotc/typer/Applications.scala index 6ce98606b..2b0cc4033 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 */ diff --git a/src/dotty/tools/dotc/typer/Implicits.scala b/src/dotty/tools/dotc/typer/Implicits.scala index 940170ceb..ff8e607ed 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._ @@ -415,6 +417,68 @@ 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 + } + + def checkCanEqual(ltp: Type, rtp: Type, pos: Position)(implicit ctx: Context): Unit = + if (!ctx.isAfterTyper && !ltp.isError && !rtp.isError) + inferImplicitArg( + defn.EqType.appliedTo(ltp, rtp), + _ => d"Values of types $ltp and $rtp cannot be compared with == or !=", + pos) + /** 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 diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index f7fa318cd..b26ff4509 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -1540,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 @@ -1609,8 +1592,14 @@ 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) + case _ => + } + tree + } + else if (tree.tpe <:< pt) tree else if (wtp.isInstanceOf[MethodType]) missingArgs else { typr.println(i"adapt to subtype ${tree.tpe} !<:< $pt") @@ -1618,29 +1607,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) diff --git a/tests/neg/EqualityStrawman2.scala b/tests/neg/EqualityStrawman2.scala deleted file mode 100644 index f71c7d3d7..000000000 --- a/tests/neg/EqualityStrawman2.scala +++ /dev/null @@ -1,96 +0,0 @@ -object equality { - - trait Eq[-T, -U] - object Eq extends Eq[Any, Any] - - 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 - - implicit def eqAny: Eq[Any, Any] = Eq - - implicit def mixedEq1 : Eq[Any, EqClass] = Eq - implicit def mixedEq1alt: Eq[Any, EqClass] = Eq - implicit def mixedEq2 : Eq[EqClass, Any] = Eq - implicit def mixedEq2alt: Eq[EqClass, Any] = Eq - -// implicit def eqEq[U, T <: U with EqClass[U]]: Eq[T, T] = Eq - - case class Str(str: String) extends EqClass - case class Num(x: Int) extends EqClass - - 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 main(args: Array[String]): Unit = { - val x = Str("abc") - (Some(x): Option[Str]) === (None: Option[Str]) - - x === x - - val n = Num(2) - val m = Num(3) - n === m - - Other(1) === Other(2) - - (Some(x) === None)//(eqOption) - (Some(x) === Some(Str("")))(eqOption) - 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 - 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 - 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/pending/neg/EqualityStrawman2.scala b/tests/pending/neg/EqualityStrawman2.scala new file mode 100644 index 000000000..e72e675c2 --- /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 +*/ + } +} |