diff options
author | Paul Phillips <paulp@improving.org> | 2011-06-06 23:53:50 +0000 |
---|---|---|
committer | Paul Phillips <paulp@improving.org> | 2011-06-06 23:53:50 +0000 |
commit | b8575e9636004b4abfbd5cd4b0a71c39bf8b1127 (patch) | |
tree | f1c107751ae890f3c0b19e349fc2a32481c84658 /src | |
parent | 29cdb5837ca05a883093bb0e7ddfac264c84afea (diff) | |
download | scala-b8575e9636004b4abfbd5cd4b0a71c39bf8b1127.tar.gz scala-b8575e9636004b4abfbd5cd4b0a71c39bf8b1127.tar.bz2 scala-b8575e9636004b4abfbd5cd4b0a71c39bf8b1127.zip |
Proliferating the number of debugging modes bec...
Proliferating the number of debugging modes because it's still way
too hard to see what's going on in there. Until we get hubert's type
debugger with its whiz-bang whizbanginess, we'll have to struggle along
with somewhat prettier ascii. This introduces:
-Yinfer-debug
which tries to print in readable fashion what is happening in the
worlds of inference and implicit search. It should be made a bit more
complementary and less overlappy with -Ytyper-debug. No review.
Diffstat (limited to 'src')
11 files changed, 491 insertions, 303 deletions
diff --git a/src/compiler/scala/reflect/internal/Trees.scala b/src/compiler/scala/reflect/internal/Trees.scala index 6d3cc8b8cb..2ee1a59355 100644 --- a/src/compiler/scala/reflect/internal/Trees.scala +++ b/src/compiler/scala/reflect/internal/Trees.scala @@ -1507,6 +1507,10 @@ trait Trees /*extends reflect.generic.Trees*/ { self: SymbolTable => if (tree eq orig) super.transform(tree) else tree } + // Create a readable string describing a substitution. + private def substituterString(fromStr: String, toStr: String, from: List[Any], to: List[Any]): String = { + "subst[%s, %s](%s)".format(fromStr, toStr, (from, to).zipped map (_ + " -> " + _) mkString ", ") + } class TreeSubstituter(from: List[Symbol], to: List[Tree]) extends Transformer { override def transform(tree: Tree): Tree = tree match { @@ -1519,11 +1523,13 @@ trait Trees /*extends reflect.generic.Trees*/ { self: SymbolTable => case _ => super.transform(tree) } + override def toString = substituterString("Symbol", "Tree", from, to) } class TreeTypeSubstituter(val from: List[Symbol], val to: List[Type]) extends Traverser { val typeSubst = new SubstTypeMap(from, to) def fromContains = typeSubst.fromContains + def isEmpty = from.isEmpty && to.isEmpty override def traverse(tree: Tree) { if (tree.tpe ne null) tree.tpe = typeSubst(tree.tpe) @@ -1552,7 +1558,7 @@ trait Trees /*extends reflect.generic.Trees*/ { self: SymbolTable => super.traverse(tree) } override def apply[T <: Tree](tree: T): T = super.apply(tree.duplicate) - override def toString() = "TreeSymSubstTraverser("+from+","+to+")" + override def toString() = "TreeSymSubstTraverser/" + substituterString("Symbol", "Symbol", from, to) } /** Substitute symbols in 'from' with symbols in 'to'. Returns a new @@ -1583,7 +1589,7 @@ trait Trees /*extends reflect.generic.Trees*/ { self: SymbolTable => super.transform(tree) } def apply[T <: Tree](tree: T): T = transform(tree).asInstanceOf[T] - override def toString() = "TreeSymSubstituter("+from+","+to+")" + override def toString() = "TreeSymSubstituter/" + substituterString("Symbol", "Symbol", from, to) } class ChangeOwnerTraverser(val oldowner: Symbol, val newowner: Symbol) extends Traverser { diff --git a/src/compiler/scala/reflect/internal/TypeDebugging.scala b/src/compiler/scala/reflect/internal/TypeDebugging.scala index 88fab9054b..3680a7996e 100644 --- a/src/compiler/scala/reflect/internal/TypeDebugging.scala +++ b/src/compiler/scala/reflect/internal/TypeDebugging.scala @@ -12,7 +12,33 @@ trait TypeDebugging { import definitions._ // @M toString that is safe during debugging (does not normalize, ...) - object TypeDebugStrings { + object typeDebug { + private def to_s(x: Any): String = x match { + // otherwise case classes are caught looking like products + case _: Tree | _: Type => "" + x + case x: TraversableOnce[_] => x mkString ", " + case x: Product => x.productIterator mkString ("(", ", ", ")") + case _ => "" + x + } + def ptIndent(x: Any) = ("" + x).replaceAll("\\n", " ") + def ptBlock(label: String, pairs: (String, Any)*): String = { + val width = pairs map (_._1.length) max + val fmt = "%-" + (width + 1) + "s %s" + val strs = pairs map { case (k, v) => fmt.format(k, to_s(v)) } + + strs.mkString(label + " {\n ", "\n ", "\n}") + } + def ptLine(label: String, pairs: (String, Any)*): String = { + val strs = pairs map { case (k, v) => k + "=" + to_s(v) } + strs.mkString(label + ": ", ", ", "") + } + def ptTree(t: Tree) = t match { + case PackageDef(pid, _) => "package " + pid + case ModuleDef(_, name, _) => "object " + name + case ClassDef(_, name, tparams, _) => "class " + name + str.brackets(tparams) + case _ => to_s(t) + } + object str { def parentheses(xs: List[_]): String = xs.mkString("(", ", ", ")") def brackets(xs: List[_]): String = if (xs.isEmpty) "" else xs.mkString("[", ", ", "]") @@ -62,11 +88,8 @@ trait TypeDebugging { } def debugString(tp: Type) = debug(tp) } - private def TDS = TypeDebugStrings - - def paramString(tp: Type) = TDS.str parentheses (tp.params map (_.defString)) - def typeParamsString(tp: Type) = TDS.str brackets (tp.typeParams map (_.defString)) - def typeArgsString(tp: Type) = TDS.str brackets (tp.typeArgs map (_.safeToString)) - def debugString(tp: Type) = TDS debugString tp + def paramString(tp: Type) = typeDebug.str parentheses (tp.params map (_.defString)) + def typeParamsString(tp: Type) = typeDebug.str brackets (tp.typeParams map (_.defString)) + def typeArgsString(tp: Type) = typeDebug.str brackets (tp.typeArgs map (_.safeToString)) + def debugString(tp: Type) = typeDebug debugString tp } - diff --git a/src/compiler/scala/reflect/internal/Types.scala b/src/compiler/scala/reflect/internal/Types.scala index 572cb8d3a4..0e9f2c44e1 100644 --- a/src/compiler/scala/reflect/internal/Types.scala +++ b/src/compiler/scala/reflect/internal/Types.scala @@ -79,8 +79,6 @@ trait Types /*extends reflect.generic.Types*/ { self: SymbolTable => private var explainSwitch = false private final val emptySymbolSet = immutable.Set.empty[Symbol] - private final val alternativeNarrow = false - private final val LogPendingSubTypesThreshold = 50 private final val LogPendingBaseTypesThreshold = 50 private final val LogVolatileThreshold = 50 @@ -1931,8 +1929,16 @@ A type's typeSymbol should never be inspected directly. case TypeRef(_, RepeatedParamClass, arg :: _) => return arg + "*" case TypeRef(_, ByNameParamClass, arg :: _) => return "=> " + arg case _ => - if (isFunctionType(this)) - return normalize.typeArgs.init.mkString("(", ", ", ")") + " => " + normalize.typeArgs.last + if (isFunctionType(this)) { + val targs = normalize.typeArgs + // Aesthetics: printing Function1 as T => R rather than (T) => R + val paramlist = targs.init match { + case Nil => "()" + case x :: Nil => "" + x + case xs => xs.mkString("(", ", ", ")") + } + return paramlist + " => " + targs.last + } else if (isTupleTypeOrSubtype(this)) return normalize.typeArgs.mkString("(", ", ", if (hasLength(normalize.typeArgs, 1)) ",)" else ")") else if (sym.isAliasType && prefixChain.exists(_.termSymbol.isSynthetic)) { @@ -2281,10 +2287,12 @@ A type's typeSymbol should never be inspected directly. //@M // a TypeVar used to be a case class with only an origin and a constr - // then, constr became mutable (to support UndoLog, I guess), but pattern-matching returned the original constr0 (a bug) + // then, constr became mutable (to support UndoLog, I guess), + // but pattern-matching returned the original constr0 (a bug) // now, pattern-matching returns the most recent constr object TypeVar { - // encapsulate suspension so we can automatically link the suspension of cloned typevars to their original if this turns out to be necessary + // encapsulate suspension so we can automatically link the suspension of cloned + // typevars to their original if this turns out to be necessary def Suspension = new Suspension class Suspension { private val suspended = mutable.HashSet[TypeVar]() @@ -2293,17 +2301,20 @@ A type's typeSymbol should never be inspected directly. suspended += tv } def resumeAll(): Unit = { - for(tv <- suspended) { + for (tv <- suspended) { tv.suspended = false } - suspended.clear + suspended.clear() } } def unapply(tv: TypeVar): Some[(Type, TypeConstraint)] = Some((tv.origin, tv.constr)) def apply(origin: Type, constr: TypeConstraint) = new TypeVar(origin, constr, List(), List()) - def apply(tparam: Symbol) = new TypeVar(tparam.tpeHK, new TypeConstraint, List(), tparam.typeParams) // TODO why not initialise TypeConstraint with bounds of tparam? - def apply(origin: Type, constr: TypeConstraint, args: List[Type], params: List[Symbol]) = new TypeVar(origin, constr, args, params) + // TODO why not initialise TypeConstraint with bounds of tparam? + // @PP: I tried that, didn't work out so well for me. + def apply(tparam: Symbol) = new TypeVar(tparam.tpeHK, new TypeConstraint, List(), tparam.typeParams) + def apply(origin: Type, constr: TypeConstraint, args: List[Type], params: List[Symbol]) = + new TypeVar(origin, constr, args, params) } /** A class representing a type variable @@ -2438,7 +2449,8 @@ A type's typeSymbol should never be inspected directly. } } - def registerTypeEquality(tp: Type, typeVarLHS: Boolean): Boolean = { //println("regTypeEq: "+(safeToString, debugString(tp), typeVarLHS)) //@MDEBUG + def registerTypeEquality(tp: Type, typeVarLHS: Boolean): Boolean = { + //println("regTypeEq: "+(safeToString, debugString(tp), typeVarLHS)) //@MDEBUG def checkIsSameType(tp: Type) = if(typeVarLHS) constr.inst =:= tp else tp =:= constr.inst @@ -2600,8 +2612,7 @@ A type's typeSymbol should never be inspected directly. // Creators --------------------------------------------------------------- - /** Rebind symbol `sym' to an overriding member in type - * `pre'. + /** Rebind symbol `sym' to an overriding member in type `pre'. */ private def rebind(pre: Type, sym: Symbol): Symbol = { val owner = sym.owner @@ -2642,7 +2653,7 @@ A type's typeSymbol should never be inspected directly. } /** the canonical creator for a refined type with a given scope */ - def refinedType(parents: List[Type], owner: Symbol, decls: Scope, pos : Position): Type = { + def refinedType(parents: List[Type], owner: Symbol, decls: Scope, pos: Position): Type = { if (phase.erasedTypes) if (parents.isEmpty) ObjectClass.tpe else parents.head else { diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala index 0f5be1791b..c777652706 100644 --- a/src/compiler/scala/tools/nsc/Global.scala +++ b/src/compiler/scala/tools/nsc/Global.scala @@ -247,7 +247,6 @@ class Global(var currentSettings: Settings, var reporter: Reporter) extends Symb // debugging def checkPhase = wasActive(settings.check) def logPhase = isActive(settings.log) - def typerDebug = settings.Ytyperdebug.value def writeICode = settings.writeICode.value // showing/printing things @@ -270,9 +269,10 @@ class Global(var currentSettings: Settings, var reporter: Reporter) extends Symb def profileClass = settings.YprofileClass.value def profileMem = settings.YprofileMem.value - // XXX: short term, but I can't bear to add another option. - // scalac -Dscala.timings will make this true. + // shortish-term property based options def timings = sys.props contains "scala.timings" + def inferDebug = (sys.props contains "scalac.debug.infer") || settings.Yinferdebug.value + def typerDebug = (sys.props contains "scalac.debug.typer") || settings.Ytyperdebug.value } // True if -Xscript has been set, indicating a script run. @@ -350,6 +350,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter) extends Symb /** Switch to turn on detailed type logs */ var printTypings = opt.typerDebug + var printInfers = opt.inferDebug // phaseName = "parser" object syntaxAnalyzer extends { diff --git a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala index 921abd6795..b896883e38 100644 --- a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala @@ -141,6 +141,7 @@ trait ScalaSettings extends AbsScalaSettings with StandardScalaSettings { val Ybuildmanagerdebug = BooleanSetting ("-Ybuild-manager-debug", "Generate debug information for the Refined Build Manager compiler.") val Ytyperdebug = BooleanSetting ("-Ytyper-debug", "Trace all type assignments.") + val Yinferdebug = BooleanSetting ("-Yinfer-debug", "Trace type inference and implicit search.") val Ypmatdebug = BooleanSetting ("-Ypmat-debug", "Trace all pattern matcher activity.") val Yrepldebug = BooleanSetting ("-Yrepl-debug", "Trace all repl activity.") . withPostSetHook(_ => interpreter.replProps.debug setValue true) diff --git a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala index ba534d322c..33822fbd43 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala @@ -124,8 +124,10 @@ trait Contexts { self: Analyzer => var savedTypeBounds: List[(Symbol, Type)] = List() // saved type bounds // for type parameters which are narrowed in a GADT - var typingIndent: String = "" + var typingIndentLevel: Int = 0 + def typingIndent = " " * typingIndentLevel + def undetparamsString = if (undetparams.isEmpty) "" else undetparams.mkString("undetparams=", ", ", "") def undetparams = _undetparams def undetparams_=(ps: List[Symbol]) = { //System.out.println("undetparams = " + ps);//debug @@ -138,6 +140,13 @@ trait Contexts { self: Analyzer => tparams } + def withoutReportingErrors[T](op: => T): T = { + val saved = reportGeneralErrors + reportGeneralErrors = false + try op + finally reportGeneralErrors = saved + } + def withImplicitsDisabled[T](op: => T): T = { val saved = implicitsEnabled implicitsEnabled = false @@ -188,7 +197,7 @@ trait Contexts { self: Analyzer => c.reportAmbiguousErrors = this.reportAmbiguousErrors c.reportGeneralErrors = this.reportGeneralErrors c.diagnostic = this.diagnostic - c.typingIndent = typingIndent + c.typingIndentLevel = typingIndentLevel c.implicitsEnabled = this.implicitsEnabled c.checking = this.checking c.retyping = this.retyping @@ -208,8 +217,6 @@ trait Contexts { self: Analyzer => def makeNewImport(imp: Import): Context = make(unit, imp, owner, scope, new ImportInfo(imp, depth) :: imports) - - def make(tree: Tree, owner: Symbol, scope: Scope): Context = { if (tree == this.tree && owner == this.owner && scope == this.scope) this else make0(tree, owner, scope) @@ -328,28 +335,39 @@ trait Contexts { self: Analyzer => } else throw new TypeError(pos, msg) } - def outerContext(clazz: Symbol): Context = { - var c = this - while (c != NoContext && c.owner != clazz) c = c.outer.enclClass - c - } - def isLocal(): Boolean = tree match { - case Block(_,_) => true + case Block(_,_) => true case PackageDef(_, _) => false - case EmptyTree => false - case _ => outer.isLocal() + case EmptyTree => false + case _ => outer.isLocal() + } + + // nextOuter determines which context is searched next for implicits + // (after `this`, which contributes `newImplicits` below.) In + // most cases, it is simply the outer context: if we're owned by + // a constructor, the actual current context and the conceptual + // context are different when it comes to scoping. The current + // conceptual scope is the context enclosing the blocks which + // represent the constructor body (TODO: why is there more than one + // such block in the outer chain?) + private def nextOuter = { + // Drop the constructor body blocks, which come in varying numbers. + // -- If the first statement is in the constructor, scopingCtx == (constructor definition) + // -- Otherwise, scopingCtx == (the class which contains the constructor) + val scopingCtx = + if (owner.isConstructor) nextEnclosing(c => !c.tree.isInstanceOf[Block]) + else this + + scopingCtx.outer } def nextEnclosing(p: Context => Boolean): Context = if (this == NoContext || p(this)) this else outer.nextEnclosing(p) - override def toString(): String = { + override def toString = ( if (this == NoContext) "NoContext" - else owner.toString() + " @ " + tree.getClass() + - " " + tree.toString() + ", scope = " + scope.## + - " " + scope.toList + "\n:: " + outer.toString() - } + else "Context(%s@%s scope=%s)".format(owner.fullName, tree.getClass.getName split "[.$]" last, scope.##) + ) /** Is `sub' a subclass of `base' or a companion object of such a subclass? */ @@ -502,7 +520,7 @@ trait Contexts { self: Analyzer => def resetCache() { implicitsRunId = NoRunId implicitsCache = null - if (outer != null && outer != this) outer.resetCache + if (outer != null && outer != this) outer.resetCache() } /** A symbol `sym` qualifies as an implicit if it has the IMPLICIT flag set, @@ -518,8 +536,8 @@ trait Contexts { self: Analyzer => }) private def collectImplicits(syms: List[Symbol], pre: Type, imported: Boolean = false): List[ImplicitInfo] = - for (sym <- syms if isQualifyingImplicit(sym, pre, imported)) - yield new ImplicitInfo(sym.name, pre, sym) + for (sym <- syms if isQualifyingImplicit(sym, pre, imported)) yield + new ImplicitInfo(sym.name, pre, sym) private def collectImplicitImports(imp: ImportInfo): List[ImplicitInfo] = { val pre = imp.qual.tpe @@ -542,16 +560,6 @@ trait Contexts { self: Analyzer => } def implicitss: List[List[ImplicitInfo]] = { - // nextOuter determines which context is searched next for implicits (after `this`, which contributes `newImplicits` below) - // in most cases, it is simply the outer context - // if we're owned by a constructor, the actual current context and the conceptual context are different when it comes to scoping: - // the current conceptual scope is the context enclosing the blocks that represent the constructor body - // (TODO: why is there more than one such block in the outer chain?) - val scopingCtx = - if(owner.isConstructor) nextEnclosing(c => !c.tree.isInstanceOf[Block]) // drop the constructor body blocks (they come in varying numbers depending on whether we are in the ctor call in the first statement or after) - // scopingCtx == the constructor definition (if we were after the ctor call) or the class that contains this constructor (if we are in the ctor call) - else this - val nextOuter = scopingCtx.outer if (implicitsRunId != currentRunId) { implicitsRunId = currentRunId diff --git a/src/compiler/scala/tools/nsc/typechecker/EtaExpansion.scala b/src/compiler/scala/tools/nsc/typechecker/EtaExpansion.scala index 3ae99a5bed..eee75e1b2a 100644 --- a/src/compiler/scala/tools/nsc/typechecker/EtaExpansion.scala +++ b/src/compiler/scala/tools/nsc/typechecker/EtaExpansion.scala @@ -25,7 +25,7 @@ trait EtaExpansion { self: Analyzer => } def unapply(tree: Tree): Option[(List[ValDef], Tree, List[Tree])] = tree match { - case Function(vparams, Apply(fn, args)) if (vparams corresponds args)(isMatch) => // @PP: corresponds + case Function(vparams, Apply(fn, args)) if (vparams corresponds args)(isMatch) => Some((vparams, fn, args)) case _ => None diff --git a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala index 53b4f0dac6..da07723b89 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala @@ -28,9 +28,8 @@ trait Implicits { import global._ import definitions._ - - def traceImplicits = printTypings - import global.typer.{printTyping, deindentTyping, indentTyping} + import typeDebug.{ ptTree, ptBlock, ptLine } + import global.typer.{ printTyping, deindentTyping, indentTyping, printInference } /** Search for an implicit value. See the comment on `result` at the end of class `ImplicitSearch` * for more info how the search is conducted. @@ -46,16 +45,29 @@ trait Implicits { * @return A search result */ def inferImplicit(tree: Tree, pt: Type, reportAmbiguous: Boolean, isView: Boolean, context: Context): SearchResult = { - printTyping("Beginning implicit search for "+ tree +" expecting "+ pt + (if(isView) " looking for a view" else "")) + printInference("[inferImplicit%s] pt = %s".format( + if (isView) " view" else "", pt) + ) + printTyping( + ptBlock("infer implicit" + (if (isView) " view" else ""), + "tree" -> tree, + "pt" -> pt, + "undetparams" -> context.outer.undetparams + ) + ) indentTyping() - val rawTypeStart = startCounter(rawTypeImpl) + + val rawTypeStart = startCounter(rawTypeImpl) val findMemberStart = startCounter(findMemberImpl) - val subtypeStart = startCounter(subtypeImpl) + val subtypeStart = startCounter(subtypeImpl) val start = startTimer(implicitNanos) - if (traceImplicits && !tree.isEmpty && !context.undetparams.isEmpty) - println("typing implicit with undetermined type params: "+context.undetparams+"\n"+tree) + if (printInfers && !tree.isEmpty && !context.undetparams.isEmpty) + printTyping("typing implicit: %s %s".format(tree, context.undetparamsString)) + val result = new ImplicitSearch(tree, pt, isView, context.makeImplicit(reportAmbiguous)).bestImplicit + printInference("[inferImplicit] result: " + result) context.undetparams = context.undetparams filterNot result.subst.fromContains + stopTimer(implicitNanos, start) stopCounter(rawTypeImpl, rawTypeStart) stopCounter(findMemberImpl, findMemberStart) @@ -87,7 +99,8 @@ trait Implicits { * that were instantiated by the winning implicit. */ class SearchResult(val tree: Tree, val subst: TreeTypeSubstituter) { - override def toString = "SearchResult("+tree+", "+subst+")" + override def toString = "SearchResult(%s, %s)".format(tree, + if (subst.isEmpty) "" else subst) } lazy val SearchFailure = new SearchResult(EmptyTree, EmptyTreeTypeSubstituter) @@ -122,12 +135,9 @@ trait Implicits { tp.isError } - def isCyclicOrErroneous = try { - containsError(tpe) - } catch { - case ex: CyclicReference => - true - } + def isCyclicOrErroneous = + try containsError(tpe) + catch { case _: CyclicReference => true } override def equals(other: Any) = other match { case that: ImplicitInfo => @@ -137,7 +147,7 @@ trait Implicits { case _ => false } override def hashCode = name.## + pre.## + sym.## - override def toString = "ImplicitInfo(" + name + "," + pre + "," + sym + ")" + override def toString = name + ": " + tpe } /** A sentinel indicating no implicit was found */ @@ -222,7 +232,15 @@ trait Implicits { */ class ImplicitSearch(tree: Tree, pt: Type, isView: Boolean, context0: Context) extends Typer(context0) { - printTyping("begin implicit search: "+(tree, pt, isView, context.outer.undetparams)) + printTyping( + ptBlock("new ImplicitSearch", + "tree" -> tree, + "pt" -> pt, + "isView" -> isView, + "context0" -> context0, + "undetparams" -> context.outer.undetparams + ) + ) // assert(tree.isEmpty || tree.pos.isDefined, tree) import infer._ @@ -324,20 +342,29 @@ trait Implicits { if (isView) { val found = pt.typeArgs(0) val req = pt.typeArgs(1) + def defaultExplanation = + "Note that implicit conversions are not applicable because they are ambiguous:\n "+ + coreMsg+"are possible conversion functions from "+ found+" to "+req - /** A nice spot to explain some common situations a little - * less confusingly. - */ def explanation = { - if ((found =:= AnyClass.tpe) && (AnyRefClass.tpe <:< req)) - "Note: Any is not implicitly converted to AnyRef. You can safely\n" + - "pattern match x: AnyRef or cast x.asInstanceOf[AnyRef] to do so." - else if ((found <:< AnyValClass.tpe) && (AnyRefClass.tpe <:< req)) - "Note: primitive types are not implicitly converted to AnyRef.\n" + - "You can safely force boxing by casting x.asInstanceOf[AnyRef]." - else - "Note that implicit conversions are not applicable because they are ambiguous:\n "+ - coreMsg+"are possible conversion functions from "+ found+" to "+req + val sym = found.typeSymbol + // Explain some common situations a bit more clearly. + if (AnyRefClass.tpe <:< req) { + if (sym == AnyClass || sym == UnitClass) { + "Note: " + sym.name + " is not implicitly converted to AnyRef. You can safely\n" + + "pattern match `x: AnyRef` or cast `x.asInstanceOf[AnyRef]` to do so." + } + else boxedClass get sym match { + case Some(boxed) => + "Note: an implicit exists from " + sym.fullName + " => " + boxed.fullName + ", but\n" + + "methods inherited from Object are rendered ambiguous. This is to avoid\n" + + "a blanket implicit which would convert any " + sym.fullName + " to any AnyRef.\n" + + "You may wish to use a type ascription: `x: " + boxed.fullName + "`." + case _ => + defaultExplanation + } + } + else defaultExplanation } typeErrorMsg(found, req) + "\n" + explanation @@ -350,7 +377,6 @@ trait Implicits { /** The type parameters to instantiate */ val undetParams = if (isView) List() else context.outer.undetparams - /** Replace undetParams in type `tp` by Any/Nothing, according to variance */ def approximate(tp: Type) = if (undetParams.isEmpty) tp else tp.instantiateTypeParams(undetParams, undetParams map (_ => WildcardType)) @@ -364,7 +390,8 @@ trait Implicits { * @param info The given implicit info describing the implicit definition * @pre <code>info.tpe</code> does not contain an error */ - private def typedImplicit(info: ImplicitInfo, ptChecked: Boolean): SearchResult = + private def typedImplicit(info: ImplicitInfo, ptChecked: Boolean): SearchResult = { + printInference("[typedImplicit] " + info) (context.openImplicits find { case (tp, sym) => sym == tree.symbol && dominates(pt, tp)}) match { case Some(pending) => // println("Pending implicit "+pending+" dominates "+pt+"/"+undetParams) //@MDEBUG @@ -390,6 +417,7 @@ trait Implicits { context.openImplicits = context.openImplicits.tail } } + } /** Todo reconcile with definition of stability given in Types.scala */ private def isStable(tp: Type): Boolean = tp match { @@ -443,9 +471,23 @@ trait Implicits { private def typedImplicit0(info: ImplicitInfo, ptChecked: Boolean): SearchResult = { incCounter(plausiblyCompatibleImplicits) - - printTyping("typed impl for "+wildPt+"? "+info.name +":"+ depoly(info.tpe)+ " orig info= "+ info.tpe +"/"+undetParams+"/"+isPlausiblyCompatible(info.tpe, wildPt)+"/"+matchesPt(depoly(info.tpe), wildPt, List())+"/"+info.pre+"/"+isStable(info.pre)) - if (ptChecked || matchesPt(depoly(info.tpe), wildPt, List()) && isStable(info.pre)) + printTyping( + ptBlock("typedImplicit0", + "info.name" -> info.name, + "info.tpe" -> depoly(info.tpe), + "ptChecked" -> ptChecked, + "pt" -> wildPt, + "orig" -> ptBlock("info", + "matchesPt" -> matchesPt(depoly(info.tpe), wildPt, Nil), + "undetParams" -> undetParams, + "isPlausiblyCompatible" -> isPlausiblyCompatible(info.tpe, wildPt), + "info.pre" -> info.pre, + "isStable" -> isStable(info.pre) + ).replaceAll("\\n", "\n ") + ) + ) + + if (ptChecked || matchesPt(depoly(info.tpe), wildPt, Nil) && isStable(info.pre)) typedImplicit1(info) else SearchFailure @@ -458,7 +500,10 @@ trait Implicits { if (info.pre == NoPrefix) Ident(info.name) else Select(gen.mkAttributedQualifier(info.pre), info.name) } - printTyping("typedImplicit0 typing"+ itree +" with wildpt = "+ wildPt +" from implicit "+ info.name+":"+info.tpe) + printTyping("typedImplicit1 %s, pt=%s, from implicit %s:%s".format( + typeDebug.ptTree(itree), wildPt, info.name, info.tpe) + ) + def fail(reason: String): SearchResult = { if (settings.XlogImplicits.value) inform(itree+" is not a valid implicit value for "+pt+" because:\n"+reason) @@ -479,10 +524,14 @@ trait Implicits { incCounter(typedImplicits) - printTyping("typed implicit "+itree1+":"+itree1.tpe+", pt = "+wildPt) + printTyping("typed implicit %s:%s, pt=%s".format(itree1, itree1.tpe, wildPt)) val itree2 = if (isView) (itree1: @unchecked) match { case Apply(fun, _) => fun } else adapt(itree1, EXPRmode, wildPt) - printTyping("adapted implicit "+itree1.symbol+":"+itree2.tpe+" to "+wildPt) + + printTyping("adapted implicit %s:%s to %s".format( + itree1.symbol, itree2.tpe, wildPt) + ) + def hasMatchingSymbol(tree: Tree): Boolean = (tree.symbol == info.sym) || { tree match { case Apply(fun, _) => hasMatchingSymbol(fun) @@ -492,11 +541,26 @@ trait Implicits { } } - if (itree2.tpe.isError) SearchFailure - else if (hasMatchingSymbol(itree1)) { + if (itree2.tpe.isError) + SearchFailure + else if (!hasMatchingSymbol(itree1)) + fail("candidate implicit %s is shadowed by other implicit %s".format( + info.sym + info.sym.locationString, itree1.symbol + itree1.symbol.locationString)) + else { val tvars = undetParams map freshVar + if (matchesPt(itree2.tpe, pt.instantiateTypeParams(undetParams, tvars), undetParams)) { - printTyping("tvars = "+tvars+"/"+(tvars map (_.constr))) + printInference( + ptBlock("matchesPt", + "itree1" -> itree1, + "tvars" -> tvars, + "undetParams" -> undetParams + ) + ) + + if (tvars.nonEmpty) + printTyping(ptLine("" + info.sym, "tvars" -> tvars, "tvars.constr" -> tvars.map(_.constr))) + val targs = solvedTypes(tvars, undetParams, undetParams map varianceInType(pt), false, lubDepth(List(itree2.tpe, pt))) @@ -505,39 +569,42 @@ trait Implicits { // filter out failures from type inference, don't want to remove them from undetParams! // we must be conservative in leaving type params in undetparams - val AdjustedTypeArgs(okParams, okArgs) = adjustTypeArgs(undetParams, targs) // prototype == WildcardType: want to remove all inferred Nothing's - var subst = EmptyTreeTypeSubstituter - if (okParams.nonEmpty) { - subst = new TreeTypeSubstituter(okParams, okArgs) - subst traverse itree2 - } + // prototype == WildcardType: want to remove all inferred Nothings + val AdjustedTypeArgs(okParams, okArgs) = adjustTypeArgs(undetParams, targs) + val subst: TreeTypeSubstituter = + if (okParams.isEmpty) EmptyTreeTypeSubstituter + else { + val subst = new TreeTypeSubstituter(okParams, okArgs) + subst traverse itree2 + subst + } - // #2421b: since type inference (which may have been performed during implicit search) - // does not check whether inferred arguments meet the bounds of the corresponding parameter (see note in solvedTypes), - // must check again here: - // TODO: I would prefer to just call typed instead of duplicating the code here, but this is probably a hotspot (and you can't just call typed, need to force re-typecheck) + // #2421b: since type inference (which may have been + // performed during implicit search) does not check whether + // inferred arguments meet the bounds of the corresponding + // parameter (see note in solvedTypes), must check again + // here: + // TODO: I would prefer to just call typed instead of + // duplicating the code here, but this is probably a + // hotspot (and you can't just call typed, need to force + // re-typecheck) + // TODO: the return tree is ignored. This seems to make + // no difference, but it's bad practice regardless. itree2 match { - case TypeApply(fun, args) => typedTypeApply(itree2, EXPRmode, fun, args) + case TypeApply(fun, args) => typedTypeApply(itree2, EXPRmode, fun, args) case Apply(TypeApply(fun, args), _) => typedTypeApply(itree2, EXPRmode, fun, args) // t2421c - case _ => + case t => t } - val result = new SearchResult(itree2, subst) incCounter(foundImplicits) - if (traceImplicits) println("RESULT = "+result) - // println("RESULT = "+itree+"///"+itree1+"///"+itree2)//DEBUG + printInference("[typedImplicit1] SearchResult: " + result) result - } else { - printTyping("incompatible: "+itree2.tpe+" does not match "+pt.instantiateTypeParams(undetParams, tvars)) - - SearchFailure } + else fail("incompatible: %s does not match expected type %s".format( + itree2.tpe, pt.instantiateTypeParams(undetParams, tvars))) } - else if (settings.XlogImplicits.value) - fail("candidate implicit "+info.sym+info.sym.locationString+ - " is shadowed by other implicit: "+itree1.symbol+itree1.symbol.locationString) - else SearchFailure - } catch { + } + catch { case ex: TypeError => fail(ex.getMessage()) } } @@ -655,6 +722,16 @@ trait Implicits { // most frequent one first matches sortBy (x => if (isView) -x.useCountView else -x.useCountArg) } + def eligibleString = { + val args = List( + "search" -> pt, + "target" -> tree, + "isView" -> isView + ) ++ eligible.map("eligible" -> _) + + ptBlock("Implicit search in " + context, args: _*) + } + printInference(eligibleString) /** Faster implicit search. Overall idea: * - prune aggressively @@ -855,7 +932,7 @@ trait Implicits { val infoMap = new InfoMap getParts(tp)(infoMap, new mutable.HashSet(), Set()) - if (traceImplicits) println("companion implicits of "+tp+" = "+infoMap) + printInference("[companionImplicitMap] "+tp+" = "+infoMap) infoMap } @@ -996,7 +1073,7 @@ trait Implicits { inferImplicit(tree, appliedType(manifestClass.typeConstructor, List(tp)), true, false, context).tree def findSubManifest(tp: Type) = findManifest(tp, if (full) FullManifestClass else OptManifestClass) - def mot(tp0: Type)(implicit from: List[Symbol] = List(), to: List[Type] = List()): SearchResult = { + def mot(tp0: Type, from: List[Symbol], to: List[Type]): SearchResult = { implicit def wrapResult(tree: Tree): SearchResult = if (tree == EmptyTree) SearchFailure else new SearchResult(tree, new TreeTypeSubstituter(from, to)) @@ -1032,24 +1109,29 @@ trait Implicits { } else if (sym.isExistentiallyBound && full) { manifestFactoryCall("wildcardType", tp, findManifest(tp.bounds.lo), findManifest(tp.bounds.hi)) - } else if(undetParams contains sym) { // looking for a manifest of a type parameter that hasn't been inferred by now, can't do much, but let's not fail - mot(NothingClass.tpe)(sym :: from, NothingClass.tpe :: to) // #3859: need to include the mapping from sym -> NothingClass.tpe in the SearchResult + } + // looking for a manifest of a type parameter that hasn't been inferred by now, + // can't do much, but let's not fail + else if (undetParams contains sym) { + // #3859: need to include the mapping from sym -> NothingClass.tpe in the SearchResult + mot(NothingClass.tpe, sym :: from, NothingClass.tpe :: to) } else { - EmptyTree // a manifest should have been found by normal searchImplicit + // a manifest should have been found by normal searchImplicit + EmptyTree } case RefinedType(parents, decls) => // refinement is not generated yet if (hasLength(parents, 1)) findManifest(parents.head) - else if (full) manifestFactoryCall("intersectionType", tp, parents map (findSubManifest(_)): _*) - else mot(erasure.erasure.intersectionDominator(parents)) + else if (full) manifestFactoryCall("intersectionType", tp, parents map findSubManifest: _*) + else mot(erasure.erasure.intersectionDominator(parents), from, to) case ExistentialType(tparams, result) => - mot(tp1.skolemizeExistential) + mot(tp1.skolemizeExistential, from, to) case _ => EmptyTree } } - mot(tp) + mot(tp, Nil, Nil) } def wrapResult(tree: Tree): SearchResult = diff --git a/src/compiler/scala/tools/nsc/typechecker/Infer.scala b/src/compiler/scala/tools/nsc/typechecker/Infer.scala index 1bd2487089..fbad63d2c9 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Infer.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Infer.scala @@ -19,10 +19,11 @@ import symtab.Flags._ */ trait Infer { self: Analyzer => + import global._ import definitions._ - - private final val inferInfo = false //@MDEBUG + import typer.printInference + import typeDebug.ptBlock /* -- Type parameter inference utility functions --------------------------- */ @@ -70,13 +71,12 @@ trait Infer { private class DeferredNoInstance(getmsg: () => String) extends NoInstance("") { override def getMessage(): String = getmsg() } + private def ifNoInstance[T](f: String => T): PartialFunction[Throwable, T] = { + case x: NoInstance => f(x.getMessage) + } - /** map every TypeVar to its constraint.inst field. + /** Map every TypeVar to its constraint.inst field. * throw a NoInstance exception if a NoType or WildcardType is encountered. - * - * @param tp ... - * @return ... - * @throws NoInstance */ object instantiate extends TypeMap { private var excludedVars = immutable.Set[TypeVar]() @@ -87,7 +87,7 @@ trait Infer { if (constr.inst == NoType) { throw new DeferredNoInstance(() => "no unique instantiation of type variable " + origin + " could be found") - } else if (excludedVars contains tv) { + } else if (excludedVars(tv)) { throw new NoInstance("cyclic instantiation") } else { excludedVars += tv @@ -126,37 +126,37 @@ trait Infer { } } - /** Solve constraint collected in types <code>tvars</code>. + /** Solve constraint collected in types `tvars`. * * @param tvars All type variables to be instantiated. - * @param tparams The type parameters corresponding to <code>tvars</code> + * @param tparams The type parameters corresponding to `tvars` * @param variances The variances of type parameters; need to reverse * solution direction for all contravariant variables. - * @param upper When <code>true</code> search for max solution else min. + * @param upper When `true` search for max solution else min. * @throws NoInstance */ def solvedTypes(tvars: List[TypeVar], tparams: List[Symbol], variances: List[Int], upper: Boolean, depth: Int): List[Type] = { -// def boundsString(tvar: TypeVar) = -// "\n "+ -// ((tvar.constr.loBounds map (_ + " <: " + tvar.origin.typeSymbol.name)) ::: -// (tvar.constr.hiBounds map (tvar.origin.typeSymbol.name + " <: " + _)) mkString ", ") + if (!solve(tvars, tparams, variances, upper, depth)) { -// no panic, it's good enough to just guess a solution, we'll find out -// later whether it works. -// @M danger, Will Robinson! this means that you should never trust inferred type arguments! -// need to call checkBounds on the args/typars or type1 on the tree for the expression that results from type inference -// see e.g., #2421: implicit search had been ignoring this caveat -// throw new DeferredNoInstance(() => -// "no solution exists for constraints"+(tvars map boundsString)) + // no panic, it's good enough to just guess a solution, we'll find out + // later whether it works. *ZAP* @M danger, Will Robinson! this means + // that you should never trust inferred type arguments! + // + // Need to call checkBounds on the args/typars or type1 on the tree + // for the expression that results from type inference see e.g., #2421: + // implicit search had been ignoring this caveat + // throw new DeferredNoInstance(() => + // "no solution exists for constraints"+(tvars map boundsString)) + } + for (tvar <- tvars ; if tvar.constr.inst == tvar) { + if (tvar.origin.typeSymbol.info eq ErrorType) + // this can happen if during solving a cyclic type parameter + // such as T <: T gets completed. See #360 + tvar.constr.inst = ErrorType + else + assert(false, tvar.origin+" at "+tvar.origin.typeSymbol.owner) } - for (tvar <- tvars) - if (tvar.constr.inst == tvar) - if (tvar.origin.typeSymbol.info eq ErrorType) { - // this can happen if during solving a cyclic type parameter - // such as T <: T gets completed. See #360 - tvar.constr.inst = ErrorType - } else assert(false, tvar.origin+" at "+tvar.origin.typeSymbol.owner) tvars map instantiate } @@ -264,23 +264,22 @@ trait Infer { Console.println("" + pre + " " + sym.owner + " " + context.owner + " " + context.outer.enclClass.owner + " " + sym.owner.thisType + (pre =:= sym.owner.thisType)) } new AccessError(tree, sym, pre, - if (settings.check.isDefault) { + if (settings.check.isDefault) analyzer.lastAccessCheckDetails - } else { - "\n because of an internal error (no accessible symbol):" + - "\nsym = " + sym + - "\nunderlying(sym) = " + underlying(sym) + - "\npre = " + pre + - "\nsite = " + site + - "\ntree = " + tree + - "\nsym.accessBoundary(sym.owner) = " + sym.accessBoundary(sym.owner) + - "\nsym.ownerChain = " + sym.ownerChain + - "\nsym.owner.thisType = " + sym.owner.thisType + - "\ncontext.owner = " + context.owner + - "\ncontext.outer.enclClass.owner = " + context.outer.enclClass.owner - } + else + ptBlock("because of an internal error (no accessible symbol)", + "sym.ownerChain" -> sym.ownerChain, + "underlying(sym)" -> underlying(sym), + "pre" -> pre, + "site" -> site, + "tree" -> tree, + "sym.accessBoundary(sym.owner)" -> sym.accessBoundary(sym.owner), + "context.owner" -> context.owner, + "context.outer.enclClass.owner" -> context.outer.enclClass.owner + ) ) - } else { + } + else { if(sym1.isTerm) sym1.cookJavaRawInfo() // xform java rawtypes into existentials @@ -585,73 +584,82 @@ trait Infer { * Undetermined type arguments are represented by `definitions.NothingClass.tpe'. * No check that inferred parameters conform to their bounds is made here. * - * bq: was private, but need it for unapply checking - * * @param tparams the type parameters of the method * @param formals the value parameter types of the method * @param restp the result type of the method * @param argtpes the argument types of the application * @param pt the expected return type of the application * @return @see adjustTypeArgs - + * * @throws NoInstance */ def methTypeArgs(tparams: List[Symbol], formals: List[Type], restpe: Type, argtpes: List[Type], pt: Type): AdjustedTypeArgs.Result = { val tvars = tparams map freshVar - if (inferInfo) - println("methTypeArgs tparams = "+tparams+ - ", formals = "+formals+ - ", restpe = "+restpe+ - ", argtpes = "+argtpes+ - ", pt = "+pt+ - ", tvars = "+tvars+" "+(tvars map (_.constr))) - if (!sameLength(formals, argtpes)) { + if (!sameLength(formals, argtpes)) throw new NoInstance("parameter lists differ in length") - } - if (inferInfo) // @MDEBUG - println("methTypeArgs "+ - " tparams = "+tparams+"\n"+ - " formals = "+formals+"\n"+ - " restpe = "+restpe+"\n"+ - " restpe_inst = "+restpe.instantiateTypeParams(tparams, tvars)+"\n"+ - " argtpes = "+argtpes+"\n"+ - " pt = "+pt) - - // check first whether type variables can be fully defined from - // expected result type. - if (!isConservativelyCompatible(restpe.instantiateTypeParams(tparams, tvars), pt)) { -// just wait and instantiate from the arguments. -// that way, we can try to apply an implicit conversion afterwards. -// This case could happen if restpe is not fully defined, so that -// search for an implicit from it to pt fails because of an ambiguity. -// See #0347. Therefore, the following two lines are commented out. -// throw new DeferredNoInstance(() => -// "result type " + normalize(restpe) + " is incompatible with expected type " + pt) - } + val restpeInst = restpe.instantiateTypeParams(tparams, tvars) + printInference( + ptBlock("methTypeArgs", + "tparams" -> tparams, + "formals" -> formals, + "restpe" -> restpe, + "restpeInst" -> restpeInst, + "argtpes" -> argtpes, + "pt" -> pt, + "tvars" -> tvars, + "constraints" -> tvars.map(_.constr) + ) + ) + + // first check if typevars can be fully defined from the expected type. + // The return value isn't used so I'm making it obvious that this side + // effects, because a function called "isXXX" is not the most obvious + // side effecter. + isConservativelyCompatible(restpeInst, pt) + + // Return value unused with the following explanation: + // + // Just wait and instantiate from the arguments. That way, + // we can try to apply an implicit conversion afterwards. + // This case could happen if restpe is not fully defined, so the + // search for an implicit from restpe => pt fails due to ambiguity. + // See #347. Therefore, the following two lines are commented out. + // + // throw new DeferredNoInstance(() => + // "result type " + normalize(restpe) + " is incompatible with expected type " + pt) + for (tvar <- tvars) if (!isFullyDefined(tvar)) tvar.constr.inst = NoType // Then define remaining type variables from argument types. (argtpes, formals).zipped map { (argtpe, formal) => - //@M isCompatible has side-effect: isSubtype0 will register subtype checks in the tvar's bounds - if (!isCompatible(argtpe.deconst.instantiateTypeParams(tparams, tvars), - formal.instantiateTypeParams(tparams, tvars))) { + val tp1 = argtpe.deconst.instantiateTypeParams(tparams, tvars) + val pt1 = formal.instantiateTypeParams(tparams, tvars) + + // Note that isCompatible side-effects: subtype checks involving typevars + // are recorded in the typevar's bounds (see TypeConstraint) + if (!isCompatible(tp1, pt1)) { throw new DeferredNoInstance(() => - "argument expression's type is not compatible with formal parameter type" + - foundReqMsg(argtpe.deconst.instantiateTypeParams(tparams, tvars), formal.instantiateTypeParams(tparams, tvars))) + "argument expression's type is not compatible with formal parameter type" + foundReqMsg(tp1, pt1)) } - () } - if (inferInfo) - println("solve "+tvars+" "+(tvars map (_.constr))) - val targs = solvedTypes(tvars, tparams, tparams map varianceInTypes(formals), - false, lubDepth(formals) max lubDepth(argtpes)) -// val res = - adjustTypeArgs(tparams, targs, restpe) -// println("meth type args "+", tparams = "+tparams+", formals = "+formals+", restpe = "+restpe+", argtpes = "+argtpes+", underlying = "+(argtpes map (_.widen))+", pt = "+pt+", uninstantiated = "+uninstantiated.toList+", result = "+res) //DEBUG -// res + val targs = solvedTypes( + tvars, tparams, tparams map varianceInTypes(formals), + false, lubDepth(formals) max lubDepth(argtpes) + ) + val result = adjustTypeArgs(tparams, targs, restpe) + + printInference( + ptBlock("methTypeArgs result", + "tvars" -> tvars, + "constraints" -> tvars.map(_.constr), + "targs" -> targs, + "adjusted type args" -> result + ) + ) + result } private[typechecker] def followApply(tp: Type): Type = tp match { @@ -684,9 +692,8 @@ trait Infer { * - namesOK is false when there's an invalid use of named arguments */ private def checkNames(argtpes: List[Type], params: List[Symbol]) = { - val argPos = (new Array[Int](argtpes.length)) map (x => -1) - var positionalAllowed = true - var namesOK = true + val argPos = Array.fill(argtpes.length)(-1) + var positionalAllowed, namesOK = true var index = 0 val argtpes1 = argtpes map { case NamedType(name, tp) => // a named argument @@ -1076,19 +1083,23 @@ trait Infer { * attempts fail, an error is produced. */ def inferArgumentInstance(tree: Tree, undetparams: List[Symbol], strictPt: Type, lenientPt: Type) { - if (inferInfo) - println("infer argument instance "+tree+":"+tree.tpe+"\n"+ - " undetparams = "+undetparams+"\n"+ - " strict pt = "+strictPt+"\n"+ - " lenient pt = "+lenientPt) + printInference( + ptBlock("inferArgumentInstance", + "tree" -> tree, + "tree.tpe" -> tree.tpe, + "undetparams" -> undetparams, + "strictPt" -> strictPt, + "lenientPt" -> lenientPt + ) + ) var targs = exprTypeArgs(undetparams, tree.tpe, strictPt) if ((targs eq null) || !(tree.tpe.subst(undetparams, targs) <:< strictPt)) { targs = exprTypeArgs(undetparams, tree.tpe, lenientPt) } substExpr(tree, undetparams, targs, lenientPt) + printInference("[inferArgumentInstance] finished, targs = " + targs) } - /** Infer type arguments `targs` for `tparams` of polymorphic expression in `tree`, given prototype `pt`. * * Substitute `tparams` to `targs` in `tree`, after adjustment by `adjustTypeArgs`, returning the type parameters that were not determined @@ -1096,11 +1107,14 @@ trait Infer { */ def inferExprInstance(tree: Tree, tparams: List[Symbol], pt: Type = WildcardType, treeTp0: Type = null, keepNothings: Boolean = true, checkCompat: (Type, Type) => Boolean = isCompatible): List[Symbol] = { val treeTp = if(treeTp0 eq null) tree.tpe else treeTp0 // can't refer to tree in default for treeTp0 - if (inferInfo) - println("infer expr instance "+tree+":"+tree.tpe+"\n"+ - " tparams = "+tparams+"\n"+ - " pt = "+pt) - + printInference( + ptBlock("inferExprInstance", + "tree" -> tree, + "tree.tpe"-> tree.tpe, + "tparams" -> tparams, + "pt" -> pt + ) + ) val targs = exprTypeArgs(tparams, treeTp, pt, checkCompat) if (keepNothings || (targs eq null)) { //@M: adjustTypeArgs fails if targs==null, neg/t0226 @@ -1108,7 +1122,13 @@ trait Infer { List() } else { val AdjustedTypeArgs.Undets(okParams, okArgs, leftUndet) = adjustTypeArgs(tparams, targs) - if (inferInfo) println("inferred expr instance for "+ tree +" --> (okParams, okArgs, leftUndet)= "+(okParams, okArgs, leftUndet)) + printInference( + ptBlock("inferExprInstance/AdjustedTypeArgs", + "okParams" -> okParams, + "okArgs" -> okArgs, + "leftUndet" -> leftUndet + ) + ) substExpr(tree, okParams, okArgs, pt) leftUndet } @@ -1146,39 +1166,49 @@ trait Infer { def inferMethodInstance(fn: Tree, undetparams: List[Symbol], args: List[Tree], pt0: Type): List[Symbol] = fn.tpe match { case MethodType(params0, _) => - if (inferInfo) - println("infer method instance "+fn+"\n"+ - " undetparams = "+undetparams+"\n"+ - " args = "+args+"\n"+ - " pt = "+pt0) + printInference( + ptBlock("inferMethodInstance", + "fn" -> fn, + "undetparams" -> undetparams, + "args" -> args, + "pt0" -> pt0 + ) + ) + try { - val pt = if (pt0.typeSymbol == UnitClass) WildcardType else pt0 + val pt = if (pt0.typeSymbol == UnitClass) WildcardType else pt0 val formals = formalTypes(params0 map (_.tpe), args.length) val argtpes = actualTypes(args map (_.tpe.deconst), formals.length) - val restpe = fn.tpe.resultType(argtpes) - val AdjustedTypeArgs.AllArgsAndUndets(okparams, okargs, allargs, leftUndet) = methTypeArgs(undetparams, formals, restpe, argtpes, pt) + val restpe = fn.tpe.resultType(argtpes) + + val AdjustedTypeArgs.AllArgsAndUndets(okparams, okargs, allargs, leftUndet) = + methTypeArgs(undetparams, formals, restpe, argtpes, pt) + checkBounds(fn.pos, NoPrefix, NoSymbol, undetparams, allargs, "inferred ") val treeSubst = new TreeTypeSubstituter(okparams, okargs) - treeSubst.traverse(fn) - treeSubst.traverseTrees(args) - if(leftUndet nonEmpty) { // #3890 - val leftUndet1 = treeSubst.typeSubst mapOver leftUndet - if(leftUndet ne leftUndet1) { - val symSubst = new TreeSymSubstTraverser(leftUndet, leftUndet1) - symSubst.traverse(fn) - symSubst.traverseTrees(args) - } - leftUndet1 - } else leftUndet - } catch { - case ex: NoInstance => - errorTree(fn, - "no type parameters for " + - applyErrorMsg( - fn, " exist so that it can be applied to arguments ", - args map (_.tpe.widen), WildcardType) + - "\n --- because ---\n" + ex.getMessage()) - List() + treeSubst traverseTrees fn :: args + + val result = leftUndet match { + case Nil => Nil + case xs => + // #3890 + val xs1 = treeSubst.typeSubst mapOver xs + if (xs ne xs1) + new TreeSymSubstTraverser(xs, xs1) traverseTrees fn :: args + + xs1 + } + if (result.nonEmpty) + printInference("inferMethodInstance, still undetermined: " + result) + + result + } + catch ifNoInstance { msg => + errorTree(fn, "no type parameters for " + + applyErrorMsg(fn, " exist so that it can be applied to arguments ", args map (_.tpe.widen), WildcardType) + + "\n --- because ---\n" + msg + ) + Nil } } @@ -1189,7 +1219,7 @@ trait Infer { case TypeRef(_, sym, _) if sym.isAliasType => widen(tp.normalize) case rtp @ RefinedType(parents, decls) => - copyRefinedType(rtp, parents mapConserve (widen), decls) + copyRefinedType(rtp, parents mapConserve widen, decls) case AnnotatedType(_, underlying, _) => widen(underlying) case _ => @@ -1498,14 +1528,11 @@ trait Infer { */ def inferExprAlternative(tree: Tree, pt: Type): Unit = tree.tpe match { case OverloadedType(pre, alts) => tryTwice { - var alts1 = alts filter (alt => isWeaklyCompatible(pre.memberType(alt), pt)) + val alts0 = alts filter (alt => isWeaklyCompatible(pre.memberType(alt), pt)) + val secondTry = alts0.isEmpty + val alts1 = if (secondTry) alts else alts0 + //println("trying "+alts1+(alts1 map (_.tpe))+(alts1 map (_.locationString))+" for "+pt) - val applicable = alts1 - var secondTry = false - if (alts1.isEmpty) { - alts1 = alts - secondTry = true - } def improves(sym1: Symbol, sym2: Symbol): Boolean = sym2 == NoSymbol || sym2.hasAnnotation(BridgeClass) || { val tp1 = pre.memberType(sym1) @@ -1513,9 +1540,12 @@ trait Infer { (tp2 == ErrorType || !global.typer.infer.isWeaklyCompatible(tp2, pt) && global.typer.infer.isWeaklyCompatible(tp1, pt) || isStrictlyMoreSpecific(tp1, tp2, sym1, sym2)) } + val best = ((NoSymbol: Symbol) /: alts1) ((best, alt) => if (improves(alt, best)) alt else best) + val competing = alts1 dropWhile (alt => best == alt || improves(best, alt)) + if (best == NoSymbol) { if (settings.debug.value) { tree match { @@ -1636,9 +1666,7 @@ trait Infer { if (context.implicitsEnabled) { val reportGeneralErrors = context.reportGeneralErrors context.reportGeneralErrors = false - try { - context.withImplicitsDisabled(infer) - } + try context.withImplicitsDisabled(infer) catch { case ex: CyclicReference => throw ex case ex: TypeError => diff --git a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala index f1eb904c58..56d5ce9842 100644 --- a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala +++ b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala @@ -1147,11 +1147,12 @@ abstract class RefChecks extends InfoTransform { */ private def checkDeprecated(sym: Symbol, pos: Position) { if (sym.isDeprecated && !currentOwner.ownerChain.exists(x => x.isDeprecated || x.hasBridgeAnnotation)) { - val dmsg = sym.deprecationMessage map (": " + _) getOrElse "" - - unit.deprecationWarning(pos, sym.fullLocationString + " is deprecated" + dmsg) + unit.deprecationWarning(pos, "%s%s is deprecated%s".format( + sym, sym.locationString, sym.deprecationMessage map (": " + _) getOrElse "") + ) } } + /** Similar to deprecation: check if the symbol is marked with @migration * indicating it has changed semantics between versions. */ @@ -1230,7 +1231,7 @@ abstract class RefChecks extends InfoTransform { private def transformCaseApply(tree: Tree, ifNot: => Unit) = { val sym = tree.symbol - if (sym.isSourceMethod && sym.hasFlag(CASE) && sym.name == nme.apply) + if (sym.isSourceMethod && sym.isCase && sym.name == nme.apply) toConstructor(tree.pos, tree.tpe) else { ifNot diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 114fa7ed1b..d2218cd977 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -80,6 +80,7 @@ trait Typers extends Modes { abstract class Typer(context0: Context) extends TyperDiagnostics { import context0.unit + import typeDebug.{ ptTree, ptBlock, ptLine } val infer = new Inferencer(context0) { override def isCoercible(tp: Type, pt: Type): Boolean = undoLog undo { // #3281 @@ -728,7 +729,7 @@ trait Typers extends Modes { val tree1 = if (tree.isType) tree else TypeApply(tree, tparams1 map (tparam => TypeTree(tparam.tpeHK) setPos tree.pos.focus)) setPos tree.pos //@M/tcpolyinfer: changed tparam.tpe to tparam.tpeHK - context.undetparams = context.undetparams ::: tparams1 + context.undetparams ++= tparams1 adapt(tree1 setType restpe.substSym(tparams, tparams1), mode, pt, original) case mt: MethodType if mt.isImplicit && ((mode & (EXPRmode | FUNmode | LHSmode)) == EXPRmode) => // (4.1) if (context.undetparams nonEmpty) { // (9) -- should revisit dropped condition `(mode & POLYmode) == 0` @@ -1315,7 +1316,6 @@ trait Typers extends Modes { treeCopy.ModuleDef(mdef, typedMods, mdef.name, impl2) setType NoType } - /** In order to override this in the TreeCheckers Typer so synthetics aren't re-added * all the time, it is exposed here the module/class typing methods go through it. */ @@ -2973,16 +2973,17 @@ trait Typers extends Modes { errorTree(tree, treeSymTypeMsg(fun)+" does not take type parameters.") } - @inline final def deindentTyping() = if (printTypings) context.typingIndent = context.typingIndent.substring(0, context.typingIndent.length() - 2) - @inline final def indentTyping() = if (printTypings) context.typingIndent += " " - @inline final def printTyping(s: => String) = if (printTypings) println(context.typingIndent+s) + @inline final def deindentTyping() = context.typingIndentLevel -= 2 + @inline final def indentTyping() = context.typingIndentLevel += 2 + @inline final def printTyping(s: => String) = { + if (printTypings) + println(context.typingIndent + s.replaceAll("\n", "\n" + context.typingIndent)) + } + @inline final def printInference(s: => String) = { + if (printInfers) + println(s) + } - /** - * @param tree ... - * @param mode ... - * @param pt ... - * @return ... - */ protected def typed1(tree: Tree, mode: Int, pt: Type): Tree = { def isPatternMode = inPatternMode(mode) @@ -3295,9 +3296,13 @@ trait Typers extends Modes { case _ => Nil }) def errorInResult(tree: Tree) = treesInResult(tree) exists (_.pos == ex.pos) - - if (fun :: tree :: args exists errorInResult) { - printTyping("second try for: "+fun+" and "+args) + val retry = fun :: tree :: args exists errorInResult + printTyping({ + val funStr = ptTree(fun) + " and " + (args map ptTree mkString ", ") + if (retry) "second try: " + funStr + else "no second try: " + funStr + " because error not in result: " + ex.pos+"!="+tree.pos + }) + if (retry) { val Select(qual, name) = fun val args1 = tryTypedArgs(args, forArgMode(fun, mode), ex) val qual1 = @@ -3307,8 +3312,7 @@ trait Typers extends Modes { val tree1 = Apply(Select(qual1, name) setPos fun.pos, args1) setPos tree.pos return typed1(tree1, mode | SNDTRYmode, pt) } - } else printTyping("no second try for "+fun+" and "+args+" because error not in result:"+ex.pos+"!="+tree.pos) - + } reportTypeError(tree.pos, ex) setError(tree) } @@ -4186,7 +4190,9 @@ trait Typers extends Modes { * @param pt ... * @return ... */ - def typed(tree: Tree, mode: Int, pt: Type): Tree = { indentTyping() + def typed(tree: Tree, mode: Int, pt: Type): Tree = { + indentTyping() + def dropExistential(tp: Type): Type = tp match { case ExistentialType(tparams, tpe) => if (settings.debug.value) @@ -4199,6 +4205,7 @@ trait Typers extends Modes { case _ => tp } + var alreadyTyped = false try { if (Statistics.enabled) { val t = currentTime() @@ -4213,15 +4220,34 @@ trait Typers extends Modes { tree.tpe = null if (tree.hasSymbol) tree.symbol = NoSymbol } - printTyping("typing "+tree+", pt = "+pt+", undetparams = "+context.undetparams+", implicits-enabled = "+context.implicitsEnabled+", silent = "+context.reportGeneralErrors+", context.owner = "+context.owner) //DEBUG - var tree1 = if (tree.tpe ne null) tree else typed1(tree, mode, dropExistential(pt)) - printTyping("typed "+tree1+":"+tree1.tpe+(if (isSingleType(tree1.tpe)) " with underlying "+tree1.tpe.widen else "")+", undetparams = "+context.undetparams+", pt = "+pt) //DEBUG + alreadyTyped = tree.tpe ne null + var tree1: Tree = if (alreadyTyped) tree else { + printTyping( + ptLine("typing %s: pt = %s".format(ptTree(tree), pt), + "undetparams" -> context.undetparams, + "implicitsEnabled" -> context.implicitsEnabled, + "silent" -> !context.reportGeneralErrors, + "context.owner" -> context.owner + ) + ) + val tree1 = typed1(tree, mode, dropExistential(pt)) + printTyping("typed %s: %s%s".format( + ptTree(tree1), tree1.tpe, + if (isSingleType(tree1.tpe)) " with underlying "+tree1.tpe.widen else "") + ) + tree1 + } tree1.tpe = addAnnotations(tree1, tree1.tpe) - val result = if (tree1.isEmpty) tree1 else adapt(tree1, mode, pt, tree) - printTyping("adapted "+tree1+":"+tree1.tpe.widen+" to "+pt+", "+context.undetparams) //DEBUG + + if (!alreadyTyped) { + printTyping("adapted %s: %s to %s, %s".format( + tree1, tree1.tpe.widen, pt, context.undetparamsString) + ) //DEBUG + } + // for (t <- tree1.tpe) assert(t != WildcardType) // if ((mode & TYPEmode) != 0) println("type: "+tree1+" has type "+tree1.tpe) if (phase.id <= currentRun.typerPhase.id) signalDone(context.asInstanceOf[analyzer.Context], tree, result) @@ -4229,7 +4255,7 @@ trait Typers extends Modes { } catch { case ex: TypeError => tree.tpe = null - printTyping("caught "+ex+" in typed: "+tree) //DEBUG + printTyping("caught %s: while typing %s".format(ex, tree)) //DEBUG reportTypeError(tree.pos, ex) setError(tree) case ex: Exception => @@ -4242,6 +4268,7 @@ trait Typers extends Modes { } finally { deindentTyping() + if (Statistics.enabled) { val t = currentTime() microsByType(pendingTreeTypes.head) += ((t - typerTime) / 1000).toInt |