Skip to content

Commit 3d248ef

Browse files
committed
Merge pull request scala#1554 from paulp/scope-lookup
Improvements to scope lookup.
2 parents 2c6777f + c04a4ed commit 3d248ef

File tree

7 files changed

+122
-95
lines changed

7 files changed

+122
-95
lines changed

src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -562,7 +562,7 @@ abstract class ClassfileParser {
562562
0 until in.nextChar foreach (_ => parseMethod())
563563
val needsConstructor = (
564564
!sawPrivateConstructor
565-
&& instanceScope.lookup(nme.CONSTRUCTOR) == NoSymbol
565+
&& !(instanceScope containsName nme.CONSTRUCTOR)
566566
&& (sflags & INTERFACE) == 0
567567
)
568568
if (needsConstructor)

src/compiler/scala/tools/nsc/transform/Flatten.scala

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,14 @@ abstract class Flatten extends InfoTransform {
1818
/** the following two members override abstract members in Transform */
1919
val phaseName: String = "flatten"
2020

21-
/** Updates the owning scope with the given symbol; returns the old symbol.
21+
/** Updates the owning scope with the given symbol, unlinking any others.
2222
*/
23-
private def replaceSymbolInCurrentScope(sym: Symbol): Symbol = exitingFlatten {
23+
private def replaceSymbolInCurrentScope(sym: Symbol): Unit = exitingFlatten {
2424
val scope = sym.owner.info.decls
25-
val old = scope lookup sym.name andAlso scope.unlink
25+
val old = (scope lookupUnshadowedEntries sym.name).toList
26+
old foreach (scope unlink _)
2627
scope enter sym
27-
28-
if (old eq NoSymbol)
29-
log(s"lifted ${sym.fullLocationString}")
30-
else
31-
log(s"lifted ${sym.fullLocationString} after unlinking existing $old from scope.")
32-
28+
log(s"lifted ${sym.fullLocationString}" + ( if (old.isEmpty) "" else " after unlinking $old from scope." ))
3329
old
3430
}
3531

src/compiler/scala/tools/nsc/typechecker/Contexts.scala

Lines changed: 45 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -758,14 +758,10 @@ trait Contexts { self: Analyzer =>
758758
def lookupSymbol(name: Name, qualifies: Symbol => Boolean): NameLookup = {
759759
var lookupError: NameLookup = null // set to non-null if a definite error is encountered
760760
var inaccessible: NameLookup = null // records inaccessible symbol for error reporting in case none is found
761-
var defEntry: ScopeEntry = null // the scope entry of defSym, if defined in a local scope
762761
var defSym: Symbol = NoSymbol // the directly found symbol
763762
var pre: Type = NoPrefix // the prefix type of defSym, if a class member
764-
var cx: Context = this
765-
var needsQualifier = false // working around package object overloading bug
766-
767-
def defEntrySymbol = if (defEntry eq null) NoSymbol else defEntry.sym
768-
def localScopeDepth = if (defEntry eq null) 0 else cx.scope.nestingLevel - defEntry.owner.nestingLevel
763+
var cx: Context = this // the context under consideration
764+
var symbolDepth: Int = -1 // the depth of the directly found symbol
769765

770766
def finish(qual: Tree, sym: Symbol): NameLookup = (
771767
if (lookupError ne null) lookupError
@@ -781,31 +777,14 @@ trait Contexts { self: Analyzer =>
781777
|| unit.exists && s.sourceFile != unit.source.file
782778
)
783779
)
784-
def requiresQualifier(s: Symbol) = needsQualifier || (
780+
def requiresQualifier(s: Symbol) = (
785781
s.owner.isClass
786782
&& !s.owner.isPackageClass
787783
&& !s.isTypeParameterOrSkolem
788784
)
789785
def lookupInPrefix(name: Name) = pre member name filter qualifies
790786
def accessibleInPrefix(s: Symbol) = isAccessible(s, pre, superAccess = false)
791787

792-
def correctForPackageObject(sym: Symbol): Symbol = {
793-
if (sym.isTerm && isInPackageObject(sym, pre.typeSymbol)) {
794-
val sym1 = lookupInPrefix(sym.name)
795-
if ((sym1 eq NoSymbol) || (sym eq sym1)) sym else {
796-
needsQualifier = true
797-
log(s"""
798-
| !!! Overloaded package object member resolved incorrectly.
799-
| prefix: $pre
800-
| Discarded: ${sym.defString}
801-
| Using: ${sym1.defString}
802-
""".stripMargin)
803-
sym1
804-
}
805-
}
806-
else sym
807-
}
808-
809788
def searchPrefix = {
810789
cx = cx.enclClass
811790
val found0 = lookupInPrefix(name)
@@ -817,31 +796,35 @@ trait Contexts { self: Analyzer =>
817796
}
818797
// cx.scope eq null arises during FixInvalidSyms in Duplicators
819798
while (defSym == NoSymbol && (cx ne NoContext) && (cx.scope ne null)) {
820-
pre = cx.enclClass.prefix
821-
// !!! FIXME. This call to lookupEntry is at the root of all the
822-
// bad behavior with overloading in package objects. lookupEntry
823-
// just takes the first symbol it finds in scope, ignoring the rest.
824-
// When a selection on a package object arrives here, the first
825-
// overload is always chosen. "correctForPackageObject" exists to
826-
// undo that decision. Obviously it would be better not to do it in
827-
// the first place; however other things seem to be tied to obtaining
828-
// that ScopeEntry, specifically calculating the nesting depth.
829-
defEntry = cx.scope lookupEntry name
830-
defSym = defEntrySymbol filter qualifies map correctForPackageObject orElse searchPrefix
799+
pre = cx.enclClass.prefix
800+
val entries = (cx.scope lookupUnshadowedEntries name filter (e => qualifies(e.sym))).toList
801+
defSym = entries match {
802+
case Nil => searchPrefix
803+
case hd :: tl =>
804+
// we have a winner: record the symbol depth
805+
symbolDepth = (cx.depth - cx.scope.nestingLevel) + hd.depth
806+
if (tl.isEmpty) hd.sym
807+
else logResult(s"!!! lookup overloaded")(cx.owner.newOverloaded(pre, entries map (_.sym)))
808+
}
831809
if (!defSym.exists)
832-
cx = cx.outer
810+
cx = cx.outer // push further outward
833811
}
812+
if (symbolDepth < 0)
813+
symbolDepth = cx.depth
834814

835-
val symbolDepth = cx.depth - localScopeDepth
836815
var impSym: Symbol = NoSymbol
837-
var imports = Context.this.imports // impSym != NoSymbol => it is imported from imports.head
816+
var imports = Context.this.imports
838817
def imp1 = imports.head
818+
def imp2 = imports.tail.head
819+
def imp1Explicit = imp1 isExplicitImport name
820+
def imp2Explicit = imp2 isExplicitImport name
839821

840822
while (!qualifies(impSym) && imports.nonEmpty && imp1.depth > symbolDepth) {
841823
impSym = importedAccessibleSymbol(imp1, name)
842824
if (!impSym.exists)
843825
imports = imports.tail
844826
}
827+
845828
if (defSym.exists && impSym.exists) {
846829
// imported symbols take precedence over package-owned symbols in different compilation units.
847830
if (isPackageOwnedInDifferentUnit(defSym))
@@ -862,40 +845,33 @@ trait Contexts { self: Analyzer =>
862845
finish(EmptyTree, defSym)
863846
}
864847
else if (impSym.exists) {
865-
// Imports against which we will test impSym for any ambiguities
866-
var importsTail = imports.tail
867-
val imp1Explicit = imp1 isExplicitImport name
868-
def imp2 = importsTail.head
869-
def sameDepth = imp1.depth == imp2.depth
870-
def isDone = importsTail.isEmpty || imp1Explicit && !sameDepth
871-
848+
def sameDepth = imp1.depth == imp2.depth
849+
def needsCheck = if (sameDepth) imp1Explicit == imp2Explicit else imp1Explicit || imp2Explicit
850+
def isDone = imports.tail.isEmpty || (!sameDepth && imp1Explicit)
851+
def ambiguous = needsCheck && isAmbiguousImport(imp1, imp2, name) && {
852+
lookupError = ambiguousImports(imp1, imp2)
853+
true
854+
}
855+
// Ambiguity check between imports.
856+
// The same name imported again is potentially ambiguous if the name is:
857+
// - after explicit import, explicitly imported again at the same or lower depth
858+
// - after explicit import, wildcard imported at lower depth
859+
// - after wildcard import, wildcard imported at the same depth
860+
// Under all such conditions isAmbiguousImport is called, which will
861+
// examine the imports in case they are importing the same thing; if that
862+
// can't be established conclusively, an error is issued.
872863
while (lookupError == null && !isDone) {
873864
val other = importedAccessibleSymbol(imp2, name)
874-
// Ambiguity check between imports.
875-
// The same name imported again is potentially ambiguous if the name is:
876-
// - after explicit import, explicitly imported again at the same or lower depth
877-
// - after explicit import, wildcard imported at lower depth
878-
// - after wildcard import, wildcard imported at the same depth
879-
// Under all such conditions isAmbiguousImport is called, which will
880-
// examine the imports in case they are importing the same thing; if that
881-
// can't be established conclusively, an error is issued.
882-
if (qualifies(other)) {
883-
val imp2Explicit = imp2 isExplicitImport name
884-
val needsCheck = (
885-
if (sameDepth) imp1Explicit == imp2Explicit
886-
else imp1Explicit || imp2Explicit
887-
)
888-
log(s"Import ambiguity: imp1=$imp1, imp2=$imp2, sameDepth=$sameDepth, needsCheck=$needsCheck")
889-
if (needsCheck && isAmbiguousImport(imp1, imp2, name))
890-
lookupError = ambiguousImports(imp1, imp2)
891-
else if (imp2Explicit) {
892-
// if we weren't ambiguous and imp2 is explicit, imp2 replaces imp1
893-
// as the current winner.
894-
impSym = other
895-
imports = importsTail
896-
}
865+
// if the competing import is unambiguous and explicit, it is the new winner.
866+
val isNewWinner = qualifies(other) && !ambiguous && imp2Explicit
867+
// imports is imp1 :: imp2 :: rest.
868+
// If there is a new winner, it is imp2, and imports drops imp1.
869+
// If there is not, imp1 is still the winner, and it drops imp2.
870+
if (isNewWinner) {
871+
impSym = other
872+
imports = imports.tail
897873
}
898-
importsTail = importsTail.tail
874+
else imports = imp1 :: imports.tail.tail
899875
}
900876
// optimization: don't write out package prefixes
901877
finish(resetPos(imp1.qual.duplicate), impSym)

src/compiler/scala/tools/nsc/typechecker/Namers.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -380,8 +380,8 @@ trait Namers extends MethodSynthesis {
380380
if (sym eq NoSymbol) return
381381

382382
val ctx = if (context.owner.isPackageObjectClass) context.outer else context
383-
val module = if (sym.isModule) sym else ctx.scope lookup tree.name.toTermName
384-
val clazz = if (sym.isClass) sym else ctx.scope lookup tree.name.toTypeName
383+
val module = if (sym.isModule) sym else ctx.scope lookupModule tree.name
384+
val clazz = if (sym.isClass) sym else ctx.scope lookupClass tree.name
385385
val fails = (
386386
module.isModule
387387
&& clazz.isClass

src/reflect/scala/reflect/internal/Scopes.scala

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,9 @@ trait Scopes extends api.Scopes { self: SymbolTable =>
2525
*/
2626
var next: ScopeEntry = null
2727

28+
def depth = owner.nestingLevel
2829
override def hashCode(): Int = sym.name.start
29-
override def toString(): String = sym.toString()
30+
override def toString() = s"$sym (depth=$depth)"
3031
}
3132

3233
/**
@@ -216,22 +217,67 @@ trait Scopes extends api.Scopes { self: SymbolTable =>
216217
}
217218
}
218219

219-
/** lookup a symbol
220-
*
221-
* @param name ...
222-
* @return ...
220+
/** Lookup a module or a class, filtering out matching names in scope
221+
* which do not match that requirement.
222+
*/
223+
def lookupModule(name: Name): Symbol = lookupAll(name.toTermName) find (_.isModule) getOrElse NoSymbol
224+
def lookupClass(name: Name): Symbol = lookupAll(name.toTypeName) find (_.isClass) getOrElse NoSymbol
225+
226+
/** True if the name exists in this scope, false otherwise. */
227+
def containsName(name: Name) = lookupEntry(name) != null
228+
229+
/** Lookup a symbol.
223230
*/
224231
def lookup(name: Name): Symbol = {
225232
val e = lookupEntry(name)
226-
if (e eq null) NoSymbol else e.sym
233+
if (e eq null) NoSymbol
234+
else if (lookupNextEntry(e) eq null) e.sym
235+
else {
236+
// We shouldn't get here: until now this method was picking a random
237+
// symbol when there was more than one with the name, so this should
238+
// only be called knowing that there are 0-1 symbols of interest. So, we
239+
// can safely return an overloaded symbol rather than throwing away the
240+
// rest of them. Most likely we still break, but at least we will break
241+
// in an understandable fashion (unexpectedly overloaded symbol) rather
242+
// than a non-deterministic bizarre one (see any bug involving overloads
243+
// in package objects.)
244+
val alts = lookupAll(name).toList
245+
log("!!! scope lookup of $name found multiple symbols: $alts")
246+
// FIXME - how is one supposed to create an overloaded symbol without
247+
// knowing the correct owner? Using the symbol owner is not correct;
248+
// say for instance this is List's scope and the symbols are its three
249+
// mkString members. Those symbols are owned by TraversableLike, which
250+
// is no more meaningful an owner than NoSymbol given that we're in
251+
// List. Maybe it makes no difference who owns the overloaded symbol, in
252+
// which case let's establish that and have a canonical creation method.
253+
//
254+
// FIXME - a similar question for prefix, although there are more
255+
// clues from the symbols on that one, as implemented here. In general
256+
// the distinct list is one type and lub becomes the identity.
257+
val prefix = lub(alts map (_.info.prefix) distinct)
258+
NoSymbol.newOverloaded(prefix, alts)
259+
}
227260
}
228261

229262
/** Returns an iterator yielding every symbol with given name in this scope.
230263
*/
231264
def lookupAll(name: Name): Iterator[Symbol] = new Iterator[Symbol] {
232265
var e = lookupEntry(name)
233266
def hasNext: Boolean = e ne null
234-
def next(): Symbol = { val r = e.sym; e = lookupNextEntry(e); r }
267+
def next(): Symbol = try e.sym finally e = lookupNextEntry(e)
268+
}
269+
270+
def lookupAllEntries(name: Name): Iterator[ScopeEntry] = new Iterator[ScopeEntry] {
271+
var e = lookupEntry(name)
272+
def hasNext: Boolean = e ne null
273+
def next(): ScopeEntry = try e finally e = lookupNextEntry(e)
274+
}
275+
276+
def lookupUnshadowedEntries(name: Name): Iterator[ScopeEntry] = {
277+
lookupEntry(name) match {
278+
case null => Iterator.empty
279+
case e => lookupAllEntries(name) filter (e1 => (e eq e1) || (e.depth == e1.depth && e.sym != e1.sym))
280+
}
235281
}
236282

237283
/** lookup a symbol entry matching given name.

src/reflect/scala/reflect/internal/Symbols.scala

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1672,12 +1672,23 @@ trait Symbols extends api.Symbols { self: SymbolTable =>
16721672

16731673
def filter(cond: Symbol => Boolean): Symbol =
16741674
if (isOverloaded) {
1675-
val alts = alternatives
1676-
val alts1 = alts filter cond
1677-
if (alts1 eq alts) this
1675+
var changed = false
1676+
var alts0: List[Symbol] = alternatives
1677+
var alts1: List[Symbol] = Nil
1678+
1679+
while (alts0.nonEmpty) {
1680+
if (cond(alts0.head))
1681+
alts1 ::= alts0.head
1682+
else
1683+
changed = true
1684+
1685+
alts0 = alts0.tail
1686+
}
1687+
1688+
if (!changed) this
16781689
else if (alts1.isEmpty) NoSymbol
16791690
else if (alts1.tail.isEmpty) alts1.head
1680-
else owner.newOverloaded(info.prefix, alts1)
1691+
else owner.newOverloaded(info.prefix, alts1.reverse)
16811692
}
16821693
else if (cond(this)) this
16831694
else NoSymbol

test/files/neg/dbldef.check

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@ dbldef.scala:1: error: type mismatch;
66
required: Int
77
case class test0(x: Int, x: Float)
88
^
9-
dbldef.scala:1: error: type mismatch;
10-
found : Float
11-
required: Int
9+
dbldef.scala:1: error: in class test0, multiple overloaded alternatives of x define default arguments
1210
case class test0(x: Int, x: Float)
1311
^
1412
three errors found

0 commit comments

Comments
 (0)