package scala.tools.nsc.typechecker; import scala.collection.jcl.WeakHashMap trait IdeSupport extends Analyzer { val global : Global with symtab.IdeSupport import global._ private class ContextInternMap extends WeakHashMap[Context,ref.WeakReference[Context]] { var last : Context = _ override def default(txt : Context) : ref.WeakReference[Context] = { if (txt eq NoContext) new ref.WeakReference(NoContext) val txt0 = txt.intern0 last = txt0 // to prevent collection val ret = new ref.WeakReference(txt0) this(txt0) = ret ret } def intern(txt : Context) = this(txt).get.get } private val internMap = new ContextInternMap override def intern(txt : Context) = if (false) super.intern(txt) else if (txt.outer eq txt) txt else internMap.intern(txt) override def newNamer(context : Context) : Namer = new Namer(context) class Namer(context: Context) extends super.Namer(context) { override protected def setInfo[Sym <: Symbol](sym : Sym)(tpe : LazyType) : Sym = { assert(!sym.hasRawInfo || sym.rawInfo == NoType) // type information has already been reset. if (currentClient.makeNoChanges) { sym.setInfo(tpe) sym.info // force completion. return sym } object tpe0 extends LazyType with SimpleTypeProxy { override def underlying = tpe override def complete(sym0 : Symbol) : Unit = { assert(sym eq sym0) toComplete -= sym val pos = sym.pos match { case pos : TrackedPosition => pos } val oldType = oldTypeFor(sym0) oldType match { case PolyType(xxx,_) => val i = xxx.elements var pause = false while (i.hasNext) { if (i.next.pos == util.NoPosition) pause = true } if (pause) { assert(true) assert(true) } case _=> } assert(sym.rawInfo == this) val hadTypeErrors = pos.owner != null && pos.owner.hasTypeErrors if (pos.owner == null) underlying.complete(sym0) else pos.owner.activate(try { underlying.complete(sym0) } catch { case te : TypeError => pos.owner.typeError(te.getMessage) sym0.setInfo(ErrorType) }) (oldType,sym0.info) match { case (PolyType(xxx,_),PolyType(yyy,_)) if xxx != yyy => val oldc = xxx val newc = yyy Console.print("DIFF old=" + oldc.map(sym => sym + ":" + sym.pos).mkString("",",","")) Console.println(" new=" + newc.map(sym => sym + ":" + sym.pos).mkString("",",","")) case _ => } if (!hadTypeErrors && pos.owner != null && pos.owner.hasTypeErrors) pos.owner.dirtyTyped if (pos.owner != null && pos.owner.hasTypeErrors) { // go back to original type. val oldType = oldTypeFor(sym0) if (oldType != NoType) sym0.setInfo(oldType) } } } toComplete += sym super.setInfo(sym)(tpe0) } override def enterSym(tree : Tree) : Context = tree match { case tree : StubTree => if (tree.symbol == NoSymbol) // reset stub symbol on refresh. tree.symbol = tree.underlying.updateNamer(this) context case tree => super.enterSym(tree) } } override def newTyper(txt : Context) : Typer = new Typer(txt) class Typer(context : Context) extends super.Typer(context) { override def qualifyingClassContext(tree: Tree, qual: Name): Context = { if (qual.isEmpty) super.qualifyingClassContext(tree, qual) else { var c = context.enclClass val client = currentClient while (c != NoContext && { // register dependency. client.notify(qual, c.owner) c.owner.owner.info.decls match { case scope : HookedScope => scope.record(client, qual) case _ => } true } && c.owner.name != qual) c = c.outer.enclClass c } } // no adapting. override protected def adapt(tree: Tree, mode: Int, pt: Type): Tree = super.adapt(tree,mode,pt) override def typed1(tree: Tree, mode: Int, pt: Type): Tree = tree match { case tree : StubTree => if (tree.tpe == null) tree.tpe = tree.underlying.updateTyper(this, mode, pt) tree case tree => super.typed1(tree, mode, pt) } } private val toComplete = new scala.collection.jcl.LinkedHashSet[Symbol] def finishTyping = while (!toComplete.isEmpty) { toComplete.toList.foreach(sym => if (sym.pos match { case pos : TrackedPosition if !pos.isValid => toComplete.remove(sym); false case _ => true }){ if (sym.info.isComplete) toComplete.remove(sym) else { sym.info if (!sym.info.isComplete) { Console.println("not-completing: " + sym) toComplete remove sym } } }) } trait TrackedPosition extends global.TrackedPosition { def owner : MemoizedTree def isValid : Boolean } trait MemoizedTree { def kind : TreeKind def pos : TrackedPosition def typeIsDirty : Boolean def dirtyTyped : Unit def useTrees : List[Tree] def setUseTrees(uses : List[Tree]) : Unit def lastTyped : List[Tree] def activate(f : => Unit) : Unit def typeError(msg : String) : Unit def hasTypeErrors : Boolean def shouldBeTyped : Boolean = true // probably invalidate parent if its not being validated now // most type changes detected via setType. protected def typeChanged : Unit protected def highlightChanged : Unit def lastSymbol = if (lastTyped.isEmpty) NoSymbol else lastTyped.last.symbol def lastType = if (lastTyped.isEmpty) null else lastTyped.last.tpe protected var namerTxt : Context = NoContext protected var typerTxt : Context = NoContext protected var mode : Int = 0 protected var pt : Type = NoType def doNamer = if (namerTxt ne NoContext) updateNamer(newNamer(namerTxt)) def updateNamer(namer : Namer) : Symbol = { val makeNoChanges = currentClient.makeNoChanges val namerTxt = intern(namer.context) if (!makeNoChanges && (this.namerTxt ne namerTxt)) { assert(namerTxt.scope ne EmptyScope) assert(namerTxt.owner ne NoSymbol) this.namerTxt = namerTxt dirtyTyped } val lastSymbol = this.lastSymbol def fakeUpdate(trees : List[Tree]) : Symbol = { trees.foreach{ case tree : DefTree if (tree.symbol != NoSymbol && tree.symbol != null) => // becareful, the symbol could have been rentered! var e = namer.context.scope.lookupEntry(tree.symbol.name) while (e != null && e.sym != tree.symbol) e = namer.context.scope.lookupNextEntry(e) if (e == null) { //Console.println("FK-ENTER: " + tree.symbol) val sym = namer.enterInScope(tree.symbol) if (sym != tree.symbol) { assert(true) assert(true) Console.println("SCREWED: " + sym + " vs. " + tree.symbol) } import symtab.Flags._ val set = reuseMap.get(namer.context.scope.asInstanceOf[PersistentScope]) if (set.isDefined && sym.isClass && sym.hasFlag(CASE)) { val name = sym.name.toTermName val factory = set.get.find(_.name == name).get val sym0 = namer.enterInScope(factory) assert(sym0 == factory) } // could be getter or local, then we need to re-add getter/setter if (sym.isGetter && set.isDefined) set.get.find(sym0 => sym0.name == nme.getterToSetter(sym.name) && sym0.isSetter) match { case None => case Some(setter) => val setter0 = namer.enterInScope(setter) assert(setter0 == setter) } else if (sym.hasGetter && set.isDefined) set.get.find(sym => sym.name == nme.getterName(sym.name) && sym.isGetter) match { case None => case Some(getter) => val getter0 = namer.enterInScope(getter) assert(getter0 == getter) if (set.isDefined) set.get.find(sym => sym.name == nme.getterToSetter(getter.name) && sym.isSetter) match { case None => case Some(setter) => val setter0 = namer.enterInScope(setter) assert(setter0 == setter) } } } case _ => }; if (trees.isEmpty) NoSymbol else trees.last.symbol } if (makeNoChanges) {} else if (!typeIsDirty && !lastTyped.isEmpty) return fakeUpdate(lastTyped) else if (namerTxt != NoContext && shouldBeTyped) {} else return fakeUpdate(lastTyped) val use = useTrees if (makeNoChanges) {} else if (use.isEmpty || use.last.symbol != NoSymbol) { assert(true) return fakeUpdate(use) // already named } if (kind.isTop) namer.context.unit.source.file match { case file : io.PlainFile => reloadSource(file) case _ => } // before we retype, unlink/recycle our previously defined symbols. if (!makeNoChanges) lastTyped.foreach{tree => if (tree.symbol != NoSymbol && tree.symbol != null) (namer.context.scope,tree) match { case (scope : PersistentScope,tree : DefTree) => if (!tree.symbol.isPackage) reuse(scope, tree.symbol) case _ => } } activate(try { use.foreach{tree => if (tree.isInstanceOf[DefTree]) { assert(true) //Console.println("RENAME: " + tree) } namer.enterSym(tree) } } catch { case te : TypeError => typeError(te.getMessage) }) if (makeNoChanges) {} else if (hasTypeErrors && lastSymbol != null && lastSymbol != NoSymbol && use.last.symbol != lastSymbol) { if (use.last.symbol != null && use.last.symbol != NoSymbol) { namer.context.scope unlink use.last.symbol } Console.println("ER-LINK: " + lastSymbol) val sym = namer.enterInScope(lastSymbol) assert(sym == lastSymbol) use.last.symbol = lastSymbol } use.last.symbol } def doTyper = if (typerTxt ne NoContext) updateTyper(newTyper(typerTxt), mode, pt) def updateTyper(typer : Typer, mode : Int, pt : Type) : Type = { assert(true) val typerTxt = intern(typer.context) val makeNoChanges = currentClient.makeNoChanges if (!makeNoChanges && ((this.typerTxt ne typerTxt) || (this.pt != pt) || (this.mode != mode))) { this.typerTxt = typerTxt this.pt = pt this.mode = mode dirtyTyped } val lastType = this.lastType if (makeNoChanges) {} else if (typeIsDirty && shouldBeTyped && typerTxt != NoContext) { } else if (lastType == null) { assert(true) assert(true) return NoType } else return lastType var use = useTrees if (use.isEmpty) return lastType; if ((use.last.tpe != null)) return use.last.tpe if (use.last.symbol == NoSymbol && namerTxt != NoContext) updateNamer(newNamer(namerTxt)) if (makeNoChanges) { assert(true) assert(true) } activate(try { setUseTrees{use = use.map{typer.typed(_,mode,pt)}; use} } catch { case te : TypeError => typeError(te.getMessage) }) if (!makeNoChanges && hasTypeErrors && lastType != null) { use.last.tpe = lastType } if (!makeNoChanges && !hasTypeErrors && use.last.tpe != null && lastType != null && !compareTypes(use.last.tpe, lastType,Nil)(_.info)) { // the type changed in a good way. typeChanged } assert(true) if (!makeNoChanges && (use.length != lastTyped.length || !use.zip(lastTyped).forall{ case (t0,t1) => t0.equalsStructure0(t1){ case (t0:StubTree,t1:StubTree) if t0.underlying == t0.underlying || true => true case _ => false } })) { assert(true) highlightChanged } if (use.last.tpe == null) ErrorType else use.last.tpe } } trait StubTree extends global.StubTree { def underlying : MemoizedTree override var symbol : Symbol = NoSymbol override def duplicate : this.type = throw new Error("not supported") override def isType = underlying.kind.isType override def isTerm = underlying.kind.isTerm override def isDef = underlying.kind.isDef override def hasSymbol = underlying.kind.hasSymbol override def hashCode = underlying.hashCode override def equals(that : Any) = that match { case that : StubTree => that.underlying == underlying case _ => false } override def toString = "st-" + underlying.toString override def pos = util.NoPosition } }