From 30f3ea1710203b7ccd004bdee414389342d589dc Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 25 Jan 2017 11:20:09 +1100 Subject: Avoid recomputation of companionRefs The previous condition for caching companionRefs contained a condition result.companionRefs.forall(implicitScopeCache.contains) which was always false because we cache types in `implicitCodeCache`, not companion refs. The new logic fixes this and does not need a second traversal because it is integrated in `iscopeRefs`. --- compiler/src/dotty/tools/dotc/typer/Implicits.scala | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'compiler/src/dotty') diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 592e80048..0b1eb1afb 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -380,7 +380,9 @@ trait ImplicitRunInfo { self: RunInfo => EmptyTermRefSet // on the other hand, the refs of `tp` are now not accurate, so `tp` is marked incomplete. } else { seen += t - iscope(t).companionRefs + val is = iscope(t) + if (!implicitScopeCache.contains(t)) incomplete += tp + is.companionRefs } } @@ -436,10 +438,8 @@ trait ImplicitRunInfo { self: RunInfo => if (ctx.typerState.ephemeral) record("ephemeral cache miss: implicitScope") else if (canCache && - ((tp eq rootTp) || // first type traversed is always cached - !incomplete.contains(tp) && // other types are cached if they are not incomplete - result.companionRefs.forall( // and all their companion refs are cached - implicitScopeCache.contains))) + ((tp eq rootTp) || // first type traversed is always cached + !incomplete.contains(tp))) // other types are cached if they are not incomplete implicitScopeCache(tp) = result result } -- cgit v1.2.3 From 801554c386253ad8474babb2d40590c2c8d3a45c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 30 Jan 2017 17:50:08 +1100 Subject: Allow implicit-by-name parameters --- compiler/src/dotty/tools/dotc/parsing/Parsers.scala | 8 ++------ compiler/src/dotty/tools/dotc/typer/Typer.scala | 3 ++- 2 files changed, 4 insertions(+), 7 deletions(-) (limited to 'compiler/src/dotty') diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index f62093db0..a733eb65d 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1765,12 +1765,8 @@ object Parsers { TypeTree() // XX-METHOD-INFER } else { accept(COLON) - if (in.token == ARROW) { - if (owner.isTypeName && !(mods is Local)) - syntaxError(s"${if (mods is Mutable) "`var'" else "`val'"} parameters may not be call-by-name") - else if (imods.hasFlags) - syntaxError("implicit parameters may not be call-by-name") - } + if (in.token == ARROW && owner.isTypeName && !(mods is Local)) + syntaxError(s"${if (mods is Mutable) "`var'" else "`val'"} parameters may not be call-by-name") paramType() } val default = diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index d05a0aaa7..22a4221ba 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1880,7 +1880,8 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit val args = (wtp.paramNames, wtp.paramTypes).zipped map { (pname, formal) => def implicitArgError(msg: String => String) = errors += (() => msg(em"parameter $pname of $methodStr")) - inferImplicitArg(formal, implicitArgError, tree.pos.endPos) + if (errors.nonEmpty) EmptyTree + else inferImplicitArg(formal.widenExpr, implicitArgError, tree.pos.endPos) } if (errors.nonEmpty) { // If there are several arguments, some arguments might already -- cgit v1.2.3 From dd0a8032f70cf7c15a928d5756ce8f27791c9a7d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 30 Jan 2017 17:51:53 +1100 Subject: Implement SearchResult.show --- .../src/dotty/tools/dotc/printing/PlainPrinter.scala | 18 ++++++++++++++++++ compiler/src/dotty/tools/dotc/printing/Printer.scala | 6 +++++- compiler/src/dotty/tools/dotc/typer/Implicits.scala | 12 ++++++++---- 3 files changed, 31 insertions(+), 5 deletions(-) (limited to 'compiler/src/dotty') diff --git a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala index 61f23c214..ac25f7cfd 100644 --- a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -6,6 +6,7 @@ import Texts._, Types._, Flags._, Names._, Symbols._, NameOps._, Constants._, De import Contexts.Context, Scopes.Scope, Denotations.Denotation, Annotations.Annotation import StdNames.{nme, tpnme} import ast.Trees._, ast._ +import typer.Implicits._ import config.Config import java.lang.Integer.toOctalString import config.Config.summarizeDepth @@ -484,6 +485,23 @@ class PlainPrinter(_ctx: Context) extends Printer { } }.close // todo: override in refined printer + def toText(result: SearchResult): Text = result match { + case result: SearchSuccess => + "SearchSuccess: " ~ toText(result.ref) ~ " via " ~ toText(result.tree) + case result: NonMatchingImplicit => + "NoImplicitMatches" + case result: DivergingImplicit => + "Diverging Implicit" + case result: ShadowedImplicit => + "Shadowed Implicit" + case result: FailedImplicit => + "Failed Implicit" + case result: AmbiguousImplicits => + "Ambiguous Implicit: " ~ toText(result.alt1) ~ " and " ~ toText(result.alt2) + case _ => + "?Unknown Implicit Result?" + } + private var maxSummarized = Int.MaxValue def summarized[T](depth: Int)(op: => T): T = { diff --git a/compiler/src/dotty/tools/dotc/printing/Printer.scala b/compiler/src/dotty/tools/dotc/printing/Printer.scala index 14b63012e..506773a4b 100644 --- a/compiler/src/dotty/tools/dotc/printing/Printer.scala +++ b/compiler/src/dotty/tools/dotc/printing/Printer.scala @@ -5,6 +5,7 @@ import core._ import Texts._, ast.Trees._ import Types.Type, Symbols.Symbol, Contexts.Context, Scopes.Scope, Constants.Constant, Names.Name, Denotations._, Annotations.Annotation +import typer.Implicits.SearchResult /** The base class of all printers */ @@ -94,7 +95,10 @@ abstract class Printer { /** Textual representation of tree */ def toText[T >: Untyped](tree: Tree[T]): Text - /** Perform string or text-producing operation `op` so that only a + /** Textual representation of implicit search result */ + def toText(result: SearchResult): Text + + /** Perform string or text-producing operation `op` so that only a * summarized text with given recursion depth is shown */ def summarized[T](depth: Int)(op: => T): T diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 0b1eb1afb..183dff282 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -6,7 +6,8 @@ import core._ import ast.{Trees, untpd, tpd, TreeInfo} import util.Positions._ import util.Stats.{track, record, monitored} -import printing.Showable +import printing.{Showable, Printer} +import printing.Texts._ import Contexts._ import Types._ import Flags._ @@ -219,14 +220,16 @@ object Implicits { } /** The result of an implicit search */ - abstract class SearchResult + abstract class SearchResult extends Showable { + def toText(printer: Printer): Text = printer.toText(this) + } /** A successful search * @param ref The implicit reference that succeeded * @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, level: Int, tstate: TyperState) extends SearchResult { + case class SearchSuccess(tree: tpd.Tree, ref: TermRef, level: Int, tstate: TyperState) extends SearchResult with Showable { override def toString = s"SearchSuccess($tree, $ref, $level)" } @@ -256,7 +259,7 @@ object Implicits { } /** An ambiguous implicits failure */ - class AmbiguousImplicits(alt1: TermRef, alt2: TermRef, val pt: Type, val argument: tpd.Tree) extends ExplainedSearchFailure { + class AmbiguousImplicits(val alt1: TermRef, val alt2: TermRef, val pt: Type, val argument: tpd.Tree) extends ExplainedSearchFailure { def explanation(implicit ctx: Context): String = em"both ${err.refStr(alt1)} and ${err.refStr(alt2)} $qualify" override def postscript(implicit ctx: Context) = @@ -604,6 +607,7 @@ trait Implicits { self: Typer => result match { case result: SearchSuccess => result.tstate.commit() + implicits.println(i"committing ${result.tstate.constraint} yielding ${ctx.typerState.constraint} ${ctx.typerState.hashesStr}") result case result: AmbiguousImplicits => val deepPt = pt.deepenProto -- cgit v1.2.3 From fc2bbfeaaf50c073ff1f22bfd0f3231a0b8d08a1 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 30 Jan 2017 17:53:54 +1100 Subject: Print typerstate nesting info as part of constr printing When printing info about adding to constraints, show the hashes of the typerstate stack, so that we know where constraints are added. --- compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala | 10 +++++----- compiler/src/dotty/tools/dotc/core/TyperState.scala | 6 ++++++ 2 files changed, 11 insertions(+), 5 deletions(-) (limited to 'compiler/src/dotty') diff --git a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala index 42df53fed..3aa20f15b 100644 --- a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala +++ b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala @@ -79,12 +79,12 @@ trait ConstraintHandling { if (Config.failOnInstantiationToNothing) assert(false, msg) else ctx.log(msg) } - constr.println(i"adding $description") + constr.println(i"adding $description in ${ctx.typerState.hashesStr}") val lower = constraint.lower(param) val res = addOneBound(param, bound, isUpper = true) && lower.forall(addOneBound(_, bound, isUpper = true)) - constr.println(i"added $description = $res") + constr.println(i"added $description = $res in ${ctx.typerState.hashesStr}") res } @@ -95,7 +95,7 @@ trait ConstraintHandling { val res = addOneBound(param, bound, isUpper = false) && upper.forall(addOneBound(_, bound, isUpper = false)) - constr.println(i"added $description = $res") + constr.println(i"added $description = $res in ${ctx.typerState.hashesStr}") res } @@ -108,12 +108,12 @@ trait ConstraintHandling { val up2 = p2 :: constraint.exclusiveUpper(p2, p1) val lo1 = constraint.nonParamBounds(p1).lo val hi2 = constraint.nonParamBounds(p2).hi - constr.println(i"adding $description down1 = $down1, up2 = $up2") + constr.println(i"adding $description down1 = $down1, up2 = $up2 ${ctx.typerState.hashesStr}") constraint = constraint.addLess(p1, p2) down1.forall(addOneBound(_, hi2, isUpper = true)) && up2.forall(addOneBound(_, lo1, isUpper = false)) } - constr.println(i"added $description = $res") + constr.println(i"added $description = $res ${ctx.typerState.hashesStr}") res } diff --git a/compiler/src/dotty/tools/dotc/core/TyperState.scala b/compiler/src/dotty/tools/dotc/core/TyperState.scala index 5c476c1cb..206438d86 100644 --- a/compiler/src/dotty/tools/dotc/core/TyperState.scala +++ b/compiler/src/dotty/tools/dotc/core/TyperState.scala @@ -79,6 +79,9 @@ class TyperState(r: Reporter) extends DotClass with Showable { def tryWithFallback[T](op: => T)(fallback: => T)(implicit ctx: Context): T = unsupported("tryWithFallBack") override def toText(printer: Printer): Text = "ImmutableTyperState" + + /** A string showing the hashes of all nested mutable typerstates */ + def hashesStr: String = "" } class MutableTyperState(previous: TyperState, r: Reporter, override val isCommittable: Boolean) @@ -207,4 +210,7 @@ extends TyperState(r) { } override def toText(printer: Printer): Text = constraint.toText(printer) + + override def hashesStr: String = hashCode.toString + " -> " + previous.hashesStr + } -- cgit v1.2.3 From b11e6d678a92187e5e9f821ba1116cec2cce0f8c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 1 Feb 2017 16:43:00 +1100 Subject: Handle Array classtags in the same way as others The previous implicit definition of arrayTag in DottyPredef priorities arrayTag over all other classtag searches, which led to surprising results in `i1907a.scala`. --- .../src/dotty/tools/dotc/typer/Implicits.scala | 47 +++++++++++++--------- library/src/dotty/DottyPredef.scala | 3 -- 2 files changed, 28 insertions(+), 22 deletions(-) (limited to 'compiler/src/dotty') diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 183dff282..2112b1221 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -507,7 +507,34 @@ trait Implicits { self: Typer => * 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 = + def inferImplicitArg(formal: Type, error: (String => String) => Unit, pos: Position)(implicit ctx: Context): Tree = { + + /** 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 => + fullyDefinedType(arg, "ClassTag argument", pos) match { + case defn.ArrayOf(elemTp) => + val etag = inferImplicitArg(defn.ClassTagType.appliedTo(elemTp), error, pos) + if (etag.isEmpty) etag else etag.select(nme.wrap) + case tp if hasStableErasure(tp) => + ref(defn.ClassTagModule) + .select(nme.apply) + .appliedToType(tp) + .appliedTo(clsOf(erasure(tp))) + .withPos(pos) + case tp => + EmptyTree + } + case _ => + EmptyTree + } + else EmptyTree + } + inferImplicit(formal, EmptyTree, pos) match { case SearchSuccess(arg, _, _, _) => arg @@ -534,24 +561,6 @@ trait Implicits { self: Typer => 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) - if (hasStableErasure(tp)) - return ref(defn.ClassTagModule) - .select(nme.apply) - .appliedToType(tp) - .appliedTo(clsOf(erasure(tp))) - .withPos(pos) - case _ => - } - EmptyTree } private def assumedCanEqual(ltp: Type, rtp: Type)(implicit ctx: Context) = { diff --git a/library/src/dotty/DottyPredef.scala b/library/src/dotty/DottyPredef.scala index cd90c4882..c7cf2a906 100644 --- a/library/src/dotty/DottyPredef.scala +++ b/library/src/dotty/DottyPredef.scala @@ -9,9 +9,6 @@ import scala.collection.Seq object DottyPredef { implicit def typeTag[T]: TypeTag[T] = ??? - 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 -- cgit v1.2.3 From e34555f69e7cdd6d19d0d1ed969127f4ee65c36e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 1 Feb 2017 17:18:13 +1100 Subject: Disallow taking a class tag of Nothing or Null. It seems in most cases this leads to weird behavior and cause confusing error messages later. It also means we cannot create an Array[Nothing], except by passing the classtag explicitly. --- compiler/src/dotty/tools/dotc/typer/Implicits.scala | 2 ++ tests/neg/i1802.scala | 4 ++-- tests/neg/i1907.scala | 7 +++++++ tests/neg/undet-classtag.scala | 5 +++++ tests/pos/t3859.scala | 2 +- tests/pos/t5859.scala | 4 ++-- tests/run/array-addition.scala | 4 ++-- 7 files changed, 21 insertions(+), 7 deletions(-) create mode 100644 tests/neg/i1907.scala create mode 100644 tests/neg/undet-classtag.scala (limited to 'compiler/src/dotty') diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 2112b1221..f7d8556a7 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -521,6 +521,8 @@ trait Implicits { self: Typer => val etag = inferImplicitArg(defn.ClassTagType.appliedTo(elemTp), error, pos) if (etag.isEmpty) etag else etag.select(nme.wrap) case tp if hasStableErasure(tp) => + if (defn.isBottomClass(tp.typeSymbol)) + error(where => i"attempt to take ClassTag of undetermined type for $where") ref(defn.ClassTagModule) .select(nme.apply) .appliedToType(tp) diff --git a/tests/neg/i1802.scala b/tests/neg/i1802.scala index 56da672a8..93e790f18 100644 --- a/tests/neg/i1802.scala +++ b/tests/neg/i1802.scala @@ -14,8 +14,8 @@ object Exception { def apply(x: Throwable): T = f(downcast(x).get) } - def mkThrowableCatcher[T](isDef: Throwable => Boolean, f: Throwable => T) = mkCatcher(isDef, f) + def mkThrowableCatcher[T](isDef: Throwable => Boolean, f: Throwable => T) = mkCatcher(isDef, f) // error: undetermined ClassTag - implicit def throwableSubtypeToCatcher[Ex <: Throwable: ClassTag, T](pf: PartialFunction[Ex, T]) = // error: cyclic reference + implicit def throwableSubtypeToCatcher[Ex <: Throwable: ClassTag, T](pf: PartialFunction[Ex, T]) = mkCatcher(pf.isDefinedAt _, pf.apply _) } diff --git a/tests/neg/i1907.scala b/tests/neg/i1907.scala new file mode 100644 index 000000000..6bc3bb56f --- /dev/null +++ b/tests/neg/i1907.scala @@ -0,0 +1,7 @@ +import java.io.File + +object Test { + Some(new File(".")) + .map(_.listFiles).getOrElse(Array.empty) // error: undetermined ClassTag + .map(_.listFiles) +} diff --git a/tests/neg/undet-classtag.scala b/tests/neg/undet-classtag.scala new file mode 100644 index 000000000..563596d14 --- /dev/null +++ b/tests/neg/undet-classtag.scala @@ -0,0 +1,5 @@ +object Test { + def f[T: reflect.ClassTag](x: T) = ??? + + f(???) // error: undetermined ClassTag +} diff --git a/tests/pos/t3859.scala b/tests/pos/t3859.scala index 992207301..486c1d4b2 100644 --- a/tests/pos/t3859.scala +++ b/tests/pos/t3859.scala @@ -1,4 +1,4 @@ class Test { - def foo: Unit = bar(Array(): _*) + def foo: Unit = bar(Array[AnyRef](): _*) def bar(values: AnyRef*): Unit = () } diff --git a/tests/pos/t5859.scala b/tests/pos/t5859.scala index 2a31e68ee..60ec8b4cb 100644 --- a/tests/pos/t5859.scala +++ b/tests/pos/t5859.scala @@ -7,9 +7,9 @@ class A { f(List[AnyRef](): _*) f(List(): _*) f(Nil: _*) - f(Array(): _*) + // f(Array(): _*) // undetermined ClassTag f(Array[AnyRef](): _*) f(List(1)) f(List(1), Nil: _*) - f(List(1), Array(): _*) + // f(List(1), Array(): _*) // undetermined ClassTag } diff --git a/tests/run/array-addition.scala b/tests/run/array-addition.scala index 8def48e85..09a1b0bad 100644 --- a/tests/run/array-addition.scala +++ b/tests/run/array-addition.scala @@ -4,8 +4,8 @@ object Test { def main(args: Array[String]): Unit = { prettyPrintArray(Array(1,2,3) :+ 4) prettyPrintArray(1 +: Array(2,3,4)) - prettyPrintArray(Array() :+ 1) - prettyPrintArray(1 +: Array()) + prettyPrintArray(Array[Int]() :+ 1) + prettyPrintArray(1 +: Array[Int]()) } } -- cgit v1.2.3