Skip to content

Commit

Permalink
Merge pull request scala#1521 from paulp/whats-in-a-name
Browse files Browse the repository at this point in the history
Saving symbol lookup from typedIdent.
  • Loading branch information
adriaanm committed Nov 1, 2012
2 parents 2dd7142 + 578c4c6 commit a70c821
Show file tree
Hide file tree
Showing 17 changed files with 588 additions and 438 deletions.
13 changes: 8 additions & 5 deletions src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala
Expand Up @@ -802,7 +802,10 @@ trait ContextErrors {
)
}

def AccessError(tree: Tree, sym: Symbol, pre: Type, owner0: Symbol, explanation: String) = {
def AccessError(tree: Tree, sym: Symbol, ctx: Context, explanation: String): AbsTypeError =
AccessError(tree, sym, ctx.enclClass.owner.thisType, ctx.enclClass.owner, explanation)

def AccessError(tree: Tree, sym: Symbol, pre: Type, owner0: Symbol, explanation: String): AbsTypeError = {
def errMsg = {
val location = if (sym.isClassConstructor) owner0 else pre.widen.directObjectString

Expand Down Expand Up @@ -835,7 +838,7 @@ trait ContextErrors {

// side-effect on the tree, break the overloaded type cycle in infer
private def setErrorOnLastTry(lastTry: Boolean, tree: Tree) = if (lastTry) setError(tree)

def NoBestMethodAlternativeError(tree: Tree, argtpes: List[Type], pt: Type, lastTry: Boolean) = {
issueNormalTypeError(tree,
applyErrorMsg(tree, " cannot be applied to ", argtpes, pt))
Expand All @@ -848,7 +851,7 @@ trait ContextErrors {

def AmbiguousMethodAlternativeError(tree: Tree, pre: Type, best: Symbol,
firstCompeting: Symbol, argtpes: List[Type], pt: Type, lastTry: Boolean) = {

if (!(argtpes exists (_.isErroneous)) && !pt.isErroneous) {
val msg0 =
"argument types " + argtpes.mkString("(", ",", ")") +
Expand All @@ -858,7 +861,7 @@ trait ContextErrors {
setErrorOnLastTry(lastTry, tree)
} else setError(tree) // do not even try further attempts because they should all fail
// even if this is not the last attempt (because of the SO's possibility on the horizon)

}

def NoBestExprAlternativeError(tree: Tree, pt: Type, lastTry: Boolean) = {
Expand Down Expand Up @@ -1191,7 +1194,7 @@ trait ContextErrors {
setError(arg)
} else arg
}

def WarnAfterNonSilentRecursiveInference(param: Symbol, arg: Tree)(implicit context: Context) = {
val note = "type-checking the invocation of "+ param.owner +" checks if the named argument expression '"+ param.name + " = ...' is a valid assignment\n"+
"in the current scope. The resulting type inference error (see above) can be fixed by providing an explicit type in the local definition for "+ param.name +"."
Expand Down
249 changes: 244 additions & 5 deletions src/compiler/scala/tools/nsc/typechecker/Contexts.scala
Expand Up @@ -35,6 +35,11 @@ trait Contexts { self: Analyzer =>
val completeList = JavaLangPackage :: ScalaPackage :: PredefModule :: Nil
}

def ambiguousImports(imp1: ImportInfo, imp2: ImportInfo) =
LookupAmbiguous(s"it is imported twice in the same scope by\n$imp1\nand $imp2")
def ambiguousDefnAndImport(owner: Symbol, imp: ImportInfo) =
LookupAmbiguous(s"it is both defined in $owner and imported subsequently by \n$imp")

private val startContext = {
NoContext.make(
Template(List(), emptyValDef, List()) setSymbol global.NoSymbol setType global.NoType,
Expand Down Expand Up @@ -480,8 +485,7 @@ trait Contexts { self: Analyzer =>
c
}

/** Is `sym` accessible as a member of tree `site` with type
* `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 = ""
Expand Down Expand Up @@ -632,7 +636,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
}
Expand Down Expand Up @@ -678,6 +682,241 @@ 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
}
)
}

/** The symbol with name `name` imported via the import in `imp`,
* if any such symbol is accessible from this context.
*/
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 inPackageObject(sym: Symbol) = (
!sym.isPackage
&& !sym.owner.isPackageClass
&& (sym.owner ne NoSymbol)
&& (sym.owner.owner == pkgClass || matchesInfo)
)

pkgClass.isPackageClass && (
if (sym.isOverloaded) sym.alternatives forall inPackageObject
else inPackageObject(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.
*
Expand Down Expand Up @@ -705,8 +944,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? */
Expand Down
1 change: 1 addition & 0 deletions src/compiler/scala/tools/nsc/typechecker/Modes.scala
Expand Up @@ -109,6 +109,7 @@ trait Modes {
final def inFunMode(mode: Int) = (mode & FUNmode) != 0
final def inPolyMode(mode: Int) = (mode & POLYmode) != 0
final def inPatternMode(mode: Int) = (mode & PATTERNmode) != 0
final def inPatternNotFunMode(mode: Int) = inPatternMode(mode) && !inFunMode(mode)
final def inExprModeOr(mode: Int, others: Int) = (mode & (EXPRmode | others)) != 0
final def inExprModeButNot(mode: Int, prohibited: Int) =
(mode & (EXPRmode | prohibited)) == EXPRmode
Expand Down

0 comments on commit a70c821

Please sign in to comment.