summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorPaul Phillips <paulp@improving.org>2012-10-23 14:37:05 -0700
committerPaul Phillips <paulp@improving.org>2012-10-23 14:51:09 -0700
commitd477a0f7f90d224f8162abd9847ecf71482e179a (patch)
treef7048662680f13b4cecb71292e0487c02647bc1d /src
parent2dc5841638f8a48bace0084ac25baaef50e865f9 (diff)
downloadscala-d477a0f7f90d224f8162abd9847ecf71482e179a.tar.gz
scala-d477a0f7f90d224f8162abd9847ecf71482e179a.tar.bz2
scala-d477a0f7f90d224f8162abd9847ecf71482e179a.zip
Adds the core symbol lookup logic to Typers.
This unifies several disparate/ad-hoc mechanisms for excluding symbols from eligibility in a single predicate. This is the method on Context: def lookupSymbol(name: Name, qualifies: Symbol => Boolean) The logic is largely that which was buried in typedIdent, except that I fixed SI-3160 so that import foo._ does not inject foo's private members into your namespace.
Diffstat (limited to 'src')
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Contexts.scala238
1 files changed, 235 insertions, 3 deletions
diff --git a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala
index 0abd8c188e..ad79468b0c 100644
--- a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala
@@ -646,7 +646,7 @@ trait Contexts { self: Analyzer =>
case ImportSelector(from, _, to, _) :: sels1 =>
var impls = collect(sels1) filter (info => info.name != from)
if (to != nme.WILDCARD) {
- for (sym <- imp.importedSymbol(to).alternatives)
+ for (sym <- importedAccessibleSymbol(imp, to).alternatives)
if (isQualifyingImplicit(to, sym, pre, imported = true))
impls = new ImplicitInfo(to, pre, sym) :: impls
}
@@ -692,6 +692,238 @@ trait Contexts { self: Analyzer =>
implicitsCache
}
+ /** 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:
+ * package object foo { type InputStream = java.io.InputStream }
+ * import foo._, java.io._
+ */
+ def isAmbiguousImport(imp1: ImportInfo, imp2: ImportInfo, name: Name): Boolean = {
+ // The imported symbols from each import.
+ def imp1Symbol = importedAccessibleSymbol(imp1, name)
+ def imp2Symbol = importedAccessibleSymbol(imp2, name)
+ // The types of the qualifiers from which the ambiguous imports come.
+ // If the ambiguous name is a value, these must be the same.
+ def t1 = imp1.qual.tpe
+ def t2 = imp2.qual.tpe
+ // The types of the ambiguous symbols, seen as members of their qualifiers.
+ // If the ambiguous name is a monomorphic type, we can relax this far.
+ def mt1 = t1 memberType imp1Symbol
+ def mt2 = t2 memberType imp2Symbol
+
+ def characterize = List(
+ s"types: $t1 =:= $t2 ${t1 =:= t2} members: ${mt1 =:= mt2}",
+ s"member type 1: $mt1",
+ s"member type 2: $mt2"
+ ).mkString("\n ")
+
+ imp1Symbol.exists && imp2Symbol.exists && (
+ // The symbol names are checked rather than the symbols themselves because
+ // each time an overloaded member is looked up it receives a new symbol.
+ // So foo.member("x") != foo.member("x") if x is overloaded. This seems
+ // likely to be the cause of other bugs too...
+ if (t1 =:= t2 && imp1Symbol.name == imp2Symbol.name) {
+ log(s"Suppressing ambiguous import: $t1 =:= $t2 && $imp1Symbol == $imp2Symbol")
+ false
+ }
+ // Monomorphism restriction on types is in part because type aliases could have the
+ // same target type but attach different variance to the parameters. Maybe it can be
+ // relaxed, but doesn't seem worth it at present.
+ else if (mt1 =:= mt2 && name.isTypeName && imp1Symbol.isMonomorphicType && imp2Symbol.isMonomorphicType) {
+ log(s"Suppressing ambiguous import: $mt1 =:= $mt2 && $imp1Symbol and $imp2Symbol are equivalent")
+ false
+ }
+ else {
+ log(s"Import is genuinely ambiguous:\n " + characterize)
+ true
+ }
+ )
+ }
+
+ def importedAccessibleSymbol(imp: ImportInfo, name: Name) = {
+ imp importedSymbol name filter (s => isAccessible(s, imp.qual.tpe, superAccess = false))
+ }
+
+ /** Is `sym` defined in package object of package `pkg`?
+ * Since sym may be defined in some parent of the package object,
+ * we cannot inspect its owner only; we have to go through the
+ * info of the package object. However to avoid cycles we'll check
+ * what other ways we can before pushing that way.
+ */
+ def isInPackageObject(sym: Symbol, pkg: Symbol) = {
+ val pkgClass = if (pkg.isTerm) pkg.moduleClass else pkg
+ def matchesInfo = (
+ pkg.isInitialized && {
+ // need to be careful here to not get a cyclic reference during bootstrap
+ val module = pkg.info member nme.PACKAGEkw
+ module.isInitialized && (module.info.member(sym.name).alternatives contains sym)
+ }
+ )
+ def isInPkgObj(sym: Symbol) = (
+ !sym.isPackage
+ && !sym.owner.isPackageClass
+ && (sym.owner ne NoSymbol)
+ && (sym.owner.owner == pkgClass || matchesInfo)
+ )
+
+ pkgClass.isPackageClass && (
+ if (sym.isOverloaded) sym.alternatives forall isInPkgObj
+ else isInPkgObj(sym)
+ )
+ }
+
+ /** 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.
+ */
+ def lookupSymbol(name: Name, qualifies: Symbol => Boolean): NameLookup = {
+ var lookupError: NameLookup = null // set to non-null if a definite error is encountered
+ var inaccessible: NameLookup = null // records inaccessible symbol for error reporting in case none is found
+ var defEntry: ScopeEntry = null // the scope entry of defSym, if defined in a local scope
+ var defSym: Symbol = NoSymbol // the directly found symbol
+ var pre: Type = NoPrefix // the prefix type of defSym, if a class member
+ var cx: Context = this
+ var needsQualifier = false // working around package object overloading bug
+
+ def defEntrySymbol = if (defEntry eq null) NoSymbol else defEntry.sym
+ def localScopeDepth = if (defEntry eq null) 0 else cx.scope.nestingLevel - defEntry.owner.nestingLevel
+
+ def finish(qual: Tree, sym: Symbol): NameLookup = (
+ if (lookupError ne null) lookupError
+ else sym match {
+ case NoSymbol if inaccessible ne null => inaccessible
+ case NoSymbol => LookupNotFound()
+ case _ => LookupSucceeded(qual, sym)
+ }
+ )
+ def isPackageOwnedInDifferentUnit(s: Symbol) = (
+ s.isDefinedInPackage && (
+ !currentRun.compiles(s)
+ || unit.exists && s.sourceFile != unit.source.file
+ )
+ )
+ def requiresQualifier(s: Symbol) = needsQualifier || (
+ s.owner.isClass
+ && !s.owner.isPackageClass
+ && !s.isTypeParameterOrSkolem
+ )
+ def lookupInPrefix(name: Name) = pre member name filter qualifies
+ def accessibleInPrefix(s: Symbol) = isAccessible(s, pre, superAccess = false)
+
+ def correctForPackageObject(sym: Symbol): Symbol = {
+ if (sym.isTerm && isInPackageObject(sym, pre.typeSymbol)) {
+ val sym1 = lookupInPrefix(sym.name)
+ if ((sym1 eq NoSymbol) || (sym eq sym1)) sym else {
+ needsQualifier = true
+ log(s"""
+ | !!! Overloaded package object member resolved incorrectly.
+ | prefix: $pre
+ | Discarded: ${sym.defString}
+ | Using: ${sym1.defString}
+ """.stripMargin)
+ sym1
+ }
+ }
+ else sym
+ }
+
+ def searchPrefix = {
+ cx = cx.enclClass
+ val found0 = lookupInPrefix(name)
+ val found1 = found0 filter accessibleInPrefix
+ if (found0.exists && !found1.exists && inaccessible == null)
+ inaccessible = LookupInaccessible(found0, analyzer.lastAccessCheckDetails)
+
+ found1
+ }
+ // cx.scope eq null arises during FixInvalidSyms in Duplicators
+ while (defSym == NoSymbol && (cx ne NoContext) && (cx.scope ne null)) {
+ pre = cx.enclClass.prefix
+ // !!! FIXME. This call to lookupEntry is at the root of all the
+ // bad behavior with overloading in package objects. lookupEntry
+ // just takes the first symbol it finds in scope, ignoring the rest.
+ // When a selection on a package object arrives here, the first
+ // overload is always chosen. "correctForPackageObject" exists to
+ // undo that decision. Obviously it would be better not to do it in
+ // the first place; however other things seem to be tied to obtaining
+ // that ScopeEntry, specifically calculating the nesting depth.
+ defEntry = cx.scope lookupEntry name
+ defSym = defEntrySymbol filter qualifies map correctForPackageObject orElse searchPrefix
+ if (!defSym.exists)
+ cx = cx.outer
+ }
+
+ val symbolDepth = cx.depth - localScopeDepth
+ var impSym: Symbol = NoSymbol
+ var imports = Context.this.imports // impSym != NoSymbol => it is imported from imports.head
+ def imp1 = imports.head
+
+ while (!qualifies(impSym) && imports.nonEmpty && imp1.depth > symbolDepth) {
+ impSym = importedAccessibleSymbol(imp1, name)
+ if (!impSym.exists)
+ imports = imports.tail
+ }
+ if (defSym.exists && impSym.exists) {
+ // imported symbols take precedence over package-owned symbols in different compilation units.
+ if (isPackageOwnedInDifferentUnit(defSym))
+ defSym = NoSymbol
+ // Defined symbols take precedence over erroneous imports.
+ else if (impSym.isError || impSym.name == nme.CONSTRUCTOR)
+ impSym = NoSymbol
+ // Otherwise they are irreconcilably ambiguous
+ else
+ return ambiguousDefnAndImport(defSym.owner, imp1)
+ }
+
+ // At this point only one or the other of defSym and impSym might be set.
+ if (defSym.exists) {
+ if (requiresQualifier(defSym))
+ finish(gen.mkAttributedQualifier(pre), defSym)
+ else
+ finish(EmptyTree, defSym)
+ }
+ else if (impSym.exists) {
+ // Imports against which we will test impSym for any ambiguities
+ var importsTail = imports.tail
+ val imp1Explicit = imp1 isExplicitImport name
+ def imp2 = importsTail.head
+ def sameDepth = imp1.depth == imp2.depth
+ def isDone = importsTail.isEmpty || imp1Explicit && !sameDepth
+
+ while (lookupError == null && !isDone) {
+ val other = importedAccessibleSymbol(imp2, name)
+ // Ambiguity check between imports.
+ // The same name imported again is potentially ambiguous if the name is:
+ // - after explicit import, explicitly imported again at the same or lower depth
+ // - after explicit import, wildcard imported at lower depth
+ // - after wildcard import, wildcard imported at the same depth
+ // Under all such conditions isAmbiguousImport is called, which will
+ // examine the imports in case they are importing the same thing; if that
+ // can't be established conclusively, an error is issued.
+ if (qualifies(other)) {
+ val imp2Explicit = imp2 isExplicitImport name
+ val needsCheck = (
+ if (sameDepth) imp1Explicit == imp2Explicit
+ else imp1Explicit || imp2Explicit
+ )
+ log(s"Import ambiguity: imp1=$imp1, imp2=$imp2, sameDepth=$sameDepth, needsCheck=$needsCheck")
+ if (needsCheck && isAmbiguousImport(imp1, imp2, name))
+ lookupError = ambiguousImports(imp1, imp2)
+ else if (imp2Explicit) {
+ // if we weren't ambiguous and imp2 is explicit, imp2 replaces imp1
+ // as the current winner.
+ impSym = other
+ imports = importsTail
+ }
+ }
+ importsTail = importsTail.tail
+ }
+ // optimization: don't write out package prefixes
+ finish(resetPos(imp1.qual.duplicate), impSym)
+ }
+ else finish(EmptyTree, NoSymbol)
+ }
+
/**
* Find a symbol in this context or one of its outers.
*
@@ -719,8 +951,8 @@ trait Contexts { self: Analyzer =>
/** The prefix expression */
def qual: Tree = tree.symbol.info match {
case ImportType(expr) => expr
- case ErrorType => tree setType NoType // fix for #2870
- case _ => throw new FatalError("symbol " + tree.symbol + " has bad type: " + tree.symbol.info) //debug
+ case ErrorType => tree setType NoType // fix for #2870
+ case _ => throw new FatalError("symbol " + tree.symbol + " has bad type: " + tree.symbol.info) //debug
}
/** Is name imported explicitly, not via wildcard? */