From 7c5e36b80e111d17910dbf122c02a458377656d1 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 18 Dec 2016 19:29:02 +0100 Subject: More lenient handling of mixed parameterless and nullary methods When faced with a denotation that combines parameterless and nullary method definitions (toString is a common example), ignore any redundant () applications. --- compiler/src/dotty/tools/dotc/core/Types.scala | 8 +++++++ .../src/dotty/tools/dotc/typer/Applications.scala | 26 ++++++++++++---------- .../src/dotty/tools/dotc/typer/ProtoTypes.scala | 16 +++++++++++++ compiler/src/dotty/tools/dotc/typer/Typer.scala | 23 ++++++++++++++----- compiler/test/dotc/scala-collections.blacklist | 5 ----- 5 files changed, 56 insertions(+), 22 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 069b4f60d..df1e68944 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -217,6 +217,14 @@ object Types { case _ => false } + /** Is this the type of a method with a leading empty parameter list? + */ + def isNullaryMethod(implicit ctx: Context): Boolean = this match { + case MethodType(Nil, _) => true + case tp: PolyType => tp.resultType.isNullaryMethod + case _ => false + } + /** Is this an alias TypeBounds? */ def isAlias: Boolean = this.isInstanceOf[TypeAlias] diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 8a18e63c0..42c24ffb7 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -657,18 +657,20 @@ trait Applications extends Compatibility { self: Typer with Dynamic => case err: ErrorType => untpd.cpy.Apply(tree)(fun1, tree.args).withType(err) case TryDynamicCallType => typedDynamicApply(tree, pt) case _ => - tryEither { - implicit ctx => simpleApply(fun1, proto) - } { - (failedVal, failedState) => - def fail = { failedState.commit(); failedVal } - // Try once with original prototype and once (if different) with tupled one. - // The reason we need to try both is that the decision whether to use tupled - // or not was already taken but might have to be revised when an implicit - // is inserted on the qualifier. - tryWithImplicitOnQualifier(fun1, originalProto).getOrElse( - if (proto eq originalProto) fail - else tryWithImplicitOnQualifier(fun1, proto).getOrElse(fail)) + if (originalProto.isDropped) fun1 + else + tryEither { + implicit ctx => simpleApply(fun1, proto) + } { + (failedVal, failedState) => + def fail = { failedState.commit(); failedVal } + // Try once with original prototype and once (if different) with tupled one. + // The reason we need to try both is that the decision whether to use tupled + // or not was already taken but might have to be revised when an implicit + // is inserted on the qualifier. + tryWithImplicitOnQualifier(fun1, originalProto).getOrElse( + if (proto eq originalProto) fail + else tryWithImplicitOnQualifier(fun1, proto).getOrElse(fail)) } } } diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index ed6b95c3b..3c318a6af 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -250,6 +250,22 @@ object ProtoTypes { /** Somebody called the `tupled` method of this prototype */ def isTupled: Boolean = myTupled.isInstanceOf[FunProto] + /** If true, the application of this prototype was canceled. */ + private var toDrop: Boolean = false + + /** Cancel the application of this prototype. This can happen for a nullary + * application `f()` if `f` refers to a symbol that exists both in parameterless + * form `def f` and nullary method form `def f()`. A common example for such + * a method is `toString`. If in that case the type in the denotation is + * parameterless, we compensate by dropping the application. + */ + def markAsDropped() = { + assert(args.isEmpty) + toDrop = true + } + + def isDropped: Boolean = toDrop + override def toString = s"FunProto(${args mkString ","} => $resultType)" def map(tm: TypeMap)(implicit ctx: Context): FunProto = diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 07a27a498..d054fe803 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1640,13 +1640,19 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit case _ => false } - /** Add apply node or implicit conversions. Two strategies are tried, and the first - * that is successful is picked. If neither of the strategies are successful, continues with - * `fallBack`. + /** Potentially add apply node or implicit conversions. Before trying either, + * if the function is applied to an empty parameter list (), we try + * + * 0th strategy: If `tree` overrides a nullary method, mark the prototype + * so that the argument is dropped and return `tree` itself. + * + * After that, two strategies are tried, and the firs that is successful is picked. + * If neither of the strategies are successful, continues with`fallBack`. * * 1st strategy: Try to insert `.apply` so that the result conforms to prototype `pt`. * This strategy is not tried if the prototype represents already * another `.apply` or `.apply()` selection. + * * 2nd strategy: If tree is a select `qual.name`, try to insert an implicit conversion * around the qualifier part `qual` so that the result conforms to the expected type * with wildcard result type. @@ -1661,8 +1667,15 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit def tryImplicit = tryInsertImplicitOnQualifier(tree, pt).getOrElse(fallBack) - if (isApplyProto(pt)) tryImplicit - else tryEither(tryApply(_))((_, _) => tryImplicit) + pt match { + case pt @ FunProto(Nil, _, _) + if tree.symbol.allOverriddenSymbols.exists(_.info.isNullaryMethod) => + pt.markAsDropped() + tree + case _ => + if (isApplyProto(pt)) tryImplicit + else tryEither(tryApply(_))((_, _) => tryImplicit) + } } /** If this tree is a select node `qual.name`, try to insert an implicit conversion diff --git a/compiler/test/dotc/scala-collections.blacklist b/compiler/test/dotc/scala-collections.blacklist index eb48d424a..03a665e4e 100644 --- a/compiler/test/dotc/scala-collections.blacklist +++ b/compiler/test/dotc/scala-collections.blacklist @@ -13,11 +13,6 @@ # | ^^^^ # | cyclic reference involving method toString -../scala-scala/src/library/scala/collection/mutable/ImmutableMapAdaptor.scala -# 78 | override def toString() = imap.toString() -# | ^^^^^^^^^^^^^^^ -# | missing argument for parameter index of method apply: (index: Int)Char - ../scala-scala/src/library/scala/collection/mutable/LinkedHashMap.scala # 102 | protected class FilteredKeys(p: A => Boolean) extends super.FilteredKeys(p) { # | ^^^^^^^^^^^^^^^^^^^^ -- cgit v1.2.3