From e658b63b40d21a3420eb978fecd220f2b11356dd Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Tue, 9 Apr 2013 10:20:06 +0200 Subject: SI-7345 Represent the boolean modes in Context in ContextMode. - This is a value class in the same spirit of Mode. - Code that temporarily changes mode can be simplified by saving and restoring this one field. --- .../scala/tools/nsc/typechecker/Contexts.scala | 220 ++++++++++++--------- src/reflect/scala/reflect/internal/Mode.scala | 2 +- 2 files changed, 131 insertions(+), 91 deletions(-) diff --git a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala index e89a860e0f..b5b77072ca 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala @@ -17,6 +17,7 @@ import scala.reflect.internal.util.shortClassOfInstance trait Contexts { self: Analyzer => import global._ import definitions.{ JavaLangPackage, ScalaPackage, PredefModule } + import ContextMode._ object NoContext extends Context { outer = this @@ -95,8 +96,7 @@ trait Contexts { self: Analyzer => } val c = sc.make(unit, tree, sc.owner, sc.scope, sc.imports) if (erasedTypes) c.setThrowErrors() else c.setReportErrors() - c.implicitsEnabled = !erasedTypes - c.enrichmentEnabled = c.implicitsEnabled + c(EnrichmentEnabled | ImplicitsEnabled) = !erasedTypes c } @@ -111,17 +111,7 @@ trait Contexts { self: Analyzer => } } - private object Errors { - final val ReportErrors = 1 << 0 - final val BufferErrors = 1 << 1 - final val AmbiguousErrors = 1 << 2 - final val notThrowMask = ReportErrors | BufferErrors - final val AllMask = ReportErrors | BufferErrors | AmbiguousErrors - } - class Context private[typechecker] { - import Errors._ - var unit: CompilationUnit = NoCompilationUnit var tree: Tree = _ // Tree associated with this context var owner: Symbol = NoSymbol // The current owner @@ -135,6 +125,16 @@ trait Contexts { self: Analyzer => try a finally enclClass = saved } + private var mode: ContextMode = ContextMode.DefaultMode + def update(mask: ContextMode, value: Boolean) { + mode = mode.set(value, mask) + } + def set(enable: ContextMode = NOmode, disable: ContextMode = NOmode): this.type = { + mode = mode.set(true, enable).set(false, disable) + this + } + def apply(mask: ContextMode): Boolean = mode.inAll(mask) + var enclMethod: Context = _ // The next outer context whose tree is a method var variance: Variance = Variance.Invariant // Variance relative to enclosing class private var _undetparams: List[Symbol] = List() // Undetermined type parameters, @@ -146,19 +146,26 @@ trait Contexts { self: Analyzer => // for a named application block (Tree) the corresponding NamedApplyInfo var namedApplyBlockInfo: Option[(Tree, NamedApplyInfo)] = None var prefix: Type = NoPrefix - var inConstructorSuffix = false // are we in a secondary constructor - // after the this constructor call? - var returnsSeen = false // for method context: were returns encountered? - var inSelfSuperCall = false // is this context (enclosed in) a constructor call? - // (the call to the super or self constructor in the first line of a constructor) - // in this context the object's fields should not be in scope + + def inConstructorSuffix_=(value: Boolean) = this(ConstructorSuffix) = value + def inConstructorSuffix = this(ConstructorSuffix) + def returnsSeen_=(value: Boolean) = this(ReturnsSeen) = value + def returnsSeen = this(ReturnsSeen) + def inSelfSuperCall_=(value: Boolean) = this(SelfSuperCall) = value + def inSelfSuperCall = this(SelfSuperCall) var diagnostic: List[String] = Nil // these messages are printed when issuing an error - var implicitsEnabled = false - var macrosEnabled = true - var enrichmentEnabled = false // to selectively allow enrichment in patterns, where other kinds of implicit conversions are not allowed - var checking = false - var retyping = false + + def implicitsEnabled_=(value: Boolean) = this(ImplicitsEnabled) = value + def implicitsEnabled = this(ImplicitsEnabled) + def macrosEnabled_=(value: Boolean) = this(MacrosEnabled) = value + def macrosEnabled = this(MacrosEnabled) + def enrichmentEnabled_=(value: Boolean) = this(EnrichmentEnabled) = value + def enrichmentEnabled = this(EnrichmentEnabled) + def checking_=(value: Boolean) = this(Checking) = value + def checking = this(Checking) + def retyping_=(value: Boolean) = this(ReTyping) = value + def retyping = this(ReTyping) var savedTypeBounds: List[(Symbol, Type)] = List() // saved type bounds // for type parameters which are narrowed in a GADT @@ -186,27 +193,27 @@ trait Contexts { self: Analyzer => tparams } - private[this] var mode = 0 - def errBuffer = buffer def hasErrors = buffer.nonEmpty def hasWarnings = warningsBuffer.nonEmpty - def state: Int = mode - def restoreState(state0: Int) = mode = state0 + def state: ContextMode = mode + def restoreState(state0: ContextMode) = mode = state0 - def reportErrors = (state & ReportErrors) != 0 - def bufferErrors = (state & BufferErrors) != 0 - def ambiguousErrors = (state & AmbiguousErrors) != 0 - def throwErrors = (state & notThrowMask) == 0 + def reportErrors = this(ReportErrors) + def bufferErrors = this(BufferErrors) + def ambiguousErrors = this(AmbiguousErrors) + def throwErrors = mode.inNone(ReportErrors | BufferErrors) - def setReportErrors() = mode = (ReportErrors | AmbiguousErrors) - def setBufferErrors() = { - //assert(bufferErrors || !hasErrors, "When entering the buffer state, context has to be clean. Current buffer: " + buffer) - mode = BufferErrors + def setReportErrors(): Unit = set(enable = ReportErrors | AmbiguousErrors, disable = BufferErrors) + def setBufferErrors(): Unit = {set(enable = BufferErrors, disable = ReportErrors | AmbiguousErrors); warnIfBufferNotClean()} + def setThrowErrors(): Unit = this(ReportErrors | AmbiguousErrors | BufferErrors) = false + def setAmbiguousErrors(report: Boolean): Unit = this(AmbiguousErrors) = report + + private def warnIfBufferNotClean() { + if (!bufferErrors && hasErrors) + devWarning("When entering the buffer state, context has to be clean. Current buffer: " + buffer) } - def setThrowErrors() = mode &= (~AllMask) - def setAmbiguousErrors(report: Boolean) = if (report) mode |= AmbiguousErrors else mode &= notThrowMask def updateBuffer(errors: mutable.Set[AbsTypeError]) = buffer ++= errors def condBufferFlush(removeP: AbsTypeError => Boolean) { @@ -225,50 +232,18 @@ trait Contexts { self: Analyzer => current } - def withImplicitsEnabled[T](op: => T): T = { - val saved = implicitsEnabled - implicitsEnabled = true - try op - finally implicitsEnabled = saved - } - - def withImplicitsDisabled[T](op: => T): T = { - val saved = implicitsEnabled - implicitsEnabled = false - val savedP = enrichmentEnabled - enrichmentEnabled = false - try op - finally { - implicitsEnabled = saved - enrichmentEnabled = savedP - } - } - - def withImplicitsDisabledAllowEnrichment[T](op: => T): T = { - val saved = implicitsEnabled - implicitsEnabled = false - val savedP = enrichmentEnabled - enrichmentEnabled = true + private def withMode[T](enabled: ContextMode = NOmode, disabled: ContextMode = NOmode)(op: => T): T = { + val saved = mode + set(enabled, disabled) try op - finally { - implicitsEnabled = saved - enrichmentEnabled = savedP - } + finally mode = saved } - def withMacrosEnabled[T](op: => T): T = { - val saved = macrosEnabled - macrosEnabled = true - try op - finally macrosEnabled = saved - } - - def withMacrosDisabled[T](op: => T): T = { - val saved = macrosEnabled - macrosEnabled = false - try op - finally macrosEnabled = saved - } + def withImplicitsEnabled[T](op: => T): T = withMode(enabled = ImplicitsEnabled)(op) + def withImplicitsDisabled[T](op: => T): T = withMode(disabled = ImplicitsEnabled | EnrichmentEnabled)(op) + def withImplicitsDisabledAllowEnrichment[T](op: => T): T = withMode(enabled = EnrichmentEnabled, disabled = ImplicitsEnabled)(op) + def withMacrosEnabled[T](op: => T): T = withMode(enabled = MacrosEnabled)(op) + def withMacrosDisabled[T](op: => T): T = withMode(disabled = MacrosEnabled)(op) def make(unit: CompilationUnit, tree: Tree, owner: Symbol, scope: Scope, imports: List[ImportInfo]): Context = { @@ -279,17 +254,18 @@ trait Contexts { self: Analyzer => c.scope = scope c.outer = this + c.restoreState(this.state) // note: ConstructorSuffix conditionally overwritten below. + tree match { case Template(_, _, _) | PackageDef(_, _) => c.enclClass = c c.prefix = c.owner.thisType - c.inConstructorSuffix = false + c(ConstructorSuffix) = false case _ => c.enclClass = this.enclClass c.prefix = if (c.owner != this.owner && c.owner.isTerm) NoPrefix else this.prefix - c.inConstructorSuffix = this.inConstructorSuffix } tree match { case DefDef(_, _, _, _, _, _) => @@ -297,18 +273,12 @@ trait Contexts { self: Analyzer => case _ => c.enclMethod = this.enclMethod } + c.variance = this.variance c.depth = if (scope == this.scope) this.depth else this.depth + 1 c.imports = imports - c.inSelfSuperCall = inSelfSuperCall - c.restoreState(this.state) c.diagnostic = this.diagnostic c.typingIndentLevel = typingIndentLevel - c.implicitsEnabled = this.implicitsEnabled - c.macrosEnabled = this.macrosEnabled - c.enrichmentEnabled = this.enrichmentEnabled - c.checking = this.checking - c.retyping = this.retyping c.openImplicits = this.openImplicits c.buffer = if (this.buffer == null) mutable.LinkedHashSet[AbsTypeError]() else this.buffer // need to initialize c.warningsBuffer = if (this.warningsBuffer == null) mutable.LinkedHashSet[(Position, String)]() else this.warningsBuffer @@ -355,8 +325,7 @@ trait Contexts { self: Analyzer => def makeImplicit(reportAmbiguousErrors: Boolean) = { val c = makeSilent(reportAmbiguousErrors) - c.implicitsEnabled = false - c.enrichmentEnabled = false + c(ImplicitsEnabled | EnrichmentEnabled) = false c } @@ -375,8 +344,8 @@ trait Contexts { self: Analyzer => while (baseContext.tree.isInstanceOf[Template]) baseContext = baseContext.outer val argContext = baseContext.makeNewScope(tree, owner) + argContext.restoreState(state) argContext.inSelfSuperCall = true - argContext.restoreState(this.state) def enterElems(c: Context) { def enterLocalElems(e: ScopeEntry) { if (e != null && e.owner == c.scope) { @@ -1107,3 +1076,74 @@ trait Contexts { self: Analyzer => override def safeToString = "ImportType("+expr+")" } } + +object ContextMode { + private implicit def liftIntBitsToContextState(bits: Int): ContextMode = apply(bits) + def apply(bits: Int): ContextMode = new ContextMode(bits) + final val NOmode: ContextMode = 0 + + final val ReportErrors: ContextMode = 1 << 0 + final val BufferErrors: ContextMode = 1 << 1 + final val AmbiguousErrors: ContextMode = 1 << 2 + + /** Are we in a secondary constructor after the this constructor call? */ + final val ConstructorSuffix: ContextMode = 1 << 3 + + /** For method context: were returns encountered? */ + final val ReturnsSeen: ContextMode = 1 << 4 + + /** Is this context (enclosed in) a constructor call? + * (the call to the super or self constructor in the first line of a constructor.) + * In such a context, the object's fields should not be in scope + */ + final val SelfSuperCall: ContextMode = 1 << 5 + + // TODO harvest documentation for this + final val ImplicitsEnabled: ContextMode = 1 << 6 + + final val MacrosEnabled: ContextMode = 1 << 7 + + /** To selectively allow enrichment in patterns, where other kinds of implicit conversions are not allowed */ + final val EnrichmentEnabled: ContextMode = 1 << 8 + + /** Are we in a run of [[scala.tools.nsc.typechecker.TreeCheckers]]? */ + final val Checking: ContextMode = 1 << 9 + + // TODO harvest documentation for this + final val ReTyping: ContextMode = 1 << 10 + + final val DefaultMode: ContextMode = MacrosEnabled + + private val contextModeNameMap = Map( + ReportErrors -> "ReportErrors", + BufferErrors -> "BufferErrors", + AmbiguousErrors -> "AmbiguousErrors", + ConstructorSuffix -> "ConstructorSuffix", + SelfSuperCall -> "SelfSuperCall", + ImplicitsEnabled -> "ImplicitsEnabled", + MacrosEnabled -> "MacrosEnabled", + Checking -> "Checking", + ReTyping -> "ReTyping" + ) +} + +/** + * A value class to carry the boolean flags of a context, such as whether errors should + * be buffered or reported. + */ +final class ContextMode private (val bits: Int) extends AnyVal { + import ContextMode._ + + def &(other: ContextMode): ContextMode = new ContextMode(bits & other.bits) + def |(other: ContextMode): ContextMode = new ContextMode(bits | other.bits) + def &~(other: ContextMode): ContextMode = new ContextMode(bits & ~(other.bits)) + def set(value: Boolean, mask: ContextMode) = if (value) |(mask) else &~(mask) + + def inAll(required: ContextMode) = (this & required) == required + def inAny(required: ContextMode) = (this & required) != NOmode + def inNone(prohibited: ContextMode) = (this & prohibited) == NOmode + + override def toString = + if (bits == 0) "NOmode" + else (contextModeNameMap filterKeys inAll).values.toList.sorted mkString " " +} diff --git a/src/reflect/scala/reflect/internal/Mode.scala b/src/reflect/scala/reflect/internal/Mode.scala index 850e3b5669..627840bbc8 100644 --- a/src/reflect/scala/reflect/internal/Mode.scala +++ b/src/reflect/scala/reflect/internal/Mode.scala @@ -95,7 +95,7 @@ object Mode { /** Translates a mask of mode flags into something readable. */ - private val modeNameMap = Map[Int, String]( + private val modeNameMap = Map[Int, String]( // TODO why duplicate the bitmasks here, rather than just referring to this.EXPRmode etc? (1 << 0) -> "EXPRmode", (1 << 1) -> "PATTERNmode", (1 << 2) -> "TYPEmode", -- cgit v1.2.3 From f2c351c8419a6b1fdeaa2c0a7047c8c25a16c7ce Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Tue, 9 Apr 2013 16:28:56 +0200 Subject: SI-7345 Rationalize overloads of Context#make Used default arguments and removed of variations only used in one place. I couldn't eliminate them all: one remaining overload avoids allocating a new context when the scope/owner/tree don't change, but moving this optimizatin to the primary overload of make breaks things. This is noted in a TODO comment. --- .../scala/tools/nsc/typechecker/Contexts.scala | 58 +++++++++------------- .../scala/tools/nsc/typechecker/Namers.scala | 3 +- .../doc/model/ModelFactoryImplicitSupport.scala | 2 +- 3 files changed, 26 insertions(+), 37 deletions(-) diff --git a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala index b5b77072ca..a6bdfcbdbd 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala @@ -91,10 +91,10 @@ trait Contexts { self: Analyzer => def rootContext(unit: CompilationUnit, tree: Tree, erasedTypes: Boolean): Context = { var sc = startContext for (sym <- rootImports(unit)) { - sc = sc.makeNewImport(sym) + sc = sc.makeNewImport(gen.mkWildcardImport(sym)) sc.depth += 1 } - val c = sc.make(unit, tree, sc.owner, sc.scope, sc.imports) + val c = sc.make(tree, unit = unit) if (erasedTypes) c.setThrowErrors() else c.setReportErrors() c(EnrichmentEnabled | ImplicitsEnabled) = !erasedTypes c @@ -245,8 +245,10 @@ trait Contexts { self: Analyzer => def withMacrosEnabled[T](op: => T): T = withMode(enabled = MacrosEnabled)(op) def withMacrosDisabled[T](op: => T): T = withMode(disabled = MacrosEnabled)(op) - def make(unit: CompilationUnit, tree: Tree, owner: Symbol, - scope: Scope, imports: List[ImportInfo]): Context = { + def make(tree: Tree = tree, owner: Symbol = owner, + scope: Scope = scope, imports: List[ImportInfo] = imports, + unit: CompilationUnit = unit): Context = { + val c = new Context c.unit = unit c.tree = tree @@ -256,26 +258,25 @@ trait Contexts { self: Analyzer => c.restoreState(this.state) // note: ConstructorSuffix conditionally overwritten below. - tree match { - case Template(_, _, _) | PackageDef(_, _) => - c.enclClass = c - c.prefix = c.owner.thisType - c(ConstructorSuffix) = false - case _ => - c.enclClass = this.enclClass - c.prefix = - if (c.owner != this.owner && c.owner.isTerm) NoPrefix - else this.prefix + val isTemplateOrPackage = tree match { + case _: Template | _: PackageDef => true + case _ => false } - tree match { - case DefDef(_, _, _, _, _, _) => - c.enclMethod = c - case _ => - c.enclMethod = this.enclMethod + val isDefDef = tree match { + case _: DefDef => true + case _ => false } + c.prefix = + if (isTemplateOrPackage) c.owner.thisType + else if (c.owner != this.owner && c.owner.isTerm) NoPrefix + else prefix + c.enclClass = if (isTemplateOrPackage) c else enclClass + c(ConstructorSuffix) = !isTemplateOrPackage && c(ConstructorSuffix) + c.enclMethod = if (isDefDef) c else enclMethod + c.variance = this.variance - c.depth = if (scope == this.scope) this.depth else this.depth + 1 + c.depth = this.depth + (if (scope == this.scope) 0 else 1) c.imports = imports c.diagnostic = this.diagnostic c.typingIndentLevel = typingIndentLevel @@ -287,34 +288,23 @@ trait Contexts { self: Analyzer => c } - def makeNewImport(sym: Symbol): Context = - makeNewImport(gen.mkWildcardImport(sym)) - def makeNewImport(imp: Import): Context = { val impInfo = new ImportInfo(imp, depth) if (settings.lint && imp.pos.isDefined) // pos.isDefined excludes java.lang/scala/Predef imports allImportInfos(unit) ::= impInfo - make(unit, imp, owner, scope, impInfo :: imports) + make(imp, imports = impInfo :: imports) } def make(tree: Tree, owner: Symbol, scope: Scope): Context = + // TODO Moving this optimization into the main overload of `make` causes all tests to fail. Why? if (tree == this.tree && owner == this.owner && scope == this.scope) this - else make0(tree, owner, scope) - - private def make0(tree: Tree, owner: Symbol, scope: Scope): Context = - make(unit, tree, owner, scope, imports) + else make(tree, owner, scope, imports, unit) def makeNewScope(tree: Tree, owner: Symbol): Context = make(tree, owner, newNestedScope(scope)) // IDE stuff: distinguish between scopes created for typing and scopes created for naming. - def make(tree: Tree, owner: Symbol): Context = - make0(tree, owner, scope) - - def make(tree: Tree): Context = - make(tree, owner) - def makeSilent(reportAmbiguousErrors: Boolean, newtree: Tree = tree): Context = { val c = make(newtree) c.setBufferErrors() diff --git a/src/compiler/scala/tools/nsc/typechecker/Namers.scala b/src/compiler/scala/tools/nsc/typechecker/Namers.scala index de3010c371..bbb1dbe8d8 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Namers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Namers.scala @@ -47,7 +47,6 @@ trait Namers extends MethodSynthesis { private class NormalNamer(context: Context) extends Namer(context) def newNamer(context: Context): Namer = new NormalNamer(context) - def newNamerFor(context: Context, tree: Tree): Namer = newNamer(context.makeNewScope(tree, tree.symbol)) abstract class Namer(val context: Context) extends MethodSynth with NamerContextErrors { thisNamer => // overridden by the presentation compiler @@ -1629,7 +1628,7 @@ trait Namers extends MethodSynthesis { // @M an abstract type's type parameters are entered. // TODO: change to isTypeMember ? if (defnSym.isAbstractType) - newNamerFor(ctx, tree) enterSyms tparams //@M + newNamer(ctx.makeNewScope(tree, tree.symbol)) enterSyms tparams //@M restp complete sym } } diff --git a/src/scaladoc/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala b/src/scaladoc/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala index 1f87f935f2..65b00d4673 100644 --- a/src/scaladoc/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala +++ b/src/scaladoc/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala @@ -228,7 +228,7 @@ trait ModelFactoryImplicitSupport { try { context.flushBuffer() /* any errors here should not prevent future findings */ // TODO: Not sure this is the right thing to do -- seems similar to what scalac should be doing - val context2 = context.make(context.unit, context.tree, sym.owner, context.scope, context.imports) + val context2 = context.make(owner = sym.owner) val search = inferImplicit(EmptyTree, tpe, false, false, context2, false) context.flushBuffer() /* any errors here should not prevent future findings */ -- cgit v1.2.3 From 281b850abc9d0774a8542ff38b6e584b730f2d78 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Tue, 9 Apr 2013 18:41:49 +0200 Subject: SI-7345 Remove comment that appears obsolete. Obsolete since ee02ad59c, as best as I can judge. --- src/compiler/scala/tools/nsc/typechecker/Contexts.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala index a6bdfcbdbd..0d408c6859 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala @@ -303,7 +303,6 @@ trait Contexts { self: Analyzer => def makeNewScope(tree: Tree, owner: Symbol): Context = make(tree, owner, newNestedScope(scope)) - // IDE stuff: distinguish between scopes created for typing and scopes created for naming. def makeSilent(reportAmbiguousErrors: Boolean, newtree: Tree = tree): Context = { val c = make(newtree) -- cgit v1.2.3 From b1cb00456e6ff545a4c6b8e8d1fe32823e832418 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Tue, 9 Apr 2013 19:38:11 +0200 Subject: SI-7345 Use combinator to find next enclosing non-template. --- src/compiler/scala/tools/nsc/typechecker/Contexts.scala | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala index 0d408c6859..9af9de37e0 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala @@ -329,9 +329,7 @@ trait Contexts { self: Analyzer => * accessible. */ def makeConstructorContext = { - var baseContext = enclClass.outer - while (baseContext.tree.isInstanceOf[Template]) - baseContext = baseContext.outer + val baseContext = enclClass.outer.nextEnclosing(!_.tree.isInstanceOf[Template]) val argContext = baseContext.makeNewScope(tree, owner) argContext.restoreState(state) argContext.inSelfSuperCall = true -- cgit v1.2.3 From ff5dde125a9a88395f52a898a26aabb28f81f5e9 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Tue, 9 Apr 2013 19:48:04 +0200 Subject: SI-7345 Add Context#isLocal, akin to Symbol#isLocal Out with the old, in with the new. `isLocal()`, which has been removed, was unused. `isLocal`, the new entry, has the same semantics as `Symbol#isLocal`. --- src/compiler/scala/tools/nsc/typechecker/Contexts.scala | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala index 9af9de37e0..bc7fb48571 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala @@ -269,7 +269,7 @@ trait Contexts { self: Analyzer => c.prefix = if (isTemplateOrPackage) c.owner.thisType - else if (c.owner != this.owner && c.owner.isTerm) NoPrefix + else if (c.owner != this.owner && c.isLocal) NoPrefix else prefix c.enclClass = if (isTemplateOrPackage) c else enclClass c(ConstructorSuffix) = !isTemplateOrPackage && c(ConstructorSuffix) @@ -340,7 +340,7 @@ trait Contexts { self: Analyzer => argContext.scope enter e.sym } } - if (c.owner.isTerm && !c.owner.isLocalDummy) { + if (c.isLocal && !c.owner.isLocalDummy) { enterElems(c.outer) enterLocalElems(c.scope.elems) } @@ -405,12 +405,8 @@ trait Contexts { self: Analyzer => else if (bufferErrors) warningsBuffer += ((pos, msg)) } - def isLocal(): Boolean = tree match { - case Block(_,_) => true - case PackageDef(_, _) => false - case EmptyTree => false - case _ => outer.isLocal() - } + /** Is the owning symbol of this context a term? */ + final def isLocal: Boolean = owner.isTerm def isNameInScope(name: Name) = lookupSymbol(name, _ => true).isSuccess -- cgit v1.2.3 From c9f5ab031a881812149908491217b531b6e29e06 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Tue, 9 Apr 2013 21:47:05 +0200 Subject: SI-7345 Encapsulate warning and error buffers in ReportBuffer. - merge `Context#{buffer, warningBuffer}` into `Context#reportBuffer`. - only expose immutable copies of the error and warnings buffers - Introduce a convenience method, `firstError`. - replace `condBufferFlush` more specific methods to retain or clear errors by error kind. --- .../scala/reflect/macros/runtime/Typers.scala | 5 +- .../scala/tools/nsc/typechecker/Contexts.scala | 99 ++++++++++++++++------ .../scala/tools/nsc/typechecker/Implicits.scala | 22 +++-- .../scala/tools/nsc/typechecker/Macros.scala | 4 +- .../scala/tools/nsc/typechecker/Typers.scala | 10 +-- .../scala/tools/reflect/ToolBoxFactory.scala | 2 +- 6 files changed, 100 insertions(+), 42 deletions(-) diff --git a/src/compiler/scala/reflect/macros/runtime/Typers.scala b/src/compiler/scala/reflect/macros/runtime/Typers.scala index 7e268247dd..99b934787d 100644 --- a/src/compiler/scala/reflect/macros/runtime/Typers.scala +++ b/src/compiler/scala/reflect/macros/runtime/Typers.scala @@ -54,7 +54,10 @@ trait Typers { wrapper(universe.analyzer.inferImplicit(tree, pt, reportAmbiguous = true, isView = isView, context = context, saveAmbiguousDivergent = !silent, pos = pos)) match { case failure if failure.tree.isEmpty => macroLogVerbose("implicit search has failed. to find out the reason, turn on -Xlog-implicits") - if (context.hasErrors) throw new TypecheckException(context.errBuffer.head.errPos, context.errBuffer.head.errMsg) + if (context.hasErrors) { + val err = context.firstError + throw new TypecheckException(err.errPos, err.errMsg) + } universe.EmptyTree case success => success.tree diff --git a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala index bc7fb48571..0a269c6723 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala @@ -6,7 +6,7 @@ package scala.tools.nsc package typechecker -import scala.collection.mutable +import scala.collection.{ immutable, mutable } import scala.annotation.tailrec import scala.reflect.internal.util.shortClassOfInstance @@ -173,8 +173,8 @@ trait Contexts { self: Analyzer => var typingIndentLevel: Int = 0 def typingIndent = " " * typingIndentLevel - var buffer: mutable.Set[AbsTypeError] = _ - var warningsBuffer: mutable.Set[(Position, String)] = _ + var _reportBuffer: ReportBuffer = new ReportBuffer + def reportBuffer = _reportBuffer def enclClassOrMethod: Context = if ((owner eq NoSymbol) || (owner.isClass) || (owner.isMethod)) this @@ -193,9 +193,11 @@ trait Contexts { self: Analyzer => tparams } - def errBuffer = buffer - def hasErrors = buffer.nonEmpty - def hasWarnings = warningsBuffer.nonEmpty + def errBuffer = reportBuffer.errors + // TODO use Option[Error] this rather then `if (c.hasErrors) c.firstError._` + def firstError: AbsTypeError = reportBuffer.firstError + def hasErrors = reportBuffer.hasErrors + def hasWarnings = reportBuffer.hasWarnings def state: ContextMode = mode def restoreState(state0: ContextMode) = mode = state0 @@ -212,23 +214,19 @@ trait Contexts { self: Analyzer => private def warnIfBufferNotClean() { if (!bufferErrors && hasErrors) - devWarning("When entering the buffer state, context has to be clean. Current buffer: " + buffer) + devWarning("When entering the buffer state, context has to be clean. Current buffer: " + reportBuffer.errors) } - def updateBuffer(errors: mutable.Set[AbsTypeError]) = buffer ++= errors - def condBufferFlush(removeP: AbsTypeError => Boolean) { - val elems = buffer.filter(removeP) - buffer --= elems - } - def flushBuffer() { buffer.clear() } - def flushAndReturnBuffer(): mutable.Set[AbsTypeError] = { - val current = buffer.clone() - buffer.clear() + def updateBuffer(errors: Traversable[AbsTypeError]) = reportBuffer ++= errors + def flushBuffer() { reportBuffer.clearAllErrors() } + def flushAndReturnBuffer(): immutable.Seq[AbsTypeError] = { + val current = reportBuffer.errors + reportBuffer.clearAllErrors() current } - def flushAndReturnWarningsBuffer(): mutable.Set[(Position, String)] = { - val current = warningsBuffer.clone() - warningsBuffer.clear() + def flushAndReturnWarningsBuffer(): immutable.Seq[(Position, String)] = { + val current = reportBuffer.warnings + reportBuffer.clearAllWarnings() current } @@ -281,8 +279,8 @@ trait Contexts { self: Analyzer => c.diagnostic = this.diagnostic c.typingIndentLevel = typingIndentLevel c.openImplicits = this.openImplicits - c.buffer = if (this.buffer == null) mutable.LinkedHashSet[AbsTypeError]() else this.buffer // need to initialize - c.warningsBuffer = if (this.warningsBuffer == null) mutable.LinkedHashSet[(Position, String)]() else this.warningsBuffer + // Usually, the parent and child contexts share the report buffer. Exception: `makeSilent` will use a fresh buffer. + c._reportBuffer = this.reportBuffer registerContext(c.asInstanceOf[analyzer.Context]) debuglog("[context] ++ " + c.unit + " / " + tree.summaryString) c @@ -308,7 +306,7 @@ trait Contexts { self: Analyzer => val c = make(newtree) c.setBufferErrors() c.setAmbiguousErrors(reportAmbiguousErrors) - c.buffer = mutable.LinkedHashSet[AbsTypeError]() + c._reportBuffer = new ReportBuffer // A fresh buffer so as not to leak errors/warnings into `this`. c } @@ -367,7 +365,7 @@ trait Contexts { self: Analyzer => (new Exception).printStackTrace() } if (pf isDefinedAt err) pf(err) - else if (bufferErrors) { buffer += err } + else if (bufferErrors) { reportBuffer += err } else throw new TypeError(err.errPos, err.errMsg) } @@ -402,7 +400,7 @@ trait Contexts { self: Analyzer => def warning(pos: Position, msg: String): Unit = warning(pos, msg, force = false) def warning(pos: Position, msg: String, force: Boolean) { if (reportErrors || force) unit.warning(pos, msg) - else if (bufferErrors) warningsBuffer += ((pos, msg)) + else if (bufferErrors) reportBuffer += (pos -> msg) } /** Is the owning symbol of this context a term? */ @@ -973,6 +971,58 @@ trait Contexts { self: Analyzer => } } //class Context + /** A buffer for warnings and errors that are accumulated during speculative type checking. */ + final class ReportBuffer { + type Error = AbsTypeError + type Warning = (Position, String) + + private def newBuffer[A] = mutable.LinkedHashSet.empty[A] // Important to use LinkedHS for stable results. + + // [JZ] Contexts, pre- the SI-7345 refactor, avoided allocating the buffers until needed. This + // is replicated here out of conservatism. + private var _errorBuffer: mutable.LinkedHashSet[Error] = _ + private def errorBuffer = {if (_errorBuffer == null) _errorBuffer = newBuffer; _errorBuffer} + def errors: immutable.Seq[Error] = errorBuffer.toVector + + private var _warningBuffer: mutable.LinkedHashSet[Warning] = _ + private def warningBuffer = {if (_warningBuffer == null) _warningBuffer = newBuffer; _warningBuffer} + def warnings: immutable.Seq[Warning] = warningBuffer.toVector + + def +=(error: AbsTypeError): this.type = { + errorBuffer += error + this + } + def ++=(errors: Traversable[AbsTypeError]): this.type = { + errorBuffer ++= errors + this + } + def +=(warning: Warning): this.type = { + warningBuffer += warning + this + } + + def clearAllErrors(): this.type = { + errorBuffer.clear() + this + } + def clearErrors(kind: ErrorKinds.ErrorKind): this.type = { + errorBuffer.retain(_.kind != kind) + this + } + def retainErrors(kind: ErrorKinds.ErrorKind): this.type = { + errorBuffer.retain(_.kind == kind) + this + } + def clearAllWarnings(): this.type = { + warningBuffer.clear() + this + } + + def hasErrors = errorBuffer.nonEmpty + def hasWarnings = warningBuffer.nonEmpty + def firstError = errorBuffer.head + } + class ImportInfo(val tree: Import, val depth: Int) { def pos = tree.pos def posOf(sel: ImportSelector) = tree.pos withPoint sel.namePos @@ -1130,3 +1180,4 @@ final class ContextMode private (val bits: Int) extends AnyVal { if (bits == 0) "NOmode" else (contextModeNameMap filterKeys inAll).values.toList.sorted mkString " " } + diff --git a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala index 85e31347be..f7f6970c14 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala @@ -81,8 +81,8 @@ trait Implicits { val implicitSearchContext = context.makeImplicit(reportAmbiguous) val result = new ImplicitSearch(tree, pt, isView, implicitSearchContext, pos).bestImplicit if (saveAmbiguousDivergent && implicitSearchContext.hasErrors) { - context.updateBuffer(implicitSearchContext.errBuffer.filter(err => err.kind == ErrorKinds.Ambiguous || err.kind == ErrorKinds.Divergent)) - debuglog("update buffer: " + implicitSearchContext.errBuffer) + context.updateBuffer(implicitSearchContext.reportBuffer.errors.filter(err => err.kind == ErrorKinds.Ambiguous || err.kind == ErrorKinds.Divergent)) + debuglog("update buffer: " + implicitSearchContext.reportBuffer.errors) } printInference("[infer implicit] inferred " + result) context.undetparams = context.undetparams filterNot result.subst.from.contains @@ -585,8 +585,9 @@ trait Implicits { case _ => fallback } if (context.hasErrors) { - log("implicit adapt failed: " + context.errBuffer.head.errMsg) - return fail(context.errBuffer.head.errMsg) + val err = context.firstError + log("implicit adapt failed: " + err.errMsg) + return fail(err.errMsg) } if (Statistics.canEnable) Statistics.incCounter(typedImplicits) @@ -609,7 +610,7 @@ trait Implicits { } if (context.hasErrors) - fail("hasMatchingSymbol reported error: " + context.errBuffer.head.errMsg) + fail("hasMatchingSymbol reported error: " + context.firstError.errMsg) else if (isLocal && !hasMatchingSymbol(itree1)) fail("candidate implicit %s is shadowed by %s".format( info.sym.fullLocationString, itree1.symbol.fullLocationString)) @@ -633,7 +634,7 @@ trait Implicits { // #2421: check that we correctly instantiated type parameters outside of the implicit tree: checkBounds(itree2, NoPrefix, NoSymbol, undetParams, targs, "inferred ") if (context.hasErrors) - return fail("type parameters weren't correctly instantiated outside of the implicit tree: " + context.errBuffer.head.errMsg) + return fail("type parameters weren't correctly instantiated outside of the implicit tree: " + context.firstError.errMsg) // filter out failures from type inference, don't want to remove them from undetParams! // we must be conservative in leaving type params in undetparams @@ -669,7 +670,7 @@ trait Implicits { } if (context.hasErrors) - fail("typing TypeApply reported errors for the implicit tree: " + context.errBuffer.head.errMsg) + fail("typing TypeApply reported errors for the implicit tree: " + context.firstError.errMsg) else { val result = new SearchResult(itree2, subst) if (Statistics.canEnable) Statistics.incCounter(foundImplicits) @@ -828,7 +829,7 @@ trait Implicits { case sr if sr.isFailure => // We don't want errors that occur during checking implicit info // to influence the check of further infos. - context.condBufferFlush(_.kind != ErrorKinds.Divergent) + context.reportBuffer.retainErrors(ErrorKinds.Divergent) rankImplicits(is, acc) case newBest => best = newBest @@ -1085,7 +1086,10 @@ trait Implicits { try { val tree1 = typedPos(pos.focus)(arg) - if (context.hasErrors) processMacroExpansionError(context.errBuffer.head.errPos, context.errBuffer.head.errMsg) + if (context.hasErrors) { + val err = context.firstError + processMacroExpansionError(err.errPos, err.errMsg) + } else new SearchResult(tree1, EmptyTreeTypeSubstituter) } catch { case ex: TypeError => diff --git a/src/compiler/scala/tools/nsc/typechecker/Macros.scala b/src/compiler/scala/tools/nsc/typechecker/Macros.scala index 2dbfa1d0d3..d07297bb35 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Macros.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Macros.scala @@ -769,12 +769,12 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces { if (macroDebugVerbose) println(s"typecheck #1 (against expectedTpe = $expectedTpe): $expanded") val expanded1 = typer.context.withImplicitsEnabled(typer.typed(expanded, mode, expectedTpe)) if (expanded1.isErrorTyped) { - if (macroDebugVerbose) println(s"typecheck #1 has failed: ${typer.context.errBuffer}") + if (macroDebugVerbose) println(s"typecheck #1 has failed: ${typer.context.reportBuffer.errors}") expanded1 } else { if (macroDebugVerbose) println(s"typecheck #2 (against pt = $pt): $expanded1") val expanded2 = typer.context.withImplicitsEnabled(super.onSuccess(expanded1)) - if (macroDebugVerbose && expanded2.isErrorTyped) println(s"typecheck #2 has failed: ${typer.context.errBuffer}") + if (macroDebugVerbose && expanded2.isErrorTyped) println(s"typecheck #2 has failed: ${typer.context.reportBuffer.errors}") expanded2 } } diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 692c24fd20..69cd802170 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -150,13 +150,13 @@ trait Typers extends Adaptations with Tags { } else { mkArg = gen.mkNamedArg // don't pass the default argument (if any) here, but start emitting named arguments for the following args if (!param.hasDefault && !paramFailed) { - context.errBuffer.find(_.kind == ErrorKinds.Divergent) match { + context.reportBuffer.errors.find(_.kind == ErrorKinds.Divergent) match { case Some(divergentImplicit) => // DivergentImplicit error has higher priority than "no implicit found" // no need to issue the problem again if we are still in silent mode if (context.reportErrors) { context.issue(divergentImplicit) - context.condBufferFlush(_.kind == ErrorKinds.Divergent) + context.reportBuffer.clearErrors(ErrorKinds.Divergent) } case None => NoImplicitFoundError(fun, param) @@ -493,7 +493,7 @@ trait Typers extends Adaptations with Tags { final def typerReportAnyContextErrors[T](c: Context)(f: Typer => T): T = { val res = f(newTyper(c)) if (c.hasErrors) - context.issue(c.errBuffer.head) + context.issue(c.firstError) res } @@ -689,7 +689,7 @@ trait Typers extends Adaptations with Tags { context.namedApplyBlockInfo = context1.namedApplyBlockInfo if (context1.hasErrors) { stopStats() - SilentTypeError(context1.errBuffer.head) + SilentTypeError(context1.firstError) } else { // If we have a successful result, emit any warnings it created. if (context1.hasWarnings) { @@ -1174,7 +1174,7 @@ trait Typers extends Adaptations with Tags { val silentContext = context.makeImplicit(context.ambiguousErrors) val res = newTyper(silentContext).typed( new ApplyImplicitView(coercion, List(tree)) setPos tree.pos, mode, pt) - if (silentContext.hasErrors) context.issue(silentContext.errBuffer.head) else return res + if (silentContext.hasErrors) context.issue(silentContext.firstError) else return res } } } diff --git a/src/compiler/scala/tools/reflect/ToolBoxFactory.scala b/src/compiler/scala/tools/reflect/ToolBoxFactory.scala index f6ed5f8f1c..132dd251db 100644 --- a/src/compiler/scala/tools/reflect/ToolBoxFactory.scala +++ b/src/compiler/scala/tools/reflect/ToolBoxFactory.scala @@ -184,7 +184,7 @@ abstract class ToolBoxFactory[U <: JavaUniverse](val u: U) { factorySelf => analyzer.inferImplicit(tree, pt, reportAmbiguous = true, isView = isView, context = context, saveAmbiguousDivergent = !silent, pos = pos) match { case failure if failure.tree.isEmpty => trace("implicit search has failed. to find out the reason, turn on -Xlog-implicits: ")(failure.tree) - if (context.hasErrors) throw ToolBoxError("reflective implicit search has failed: %s".format(context.errBuffer.head.errMsg)) + if (context.hasErrors) throw ToolBoxError("reflective implicit search has failed: %s".format(context.firstError.errMsg)) EmptyTree case success => success.tree -- cgit v1.2.3 From 190aea9f015280d0f48cc2928515d11eccce4e33 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Tue, 9 Apr 2013 23:17:18 +0200 Subject: SI-7345 Exploit named/default args - Collapse overloads of `rootContext` - make `atOwner` more concise --- src/compiler/scala/tools/nsc/typechecker/Contexts.scala | 4 +--- src/compiler/scala/tools/nsc/typechecker/Typers.scala | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala index 0a269c6723..22cda50591 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala @@ -86,9 +86,7 @@ trait Contexts { self: Analyzer => else RootImports.completeList } - def rootContext(unit: CompilationUnit): Context = rootContext(unit, EmptyTree, erasedTypes = false) - def rootContext(unit: CompilationUnit, tree: Tree): Context = rootContext(unit, tree, erasedTypes = false) - def rootContext(unit: CompilationUnit, tree: Tree, erasedTypes: Boolean): Context = { + def rootContext(unit: CompilationUnit, tree: Tree = EmptyTree, erasedTypes: Boolean = false): Context = { var sc = startContext for (sym <- rootImports(unit)) { sc = sc.makeNewImport(gen.mkWildcardImport(sym)) diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 69cd802170..f57352b11e 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -5292,7 +5292,7 @@ trait Typers extends Adaptations with Tags { } def atOwner(owner: Symbol): Typer = - newTyper(context.make(context.tree, owner)) + newTyper(context.make(owner = owner)) def atOwner(tree: Tree, owner: Symbol): Typer = newTyper(context.make(tree, owner)) -- cgit v1.2.3 From ec5eaee3ec27b614c3ffe0496a755623c912cfdd Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Wed, 10 Apr 2013 11:52:01 +0200 Subject: SI-7345 More refactoring and documentation in Contexts - Use `firstError match {}` rather than `if (c.hasErrors) c.firstError ..` - Convert implementation comments to doc comments - Add new doc comments - Refactor `Context#make` for better readability. - Note a few TODOs. - Roughly delineate the internal structure of Contexts with comments. - Combine `mode` and `state` methods into `contextMode`. - Move `isNameInScope` closer to its compadres. - Improving alignment, tightening access. --- .../scala/reflect/macros/runtime/Typers.scala | 7 +- .../scala/tools/nsc/typechecker/Contexts.scala | 260 ++++++++++++++------- .../scala/tools/nsc/typechecker/Implicits.scala | 40 ++-- .../scala/tools/nsc/typechecker/Infer.scala | 19 +- .../scala/tools/nsc/typechecker/Typers.scala | 38 +-- .../scala/tools/reflect/ToolBoxFactory.scala | 4 +- 6 files changed, 238 insertions(+), 130 deletions(-) diff --git a/src/compiler/scala/reflect/macros/runtime/Typers.scala b/src/compiler/scala/reflect/macros/runtime/Typers.scala index 99b934787d..79833d155e 100644 --- a/src/compiler/scala/reflect/macros/runtime/Typers.scala +++ b/src/compiler/scala/reflect/macros/runtime/Typers.scala @@ -54,11 +54,10 @@ trait Typers { wrapper(universe.analyzer.inferImplicit(tree, pt, reportAmbiguous = true, isView = isView, context = context, saveAmbiguousDivergent = !silent, pos = pos)) match { case failure if failure.tree.isEmpty => macroLogVerbose("implicit search has failed. to find out the reason, turn on -Xlog-implicits") - if (context.hasErrors) { - val err = context.firstError - throw new TypecheckException(err.errPos, err.errMsg) + context.firstError match { + case Some(err) => throw new TypecheckException(err.errPos, err.errMsg) + case None => universe.EmptyTree } - universe.EmptyTree case success => success.tree } diff --git a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala index 22cda50591..4b9d3fda01 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala @@ -111,37 +111,58 @@ trait Contexts { self: Analyzer => class Context private[typechecker] { var unit: CompilationUnit = NoCompilationUnit - var tree: Tree = _ // Tree associated with this context - var owner: Symbol = NoSymbol // The current owner - var scope: Scope = _ // The current scope - var outer: Context = _ // The next outer context - var enclClass: Context = _ // The next outer context whose tree is a - // template or package definition - @inline final def savingEnclClass[A](c: Context)(a: => A): A = { + /** Tree associated with this context */ + var tree: Tree = _ + /** The current owner */ + var owner: Symbol = NoSymbol + /** The current scope */ + var scope: Scope = _ + /** The next outer context */ + var outer: Context = _ + /** The next outer context whose tree is a template or package definition */ + var enclClass: Context = _ + + @inline private def savingEnclClass[A](c: Context)(a: => A): A = { val saved = enclClass enclClass = c try a finally enclClass = saved } - private var mode: ContextMode = ContextMode.DefaultMode + /** A bitmask containing all the boolean flags in a context, e.g. are implicit views enabled */ + var contextMode: ContextMode = ContextMode.DefaultMode + + /** Update all modes in `mask` to `value` */ def update(mask: ContextMode, value: Boolean) { - mode = mode.set(value, mask) + contextMode = contextMode.set(value, mask) } + + /** Set all modes in the mask `enable` to true, and all in `disable` to false. */ def set(enable: ContextMode = NOmode, disable: ContextMode = NOmode): this.type = { - mode = mode.set(true, enable).set(false, disable) - this + contextMode = contextMode.set(true, enable).set(false, disable) + this } - def apply(mask: ContextMode): Boolean = mode.inAll(mask) - var enclMethod: Context = _ // The next outer context whose tree is a method - var variance: Variance = Variance.Invariant // Variance relative to enclosing class - private var _undetparams: List[Symbol] = List() // Undetermined type parameters, - // not inherited to child contexts + /** Is this context in all modes in the given `mask`? */ + def apply(mask: ContextMode): Boolean = contextMode.inAll(mask) + + /** The next outer context whose tree is a method */ + var enclMethod: Context = _ + + /** Variance relative to enclosing class */ + var variance: Variance = Variance.Invariant + + /** Undetermined type parameters. Not inherited to child contexts */ + private var _undetparams: List[Symbol] = List() + var depth: Int = 0 - var imports: List[ImportInfo] = List() // currently visible imports - var openImplicits: List[(Type,Tree)] = List() // types for which implicit arguments - // are currently searched - // for a named application block (Tree) the corresponding NamedApplyInfo + + /** The currently visible imports */ + var imports: List[ImportInfo] = List() + + /** Types for which implicit arguments are currently searched */ + var openImplicits: List[(Type,Tree)] = List() + + /* For a named application block (`Tree`) the corresponding `NamedApplyInfo`. */ var namedApplyBlockInfo: Option[(Tree, NamedApplyInfo)] = None var prefix: Type = NoPrefix @@ -151,9 +172,6 @@ trait Contexts { self: Analyzer => def returnsSeen = this(ReturnsSeen) def inSelfSuperCall_=(value: Boolean) = this(SelfSuperCall) = value def inSelfSuperCall = this(SelfSuperCall) - - var diagnostic: List[String] = Nil // these messages are printed when issuing an error - def implicitsEnabled_=(value: Boolean) = this(ImplicitsEnabled) = value def implicitsEnabled = this(ImplicitsEnabled) def macrosEnabled_=(value: Boolean) = this(MacrosEnabled) = value @@ -165,45 +183,61 @@ trait Contexts { self: Analyzer => def retyping_=(value: Boolean) = this(ReTyping) = value def retyping = this(ReTyping) - var savedTypeBounds: List[(Symbol, Type)] = List() // saved type bounds - // for type parameters which are narrowed in a GADT + /** These messages are printed when issuing an error */ + var diagnostic: List[String] = Nil + + /** Saved type bounds for type parameters which are narrowed in a GADT. */ + var savedTypeBounds: List[(Symbol, Type)] = List() + /** Indentation level, in columns, for output under -Ytyper-debug */ var typingIndentLevel: Int = 0 def typingIndent = " " * typingIndentLevel - var _reportBuffer: ReportBuffer = new ReportBuffer - def reportBuffer = _reportBuffer - + /** The next enclosing context (potentially `this`) that is owned by a class or method */ def enclClassOrMethod: Context = if ((owner eq NoSymbol) || (owner.isClass) || (owner.isMethod)) this else outer.enclClassOrMethod + /** The next enclosing context (potentially `this`) that has a `CaseDef` as a tree */ def enclosingCaseDef = nextEnclosing(_.tree.isInstanceOf[CaseDef]) + def undetparamsString = if (undetparams.isEmpty) "" else undetparams.mkString("undetparams=", ", ", "") - def undetparams = _undetparams + /** Undetermined type parameters. See `Infer#{inferExprInstance, adjustTypeArgs}`. */ + def undetparams: List[Symbol] = _undetparams def undetparams_=(ps: List[Symbol]) = { _undetparams = ps } - def extractUndetparams() = { + /** Return and clear the undetermined type parameters */ + def extractUndetparams(): List[Symbol] = { val tparams = undetparams undetparams = List() tparams } - def errBuffer = reportBuffer.errors - // TODO use Option[Error] this rather then `if (c.hasErrors) c.firstError._` - def firstError: AbsTypeError = reportBuffer.firstError - def hasErrors = reportBuffer.hasErrors - def hasWarnings = reportBuffer.hasWarnings + // + // Error reporting policies and buffer. + // - def state: ContextMode = mode - def restoreState(state0: ContextMode) = mode = state0 + private var _reportBuffer: ReportBuffer = new ReportBuffer + /** A buffer for errors and warnings, used with `this.bufferErrors == true` */ + def reportBuffer = _reportBuffer + /** Discard the current report buffer, and replace with an empty one */ + def useFreshReportBuffer() = _reportBuffer = new ReportBuffer + /** Discard the current report buffer, and replace with `other` */ + def restoreReportBuffer(other: ReportBuffer) = _reportBuffer = other + + /** The first error, if any, in the report buffer */ + def firstError: Option[AbsTypeError] = reportBuffer.firstErrorOpt + /** The first warning, if any, in the report buffer */ + def firstWarning: Option[(Position, String)] = reportBuffer.firstWarningOpt + /** Does the report buffer contain any errors? */ + def hasErrors = reportBuffer.hasErrors def reportErrors = this(ReportErrors) def bufferErrors = this(BufferErrors) def ambiguousErrors = this(AmbiguousErrors) - def throwErrors = mode.inNone(ReportErrors | BufferErrors) + def throwErrors = contextMode.inNone(ReportErrors | BufferErrors) def setReportErrors(): Unit = set(enable = ReportErrors | AmbiguousErrors, disable = BufferErrors) def setBufferErrors(): Unit = {set(enable = BufferErrors, disable = ReportErrors | AmbiguousErrors); warnIfBufferNotClean()} @@ -215,24 +249,34 @@ trait Contexts { self: Analyzer => devWarning("When entering the buffer state, context has to be clean. Current buffer: " + reportBuffer.errors) } + // TODO SI-7345 refactor this part of the API. + + /** Append the given errors to the report buffer */ def updateBuffer(errors: Traversable[AbsTypeError]) = reportBuffer ++= errors + /** Clear all errors from the report buffer */ def flushBuffer() { reportBuffer.clearAllErrors() } + /** Return and clear all errors from the report buffer */ def flushAndReturnBuffer(): immutable.Seq[AbsTypeError] = { val current = reportBuffer.errors reportBuffer.clearAllErrors() current } + /** Return and clear all warnings from the report buffer */ def flushAndReturnWarningsBuffer(): immutable.Seq[(Position, String)] = { val current = reportBuffer.warnings reportBuffer.clearAllWarnings() current } + // + // Temporary mode adjustment + // + private def withMode[T](enabled: ContextMode = NOmode, disabled: ContextMode = NOmode)(op: => T): T = { - val saved = mode + val saved = contextMode set(enabled, disabled) try op - finally mode = saved + finally contextMode = saved } def withImplicitsEnabled[T](op: => T): T = withMode(enabled = ImplicitsEnabled)(op) @@ -241,19 +285,17 @@ trait Contexts { self: Analyzer => def withMacrosEnabled[T](op: => T): T = withMode(enabled = MacrosEnabled)(op) def withMacrosDisabled[T](op: => T): T = withMode(disabled = MacrosEnabled)(op) + // + // Child Context Creation + // + + /** + * Construct a child context. The parent and child will share the report buffer. + * Compare with `makeSilent`, in which the child has a fresh report buffer. + */ def make(tree: Tree = tree, owner: Symbol = owner, scope: Scope = scope, imports: List[ImportInfo] = imports, unit: CompilationUnit = unit): Context = { - - val c = new Context - c.unit = unit - c.tree = tree - c.owner = owner - c.scope = scope - c.outer = this - - c.restoreState(this.state) // note: ConstructorSuffix conditionally overwritten below. - val isTemplateOrPackage = tree match { case _: Template | _: PackageDef => true case _ => false @@ -262,28 +304,49 @@ trait Contexts { self: Analyzer => case _: DefDef => true case _ => false } - - c.prefix = - if (isTemplateOrPackage) c.owner.thisType - else if (c.owner != this.owner && c.isLocal) NoPrefix + val sameOwner = owner == this.owner + val sameScope = scope == this.scope + val prefixInChild = + if (isTemplateOrPackage) owner.thisType + else if (!sameOwner && owner.isTerm) NoPrefix else prefix - c.enclClass = if (isTemplateOrPackage) c else enclClass + + // The blank canvas + val c = new Context + + // Fields that are directly propagated + c.unit = unit + c.tree = tree + c.owner = owner + c.scope = scope + c.variance = variance + c.imports = imports + c.diagnostic = diagnostic + c.typingIndentLevel = typingIndentLevel + c.openImplicits = openImplicits + c.contextMode = contextMode // note: ConstructorSuffix, a bit within `mode`, is conditionally overwritten below. + c._reportBuffer = reportBuffer + + // Fields that may take on a different value in the child + c.outer = this + c.prefix = prefixInChild + c.enclClass = if (isTemplateOrPackage) c else enclClass c(ConstructorSuffix) = !isTemplateOrPackage && c(ConstructorSuffix) - c.enclMethod = if (isDefDef) c else enclMethod - - c.variance = this.variance - c.depth = this.depth + (if (scope == this.scope) 0 else 1) - c.imports = imports - c.diagnostic = this.diagnostic - c.typingIndentLevel = typingIndentLevel - c.openImplicits = this.openImplicits - // Usually, the parent and child contexts share the report buffer. Exception: `makeSilent` will use a fresh buffer. - c._reportBuffer = this.reportBuffer + c.enclMethod = if (isDefDef) c else enclMethod + c.depth = depth + (if (sameScope) 0 else 1) + registerContext(c.asInstanceOf[analyzer.Context]) debuglog("[context] ++ " + c.unit + " / " + tree.summaryString) c } + def make(tree: Tree, owner: Symbol, scope: Scope): Context = + // TODO SI-7345 Moving this optimization into the main overload of `make` causes all tests to fail. + // even if it is extened to check that `unit == this.unit`. Why is this? + if (tree == this.tree && owner == this.owner && scope == this.scope) this + else make(tree, owner, scope, imports, unit) + + /** Make a child context that includes `imp` in the list of visible imports */ def makeNewImport(imp: Import): Context = { val impInfo = new ImportInfo(imp, depth) if (settings.lint && imp.pos.isDefined) // pos.isDefined excludes java.lang/scala/Predef imports @@ -292,14 +355,11 @@ trait Contexts { self: Analyzer => make(imp, imports = impInfo :: imports) } - def make(tree: Tree, owner: Symbol, scope: Scope): Context = - // TODO Moving this optimization into the main overload of `make` causes all tests to fail. Why? - if (tree == this.tree && owner == this.owner && scope == this.scope) this - else make(tree, owner, scope, imports, unit) - + /** Make a child context that represents a new nested scope */ def makeNewScope(tree: Tree, owner: Symbol): Context = make(tree, owner, newNestedScope(scope)) + /** Make a child context that buffers errors and warnings into a fresh report buffer. */ def makeSilent(reportAmbiguousErrors: Boolean, newtree: Tree = tree): Context = { val c = make(newtree) c.setBufferErrors() @@ -308,6 +368,7 @@ trait Contexts { self: Analyzer => c } + /** Make a silent child context does not allow implicits. Used to prevent chaining of implicit views. */ def makeImplicit(reportAmbiguousErrors: Boolean) = { val c = makeSilent(reportAmbiguousErrors) c(ImplicitsEnabled | EnrichmentEnabled) = false @@ -327,7 +388,7 @@ trait Contexts { self: Analyzer => def makeConstructorContext = { val baseContext = enclClass.outer.nextEnclosing(!_.tree.isInstanceOf[Template]) val argContext = baseContext.makeNewScope(tree, owner) - argContext.restoreState(state) + argContext.contextMode = contextMode argContext.inSelfSuperCall = true def enterElems(c: Context) { def enterLocalElems(e: ScopeEntry) { @@ -347,6 +408,10 @@ trait Contexts { self: Analyzer => argContext } + // + // Error and warning issuance + // + private def addDiagString(msg: String) = { val ds = if (diagnostic.isEmpty) "" @@ -367,12 +432,14 @@ trait Contexts { self: Analyzer => else throw new TypeError(err.errPos, err.errMsg) } + /** Issue/buffer/throw the given type error according to the current mode for error reporting. */ def issue(err: AbsTypeError) { issueCommon(err) { case _ if reportErrors => unitError(err.errPos, addDiagString(err.errMsg)) } } + /** Issue/buffer/throw the given implicit ambiguity error according to the current mode for error reporting. */ def issueAmbiguousError(pre: Type, sym1: Symbol, sym2: Symbol, err: AbsTypeError) { issueCommon(err) { case _ if ambiguousErrors => if (!pre.isErroneous && !sym1.isErroneous && !sym2.isErroneous) @@ -380,23 +447,25 @@ trait Contexts { self: Analyzer => } } + /** Issue/buffer/throw the given implicit ambiguity error according to the current mode for error reporting. */ def issueAmbiguousError(err: AbsTypeError) { issueCommon(err) { case _ if ambiguousErrors => unitError(err.errPos, addDiagString(err.errMsg)) } } - // TODO remove + /** Issue/throw the given `err` according to the current mode for error reporting. */ def error(pos: Position, err: Throwable) = if (reportErrors) unitError(pos, addDiagString(err.getMessage())) else throw err + /** Issue/throw the given error message according to the current mode for error reporting. */ def error(pos: Position, msg: String) = { val msg1 = addDiagString(msg) if (reportErrors) unitError(pos, msg1) else throw new TypeError(pos, msg1) } - def warning(pos: Position, msg: String): Unit = warning(pos, msg, force = false) - def warning(pos: Position, msg: String, force: Boolean) { + /** Issue/throw the given error message according to the current mode for error reporting. */ + def warning(pos: Position, msg: String, force: Boolean = false) { if (reportErrors || force) unit.warning(pos, msg) else if (bufferErrors) reportBuffer += (pos -> msg) } @@ -404,8 +473,6 @@ trait Contexts { self: Analyzer => /** Is the owning symbol of this context a term? */ final def isLocal: Boolean = owner.isTerm - def isNameInScope(name: Name) = lookupSymbol(name, _ => true).isSuccess - // 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 @@ -433,11 +500,15 @@ trait Contexts { self: Analyzer => override def toString = "Context(%s@%s unit=%s scope=%s errors=%b, reportErrors=%b, throwErrors=%b)".format( owner.fullName, tree.shortClass, unit, scope.##, hasErrors, reportErrors, throwErrors ) - /** Is `sub` a subclass of `base` or a companion object of such a subclass? - */ - def isSubClassOrCompanion(sub: Symbol, base: Symbol) = + + // + // Accessibility checking + // + + /** Is `sub` a subclass of `base` or a companion object of such a subclass? */ + private def isSubClassOrCompanion(sub: Symbol, base: Symbol) = sub.isNonBottomSubClass(base) || - sub.isModuleClass && sub.linkedClassOfClass.isNonBottomSubClass(base) + sub.isModuleClass && sub.linkedClassOfClass.isNonBottomSubClass(base) /** Return the closest enclosing context that defines a subclass of `clazz` * or a companion object thereof, or `NoContext` if no such context exists. @@ -449,8 +520,7 @@ trait Contexts { self: Analyzer => c } - /** Is `sym` accessible as a member of `pre` in current context? - */ + /** Is `sym` accessible as a member of `pre` in current context? */ def isAccessible(sym: Symbol, pre: Type, superAccess: Boolean = false): Boolean = { lastAccessCheckDetails = "" // Console.println("isAccessible(%s, %s, %s)".format(sym, pre, superAccess)) @@ -530,6 +600,10 @@ trait Contexts { self: Analyzer => } } + // + // Type bound management + // + def pushTypeBounds(sym: Symbol) { sym.info match { case tb: TypeBounds => if (!tb.isEmptyBounds) log(s"Saving $sym info=$tb") @@ -566,6 +640,10 @@ trait Contexts { self: Analyzer => } } + // + // Implicit collection + // + private var implicitsCache: List[List[ImplicitInfo]] = null private var implicitsRunId = NoRunId @@ -654,6 +732,10 @@ trait Contexts { self: Analyzer => implicitsCache } + // + // Imports and symbol lookup + // + /** It's possible that seemingly conflicting identifiers are * identifiably the same after type normalization. In such cases, * allow compilation to proceed. A typical example is: @@ -763,6 +845,8 @@ trait Contexts { self: Analyzer => ) } + def isNameInScope(name: Name) = lookupSymbol(name, _ => true).isSuccess + /** Find the symbol of a simple name starting from this context. * All names are filtered through the "qualifies" predicate, * the search continuing as long as no qualifying name is found. @@ -999,6 +1083,10 @@ trait Contexts { self: Analyzer => this } + def clearAll(): this.type = { + clearAllErrors(); clearAllWarnings(); + } + def clearAllErrors(): this.type = { errorBuffer.clear() this @@ -1016,9 +1104,11 @@ trait Contexts { self: Analyzer => this } - def hasErrors = errorBuffer.nonEmpty - def hasWarnings = warningBuffer.nonEmpty - def firstError = errorBuffer.head + def hasErrors = errorBuffer.nonEmpty + def hasWarnings = warningBuffer.nonEmpty + def firstError = errorBuffer.head + def firstErrorOpt = errorBuffer.headOption + def firstWarningOpt = warningBuffer.headOption } class ImportInfo(val tree: Import, val depth: Int) { diff --git a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala index f7f6970c14..b0c8baae20 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala @@ -584,10 +584,11 @@ trait Implicits { ) case _ => fallback } - if (context.hasErrors) { - val err = context.firstError - log("implicit adapt failed: " + err.errMsg) - return fail(err.errMsg) + context.firstError match { // using match rather than foreach to avoid non local return. + case Some(err) => + log("implicit adapt failed: " + err.errMsg) + return fail(err.errMsg) + case None => } if (Statistics.canEnable) Statistics.incCounter(typedImplicits) @@ -610,7 +611,7 @@ trait Implicits { } if (context.hasErrors) - fail("hasMatchingSymbol reported error: " + context.firstError.errMsg) + fail("hasMatchingSymbol reported error: " + context.firstError.get.errMsg) else if (isLocal && !hasMatchingSymbol(itree1)) fail("candidate implicit %s is shadowed by %s".format( info.sym.fullLocationString, itree1.symbol.fullLocationString)) @@ -633,8 +634,11 @@ trait Implicits { // #2421: check that we correctly instantiated type parameters outside of the implicit tree: checkBounds(itree2, NoPrefix, NoSymbol, undetParams, targs, "inferred ") - if (context.hasErrors) - return fail("type parameters weren't correctly instantiated outside of the implicit tree: " + context.firstError.errMsg) + context.firstError match { + case Some(err) => + return fail("type parameters weren't correctly instantiated outside of the implicit tree: " + err.errMsg) + case None => + } // filter out failures from type inference, don't want to remove them from undetParams! // we must be conservative in leaving type params in undetparams @@ -669,13 +673,14 @@ trait Implicits { case t => t } - if (context.hasErrors) - fail("typing TypeApply reported errors for the implicit tree: " + context.firstError.errMsg) - else { - val result = new SearchResult(itree2, subst) - if (Statistics.canEnable) Statistics.incCounter(foundImplicits) - printInference("[success] found %s for pt %s".format(result, ptInstantiated)) - result + context.firstError match { + case Some(err) => + fail("typing TypeApply reported errors for the implicit tree: " + err.errMsg) + case None => + val result = new SearchResult(itree2, subst) + if (Statistics.canEnable) Statistics.incCounter(foundImplicits) + printInference("[success] found %s for pt %s".format(result, ptInstantiated)) + result } } else fail("incompatible: %s does not match expected type %s".format(itree2.tpe, ptInstantiated)) @@ -1086,11 +1091,10 @@ trait Implicits { try { val tree1 = typedPos(pos.focus)(arg) - if (context.hasErrors) { - val err = context.firstError - processMacroExpansionError(err.errPos, err.errMsg) + context.firstError match { + case Some(err) => processMacroExpansionError(err.errPos, err.errMsg) + case None => new SearchResult(tree1, EmptyTreeTypeSubstituter) } - else new SearchResult(tree1, EmptyTreeTypeSubstituter) } catch { case ex: TypeError => processMacroExpansionError(ex.pos, ex.msg) diff --git a/src/compiler/scala/tools/nsc/typechecker/Infer.scala b/src/compiler/scala/tools/nsc/typechecker/Infer.scala index 8d7830897d..8c87cf51d3 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Infer.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Infer.scala @@ -1531,14 +1531,15 @@ trait Infer extends Checkable { } } + /** @return true if the `expr` evaluates to true within a silent Context that incurs no errors */ @inline private def inSilentMode(context: Context)(expr: => Boolean): Boolean = { - val oldState = context.state + val savedContextMode = context.contextMode context.setBufferErrors() val res = expr - val contextWithErrors = context.hasErrors - context.flushBuffer() - context.restoreState(oldState) - res && !contextWithErrors + val contextHadErrors = context.hasErrors + context.reportBuffer.clearAll() + context.contextMode = savedContextMode + res && !contextHadErrors } // Checks against the name of the parameter and also any @deprecatedName. @@ -1636,7 +1637,7 @@ trait Infer extends Checkable { */ def tryTwice(infer: Boolean => Unit): Unit = { if (context.implicitsEnabled) { - val saved = context.state + val savedContextMode = context.contextMode var fallback = false context.setBufferErrors() // We cache the current buffer because it is impossible to @@ -1650,17 +1651,17 @@ trait Infer extends Checkable { context.withImplicitsDisabled(infer(false)) if (context.hasErrors) { fallback = true - context.restoreState(saved) + context.contextMode = savedContextMode context.flushBuffer() infer(true) } } catch { case ex: CyclicReference => throw ex case ex: TypeError => // recoverable cyclic references - context.restoreState(saved) + context.contextMode = savedContextMode if (!fallback) infer(true) else () } finally { - context.restoreState(saved) + context.contextMode = savedContextMode context.updateBuffer(errorsToRestore) } } diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index f57352b11e..3736380954 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -492,8 +492,16 @@ trait Typers extends Adaptations with Tags { @inline final def typerReportAnyContextErrors[T](c: Context)(f: Typer => T): T = { val res = f(newTyper(c)) - if (c.hasErrors) - context.issue(c.firstError) + c.firstError match { + case Some(err) => + context.issue(err) + c.reportBuffer.warnings foreach { + case (pos, msg) => context.warning(pos, msg) + } + // Seemingly we should perform `c.reportBuffer.clearAll()` here. But + // `context` might share the ReportBuffer with `c`. + case None => + } res } @@ -687,17 +695,18 @@ trait Typers extends Adaptations with Tags { context.undetparams = context1.undetparams context.savedTypeBounds = context1.savedTypeBounds context.namedApplyBlockInfo = context1.namedApplyBlockInfo - if (context1.hasErrors) { - stopStats() - SilentTypeError(context1.firstError) - } else { - // If we have a successful result, emit any warnings it created. - if (context1.hasWarnings) { - context1.flushAndReturnWarningsBuffer() foreach { - case (pos, msg) => unit.warning(pos, msg) + context1.firstError match { + case Some(err) => + stopStats() + SilentTypeError(err) + case None => + // If we have a successful result, emit any warnings it created. + if (context1.reportBuffer.hasWarnings) { + context1.flushAndReturnWarningsBuffer() foreach { + case (pos, msg) => unit.warning(pos, msg) + } } - } - SilentResultValue(result) + SilentResultValue(result) } } else { assert(context.bufferErrors || isPastTyper, "silent mode is not available past typer") @@ -1174,7 +1183,10 @@ trait Typers extends Adaptations with Tags { val silentContext = context.makeImplicit(context.ambiguousErrors) val res = newTyper(silentContext).typed( new ApplyImplicitView(coercion, List(tree)) setPos tree.pos, mode, pt) - if (silentContext.hasErrors) context.issue(silentContext.firstError) else return res + silentContext.firstError match { + case Some(err) => context.issue(err) + case None => return res + } } } } diff --git a/src/compiler/scala/tools/reflect/ToolBoxFactory.scala b/src/compiler/scala/tools/reflect/ToolBoxFactory.scala index 132dd251db..66dda2b530 100644 --- a/src/compiler/scala/tools/reflect/ToolBoxFactory.scala +++ b/src/compiler/scala/tools/reflect/ToolBoxFactory.scala @@ -184,7 +184,9 @@ abstract class ToolBoxFactory[U <: JavaUniverse](val u: U) { factorySelf => analyzer.inferImplicit(tree, pt, reportAmbiguous = true, isView = isView, context = context, saveAmbiguousDivergent = !silent, pos = pos) match { case failure if failure.tree.isEmpty => trace("implicit search has failed. to find out the reason, turn on -Xlog-implicits: ")(failure.tree) - if (context.hasErrors) throw ToolBoxError("reflective implicit search has failed: %s".format(context.firstError.errMsg)) + context.firstError foreach { err => + throw ToolBoxError("reflective implicit search has failed: %s".format(err.errMsg)) + } EmptyTree case success => success.tree -- cgit v1.2.3 From 510ebeca9e55b27bbe6e6c54bf0ea6ea294ee7be Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Wed, 10 Apr 2013 12:51:57 +0200 Subject: SI-7345 Prefer using a throwaway silent context over buffer flushing. When we are using a throwaway silent context, we can just let it drift out of scope and over the horizon, rather than ceremoniously flushing its buffers on completion. - Applied to Scaladoc. - Applied to Infer#isApplicableSafe. Less manual error buffer management affords greater opportunity to cleanly express the logic. - Applied to `typerReportAnyContextErrors`. The reasoning for the last case is as follows: - There were only two callers to `typerReportAnyContextErrors`, and they both passed in as `c` a child context of `context`. - That child context must share the error reporting mode and buffer with `context`. - Therefore, extracting an error from `c` and issuing it into `context` is a no-op. Because the error buffer is Set, it was harmless. This part will probably textually conflict with the same change made in SI-7319, but the end results are identical. --- .../scala/tools/nsc/typechecker/Contexts.scala | 10 +++-- .../scala/tools/nsc/typechecker/Infer.scala | 29 +++++++----- .../scala/tools/nsc/typechecker/Typers.scala | 52 +++++++--------------- .../doc/model/ModelFactoryImplicitSupport.scala | 9 ++-- 4 files changed, 44 insertions(+), 56 deletions(-) diff --git a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala index 4b9d3fda01..4fc9cde070 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala @@ -261,11 +261,13 @@ trait Contexts { self: Analyzer => reportBuffer.clearAllErrors() current } - /** Return and clear all warnings from the report buffer */ - def flushAndReturnWarningsBuffer(): immutable.Seq[(Position, String)] = { - val current = reportBuffer.warnings + + /** Issue and clear all warnings from the report buffer */ + def flushAndIssueWarnings() { + reportBuffer.warnings foreach { + case (pos, msg) => unit.warning(pos, msg) + } reportBuffer.clearAllWarnings() - current } // diff --git a/src/compiler/scala/tools/nsc/typechecker/Infer.scala b/src/compiler/scala/tools/nsc/typechecker/Infer.scala index 8c87cf51d3..323ed3287e 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Infer.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Infer.scala @@ -910,19 +910,28 @@ trait Infer extends Checkable { } /** - * Todo: Try to make isApplicable always safe (i.e. not cause TypeErrors). - * The chance of TypeErrors should be reduced through context errors + * Are arguments of the given types applicable to `ftpe`? Type argument inference + * is tried twice: firstly with the given expected type, and secondly with `WildcardType`. */ + // Todo: Try to make isApplicable always safe (i.e. not cause TypeErrors). + // The chance of TypeErrors should be reduced through context errors private[typechecker] def isApplicableSafe(undetparams: List[Symbol], ftpe: Type, argtpes0: List[Type], pt: Type): Boolean = { - val silentContext = context.makeSilent(reportAmbiguousErrors = false) - val typer0 = newTyper(silentContext) - val res1 = typer0.infer.isApplicable(undetparams, ftpe, argtpes0, pt) - if (pt != WildcardType && silentContext.hasErrors) { - silentContext.flushBuffer() - val res2 = typer0.infer.isApplicable(undetparams, ftpe, argtpes0, WildcardType) - if (silentContext.hasErrors) false else res2 - } else res1 + final case class Result(error: Boolean, applicable: Boolean) + def isApplicableWithExpectedType(pt0: Type): Result = { + val silentContext = context.makeSilent(reportAmbiguousErrors = false) + val applicable = newTyper(silentContext).infer.isApplicable(undetparams, ftpe, argtpes0, pt0) + Result(silentContext.hasErrors, applicable) + } + val canSecondTry = pt != WildcardType + val firstTry = isApplicableWithExpectedType(pt) + if (!firstTry.error || !canSecondTry) + firstTry.applicable + else { + val secondTry = isApplicableWithExpectedType(WildcardType) + // TODO `!secondTry.error &&` was faithfully replicated as part of the refactoring, but mayberedundant. + !secondTry.error && secondTry.applicable + } } /** Is type `ftpe1` strictly more specific than type `ftpe2` diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 3736380954..5523dfd388 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -489,20 +489,9 @@ trait Typers extends Adaptations with Tags { res } - @inline - final def typerReportAnyContextErrors[T](c: Context)(f: Typer => T): T = { - val res = f(newTyper(c)) - c.firstError match { - case Some(err) => - context.issue(err) - c.reportBuffer.warnings foreach { - case (pos, msg) => context.warning(pos, msg) - } - // Seemingly we should perform `c.reportBuffer.clearAll()` here. But - // `context` might share the ReportBuffer with `c`. - case None => - } - res + final def newImplTyper[T](impl: ImplDef, clazz: Symbol): Typer = { + val c = context.make(impl.impl, clazz, newScope) + newTyper(c) } @inline @@ -701,11 +690,7 @@ trait Typers extends Adaptations with Tags { SilentTypeError(err) case None => // If we have a successful result, emit any warnings it created. - if (context1.reportBuffer.hasWarnings) { - context1.flushAndReturnWarningsBuffer() foreach { - case (pos, msg) => unit.warning(pos, msg) - } - } + context1.flushAndIssueWarnings() SilentResultValue(result) } } else { @@ -1812,9 +1797,7 @@ trait Typers extends Adaptations with Tags { assert(clazz != NoSymbol, cdef) reenterTypeParams(cdef.tparams) val tparams1 = cdef.tparams mapConserve (typedTypeDef) - val impl1 = typerReportAnyContextErrors(context.make(cdef.impl, clazz, newScope)) { - _.typedTemplate(cdef.impl, typedParentTypes(cdef.impl)) - } + val impl1 = newImplTyper(cdef, clazz).typedTemplate(cdef.impl, typedParentTypes(cdef.impl)) val impl2 = finishMethodSynthesis(impl1, clazz, context) if (clazz.isTrait && clazz.info.parents.nonEmpty && clazz.info.firstParent.typeSymbol == AnyClass) checkEphemeral(clazz, impl2.body) @@ -1855,17 +1838,16 @@ trait Typers extends Adaptations with Tags { || !linkedClass.isSerializable || clazz.isSerializable ) - val impl1 = typerReportAnyContextErrors(context.make(mdef.impl, clazz, newScope)) { - _.typedTemplate(mdef.impl, { - typedParentTypes(mdef.impl) ++ ( - if (noSerializable) Nil - else { - clazz.makeSerializable() - List(TypeTree(SerializableClass.tpe) setPos clazz.pos.focus) - } - ) - }) - } + val impl1 = newImplTyper(mdef, clazz).typedTemplate(mdef.impl, { + typedParentTypes(mdef.impl) ++ ( + if (noSerializable) Nil + else { + clazz.makeSerializable() + List(TypeTree(SerializableClass.tpe) setPos clazz.pos.focus) + } + ) + }) + val impl2 = finishMethodSynthesis(impl1, clazz, context) // SI-5954. On second compile of a companion class contained in a package object we end up @@ -4360,11 +4342,9 @@ trait Typers extends Adaptations with Tags { case ex: CyclicReference => throw ex case te: TypeError => - // @H some of typer erros can still leak, + // @H some of typer errors can still leak, // for instance in continuations None - } finally { - c.flushBuffer() } } diff --git a/src/scaladoc/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala b/src/scaladoc/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala index 65b00d4673..6cefe34887 100644 --- a/src/scaladoc/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala +++ b/src/scaladoc/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala @@ -226,12 +226,9 @@ trait ModelFactoryImplicitSupport { // look for type variables in the type. If there are none, we can decide if the implicit is there or not if (implType.isTrivial) { try { - context.flushBuffer() /* any errors here should not prevent future findings */ - // TODO: Not sure this is the right thing to do -- seems similar to what scalac should be doing - val context2 = context.make(owner = sym.owner) - val search = inferImplicit(EmptyTree, tpe, false, false, context2, false) - context.flushBuffer() /* any errors here should not prevent future findings */ - + // TODO: Not sure if `owner = sym.owner` is the right thing to do -- seems similar to what scalac should be doing + val silentContext = context.make(owner = sym.owner).makeSilent(reportAmbiguousErrors = false) + val search = inferImplicit(EmptyTree, tpe, false, false, silentContext, false) available = Some(search.tree != EmptyTree) } catch { case _: TypeError => -- cgit v1.2.3 From bba9d3d468a4be6a867aa4494375fcc108c8b97e Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Mon, 15 Apr 2013 13:38:27 +0200 Subject: SI-7345 remove unused methods. --- src/compiler/scala/tools/nsc/typechecker/Contexts.scala | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala index 4fc9cde070..5bbb96ccd2 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala @@ -228,9 +228,7 @@ trait Contexts { self: Analyzer => def restoreReportBuffer(other: ReportBuffer) = _reportBuffer = other /** The first error, if any, in the report buffer */ - def firstError: Option[AbsTypeError] = reportBuffer.firstErrorOpt - /** The first warning, if any, in the report buffer */ - def firstWarning: Option[(Position, String)] = reportBuffer.firstWarningOpt + def firstError: Option[AbsTypeError] = reportBuffer.firstError /** Does the report buffer contain any errors? */ def hasErrors = reportBuffer.hasErrors @@ -249,8 +247,6 @@ trait Contexts { self: Analyzer => devWarning("When entering the buffer state, context has to be clean. Current buffer: " + reportBuffer.errors) } - // TODO SI-7345 refactor this part of the API. - /** Append the given errors to the report buffer */ def updateBuffer(errors: Traversable[AbsTypeError]) = reportBuffer ++= errors /** Clear all errors from the report buffer */ @@ -1106,11 +1102,8 @@ trait Contexts { self: Analyzer => this } - def hasErrors = errorBuffer.nonEmpty - def hasWarnings = warningBuffer.nonEmpty - def firstError = errorBuffer.head - def firstErrorOpt = errorBuffer.headOption - def firstWarningOpt = warningBuffer.headOption + def hasErrors = errorBuffer.nonEmpty + def firstError = errorBuffer.headOption } class ImportInfo(val tree: Import, val depth: Int) { -- cgit v1.2.3 From 7ce4de49c62258356cb84593f623e38bcefe7f22 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Tue, 16 Apr 2013 09:18:17 +0200 Subject: SI-7345 Move `inSilentMode` from Infer to Context. --- src/compiler/scala/tools/nsc/typechecker/Contexts.scala | 13 +++++++++++-- src/compiler/scala/tools/nsc/typechecker/Infer.scala | 17 +++-------------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala index 5bbb96ccd2..fd15d9f675 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala @@ -270,7 +270,7 @@ trait Contexts { self: Analyzer => // Temporary mode adjustment // - private def withMode[T](enabled: ContextMode = NOmode, disabled: ContextMode = NOmode)(op: => T): T = { + @inline def withMode[T](enabled: ContextMode = NOmode, disabled: ContextMode = NOmode)(op: => T): T = { val saved = contextMode set(enabled, disabled) try op @@ -283,6 +283,15 @@ trait Contexts { self: Analyzer => def withMacrosEnabled[T](op: => T): T = withMode(enabled = MacrosEnabled)(op) def withMacrosDisabled[T](op: => T): T = withMode(disabled = MacrosEnabled)(op) + /** @return true if the `expr` evaluates to true within a silent Context that incurs no errors */ + @inline final def inSilentMode(expr: => Boolean): Boolean = { + withMode() { // withMode with no arguments to restore the mode mutated by `setBufferErrors`. + setBufferErrors() + try expr && !hasErrors + finally reportBuffer.clearAll() + } + } + // // Child Context Creation // @@ -358,7 +367,7 @@ trait Contexts { self: Analyzer => make(tree, owner, newNestedScope(scope)) /** Make a child context that buffers errors and warnings into a fresh report buffer. */ - def makeSilent(reportAmbiguousErrors: Boolean, newtree: Tree = tree): Context = { + def makeSilent(reportAmbiguousErrors: Boolean = ambiguousErrors, newtree: Tree = tree): Context = { val c = make(newtree) c.setBufferErrors() c.setAmbiguousErrors(reportAmbiguousErrors) diff --git a/src/compiler/scala/tools/nsc/typechecker/Infer.scala b/src/compiler/scala/tools/nsc/typechecker/Infer.scala index 323ed3287e..1bd17e51d2 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Infer.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Infer.scala @@ -1540,17 +1540,6 @@ trait Infer extends Checkable { } } - /** @return true if the `expr` evaluates to true within a silent Context that incurs no errors */ - @inline private def inSilentMode(context: Context)(expr: => Boolean): Boolean = { - val savedContextMode = context.contextMode - context.setBufferErrors() - val res = expr - val contextHadErrors = context.hasErrors - context.reportBuffer.clearAll() - context.contextMode = savedContextMode - res && !contextHadErrors - } - // Checks against the name of the parameter and also any @deprecatedName. private def paramMatchesName(param: Symbol, name: Name) = param.name == name || param.deprecatedParamName.exists(_ == name) @@ -1619,7 +1608,7 @@ trait Infer extends Checkable { } def followType(sym: Symbol) = followApply(pre memberType sym) def bestForExpectedType(pt: Type, isLastTry: Boolean): Unit = { - val applicable0 = alts filter (alt => inSilentMode(context)(isApplicable(undetparams, followType(alt), argtpes, pt))) + val applicable0 = alts filter (alt => context inSilentMode (isApplicable(undetparams, followType(alt), argtpes, pt))) val applicable = overloadsToConsiderBySpecificity(applicable0, argtpes, varargsStar) val ranked = bestAlternatives(applicable)((sym1, sym2) => isStrictlyMoreSpecific(followType(sym1), followType(sym2), sym1, sym2) @@ -1629,8 +1618,8 @@ trait Infer extends Checkable { case best :: Nil => tree setSymbol best setType (pre memberType best) // success case Nil if pt eq WildcardType => NoBestMethodAlternativeError(tree, argtpes, pt, isLastTry) // failed case Nil => bestForExpectedType(WildcardType, isLastTry) // failed, but retry with WildcardType - } - } + } + } // This potentially makes up to four attempts: tryTwice may execute // with and without views enabled, and bestForExpectedType will try again // with pt = WildcardType if it fails with pt != WildcardType. -- cgit v1.2.3 From 78e7ebab7d8acce2632c7ba6a63546cc174e1467 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Tue, 16 Apr 2013 09:31:19 +0200 Subject: SI-7345 Refactor manual iteration to use foreach. --- src/compiler/scala/tools/nsc/typechecker/Contexts.scala | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala index fd15d9f675..a585c24fb1 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala @@ -99,13 +99,11 @@ trait Contexts { self: Analyzer => } def resetContexts() { - var sc = startContext - while (sc != NoContext) { - sc.tree match { + startContext.enclosingContextChain foreach { context => + context.tree match { case Import(qual, _) => qual setType singleType(qual.symbol.owner.thisType, qual.symbol) case _ => } - sc = sc.outer } } -- cgit v1.2.3 From dbd8457e441bda6a444efd19b5eb1eaec373a535 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Tue, 16 Apr 2013 11:12:44 +0200 Subject: SI-7345 Produce Context#imports from the context chain Rather than keeping a List[ImportInfo] around as a field. This sort of change reduces our bug exposure by structurally enforcing the invariant that `Context#imports` must mirror the `outer` chain. This entails changing a few elements of Contexts from mutable vars to constructor pararameters, which seems desirable anyway. We no longer need `makeNewImport`, as `make` now inspects the provided tree and determines does import specific things (tracking of used imports / mixin of ImportContext) when appropriate. The only wrinkle in this commit is the unfortunate need to pass `owner = null` in the extends clause of NoContext. I don't see cleaner way around this. Applied minor rework to `implicitss` to avoid needlessly re-evaluating `imports` and `nextOuter`. --- .../scala/tools/nsc/typechecker/Contexts.scala | 88 +++++++++++++--------- .../scala/tools/nsc/typechecker/Namers.scala | 2 +- .../scala/tools/nsc/typechecker/Typers.scala | 2 +- 3 files changed, 55 insertions(+), 37 deletions(-) diff --git a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala index a585c24fb1..53aa6bdf7a 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala @@ -19,14 +19,20 @@ trait Contexts { self: Analyzer => import definitions.{ JavaLangPackage, ScalaPackage, PredefModule } import ContextMode._ - object NoContext extends Context { - outer = this + object NoContext + extends Context(EmptyTree, NoSymbol, EmptyScope, NoCompilationUnit, + outer = null /*We can't pass NoContext here, overriden below*/) { + + override val outer = this + enclClass = this enclMethod = this override def nextEnclosing(p: Context => Boolean): Context = this override def enclosingContextChain: List[Context] = Nil override def implicitss: List[List[ImplicitInfo]] = Nil + override def imports: List[ImportInfo] = Nil + override def firstImport: Option[ImportInfo] = None override def toString = "NoContext" } private object RootImports { @@ -89,7 +95,7 @@ trait Contexts { self: Analyzer => def rootContext(unit: CompilationUnit, tree: Tree = EmptyTree, erasedTypes: Boolean = false): Context = { var sc = startContext for (sym <- rootImports(unit)) { - sc = sc.makeNewImport(gen.mkWildcardImport(sym)) + sc = sc.make(gen.mkWildcardImport(sym)) sc.depth += 1 } val c = sc.make(tree, unit = unit) @@ -107,16 +113,14 @@ trait Contexts { self: Analyzer => } } - class Context private[typechecker] { - var unit: CompilationUnit = NoCompilationUnit - /** Tree associated with this context */ - var tree: Tree = _ - /** The current owner */ - var owner: Symbol = NoSymbol - /** The current scope */ - var scope: Scope = _ - /** The next outer context */ - var outer: Context = _ + /** + * @param tree Tree associated with this context + * @param owner The current owner + * @param scope The current scope + * @param outer The next outer context. + */ + class Context private[typechecker](val tree: Tree, val owner: Symbol, val scope: Scope, + val unit: CompilationUnit, val outer: Context) { /** The next outer context whose tree is a template or package definition */ var enclClass: Context = _ @@ -155,7 +159,9 @@ trait Contexts { self: Analyzer => var depth: Int = 0 /** The currently visible imports */ - var imports: List[ImportInfo] = List() + def imports: List[ImportInfo] = outer.imports + /** Equivalent to `imports.headOption`, but more efficient */ + def firstImport: Option[ImportInfo] = outer.firstImport /** Types for which implicit arguments are currently searched */ var openImplicits: List[(Type,Tree)] = List() @@ -297,10 +303,12 @@ trait Contexts { self: Analyzer => /** * Construct a child context. The parent and child will share the report buffer. * Compare with `makeSilent`, in which the child has a fresh report buffer. + * + * If `tree` is an `Import`, that import will be avaiable at the head of + * `Context#imports`. */ def make(tree: Tree = tree, owner: Symbol = owner, - scope: Scope = scope, imports: List[ImportInfo] = imports, - unit: CompilationUnit = unit): Context = { + scope: Scope = scope, unit: CompilationUnit = unit): Context = { val isTemplateOrPackage = tree match { case _: Template | _: PackageDef => true case _ => false @@ -309,6 +317,10 @@ trait Contexts { self: Analyzer => case _: DefDef => true case _ => false } + val isImport = tree match { + case _: Import => true + case _ => false + } val sameOwner = owner == this.owner val sameScope = scope == this.scope val prefixInChild = @@ -317,15 +329,13 @@ trait Contexts { self: Analyzer => else prefix // The blank canvas - val c = new Context + val c = if (isImport) + new Context(tree, owner, scope, unit, this) with ImportContext + else + new Context(tree, owner, scope, unit, this) // Fields that are directly propagated - c.unit = unit - c.tree = tree - c.owner = owner - c.scope = scope c.variance = variance - c.imports = imports c.diagnostic = diagnostic c.typingIndentLevel = typingIndentLevel c.openImplicits = openImplicits @@ -333,7 +343,6 @@ trait Contexts { self: Analyzer => c._reportBuffer = reportBuffer // Fields that may take on a different value in the child - c.outer = this c.prefix = prefixInChild c.enclClass = if (isTemplateOrPackage) c else enclClass c(ConstructorSuffix) = !isTemplateOrPackage && c(ConstructorSuffix) @@ -349,16 +358,7 @@ trait Contexts { self: Analyzer => // TODO SI-7345 Moving this optimization into the main overload of `make` causes all tests to fail. // even if it is extened to check that `unit == this.unit`. Why is this? if (tree == this.tree && owner == this.owner && scope == this.scope) this - else make(tree, owner, scope, imports, unit) - - /** Make a child context that includes `imp` in the list of visible imports */ - def makeNewImport(imp: Import): Context = { - val impInfo = new ImportInfo(imp, depth) - if (settings.lint && imp.pos.isDefined) // pos.isDefined excludes java.lang/scala/Predef imports - allImportInfos(unit) ::= impInfo - - make(imp, imports = impInfo :: imports) - } + else make(tree, owner, scope, unit) /** Make a child context that represents a new nested scope */ def makeNewScope(tree: Tree, owner: Symbol): Context = @@ -708,6 +708,8 @@ trait Contexts { self: Analyzer => * filtered out later by `eligibleInfos` (SI-4270 / 9129cfe9), as they don't type-check. */ def implicitss: List[List[ImplicitInfo]] = { + val imports = this.imports + val nextOuter = this.nextOuter if (implicitsRunId != currentRunId) { implicitsRunId = currentRunId implicitsCache = List() @@ -724,8 +726,8 @@ trait Contexts { self: Analyzer => } else if (scope != nextOuter.scope && !owner.isPackageClass) { debuglog("collect local implicits " + scope.toList)//DEBUG collectImplicits(scope, NoPrefix) - } else if (imports != nextOuter.imports) { - assert(imports.tail == nextOuter.imports, (imports, nextOuter.imports)) + } else if (firstImport != nextOuter.firstImport) { + assert(imports.tail.headOption == nextOuter.firstImport, (imports, nextOuter.imports)) collectImplicitImports(imports.head) } else if (owner.isPackageClass) { // the corresponding package object may contain implicit members. @@ -1058,6 +1060,22 @@ trait Contexts { self: Analyzer => } } //class Context + trait ImportContext extends Context { + private def imp = tree.asInstanceOf[Import] + final def isRootImport = !imp.pos.isDefined // excludes java.lang/scala/Predef imports + + private val impInfo = { + val info = new ImportInfo(imp, outer.depth) + if (settings.lint && !isRootImport) + allImportInfos(unit) ::= info + info + } + + override final def imports = impInfo :: super.imports + override final def firstImport = Some(impInfo) + override final def toString = "" + } + /** A buffer for warnings and errors that are accumulated during speculative type checking. */ final class ReportBuffer { type Error = AbsTypeError diff --git a/src/compiler/scala/tools/nsc/typechecker/Namers.scala b/src/compiler/scala/tools/nsc/typechecker/Namers.scala index bbb1dbe8d8..e4bfa9d66a 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Namers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Namers.scala @@ -253,7 +253,7 @@ trait Namers extends MethodSynthesis { case DocDef(_, defn) => enterSym(defn) case tree @ Import(_, _) => assignSymbol(tree) - returnContext = context.makeNewImport(tree) + returnContext = context.make(tree) case _ => } returnContext diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 5523dfd388..58982a7aef 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -2837,7 +2837,7 @@ trait Typers extends Adaptations with Tags { case imp @ Import(_, _) => imp.symbol.initialize if (!imp.symbol.isError) { - context = context.makeNewImport(imp) + context = context.make(imp) typedImport(imp) } else EmptyTree case _ => -- cgit v1.2.3 From ec33ad00b47ac75b40369725e8d5cf197f313458 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Tue, 16 Apr 2013 22:00:32 +0200 Subject: SI-7345 Doc and TODO comments around Context. --- .../scala/tools/nsc/typechecker/Contexts.scala | 45 +++++++++++++++++++++- .../tools/nsc/typechecker/NamesDefaults.scala | 1 + 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala index 53aa6bdf7a..64ea17ba29 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala @@ -114,6 +114,49 @@ trait Contexts { self: Analyzer => } /** + * A motley collection of the state and loosely associated behaviour of the type checker. + * Each `Typer` has an associated context, and as it descends into the tree new `(Typer, Context)` + * pairs are spawned. + * + * Meet the crew; first the state: + * + * - A tree, symbol, and scope representing the focus of the typechecker + * - An enclosing context, `outer`. + * - The current compilation unit. + * - A variety of bits that track the current error reporting policy (more on this later); + * whether or not implicits/macros are enabled, whether we are in a self or super call or + * in a constructor suffix. These are represented as bits in the mask `contextMode`. + * - Some odds and ends: undetermined type pararameters of the current line of type inference; + * contextual augmentation for error messages, tracking of the nesting depth. + * + * And behaviour: + * + * - The central point for issuing errors and warnings from the typechecker, with a means + * to buffer these for use in 'silent' type checking, when some recovery might be possible. + * - `Context` is something of a Zipper for the tree were are typechecking: it `enclosingContextChain` + * is the path back to the root. This is exactly what we need to resolve names (`lookupSymbol`) + * and to collect in-scope implicit defintions (`implicitss`) + * Supporting these are `imports`, which represents all `Import` trees in in the enclosing context chain. + * - In a similar vein, we can assess accessiblity (`isAccessible`.) + * + * More on error buffering: + * When are type errors recoverable? In quite a few places, it turns out. Some examples: + * trying to type an application with/without the expected type, or with.without implicit views + * enabled. This is usually mediated by in `Typer.silent`, `Inferencer#tryTwice`. + * + * Intially, starting from the `typer` phase, the contexts either either buffer or report errors; + * from `erasure` errors are thrown. This is configured in `rootContext`. Additionally, more + * fine grained control is needed based on the kind of error; ambiguity errors are often + * suppressed during exploraratory typing, such as determining whether `a == b` in an argument + * position is an assignment or a named argument, when `Infererencer#isApplicableSafe` type checks + * applications with and without an expected type, or whtn `Typer#tryTypedApply` tries to fit arguments to + * a function type with/without implicit views. + * + * When the error policies entails error/warning buffering, the mutable [[ReportBuffer]] records + * everything that is issued. It is important to note, that child Contexts created with `make` + * "inherit" the very same `ReportBuffer` instance, whereas children spawned through `makeSilent` + * receive an separate, fresh buffer. + * * @param tree Tree associated with this context * @param owner The current owner * @param scope The current scope @@ -246,6 +289,7 @@ trait Contexts { self: Analyzer => def setThrowErrors(): Unit = this(ReportErrors | AmbiguousErrors | BufferErrors) = false def setAmbiguousErrors(report: Boolean): Unit = this(AmbiguousErrors) = report + // TODO SI-7345 According to Hubert, this warning will be noisy and is unneccessary. private def warnIfBufferNotClean() { if (!bufferErrors && hasErrors) devWarning("When entering the buffer state, context has to be clean. Current buffer: " + reportBuffer.errors) @@ -1288,4 +1332,3 @@ final class ContextMode private (val bits: Int) extends AnyVal { if (bits == 0) "NOmode" else (contextModeNameMap filterKeys inAll).values.toList.sorted mkString " " } - diff --git a/src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala b/src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala index 6921f8ce27..40a2690ed6 100644 --- a/src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala +++ b/src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala @@ -451,6 +451,7 @@ trait NamesDefaults { self: Analyzer => } else NoSymbol } + // TODO SI-7345 Use a method in Context for this. private def savingUndeterminedTParams[T](context: Context)(fn: List[Symbol] => T): T = { val savedParams = context.extractUndetparams() val savedReporting = context.ambiguousErrors -- cgit v1.2.3 From 0ce81c855fc8c12623ae49f1cfa6e1fb255c872a Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Tue, 16 Apr 2013 22:01:52 +0200 Subject: SI-7345 Remove unneeded warning. According to Hubert, during normal operation we can start buffering errors in a context with existing errors. So the warning would be noisy. --- src/compiler/scala/tools/nsc/typechecker/Contexts.scala | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala index 64ea17ba29..dd43e22159 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala @@ -285,16 +285,10 @@ trait Contexts { self: Analyzer => def throwErrors = contextMode.inNone(ReportErrors | BufferErrors) def setReportErrors(): Unit = set(enable = ReportErrors | AmbiguousErrors, disable = BufferErrors) - def setBufferErrors(): Unit = {set(enable = BufferErrors, disable = ReportErrors | AmbiguousErrors); warnIfBufferNotClean()} + def setBufferErrors(): Unit = set(enable = BufferErrors, disable = ReportErrors | AmbiguousErrors) def setThrowErrors(): Unit = this(ReportErrors | AmbiguousErrors | BufferErrors) = false def setAmbiguousErrors(report: Boolean): Unit = this(AmbiguousErrors) = report - // TODO SI-7345 According to Hubert, this warning will be noisy and is unneccessary. - private def warnIfBufferNotClean() { - if (!bufferErrors && hasErrors) - devWarning("When entering the buffer state, context has to be clean. Current buffer: " + reportBuffer.errors) - } - /** Append the given errors to the report buffer */ def updateBuffer(errors: Traversable[AbsTypeError]) = reportBuffer ++= errors /** Clear all errors from the report buffer */ -- cgit v1.2.3 From e112db6fc4afe0a7721ec87423eacd405fa6c89b Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Tue, 16 Apr 2013 22:42:07 +0200 Subject: SI-7345 Factor out method to clear and restore undetparams. Also refactored `handleOverloaded` to avoid use of a mutable Buffer --- .../scala/tools/nsc/typechecker/Contexts.scala | 14 ++++++++ .../tools/nsc/typechecker/NamesDefaults.scala | 18 ++-------- .../scala/tools/nsc/typechecker/Typers.scala | 39 +++++++++++----------- 3 files changed, 35 insertions(+), 36 deletions(-) diff --git a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala index dd43e22159..ae1ff24e76 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala @@ -262,6 +262,20 @@ trait Contexts { self: Analyzer => tparams } + /** Run `body` with this context with no undetermined type parameters, restore the original + * the original list afterwards. + * @param reportAmbiguous Should ambiguous errors be reported during evaluation of `body`? + */ + def savingUndeterminedTypeParams[A](reportAmbiguous: Boolean = ambiguousErrors)(body: => A): A = { + withMode() { + this(AmbiguousErrors) = reportAmbiguous + val savedParams = extractUndetparams() + try body + finally { + undetparams = savedParams + } + } + } // // Error reporting policies and buffer. // diff --git a/src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala b/src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala index 40a2690ed6..2e1fb4d98c 100644 --- a/src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala +++ b/src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala @@ -451,21 +451,6 @@ trait NamesDefaults { self: Analyzer => } else NoSymbol } - // TODO SI-7345 Use a method in Context for this. - private def savingUndeterminedTParams[T](context: Context)(fn: List[Symbol] => T): T = { - val savedParams = context.extractUndetparams() - val savedReporting = context.ambiguousErrors - - context.setAmbiguousErrors(false) - try fn(savedParams) - finally { - context.setAmbiguousErrors(savedReporting) - //@M note that we don't get here when an ambiguity was detected (during the computation of res), - // as errorTree throws an exception - context.undetparams = savedParams - } - } - /** A full type check is very expensive; let's make sure there's a name * somewhere which could potentially be ambiguous before we go that route. */ @@ -480,7 +465,8 @@ trait NamesDefaults { self: Analyzer => // def f[T](x: T) = x // var x = 0 // f(x = 1) << "x = 1" typechecks with expected type WildcardType - savingUndeterminedTParams(context) { udp => + val udp = context.undetparams + context.savingUndeterminedTypeParams(reportAmbiguous = false) { val subst = new SubstTypeMap(udp, udp map (_ => WildcardType)) { override def apply(tp: Type): Type = super.apply(tp match { case TypeRef(_, ByNameParamClass, x :: Nil) => x diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 58982a7aef..83157688af 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -3090,30 +3090,29 @@ trait Typers extends Adaptations with Tags { fun.tpe match { case OverloadedType(pre, alts) => def handleOverloaded = { - val undetparams = context.extractUndetparams() - val argtpes = new ListBuffer[Type] - val amode = forArgMode(fun, mode) - val args1 = args map { - case arg @ AssignOrNamedArg(Ident(name), rhs) => - // named args: only type the righthand sides ("unknown identifier" errors otherwise) - val rhs1 = typedArg(rhs, amode, BYVALmode, WildcardType) - argtpes += NamedType(name, rhs1.tpe.deconst) - // the assign is untyped; that's ok because we call doTypedApply - treeCopy.AssignOrNamedArg(arg, arg.lhs, rhs1) - case arg @ Typed(repeated, Ident(tpnme.WILDCARD_STAR)) => - val arg1 = typedArg(arg, amode, BYVALmode, WildcardType) - argtpes += RepeatedType(arg1.tpe.deconst) - arg1 - case arg => - val arg1 = typedArg(arg, amode, BYVALmode, WildcardType) - argtpes += arg1.tpe.deconst - arg1 + val undetparams = context.undetparams + val (args1, argTpes) = context.savingUndeterminedTypeParams() { + val amode = forArgMode(fun, mode) + def typedArg0(tree: Tree) = typedArg(tree, amode, BYVALmode, WildcardType) + args.map { + case arg @ AssignOrNamedArg(Ident(name), rhs) => + // named args: only type the righthand sides ("unknown identifier" errors otherwise) + val rhs1 = typedArg0(rhs) + // the assign is untyped; that's ok because we call doTypedApply + val arg1 = treeCopy.AssignOrNamedArg(arg, arg.lhs, rhs1) + (arg1, NamedType(name, rhs1.tpe.deconst)) + case arg @ Typed(repeated, Ident(tpnme.WILDCARD_STAR)) => + val arg1 = typedArg0(arg) + (arg1, RepeatedType(arg1.tpe.deconst)) + case arg => + val arg1 = typedArg0(arg) + (arg1, arg1.tpe.deconst) + }.unzip } - context.undetparams = undetparams if (context.hasErrors) setError(tree) else { - inferMethodAlternative(fun, undetparams, argtpes.toList, pt) + inferMethodAlternative(fun, undetparams, argTpes, pt) doTypedApply(tree, adapt(fun, mode.forFunMode, WildcardType), args1, mode, pt) } } -- cgit v1.2.3 From 2304a78a00fe45cb05d1c3b77e4381813109cbfd Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Tue, 16 Apr 2013 23:16:09 +0200 Subject: SI-7345 Drive by refactoring of pattern matching for `arg: _*`. --- src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala | 12 +++++------- src/compiler/scala/tools/nsc/typechecker/RefChecks.scala | 2 +- src/compiler/scala/tools/nsc/typechecker/Typers.scala | 4 ++-- src/reflect/scala/reflect/internal/TreeGen.scala | 5 +++++ src/reflect/scala/reflect/internal/TreeInfo.scala | 11 +++++++++-- 5 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala b/src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala index 2e1fb4d98c..e22e2c603a 100644 --- a/src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala +++ b/src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala @@ -19,6 +19,7 @@ trait NamesDefaults { self: Analyzer => import global._ import definitions._ import NamesDefaultsErrorsGen._ + import treeInfo.WildcardStarArg // Default getters of constructors are added to the companion object in the // typeCompleter of the constructor (methodSig). To compute the signature, @@ -278,8 +279,8 @@ trait NamesDefaults { self: Analyzer => val repeated = isScalaRepeatedParamType(paramTpe) val argTpe = ( if (repeated) arg match { - case Typed(expr, Ident(tpnme.WILDCARD_STAR)) => expr.tpe - case _ => seqType(arg.tpe) + case WildcardStarArg(expr) => expr.tpe + case _ => seqType(arg.tpe) } else // Note stabilizing can lead to a non-conformant argument when existentials are involved, e.g. neg/t3507-old.scala, hence the filter. @@ -302,11 +303,8 @@ trait NamesDefaults { self: Analyzer => } else { new ChangeOwnerTraverser(context.owner, sym) traverse arg // fixes #4502 if (repeated) arg match { - case Typed(expr, Ident(tpnme.WILDCARD_STAR)) => - expr - case _ => - val factory = Select(gen.mkAttributedRef(SeqModule), nme.apply) - blockTyper.typed(Apply(factory, List(resetLocalAttrs(arg)))) + case WildcardStarArg(expr) => expr + case _ => blockTyper typed gen.mkSeqApply(resetLocalAttrs(arg)) } else arg } Some(atPos(body.pos)(ValDef(sym, body).setType(NoType))) diff --git a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala index efd4fd804f..4d8c9d84a5 100644 --- a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala +++ b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala @@ -1591,7 +1591,7 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans enterReference(tree.pos, tpt.tpe.typeSymbol) tree - case Typed(_, Ident(tpnme.WILDCARD_STAR)) if !isRepeatedParamArg(tree) => + case treeInfo.WildcardStarArg(_) if !isRepeatedParamArg(tree) => unit.error(tree.pos, "no `: _*' annotation allowed here\n"+ "(such annotations are only allowed in arguments to *-parameters)") tree diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 83157688af..01f7b11362 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -3101,7 +3101,7 @@ trait Typers extends Adaptations with Tags { // the assign is untyped; that's ok because we call doTypedApply val arg1 = treeCopy.AssignOrNamedArg(arg, arg.lhs, rhs1) (arg1, NamedType(name, rhs1.tpe.deconst)) - case arg @ Typed(repeated, Ident(tpnme.WILDCARD_STAR)) => + case arg @ treeInfo.WildcardStarArg(repeated) => val arg1 = typedArg0(arg) (arg1, RepeatedType(arg1.tpe.deconst)) case arg => @@ -4986,7 +4986,7 @@ trait Typers extends Adaptations with Tags { typedEta(checkDead(exprTyped)) } - case Ident(tpnme.WILDCARD_STAR) => + case t if treeInfo isWildcardStarType t => val exprTyped = typed(expr, mode.onlySticky, WildcardType) def subArrayType(pt: Type) = if (isPrimitiveValueClass(pt.typeSymbol) || !isFullyDefined(pt)) arrayType(pt) diff --git a/src/reflect/scala/reflect/internal/TreeGen.scala b/src/reflect/scala/reflect/internal/TreeGen.scala index d3e486311e..11574ad8ac 100644 --- a/src/reflect/scala/reflect/internal/TreeGen.scala +++ b/src/reflect/scala/reflect/internal/TreeGen.scala @@ -298,4 +298,9 @@ abstract class TreeGen extends macros.TreeBuilder { def mkPackageDef(packageName: String, stats: List[Tree]): PackageDef = { PackageDef(mkUnattributedRef(newTermName(packageName)), stats) } + + def mkSeqApply(arg: Tree): Apply = { + val factory = Select(gen.mkAttributedRef(SeqModule), nme.apply) + Apply(factory, List(arg)) + } } diff --git a/src/reflect/scala/reflect/internal/TreeInfo.scala b/src/reflect/scala/reflect/internal/TreeInfo.scala index b1f58814c7..461d929e7a 100644 --- a/src/reflect/scala/reflect/internal/TreeInfo.scala +++ b/src/reflect/scala/reflect/internal/TreeInfo.scala @@ -401,8 +401,15 @@ abstract class TreeInfo { /** Is this argument node of the form : _* ? */ def isWildcardStarArg(tree: Tree): Boolean = tree match { - case Typed(_, Ident(tpnme.WILDCARD_STAR)) => true - case _ => false + case WildcardStarArg(_) => true + case _ => false + } + + object WildcardStarArg { + def unapply(tree: Typed): Option[Tree] = tree match { + case Typed(expr, Ident(tpnme.WILDCARD_STAR)) => Some(expr) + case _ => None + } } /** If this tree has type parameters, those. Otherwise Nil. -- cgit v1.2.3 From 85af192fffd3801692b91c2d8f806cd55d559ffd Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Wed, 17 Apr 2013 14:50:30 +0200 Subject: SI-7345 Eliminate the `depth` var. In favour of a val calculated during construction. Internalizes the depth calculation into the Context constructor rather than leaving it in the hands of the factory method `make`. Also touched a few unrelated doc comments. --- .../scala/tools/nsc/typechecker/Contexts.scala | 50 +++++++++++++--------- 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala index ae1ff24e76..4343845e80 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala @@ -28,6 +28,7 @@ trait Contexts { self: Analyzer => enclClass = this enclMethod = this + override val depth = 0 override def nextEnclosing(p: Context => Boolean): Context = this override def enclosingContextChain: List[Context] = Nil override def implicitss: List[List[ImplicitInfo]] = Nil @@ -93,12 +94,8 @@ trait Contexts { self: Analyzer => } def rootContext(unit: CompilationUnit, tree: Tree = EmptyTree, erasedTypes: Boolean = false): Context = { - var sc = startContext - for (sym <- rootImports(unit)) { - sc = sc.make(gen.mkWildcardImport(sym)) - sc.depth += 1 - } - val c = sc.make(tree, unit = unit) + val rootImportsContext = (startContext /: rootImports(unit))((c, sym) => c.make(gen.mkWildcardImport(sym))) + val c = rootImportsContext.make(tree, unit = unit) if (erasedTypes) c.setThrowErrors() else c.setReportErrors() c(EnrichmentEnabled | ImplicitsEnabled) = !erasedTypes c @@ -196,15 +193,19 @@ trait Contexts { self: Analyzer => /** Variance relative to enclosing class */ var variance: Variance = Variance.Invariant - /** Undetermined type parameters. Not inherited to child contexts */ private var _undetparams: List[Symbol] = List() - var depth: Int = 0 + val depth: Int = { + val increasesDepth = isRootImport || (outer eq null) || (outer.scope != scope) + def outerDepth = if (outer eq null) 0 else outer.depth + ( if (increasesDepth) 1 else 0 ) + outerDepth + } /** The currently visible imports */ def imports: List[ImportInfo] = outer.imports /** Equivalent to `imports.headOption`, but more efficient */ def firstImport: Option[ImportInfo] = outer.firstImport + def isRootImport: Boolean = false /** Types for which implicit arguments are currently searched */ var openImplicits: List[(Type,Tree)] = List() @@ -248,10 +249,13 @@ trait Contexts { self: Analyzer => /** The next enclosing context (potentially `this`) that has a `CaseDef` as a tree */ def enclosingCaseDef = nextEnclosing(_.tree.isInstanceOf[CaseDef]) + // + // Tracking undetermined type parameters for type argument inference. + // def undetparamsString = if (undetparams.isEmpty) "" else undetparams.mkString("undetparams=", ", ", "") - /** Undetermined type parameters. See `Infer#{inferExprInstance, adjustTypeArgs}`. */ + /** Undetermined type parameters. See `Infer#{inferExprInstance, adjustTypeArgs}`. Not inherited to child contexts */ def undetparams: List[Symbol] = _undetparams def undetparams_=(ps: List[Symbol]) = { _undetparams = ps } @@ -276,6 +280,7 @@ trait Contexts { self: Analyzer => } } } + // // Error reporting policies and buffer. // @@ -374,7 +379,6 @@ trait Contexts { self: Analyzer => case _ => false } val sameOwner = owner == this.owner - val sameScope = scope == this.scope val prefixInChild = if (isTemplateOrPackage) owner.thisType else if (!sameOwner && owner.isTerm) NoPrefix @@ -399,7 +403,6 @@ trait Contexts { self: Analyzer => c.enclClass = if (isTemplateOrPackage) c else enclClass c(ConstructorSuffix) = !isTemplateOrPackage && c(ConstructorSuffix) c.enclMethod = if (isDefDef) c else enclMethod - c.depth = depth + (if (sameScope) 0 else 1) registerContext(c.asInstanceOf[analyzer.Context]) debuglog("[context] ++ " + c.unit + " / " + tree.summaryString) @@ -1112,20 +1115,25 @@ trait Contexts { self: Analyzer => } } //class Context + /** A `Context` focussed on an `Import` tree */ trait ImportContext extends Context { - private def imp = tree.asInstanceOf[Import] - final def isRootImport = !imp.pos.isDefined // excludes java.lang/scala/Predef imports - - private val impInfo = { - val info = new ImportInfo(imp, outer.depth) - if (settings.lint && !isRootImport) + private def makeImpInfo = { + val info = new ImportInfo(tree.asInstanceOf[Import], outer.depth) + if (settings.lint && !info.isRootImport) // excludes java.lang/scala/Predef imports allImportInfos(unit) ::= info info } - override final def imports = impInfo :: super.imports - override final def firstImport = Some(impInfo) - override final def toString = "" + private var _impInfo: ImportInfo = null // hand rolled lazy val, we don't need/want synchronization. + private def impInfo: ImportInfo = { + if (_impInfo eq null) _impInfo = makeImpInfo + _impInfo + } + + override final def imports = impInfo :: super.imports + override final def firstImport = Some(impInfo) + override final def isRootImport = impInfo.isRootImport + override final def toString = "" } /** A buffer for warnings and errors that are accumulated during speculative type checking. */ @@ -1198,6 +1206,8 @@ trait Contexts { self: Analyzer => def isExplicitImport(name: Name): Boolean = tree.selectors exists (_.rename == name.toTermName) + final def isRootImport: Boolean = !tree.pos.isDefined + /** The symbol with name `name` imported from import clause `tree`. */ def importedSymbol(name: Name): Symbol = importedSymbol(name, requireExplicit = false) -- cgit v1.2.3 From c598e764b91307587388ab2a3f5188e64f42af44 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Wed, 17 Apr 2013 15:16:53 +0200 Subject: SI-7345 Improved Context.toString ticket/7345-2 ~/code/scala qbin/scala Welcome to Scala version 2.11.0-20130416-231609-7829011884 (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_37). Type in expressions to have them evaluated. Type :help for more information. scala> import language.experimental._import language.experimental._ scala> import reflect.macros.Contextimport reflect.macros.Context scala> def showContextImpl(c: Context) = {println(c.asInstanceOf[reflect.macros.runtime.Context].callsiteTyper.context.enclosingContextChain.mkString("\n\n")); c.literalUnit} showContextImpl: (c: scala.reflect.macros.Context)c.Expr[Unit] scala> def showContext = macro showContextImpldefined term macro showContext: Unit scala> object Foo { def foo(a: Any) { {class C { println("") }; showContext } } }Context() { owner = method foo tree = Block:{ class C extends scala.AnyRef { def (): C = { super.(); ( scope = 1 decls contextMode = AmbiguousErrors ImplicitsEnabled MacrosEnabled ReportErrors outer.owner = method foo } Context() { owner = method foo tree = DefDef:def foo(a: Any): Unit = { class C extends scala.AnyRef { def (): scope = 1 decls contextMode = AmbiguousErrors ImplicitsEnabled MacrosEnabled ReportErrors outer.owner = object Foo } Context() { owner = object Foo tree = Template(scala.AnyRef, _, 2 stats) scope = 0 decls contextMode = AmbiguousErrors ImplicitsEnabled MacrosEnabled ReportErrors outer.owner = object Foo } Context() { owner = object Foo tree = ModuleDef:object Foo extends scala.AnyRef { def (): Foo.type = { super.) { owner = object $iw tree = Template(scala.AnyRef, _, 2 stats) scope = 0 decls contextMode = AmbiguousErrors ImplicitsEnabled MacrosEnabled ReportErrors outer.owner = object $iw } Context() { owner = object $iw tree = ModuleDef:object $iw extends scala.AnyRef { def (): type = { super.( scope = 0 decls contextMode = AmbiguousErrors ImplicitsEnabled MacrosEnabled ReportErrors outer.owner = object $iw } ImportContext { import $line18.$read.$iw.$iw.$iw.$iw.showContext; outer.owner = object $iw } ImportContext { import $line17.$read.$iw.$iw.$iw.$iw.showContextImpl; outer.owner = object $iw } ImportContext { import reflect.macros.Context; outer.owner = object $iw } Context() { owner = object $iw tree = Template(scala.AnyRef, _, 5 stats) scope = 0 decls contextMode = AmbiguousErrors ImplicitsEnabled MacrosEnabled ReportErrors outer.owner = object $iw } Context() { owner = object $iw tree = ModuleDef:object $iw extends scala.AnyRef { def (): type = { super.( scope = 0 decls contextMode = AmbiguousErrors ImplicitsEnabled MacrosEnabled ReportErrors outer.owner = object $iw } ImportContext { import language.experimental._; outer.owner = object $iw } Context() { owner = object $iw tree = Template(scala.AnyRef, _, 3 stats) scope = 0 decls contextMode = AmbiguousErrors ImplicitsEnabled MacrosEnabled ReportErrors outer.owner = object $iw } Context() { owner = object $iw tree = ModuleDef:object $iw extends scala.AnyRef { def (): type = { super.( scope = 0 decls contextMode = AmbiguousErrors ImplicitsEnabled MacrosEnabled ReportErrors outer.owner = object $iw } Context() { owner = object $iw tree = Template(scala.AnyRef, _, 2 stats) scope = 0 decls contextMode = AmbiguousErrors ImplicitsEnabled MacrosEnabled ReportErrors outer.owner = object $iw } Context() { owner = object $iw tree = ModuleDef:object $iw extends scala.AnyRef { def (): type = { super.( scope = 0 decls contextMode = AmbiguousErrors ImplicitsEnabled MacrosEnabled ReportErrors outer.owner = object $read } Context() { owner = object $read tree = Template(scala.AnyRef, _, 2 stats) scope = 0 decls contextMode = AmbiguousErrors ImplicitsEnabled MacrosEnabled ReportErrors outer.owner = object $read } Context() { owner = object $read tree = ModuleDef:object $read extends scala.AnyRef { def (): $line19.$read.type = scope = 1 decls contextMode = AmbiguousErrors ImplicitsEnabled MacrosEnabled ReportErrors outer.owner = package $line19 } Context() { owner = package $line19 tree = PackageDef:package $line19 { object $read extends scala.AnyRef { def (): $l scope = 1 decls contextMode = AmbiguousErrors ImplicitsEnabled MacrosEnabled ReportErrors outer.owner = package } Context() { owner = package tree = EmptyTree: scope = 50 decls contextMode = AmbiguousErrors ImplicitsEnabled MacrosEnabled ReportErrors outer.owner = package } ImportContext { import scala.this.Predef._; outer.owner = package } ImportContext { import scala._; outer.owner = package } ImportContext { import java.this.lang._; outer.owner = package } Context(NoCompilationUnit) { owner = package tree = Template(Nil, _, 0 stats) scope = 50 decls contextMode = MacrosEnabled outer.owner = } defined object Foo --- .../scala/tools/nsc/typechecker/Contexts.scala | 24 ++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala index 4343845e80..bcc6f8eed6 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala @@ -557,9 +557,25 @@ trait Contexts { self: Analyzer => def enclosingContextChain: List[Context] = this :: outer.enclosingContextChain - override def toString = "Context(%s@%s unit=%s scope=%s errors=%b, reportErrors=%b, throwErrors=%b)".format( - owner.fullName, tree.shortClass, unit, scope.##, hasErrors, reportErrors, throwErrors - ) + private def treeTruncated = tree.toString.replaceAll("\\s+", " ").lines.mkString("\\n").take(70) + private def treeIdString = if (settings.uniqid.value) "#" + System.identityHashCode(tree).toString.takeRight(3) else "" + private def treeString = tree match { + case x: Import => "" + x + case Template(parents, `emptyValDef`, body) => + val pstr = if ((parents eq null) || parents.isEmpty) "Nil" else parents mkString " " + val bstr = if (body eq null) "" else body.length + " stats" + s"""Template($pstr, _, $bstr)""" + case x => s"${tree.shortClass}${treeIdString}:${treeTruncated}" + } + + override def toString = + sm"""|Context($unit) { + | owner = $owner + | tree = $treeString + | scope = ${scope.size} decls + | contextMode = $contextMode + | outer.owner = ${outer.owner} + |}""" // // Accessibility checking @@ -1133,7 +1149,7 @@ trait Contexts { self: Analyzer => override final def imports = impInfo :: super.imports override final def firstImport = Some(impInfo) override final def isRootImport = impInfo.isRootImport - override final def toString = "" + override final def toString = s"ImportContext { $impInfo; outer.owner = ${outer.owner} }" } /** A buffer for warnings and errors that are accumulated during speculative type checking. */ -- cgit v1.2.3 From 648784cdf7918ec581051c9df5ffd1a137cb8a5a Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sun, 21 Apr 2013 12:47:23 +0200 Subject: SI-7345 Address review comments. --- src/compiler/scala/tools/nsc/typechecker/Contexts.scala | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala index bcc6f8eed6..6daa00e7f8 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala @@ -138,11 +138,11 @@ trait Contexts { self: Analyzer => * * More on error buffering: * When are type errors recoverable? In quite a few places, it turns out. Some examples: - * trying to type an application with/without the expected type, or with.without implicit views - * enabled. This is usually mediated by in `Typer.silent`, `Inferencer#tryTwice`. + * trying to type an application with/without the expected type, or with/without implicit views + * enabled. This is usually mediated by `Typer.silent`, `Inferencer#tryTwice`. * - * Intially, starting from the `typer` phase, the contexts either either buffer or report errors; - * from `erasure` errors are thrown. This is configured in `rootContext`. Additionally, more + * Intially, starting from the `typer` phase, the contexts either buffer or report errors; + * afterwards errors are thrown. This is configured in `rootContext`. Additionally, more * fine grained control is needed based on the kind of error; ambiguity errors are often * suppressed during exploraratory typing, such as determining whether `a == b` in an argument * position is an assignment or a named argument, when `Infererencer#isApplicableSafe` type checks @@ -273,11 +273,9 @@ trait Contexts { self: Analyzer => def savingUndeterminedTypeParams[A](reportAmbiguous: Boolean = ambiguousErrors)(body: => A): A = { withMode() { this(AmbiguousErrors) = reportAmbiguous - val savedParams = extractUndetparams() + val saved = extractUndetparams() try body - finally { - undetparams = savedParams - } + finally undetparams = saved } } @@ -1328,7 +1326,8 @@ object ContextMode { /** Are we in a run of [[scala.tools.nsc.typechecker.TreeCheckers]]? */ final val Checking: ContextMode = 1 << 9 - // TODO harvest documentation for this + /** Are we retypechecking arguments independently from the function applied to them? See `Typer.tryTypedApply` */ + // TODO This seems to directly overlap with Mode.SNDTRYmode final val ReTyping: ContextMode = 1 << 10 final val DefaultMode: ContextMode = MacrosEnabled -- cgit v1.2.3