From 3028327e2a2b553b12ee45519413515c8aa0865f Mon Sep 17 00:00:00 2001 From: Luc Bourlier Date: Wed, 13 Nov 2013 17:24:59 +0100 Subject: SI-7280 Scope completion not returning members provided by imports Updates localeContext() to return the best context possible when there are none directly associated with the given position. It happens when an expression cannot be successfully typed, as no precise ContextTree covers the expression location, or if the position is not inside any expression. Adds corresponding tests --- .../scala/tools/nsc/interactive/ContextTrees.scala | 64 ++++++++++++++-------- 1 file changed, 40 insertions(+), 24 deletions(-) (limited to 'src/interactive') diff --git a/src/interactive/scala/tools/nsc/interactive/ContextTrees.scala b/src/interactive/scala/tools/nsc/interactive/ContextTrees.scala index 93ef4c4d6c..4f67a22b8f 100644 --- a/src/interactive/scala/tools/nsc/interactive/ContextTrees.scala +++ b/src/interactive/scala/tools/nsc/interactive/ContextTrees.scala @@ -6,6 +6,7 @@ package scala.tools.nsc package interactive import scala.collection.mutable.ArrayBuffer +import scala.annotation.tailrec trait ContextTrees { self: Global => @@ -28,44 +29,59 @@ trait ContextTrees { self: Global => override def toString = "ContextTree("+pos+", "+children+")" } - /** Optionally returns the smallest context that contains given `pos`, or None if none exists. + /** Returns the most precise context possible for the given `pos`. + * + * It looks for the finest ContextTree containing `pos`, and then look inside + * this ContextTree for a child ContextTree located immediately before `pos`. + * If such a child exists, returns its context, otherwise returns the context of + * the parent ContextTree. + * + * This is required to always return a context which contains the all the imports + * declared up to `pos` (see SI-7280 for a test case). + * + * Can return None if `pos` is before any valid Scala code. */ def locateContext(contexts: Contexts, pos: Position): Option[Context] = synchronized { - def locateNearestContextTree(contexts: Contexts, pos: Position, recent: Array[ContextTree]): Option[ContextTree] = { - locateContextTree(contexts, pos) match { - case Some(x) => - recent(0) = x - locateNearestContextTree(x.children, pos, recent) - case None => recent(0) match { - case null => None - case x => Some(x) - } + @tailrec + def locateFinestContextTree(context: ContextTree): ContextTree = { + if (context.pos includes pos) { + locateContextTree(context.children, pos) match { + case Some(x) => + locateFinestContextTree(x) + case None => + context + } + } else { + context } } - locateNearestContextTree(contexts, pos, new Array[ContextTree](1)) map (_.context) + locateContextTree(contexts, pos) map locateFinestContextTree map (_.context) } + /** Returns the ContextTree containing `pos`, or the ContextTree positioned just before `pos`, + * or None if `pos` is located before all ContextTrees. + */ def locateContextTree(contexts: Contexts, pos: Position): Option[ContextTree] = { if (contexts.isEmpty) None else { - val hi = contexts.length - 1 - if ((contexts(hi).pos properlyPrecedes pos) || (pos properlyPrecedes contexts(0).pos)) None - else { - def loop(lo: Int, hi: Int): Option[ContextTree] = { + @tailrec + def loop(lo: Int, hi: Int, previousSibling: Option[ContextTree]): Option[ContextTree] = { + if (pos properlyPrecedes contexts(lo).pos) + previousSibling + else if (contexts(hi).pos properlyPrecedes pos) + Some(contexts(hi)) + else { val mid = (lo + hi) / 2 val midpos = contexts(mid).pos - if ((pos precedes midpos) && (mid < hi)) - loop(lo, mid) - else if ((midpos precedes pos) && (lo < mid)) - loop(mid, hi) - else if (midpos includes pos) + if (midpos includes pos) Some(contexts(mid)) - else if (contexts(mid+1).pos includes pos) - Some(contexts(mid+1)) - else None + else if (midpos properlyPrecedes pos) + loop(mid + 1, hi, Some(contexts(mid))) + else + loop(lo, mid, previousSibling) } - loop(0, hi) } + loop(0, contexts.length - 1, None) } } -- cgit v1.2.3