66package scala .tools .nsc
77package typechecker
88
9- import scala .collection .mutable .{ LinkedHashSet , Set }
9+ import scala .collection .mutable
1010import scala .annotation .tailrec
1111
1212/**
@@ -15,6 +15,7 @@ import scala.annotation.tailrec
1515 */
1616trait Contexts { self : Analyzer =>
1717 import global ._
18+ import definitions .{ JavaLangPackage , ScalaPackage , PredefModule }
1819
1920 object NoContext extends Context {
2021 outer = this
@@ -27,7 +28,6 @@ trait Contexts { self: Analyzer =>
2728 override def toString = " NoContext"
2829 }
2930 private object RootImports {
30- import definitions ._
3131 // Possible lists of root imports
3232 val javaList = JavaLangPackage :: Nil
3333 val javaAndScalaList = JavaLangPackage :: ScalaPackage :: Nil
@@ -46,6 +46,28 @@ trait Contexts { self: Analyzer =>
4646 rootMirror.RootClass .info.decls)
4747 }
4848
49+ private lazy val allUsedSelectors =
50+ mutable.Map [ImportInfo , Set [ImportSelector ]]() withDefaultValue Set ()
51+ private lazy val allImportInfos =
52+ mutable.Map [CompilationUnit , List [ImportInfo ]]() withDefaultValue Nil
53+
54+ def clearUnusedImports () {
55+ allUsedSelectors.clear()
56+ allImportInfos.clear()
57+ }
58+ def warnUnusedImports (unit : CompilationUnit ) = {
59+ val imps = allImportInfos(unit).reverse.distinct
60+
61+ for (imp <- imps) {
62+ val used = allUsedSelectors(imp)
63+ def isMask (s : ImportSelector ) = s.name != nme.WILDCARD && s.rename == nme.WILDCARD
64+
65+ imp.tree.selectors filterNot (s => isMask(s) || used(s)) foreach { sel =>
66+ unit.warning(imp posOf sel, " Unused import" )
67+ }
68+ }
69+ }
70+
4971 var lastAccessCheckDetails : String = " "
5072
5173 /** List of symbols to import from in a root context. Typically that
@@ -146,7 +168,7 @@ trait Contexts { self: Analyzer =>
146168 var typingIndentLevel : Int = 0
147169 def typingIndent = " " * typingIndentLevel
148170
149- var buffer : Set [AbsTypeError ] = _
171+ var buffer : mutable. Set [AbsTypeError ] = _
150172
151173 def enclClassOrMethod : Context =
152174 if ((owner eq NoSymbol ) || (owner.isClass) || (owner.isMethod)) this
@@ -185,13 +207,13 @@ trait Contexts { self: Analyzer =>
185207 def setThrowErrors () = mode &= (~ AllMask )
186208 def setAmbiguousErrors (report : Boolean ) = if (report) mode |= AmbiguousErrors else mode &= notThrowMask
187209
188- def updateBuffer (errors : Set [AbsTypeError ]) = buffer ++= errors
210+ def updateBuffer (errors : mutable. Set [AbsTypeError ]) = buffer ++= errors
189211 def condBufferFlush (removeP : AbsTypeError => Boolean ) {
190212 val elems = buffer.filter(removeP)
191213 buffer --= elems
192214 }
193215 def flushBuffer () { buffer.clear() }
194- def flushAndReturnBuffer (): Set [AbsTypeError ] = {
216+ def flushAndReturnBuffer (): mutable. Set [AbsTypeError ] = {
195217 val current = buffer.clone()
196218 buffer.clear()
197219 current
@@ -284,7 +306,7 @@ trait Contexts { self: Analyzer =>
284306 c.checking = this .checking
285307 c.retyping = this .retyping
286308 c.openImplicits = this .openImplicits
287- c.buffer = if (this .buffer == null ) LinkedHashSet [AbsTypeError ]() else this .buffer // need to initialize
309+ c.buffer = if (this .buffer == null ) mutable. LinkedHashSet [AbsTypeError ]() else this .buffer // need to initialize
288310 registerContext(c.asInstanceOf [analyzer.Context ])
289311 debuglog(" [context] ++ " + c.unit + " / " + tree.summaryString)
290312 c
@@ -302,8 +324,13 @@ trait Contexts { self: Analyzer =>
302324 def makeNewImport (sym : Symbol ): Context =
303325 makeNewImport(gen.mkWildcardImport(sym))
304326
305- def makeNewImport (imp : Import ): Context =
306- make(unit, imp, owner, scope, new ImportInfo (imp, depth) :: imports)
327+ def makeNewImport (imp : Import ): Context = {
328+ val impInfo = new ImportInfo (imp, depth)
329+ if (settings.lint.value && imp.pos.isDefined) // pos.isDefined excludes java.lang/scala/Predef imports
330+ allImportInfos(unit) ::= impInfo
331+
332+ make(unit, imp, owner, scope, impInfo :: imports)
333+ }
307334
308335 def make (tree : Tree , owner : Symbol , scope : Scope ): Context =
309336 if (tree == this .tree && owner == this .owner && scope == this .scope) this
@@ -326,7 +353,7 @@ trait Contexts { self: Analyzer =>
326353 val c = make(newtree)
327354 c.setBufferErrors()
328355 c.setAmbiguousErrors(reportAmbiguousErrors)
329- c.buffer = new LinkedHashSet [AbsTypeError ]()
356+ c.buffer = mutable. LinkedHashSet [AbsTypeError ]()
330357 c
331358 }
332359
@@ -672,10 +699,13 @@ trait Contexts { self: Analyzer =>
672699 * package object foo { type InputStream = java.io.InputStream }
673700 * import foo._, java.io._
674701 */
675- def isAmbiguousImport (imp1 : ImportInfo , imp2 : ImportInfo , name : Name ): Boolean = {
676- // The imported symbols from each import.
677- def imp1Symbol = importedAccessibleSymbol(imp1, name)
678- def imp2Symbol = importedAccessibleSymbol(imp2, name)
702+ private def resolveAmbiguousImport (name : Name , imp1 : ImportInfo , imp2 : ImportInfo ): Option [ImportInfo ] = {
703+ val imp1Explicit = imp1 isExplicitImport name
704+ val imp2Explicit = imp2 isExplicitImport name
705+ val ambiguous = if (imp1.depth == imp2.depth) imp1Explicit == imp2Explicit else ! imp1Explicit && imp2Explicit
706+ val imp1Symbol = (imp1 importedSymbol name).initialize filter (s => isAccessible(s, imp1.qual.tpe, superAccess = false ))
707+ val imp2Symbol = (imp2 importedSymbol name).initialize filter (s => isAccessible(s, imp2.qual.tpe, superAccess = false ))
708+
679709 // The types of the qualifiers from which the ambiguous imports come.
680710 // If the ambiguous name is a value, these must be the same.
681711 def t1 = imp1.qual.tpe
@@ -691,35 +721,39 @@ trait Contexts { self: Analyzer =>
691721 s " member type 2: $mt2"
692722 ).mkString(" \n " )
693723
694- imp1Symbol.exists && imp2Symbol.exists && (
724+ if (! ambiguous || ! imp2Symbol.exists) Some (imp1)
725+ else if (! imp1Symbol.exists) Some (imp2)
726+ else (
695727 // The symbol names are checked rather than the symbols themselves because
696728 // each time an overloaded member is looked up it receives a new symbol.
697729 // So foo.member("x") != foo.member("x") if x is overloaded. This seems
698730 // likely to be the cause of other bugs too...
699731 if (t1 =:= t2 && imp1Symbol.name == imp2Symbol.name) {
700732 log(s " Suppressing ambiguous import: $t1 =:= $t2 && $imp1Symbol == $imp2Symbol" )
701- false
733+ Some (imp1)
702734 }
703735 // Monomorphism restriction on types is in part because type aliases could have the
704736 // same target type but attach different variance to the parameters. Maybe it can be
705737 // relaxed, but doesn't seem worth it at present.
706738 else if (mt1 =:= mt2 && name.isTypeName && imp1Symbol.isMonomorphicType && imp2Symbol.isMonomorphicType) {
707739 log(s " Suppressing ambiguous import: $mt1 =:= $mt2 && $imp1Symbol and $imp2Symbol are equivalent " )
708- false
740+ Some (imp1)
709741 }
710742 else {
711743 log(s " Import is genuinely ambiguous: \n " + characterize)
712- true
744+ None
713745 }
714746 )
715747 }
716748
717749 /** The symbol with name `name` imported via the import in `imp`,
718750 * if any such symbol is accessible from this context.
719751 */
720- def importedAccessibleSymbol (imp : ImportInfo , name : Name ) = {
721- imp importedSymbol name filter (s => isAccessible(s, imp.qual.tpe, superAccess = false ))
722- }
752+ def importedAccessibleSymbol (imp : ImportInfo , name : Name ): Symbol =
753+ importedAccessibleSymbol(imp, name, requireExplicit = false )
754+
755+ private def importedAccessibleSymbol (imp : ImportInfo , name : Name , requireExplicit : Boolean ): Symbol =
756+ imp.importedSymbol(name, requireExplicit) filter (s => isAccessible(s, imp.qual.tpe, superAccess = false ))
723757
724758 /** Is `sym` defined in package object of package `pkg`?
725759 * Since sym may be defined in some parent of the package object,
@@ -814,11 +848,15 @@ trait Contexts { self: Analyzer =>
814848 var imports = Context .this .imports
815849 def imp1 = imports.head
816850 def imp2 = imports.tail.head
851+ def sameDepth = imp1.depth == imp2.depth
817852 def imp1Explicit = imp1 isExplicitImport name
818853 def imp2Explicit = imp2 isExplicitImport name
819854
820- while (! qualifies(impSym) && imports.nonEmpty && imp1.depth > symbolDepth) {
821- impSym = importedAccessibleSymbol(imp1, name)
855+ def lookupImport (imp : ImportInfo , requireExplicit : Boolean ) =
856+ importedAccessibleSymbol(imp, name, requireExplicit) filter qualifies
857+
858+ while (! impSym.exists && imports.nonEmpty && imp1.depth > symbolDepth) {
859+ impSym = lookupImport(imp1, requireExplicit = false )
822860 if (! impSym.exists)
823861 imports = imports.tail
824862 }
@@ -843,33 +881,45 @@ trait Contexts { self: Analyzer =>
843881 finish(EmptyTree , defSym)
844882 }
845883 else if (impSym.exists) {
846- def sameDepth = imp1.depth == imp2.depth
847- def needsCheck = if (sameDepth) imp1Explicit == imp2Explicit else imp1Explicit || imp2Explicit
848- def isDone = imports.tail.isEmpty || (! sameDepth && imp1Explicit)
849- def ambiguous = needsCheck && isAmbiguousImport(imp1, imp2, name) && {
850- lookupError = ambiguousImports(imp1, imp2)
851- true
852- }
853- // Ambiguity check between imports.
854- // The same name imported again is potentially ambiguous if the name is:
855- // - after explicit import, explicitly imported again at the same or lower depth
856- // - after explicit import, wildcard imported at lower depth
857- // - after wildcard import, wildcard imported at the same depth
858- // Under all such conditions isAmbiguousImport is called, which will
859- // examine the imports in case they are importing the same thing; if that
860- // can't be established conclusively, an error is issued.
861- while (lookupError == null && ! isDone) {
862- val other = importedAccessibleSymbol(imp2, name)
863- // if the competing import is unambiguous and explicit, it is the new winner.
864- val isNewWinner = qualifies(other) && ! ambiguous && imp2Explicit
865- // imports is imp1 :: imp2 :: rest.
866- // If there is a new winner, it is imp2, and imports drops imp1.
867- // If there is not, imp1 is still the winner, and it drops imp2.
868- if (isNewWinner) {
869- impSym = other
870- imports = imports.tail
884+ // We continue walking down the imports as long as the tail is non-empty, which gives us:
885+ // imports == imp1 :: imp2 :: _
886+ // And at least one of the following is true:
887+ // - imp1 and imp2 are at the same depth
888+ // - imp1 is a wildcard import, so all explicit imports from outer scopes must be checked
889+ def keepLooking = (
890+ lookupError == null
891+ && imports.tail.nonEmpty
892+ && (sameDepth || ! imp1Explicit)
893+ )
894+ // If we find a competitor imp2 which imports the same name, possible outcomes are:
895+ //
896+ // - same depth, imp1 wild, imp2 explicit: imp2 wins, drop imp1
897+ // - same depth, imp1 wild, imp2 wild: ambiguity check
898+ // - same depth, imp1 explicit, imp2 explicit: ambiguity check
899+ // - differing depth, imp1 wild, imp2 explicit: ambiguity check
900+ // - all others: imp1 wins, drop imp2
901+ //
902+ // The ambiguity check is: if we can verify that both imports refer to the same
903+ // symbol (e.g. import foo.X followed by import foo._) then we discard imp2
904+ // and proceed. If we cannot, issue an ambiguity error.
905+ while (keepLooking) {
906+ // If not at the same depth, limit the lookup to explicit imports.
907+ // This is desirable from a performance standpoint (compare to
908+ // filtering after the fact) but also necessary to keep the unused
909+ // import check from being misled by symbol lookups which are not
910+ // actually used.
911+ val other = lookupImport(imp2, requireExplicit = ! sameDepth)
912+ def imp1wins = { imports = imp1 :: imports.tail.tail }
913+ def imp2wins = { impSym = other ; imports = imports.tail }
914+
915+ if (! other.exists) // imp1 wins; drop imp2 and continue.
916+ imp1wins
917+ else if (sameDepth && ! imp1Explicit && imp2Explicit) // imp2 wins; drop imp1 and continue.
918+ imp2wins
919+ else resolveAmbiguousImport(name, imp1, imp2) match {
920+ case Some (imp) => if (imp eq imp1) imp1wins else imp2wins
921+ case _ => lookupError = ambiguousImports(imp1, imp2)
871922 }
872- else imports = imp1 :: imports.tail.tail
873923 }
874924 // optimization: don't write out package prefixes
875925 finish(resetPos(imp1.qual.duplicate), impSym)
@@ -901,6 +951,9 @@ trait Contexts { self: Analyzer =>
901951 } // class Context
902952
903953 class ImportInfo (val tree : Import , val depth : Int ) {
954+ def pos = tree.pos
955+ def posOf (sel : ImportSelector ) = tree.pos withPoint sel.namePos
956+
904957 /** The prefix expression */
905958 def qual : Tree = tree.symbol.info match {
906959 case ImportType (expr) => expr
@@ -914,22 +967,43 @@ trait Contexts { self: Analyzer =>
914967
915968 /** The symbol with name `name` imported from import clause `tree`.
916969 */
917- def importedSymbol (name : Name ): Symbol = {
970+ def importedSymbol (name : Name ): Symbol = importedSymbol(name, requireExplicit = false )
971+
972+ private def recordUsage (sel : ImportSelector , result : Symbol ) {
973+ def posstr = pos.source.file.name + " :" + posOf(sel).safeLine
974+ def resstr = if (tree.symbol.hasCompleteInfo) s " (qual= $qual, $result) " else s " (expr= ${tree.expr}, ${result.fullLocationString}) "
975+ debuglog(s " In $this at $posstr, selector ' ${selectorString(sel)}' resolved to $resstr" )
976+ allUsedSelectors(this ) += sel
977+ }
978+
979+ /** If requireExplicit is true, wildcard imports are not considered. */
980+ def importedSymbol (name : Name , requireExplicit : Boolean ): Symbol = {
918981 var result : Symbol = NoSymbol
919982 var renamed = false
920983 var selectors = tree.selectors
921- while (selectors != Nil && result == NoSymbol ) {
922- if (selectors.head.rename == name.toTermName)
984+ def current = selectors.head
985+ while (selectors.nonEmpty && result == NoSymbol ) {
986+ if (current.rename == name.toTermName)
923987 result = qual.tpe.nonLocalMember( // new to address #2733: consider only non-local members for imports
924- if (name.isTypeName) selectors.head. name.toTypeName else selectors.head .name)
925- else if (selectors.head .name == name.toTermName)
988+ if (name.isTypeName) current. name.toTypeName else current .name)
989+ else if (current .name == name.toTermName)
926990 renamed = true
927- else if (selectors.head. name == nme.WILDCARD && ! renamed)
991+ else if (current. name == nme.WILDCARD && ! renamed && ! requireExplicit )
928992 result = qual.tpe.nonLocalMember(name)
929- selectors = selectors.tail
993+
994+ if (result == NoSymbol )
995+ selectors = selectors.tail
930996 }
997+ if (settings.lint.value && selectors.nonEmpty && result != NoSymbol && pos != NoPosition )
998+ recordUsage(current, result)
999+
9311000 result
9321001 }
1002+ private def selectorString (s : ImportSelector ): String = {
1003+ if (s.name == nme.WILDCARD && s.rename == null ) " _"
1004+ else if (s.name == s.rename) " " + s.name
1005+ else s.name + " => " + s.rename
1006+ }
9331007
9341008 def allImportedSymbols : Iterable [Symbol ] =
9351009 qual.tpe.members flatMap (transformImport(tree.selectors, _))
@@ -943,7 +1017,12 @@ trait Contexts { self: Analyzer =>
9431017 case _ :: rest => transformImport(rest, sym)
9441018 }
9451019
946- override def toString () = tree.toString()
1020+ override def hashCode = tree.##
1021+ override def equals (other : Any ) = other match {
1022+ case that : ImportInfo => (tree == that.tree)
1023+ case _ => false
1024+ }
1025+ override def toString = tree.toString
9471026 }
9481027
9491028 case class ImportType (expr : Tree ) extends Type {
0 commit comments