aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMartin Odersky <odersky@gmail.com>2014-05-09 18:55:10 +0200
committerSamuel Gruetter <samuel.gruetter@epfl.ch>2014-05-20 13:38:49 +0200
commit394b645dd2d44ff68597527c6c690c73653f1bcb (patch)
tree22e72a22f891c002d5fef946361d223d6f6fc1a4 /src
parent67e5130e63b10631106490cbc1cec5adcd5273e6 (diff)
downloaddotty-394b645dd2d44ff68597527c6c690c73653f1bcb.tar.gz
dotty-394b645dd2d44ff68597527c6c690c73653f1bcb.tar.bz2
dotty-394b645dd2d44ff68597527c6c690c73653f1bcb.zip
Fix of pos/t2429
This was a hard nut to crack. The problem exemplified by t2429 is that in a situation like val x: T = foo(...) where `foo` needs implicit parameters the expected result type `T` is propagated into the typechecking of foo(...) and consequently also into the implicit parameter search. This is often necessary, for instance to instantiate type parameters. But it can lead to overconstrained searches if in fact the right expansion is val x: T = viewToT(foo(...)(implicit params)) where `viewToT` is some implicit conversion. The fix handles that case by re-hecking the application foo(...) with an empty result type, if the implicit parameter search fails. But the re-checking is very subtle as is explained in the comment to `TyperState#tryWithFallback`.
Diffstat (limited to 'src')
-rw-r--r--src/dotty/tools/dotc/config/Printers.scala2
-rw-r--r--src/dotty/tools/dotc/core/TyperState.scala54
-rw-r--r--src/dotty/tools/dotc/reporting/StoreReporter.scala5
-rw-r--r--src/dotty/tools/dotc/transform/Erasure.scala4
-rw-r--r--src/dotty/tools/dotc/typer/Applications.scala2
-rw-r--r--src/dotty/tools/dotc/typer/Namer.scala12
-rw-r--r--src/dotty/tools/dotc/typer/ProtoTypes.scala9
-rw-r--r--src/dotty/tools/dotc/typer/Typer.scala53
8 files changed, 105 insertions, 36 deletions
diff --git a/src/dotty/tools/dotc/config/Printers.scala b/src/dotty/tools/dotc/config/Printers.scala
index 680dee7ab..5bfe1d0b6 100644
--- a/src/dotty/tools/dotc/config/Printers.scala
+++ b/src/dotty/tools/dotc/config/Printers.scala
@@ -25,5 +25,5 @@ object Printers {
val hk = noPrinter
val incremental = noPrinter
val config = noPrinter
- val transforms = new Printer
+ val transforms = noPrinter
} \ No newline at end of file
diff --git a/src/dotty/tools/dotc/core/TyperState.scala b/src/dotty/tools/dotc/core/TyperState.scala
index 59c934e0d..6a3ac4467 100644
--- a/src/dotty/tools/dotc/core/TyperState.scala
+++ b/src/dotty/tools/dotc/core/TyperState.scala
@@ -11,7 +11,10 @@ import printing.{Showable, Printer}
import printing.Texts._
import collection.mutable
-class TyperState(val reporter: Reporter) extends DotClass with Showable {
+class TyperState(r: Reporter) extends DotClass with Showable {
+
+ /** The current reporter */
+ def reporter = r
/** The current constraint set */
def constraint: Constraint = new Constraint(SimpleMap.Empty, SimpleMap.Empty)
@@ -56,11 +59,17 @@ class TyperState(val reporter: Reporter) extends DotClass with Showable {
/** Can this state be transitively committed until the top-level? */
def isGlobalCommittable: Boolean = false
+ def tryWithFallback[T](op: => T)(fallback: => T)(implicit ctx: Context): T = unsupported("tryWithFallBack")
+
override def toText(printer: Printer): Text = "ImmutableTyperState"
}
-class MutableTyperState(previous: TyperState, reporter: Reporter, override val isCommittable: Boolean)
-extends TyperState(reporter) {
+class MutableTyperState(previous: TyperState, r: Reporter, override val isCommittable: Boolean)
+extends TyperState(r) {
+
+ private var myReporter = r
+
+ override def reporter = myReporter
private var myConstraint: Constraint = previous.constraint
@@ -112,5 +121,44 @@ extends TyperState(reporter) {
constraint = constraint.remove(poly)
}
+ /** Try operation `op`; if it produces errors, execute `fallback` with constraint and
+ * reporter as they were before `op` was executed. This is similar to `typer/tryEither`,
+ * but with one important difference: Any type variable instantiations produced by `op`
+ * are persisted even if `op` fails. This is normally not what one wants and therefore
+ * it is recommended to use
+ *
+ * tryEither { implicit ctx => op } { (_, _) => fallBack }
+ *
+ * instead of
+ *
+ * ctx.tryWithFallback(op)(fallBack)
+ *
+ * `tryWithFallback` is only used when an implicit parameter search fails
+ * and the whole expression is subsequently retype-checked with a Wildcard
+ * expected type (so as to allow an implicit conversion on the result and
+ * avoid over-constraining the implicit parameter search). In this case,
+ * the only type variables that might be falsely instantiated by `op` but
+ * not by `fallBack` are type variables in the typed expression itself, and
+ * these will be thrown away and new ones will be created on re-typing.
+ * So `tryWithFallback` is safe. It is also necessary because without it
+ * we do not propagate enough instantiation information into the implicit search
+ * and this might lead to a missing parameter type error. This is exhibited
+ * at several places in the test suite (for instance in `pos_typers`).
+ * Overall, this is rather ugly, but despite trying for 2 days I have not
+ * found a better solution.
+ */
+ override def tryWithFallback[T](op: => T)(fallback: => T)(implicit ctx: Context): T = {
+ val savedReporter = myReporter
+ val savedConstraint = myConstraint
+ myReporter = new StoreReporter
+ val result = op
+ if (!reporter.hasErrors) result
+ else {
+ myReporter = savedReporter
+ myConstraint = savedConstraint
+ fallback
+ }
+ }
+
override def toText(printer: Printer): Text = constraint.toText(printer)
}
diff --git a/src/dotty/tools/dotc/reporting/StoreReporter.scala b/src/dotty/tools/dotc/reporting/StoreReporter.scala
index ea8199102..2864c01f8 100644
--- a/src/dotty/tools/dotc/reporting/StoreReporter.scala
+++ b/src/dotty/tools/dotc/reporting/StoreReporter.scala
@@ -29,5 +29,8 @@ class StoreReporter extends Reporter {
}
override def flush()(implicit ctx: Context) =
- if (infos != null) infos foreach ctx.reporter.report
+ if (infos != null) {
+ infos foreach ctx.reporter.report
+ infos = null
+ }
}
diff --git a/src/dotty/tools/dotc/transform/Erasure.scala b/src/dotty/tools/dotc/transform/Erasure.scala
index e6d012d68..e56132057 100644
--- a/src/dotty/tools/dotc/transform/Erasure.scala
+++ b/src/dotty/tools/dotc/transform/Erasure.scala
@@ -349,7 +349,7 @@ object Erasure {
tpd.DefDef(bridge, { paramss: List[List[tpd.Tree]] =>
val rhs = paramss.foldLeft(sel)((fun, vparams) =>
fun.tpe.widen match {
- case MethodType(names, types) => Apply(fun, (vparams, types).zipped.map(adapt))
+ case MethodType(names, types) => Apply(fun, (vparams, types).zipped.map(adapt(_, _, untpd.EmptyTree)))
case a => error(s"can not resolve apply type $a")
})
@@ -357,7 +357,7 @@ object Erasure {
})
}
- override def adapt(tree: Tree, pt: Type)(implicit ctx: Context): Tree =
+ override def adapt(tree: Tree, pt: Type, original: untpd.Tree)(implicit ctx: Context): Tree =
ctx.traceIndented(i"adapting ${tree.showSummary}: ${tree.tpe} to $pt", show = true) {
assert(ctx.phase == ctx.erasurePhase.next, ctx.phase)
if (tree.isEmpty) tree else adaptToType(tree, pt)
diff --git a/src/dotty/tools/dotc/typer/Applications.scala b/src/dotty/tools/dotc/typer/Applications.scala
index 92acb7939..a4c26080d 100644
--- a/src/dotty/tools/dotc/typer/Applications.scala
+++ b/src/dotty/tools/dotc/typer/Applications.scala
@@ -346,7 +346,7 @@ trait Applications extends Compatibility { self: Typer =>
init()
def addArg(arg: Tree, formal: Type): Unit =
- typedArgBuf += adaptInterpolated(arg, formal.widenExpr)
+ typedArgBuf += adaptInterpolated(arg, formal.widenExpr, EmptyTree)
def makeVarArg(n: Int, elemFormal: Type): Unit = {
val args = typedArgBuf.takeRight(n).toList
diff --git a/src/dotty/tools/dotc/typer/Namer.scala b/src/dotty/tools/dotc/typer/Namer.scala
index c3f1dcc81..e9195a072 100644
--- a/src/dotty/tools/dotc/typer/Namer.scala
+++ b/src/dotty/tools/dotc/typer/Namer.scala
@@ -404,8 +404,18 @@ class Namer { typer: Typer =>
}
}
- final override def complete(denot: SymDenotation)(implicit ctx: Context) =
+ final override def complete(denot: SymDenotation)(implicit ctx: Context) = {
+ if (completions != noPrinter && ctx.typerState != this.ctx.typerState) {
+ completions.println(completions.getClass.toString)
+ def levels(c: Context): Int =
+ if (c.typerState eq this.ctx.typerState) 0
+ else if (c.typerState == null) -1
+ else if (c.outer.typerState == c.typerState) levels(c.outer)
+ else levels(c.outer) + 1
+ completions.println(s"!!!completing ${denot.symbol.showLocated} in buried typerState, gap = ${levels(ctx)}")
+ }
completeInCreationContext(denot)
+ }
def completeInCreationContext(denot: SymDenotation): Unit =
denot.info = typeSig(denot.symbol)
diff --git a/src/dotty/tools/dotc/typer/ProtoTypes.scala b/src/dotty/tools/dotc/typer/ProtoTypes.scala
index 1438f9e16..a72e98418 100644
--- a/src/dotty/tools/dotc/typer/ProtoTypes.scala
+++ b/src/dotty/tools/dotc/typer/ProtoTypes.scala
@@ -204,7 +204,7 @@ object ProtoTypes {
targ = typer.typedUnadapted(arg, formal)
if (!ctx.reporter.hasPending) myTypedArg = myTypedArg.updated(arg, targ)
}
- typer.adapt(targ, formal)
+ typer.adapt(targ, formal, arg)
}
private var myTupled: Type = NoType
@@ -236,7 +236,7 @@ object ProtoTypes {
*
* []: argType => resultType
*/
- abstract case class ViewProto(argType: Type, override val resultType: Type)(implicit ctx: Context)
+ abstract case class ViewProto(argType: Type, override val resultType: Type)
extends CachedGroundType with ApplyingProto {
def isMatchedBy(tp: Type)(implicit ctx: Context): Boolean =
ctx.typer.isApplicable(tp, argType :: Nil, resultType)
@@ -253,7 +253,7 @@ object ProtoTypes {
override def deepenProto(implicit ctx: Context) = derivedViewProto(argType, resultType.deepenProto)
}
- class CachedViewProto(argType: Type, resultType: Type)(implicit ctx: Context) extends ViewProto(argType, resultType) {
+ class CachedViewProto(argType: Type, resultType: Type) extends ViewProto(argType, resultType) {
override def computeHash = doHash(argType, resultType)
}
@@ -373,7 +373,8 @@ object ProtoTypes {
case tp @ PolyParam(poly, pnum) =>
ctx.typerState.constraint.at(tp) match {
case bounds: TypeBounds => wildApprox(WildcardType(bounds))
- case _ => WildcardType(wildApprox(poly.paramBounds(pnum)).bounds)
+ case NoType => WildcardType(wildApprox(poly.paramBounds(pnum)).bounds)
+ case inst => wildApprox(inst)
}
case MethodParam(mt, pnum) =>
WildcardType(TypeBounds.upper(wildApprox(mt.paramTypes(pnum))))
diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala
index 5d477193c..050fcbc76 100644
--- a/src/dotty/tools/dotc/typer/Typer.scala
+++ b/src/dotty/tools/dotc/typer/Typer.scala
@@ -954,7 +954,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
def typed(tree: untpd.Tree, pt: Type = WildcardType)(implicit ctx: Context): Tree = /*>|>*/ ctx.traceIndented (i"typing $tree", typr, show = true) /*<|<*/ {
if (!tree.isEmpty && ctx.typerState.isGlobalCommittable) assert(tree.pos.exists, i"position not set for $tree")
- try adapt(typedUnadapted(tree, pt), pt)
+ try adapt(typedUnadapted(tree, pt), pt, tree)
catch {
case ex: CyclicReference => errorTree(tree, cyclicErrorMsg(ex))
case ex: FatalTypeError => errorTree(tree, ex.getMessage)
@@ -1036,7 +1036,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
}
val qualProto = SelectionProto(name, normalizedProto, NoViewsAllowed)
tryEither { implicit ctx =>
- val qual1 = adaptInterpolated(qual, qualProto)
+ val qual1 = adaptInterpolated(qual, qualProto, EmptyTree)
if ((qual eq qual1) || ctx.reporter.hasErrors) tree
else typedSelect(cpy.Select(tree, untpd.TypedSplice(qual1), name), pt)
} { (_, _) => tree
@@ -1044,11 +1044,11 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
case _ => tree
}}
- def adapt(tree: Tree, pt: Type)(implicit ctx: Context) = /*>|>*/ track("adapt") /*<|<*/ {
+ def adapt(tree: Tree, pt: Type, original: untpd.Tree = untpd.EmptyTree)(implicit ctx: Context) = /*>|>*/ track("adapt") /*<|<*/ {
/*>|>*/ ctx.traceIndented(i"adapting $tree of type ${tree.tpe} to $pt", typr, show = true) /*<|<*/ {
interpolateUndetVars(tree)
tree overwriteType tree.tpe.simplified
- adaptInterpolated(tree, pt)
+ adaptInterpolated(tree, pt, original)
}
}
@@ -1090,7 +1090,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
* (14) When in mode EXPRmode, apply a view
* If all this fails, error
*/
- def adaptInterpolated(tree: Tree, pt: Type)(implicit ctx: Context): Tree = {
+ def adaptInterpolated(tree: Tree, pt: Type, original: untpd.Tree)(implicit ctx: Context): Tree = {
assert(pt.exists)
@@ -1104,7 +1104,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
def expectedStr = err.expectedTypeStr(pt)
resolveOverloaded(alts, pt) match {
case alt :: Nil =>
- adapt(tree.withType(alt), pt)
+ adapt(tree.withType(alt), pt, original)
case Nil =>
def noMatches =
errorTree(tree,
@@ -1150,24 +1150,31 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
def adaptNoArgs(wtp: Type): Tree = wtp match {
case wtp: ExprType =>
- adaptInterpolated(tree.withType(wtp.resultType), pt)
+ adaptInterpolated(tree.withType(wtp.resultType), pt, original)
case wtp: ImplicitMethodType if constrainResult(wtp, pt) =>
- def implicitArgError(msg: => String): Tree = {
- ctx.error(msg, tree.pos.endPos)
- EmptyTree
- }
- 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, _, _) =>
- adapt(arg, formal)
- case ambi: AmbiguousImplicits =>
- implicitArgError(s"ambiguous implicits: ${ambi.explanation} of $where")
- case failure: SearchFailure =>
- implicitArgError(d"no implicit argument of type $formal found for $where" + failure.postscript)
+ def addImplicitArgs = {
+ def implicitArgError(msg: => String): Tree = {
+ ctx.error(msg, tree.pos.endPos)
+ EmptyTree
+ }
+ 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, _, _) =>
+ adapt(arg, formal)
+ case ambi: AmbiguousImplicits =>
+ implicitArgError(s"ambiguous implicits: ${ambi.explanation} of $where")
+ case failure: SearchFailure =>
+ implicitArgError(d"no implicit argument of type $formal found for $where" + failure.postscript)
+ }
}
+ adapt(tpd.Apply(tree, args), pt)
}
- adapt(tpd.Apply(tree, args), pt)
+ if ((pt eq WildcardType) || original.isEmpty) addImplicitArgs
+ else
+ ctx.typerState.tryWithFallback(addImplicitArgs) {
+ adapt(typed(original, WildcardType), pt, EmptyTree)
+ }
case wtp: MethodType if !pt.isInstanceOf[SingletonType] =>
val arity =
if (defn.isFunctionType(pt)) defn.functionArity(pt)
@@ -1176,7 +1183,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
if (arity >= 0 && !tree.symbol.isConstructor)
typed(etaExpand(tree, wtp, arity), pt)
else if (wtp.paramTypes.isEmpty)
- adaptInterpolated(tpd.Apply(tree, Nil), pt)
+ adaptInterpolated(tpd.Apply(tree, Nil), pt, EmptyTree)
else
errorTree(tree,
d"""missing arguments for $methodStr
@@ -1235,7 +1242,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
if (pt.isInstanceOf[PolyProto]) tree
else {
val (_, tvars) = constrained(poly, tree)
- adaptInterpolated(tree appliedToTypes tvars, pt)
+ adaptInterpolated(tree appliedToTypes tvars, pt, original)
}
case wtp =>
pt match {