Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/ast/untpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
case Ident(rename: TermName) => rename
case _ => name

/** It's a masking import if `!isWildcard`. */
def isUnimport = rename == nme.WILDCARD
}

Expand Down
6 changes: 0 additions & 6 deletions compiler/src/dotty/tools/dotc/config/ScalaSettings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -193,12 +193,6 @@ private sealed trait WarningSettings:
ChoiceWithHelp("patvars","Warn if a variable bound in a pattern is unused"),
//ChoiceWithHelp("inlined", "Apply -Wunused to inlined expansions"), // TODO
ChoiceWithHelp("linted", "Enable -Wunused:imports,privates,locals,implicits"),
ChoiceWithHelp(
name = "strict-no-implicit-warn",
description = """Same as -Wunused:imports, only for imports of explicit named members.
|NOTE : This overrides -Wunused:imports and NOT set by -Wunused:all""".stripMargin
),
ChoiceWithHelp("unsafe-warn-patvars", "Deprecated alias for `patvars`"),
),
default = Nil
)
Expand Down
5 changes: 0 additions & 5 deletions compiler/src/dotty/tools/dotc/core/TypeUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -206,10 +206,5 @@ class TypeUtils:

self.decl(nme.CONSTRUCTOR).altsWith(isApplicable).map(_.symbol)

/** Strip all outer refinements off this type */
def stripRefinement: Type = self match
case self: RefinedOrRecType => self.parent.stripRefinement
case seld => self

end TypeUtils

33 changes: 17 additions & 16 deletions compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6587,23 +6587,24 @@ object Types extends TypeUtils {
class TypeSizeAccumulator(using Context) extends TypeAccumulator[Int] {
var seen = util.HashSet[Type](initialCapacity = 8)
def apply(n: Int, tp: Type): Int =
if seen.contains(tp) then n
else {
seen += tp
tp match {
case tp: AppliedType =>
val tpNorm = tp.tryNormalize
if tpNorm.exists then apply(n, tpNorm)
else foldOver(n + 1, tp)
case tp: RefinedType =>
foldOver(n + 1, tp)
case tp: TypeRef if tp.info.isTypeAlias =>
apply(n, tp.superType)
case tp: TypeParamRef =>
apply(n, TypeComparer.bounds(tp))
case _ =>
tp match {
case tp: AppliedType =>
val tpNorm = tp.tryNormalize
if tpNorm.exists then apply(n, tpNorm)
else foldOver(n + 1, tp)
case tp: RefinedType =>
foldOver(n + 1, tp)
case tp: TypeRef if tp.info.isTypeAlias =>
apply(n, tp.superType)
case tp: TypeParamRef =>
apply(n, TypeComparer.bounds(tp))
case tp: LazyRef =>
if seen.contains(tp) then n
else
seen += tp
foldOver(n, tp)
}
case _ =>
foldOver(n, tp)
}
}

Expand Down
46 changes: 21 additions & 25 deletions compiler/src/dotty/tools/dotc/transform/CheckUnused.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import dotty.tools.dotc.util.chaining.*

import java.util.IdentityHashMap

import scala.annotation.*
import scala.collection.mutable, mutable.{ArrayBuilder, ListBuffer, Stack}

import CheckUnused.*
Expand Down Expand Up @@ -288,6 +289,8 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha
alt.symbol == sym
|| nm.isTypeName && alt.symbol.isAliasType && alt.info.dealias.typeSymbol == sym
sameSym && alt.symbol.isAccessibleFrom(qtpe)
def hasAltMemberNamed(nm: Name) = qtpe.member(nm).hasAltWith(_.symbol.isAccessibleFrom(qtpe))

def loop(sels: List[ImportSelector]): ImportSelector | Null = sels match
case sel :: sels =>
val matches =
Expand All @@ -304,9 +307,17 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha
else
!sym.is(Given) // Normal wildcard, check that the symbol is not a given (but can be implicit)
}
else if sel.isUnimport then
val masksMatchingMember =
name != nme.NO_NAME
&& sels.exists(x => x.isWildcard && !x.isGiven)
&& !name.exists(_.toTermName != sel.name) // import a.b as _, b must match name
&& (hasAltMemberNamed(sel.name) || hasAltMemberNamed(sel.name.toTypeName))
if masksMatchingMember then
refInfos.sels.put(sel, ()) // imprecise due to precedence but errs on the side of false negative
false
else
// if there is an explicit name, it must match
!name.exists(_.toTermName != sel.rename)
!name.exists(_.toTermName != sel.rename) // if there is an explicit name, it must match
&& (prefix.eq(NoPrefix) || qtpe =:= prefix)
&& (hasAltMember(sel.name) || hasAltMember(sel.name.toTypeName))
if matches then sel else loop(sels)
Expand Down Expand Up @@ -639,7 +650,6 @@ object CheckUnused:
|| m.is(Synthetic)
|| m.hasAnnotation(dd.UnusedAnnot) // param of unused method
|| sym.owner.name.isContextFunction // a ubiquitous parameter
|| sym.isCanEqual
|| sym.info.dealias.typeSymbol.match // more ubiquity
case dd.DummyImplicitClass | dd.SubTypeClass | dd.SameTypeClass => true
case tps =>
Expand Down Expand Up @@ -671,7 +681,6 @@ object CheckUnused:
def checkLocal(sym: Symbol, pos: SrcPos) =
if ctx.settings.WunusedHas.locals
&& !sym.is(InlineProxy)
&& !sym.isCanEqual
then
if sym.is(Mutable) && infos.asss(sym) then
warnAt(pos)(UnusedSymbol.localVars)
Expand Down Expand Up @@ -699,7 +708,6 @@ object CheckUnused:
warnAt(pos)(UnusedSymbol.unsetPrivates)

def checkImports() =
// TODO check for unused masking import
import scala.jdk.CollectionConverters.given
import Rewrites.ActionPatch
type ImpSel = (Import, ImportSelector)
Expand All @@ -712,7 +720,7 @@ object CheckUnused:
warnAt(sel.srcPos)(msg, origin)

if !actionable then
for imp <- infos.imps.keySet.nn.asScala; sel <- imp.selectors if !isUsable(imp, sel) do
for imp <- infos.imps.keySet.nn.asScala; sel <- imp.selectors if !isUsed(sel) do
warnImport(imp -> sel)
else
// If the rest of the line is blank, include it in the final edit position. (Delete trailing whitespace.)
Expand Down Expand Up @@ -767,7 +775,7 @@ object CheckUnused:
while index < sortedImps.length do
val nextImport = sortedImps.indexSatisfying(from = index + 1)(_.isPrimaryClause) // next import statement
if sortedImps.indexSatisfying(from = index, until = nextImport): imp =>
imp.selectors.exists(!isUsable(imp, _)) // check if any selector in statement was unused
imp.selectors.exists(!isUsed(_)) // check if any selector in statement was unused
< nextImport then
// if no usable selectors in the import statement, delete it entirely.
// if there is exactly one usable selector, then replace with just that selector (i.e., format it).
Expand All @@ -776,7 +784,7 @@ object CheckUnused:
// Reminder that first clause span includes the keyword, so delete point-to-start instead.
val existing = sortedImps.slice(index, nextImport)
val (keeping, deleting) = existing.iterator.flatMap(imp => imp.selectors.map(imp -> _)).toList
.partition(isUsable(_, _))
.partition((imp, sel) => isUsed(sel))
if keeping.isEmpty then
val editPos = existing.head.srcPos.sourcePos.withSpan:
Span(start = existing.head.srcPos.span.start, end = existing.last.srcPos.span.end)
Expand Down Expand Up @@ -978,8 +986,6 @@ object CheckUnused:
def isSerializationSupport: Boolean =
sym.is(Method) && serializationNames(sym.name.toTermName) && sym.owner.isClass
&& sym.owner.derivesFrom(defn.JavaSerializableClass)
def isCanEqual: Boolean =
sym.isOneOf(GivenOrImplicit) && sym.info.finalResultType.baseClasses.exists(_.derivesFrom(defn.CanEqualClass))
def isMarkerTrait: Boolean =
sym.info.hiBound.resultType.allMembers.forall: d =>
val m = d.symbol
Expand All @@ -1003,6 +1009,11 @@ object CheckUnused:
def boundTpe: Type = sel.bound match
case untpd.TypedSplice(tree) => tree.tpe
case _ => NoType
/** Is a "masking" import of the form import `qual.member as _`.
* Both conditions must be checked.
*/
@unused // matchingSelector checks isWildcard first
def isImportExclusion: Boolean = sel.isUnimport && !sel.isWildcard

extension (imp: Import)(using Context)
/** Is it the first import clause in a statement? `a.x` in `import a.x, b.{y, z}` */
Expand All @@ -1013,21 +1024,6 @@ object CheckUnused:
def isGeneratedByEnum: Boolean =
imp.symbol.exists && imp.symbol.owner.is(Enum, butNot = Case)

/** Under -Wunused:strict-no-implicit-warn, avoid false positives
* if this selector is a wildcard that might import implicits or
* specifically does import an implicit.
* Similarly, import of CanEqual must not warn, as it is always witness.
*/
def isLoose(sel: ImportSelector): Boolean =
if ctx.settings.WunusedHas.strictNoImplicitWarn then
if sel.isWildcard
|| imp.expr.tpe.member(sel.name.toTermName).hasAltWith(_.symbol.isOneOf(GivenOrImplicit))
|| imp.expr.tpe.member(sel.name.toTypeName).hasAltWith(_.symbol.isOneOf(GivenOrImplicit))
then return true
if sel.isWildcard && sel.isGiven
then imp.expr.tpe.allMembers.exists(_.symbol.isCanEqual)
else imp.expr.tpe.member(sel.name.toTermName).hasAltWith(_.symbol.isCanEqual)

extension (pos: SrcPos)
def isZeroExtentSynthetic: Boolean = pos.span.isSynthetic && pos.span.isZeroExtent
def isSynthetic: Boolean = pos.span.isSynthetic && pos.span.exists
Expand Down
5 changes: 5 additions & 0 deletions tests/neg/i15692.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
trait TC[X]
object TC {
given [T, S <: TC[S]](using TC[S]): TC[T] = ???
summon[TC[Int]] // error
}
24 changes: 24 additions & 0 deletions tests/pos/i15692.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
sealed trait Nat
sealed trait Succ[Prev <: Nat] extends Nat
sealed trait Zero extends Nat

class Sum[M <: Nat, N <: Nat] {
type Out <: Nat
}

object Sum {
type Aux[M <: Nat, N <: Nat, R <: Nat] = Sum[M, N] { type Out = R }

implicit def sum0[N <: Nat]: Sum.Aux[Zero, N, N] = new Sum[Zero, N] { type Out = N }
implicit def sum1[M <: Nat, N <: Nat, R <: Nat](implicit sum: Sum.Aux[M, Succ[N], R]): Sum.Aux[Succ[M], N, R] =
new Sum[Succ[M], N] { type Out = R }
}

object Test {
def main(args: Array[String]): Unit = {
type _3 = Succ[Succ[Succ[Zero]]]
type _5 = Succ[Succ[_3]]

implicitly[Sum[_3, _5]]
}
}
4 changes: 2 additions & 2 deletions tests/warn/i15503a.scala
Original file line number Diff line number Diff line change
Expand Up @@ -85,12 +85,12 @@ object InnerMostCheck:
val a = Set(1)

object IgnoreExclusion:
import collection.mutable.{Set => _} // OK
import collection.mutable.{Map => _} // OK
import collection.mutable.{Map => _, Set => _, *} // OK??
import collection.mutable.{ListBuffer} // warn
def check =
val a = Set(1)
val b = Map(1 -> 2)
def c = Seq(42)
/**
* Some given values for the test
*/
Expand Down
22 changes: 11 additions & 11 deletions tests/warn/i15503j.scala
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//> using options -Wunused:strict-no-implicit-warn
//> using options -Wunused:imports

package foo.unused.strict.test:
package a:
Expand All @@ -7,15 +7,15 @@ package foo.unused.strict.test:
val z: Int = 2
def f: Int = 3
package b:
import a.given // OK
import a._ // OK
import a.* // OK
import a.x // OK
import a.y // OK
import a.given // warn
import a._ // warn
import a.* // warn
import a.x // warn
import a.y // warn
import a.z // warn
import a.f // warn
package c:
import a.given // OK
import a.given // warn
import a.x // OK
import a.y // OK
import a.z // OK
Expand All @@ -28,8 +28,8 @@ package foo.implicits.resolution:
object A { implicit val x: X = new X }
object B { implicit val y: Y = new Y }
class C {
import A._ // OK
import B._ // OK
import A.given // warn
import B.given // OK
def t = implicitly[X]
}

Expand All @@ -44,7 +44,7 @@ package foo.unused.summon.inlines:
given willBeUsed: (A & B) = new A with B {}

package use:
import lib.{A, B, C, willBeUnused, willBeUsed} //OK
import lib.{A, B, C, willBeUnused, willBeUsed} // warn
import compiletime.summonInline //OK

transparent inline given conflictInside: C =
Expand All @@ -56,4 +56,4 @@ package foo.unused.summon.inlines:
???

val b: B = summon[B]
val c: C = summon[C]
val c: C = summon[C]
5 changes: 2 additions & 3 deletions tests/pos/i17762.scala → tests/warn/i17762.scala
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//> using options -Xfatal-warnings -Wunused:all
//> using options -Werror -Wunused:all

class SomeType

Expand All @@ -16,6 +16,5 @@ object UsesCanEqual:

object UsesCanEqual2:
import HasCanEqual.f

def testIt(st1: SomeType, st2: SomeType): Boolean =
st1 == st2
st1 != st2
11 changes: 11 additions & 0 deletions tests/warn/i23758.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//> using options -Wunused:imports

import scala.util.Try as _ // warn

class Promise(greeting: String):
override def toString = greeting

@main def test = println:
import scala.concurrent.{Promise as _, *}, ExecutionContext.Implicits.given
val promise = new Promise("world")
Future(s"hello, $promise")
6 changes: 3 additions & 3 deletions tests/warn/unused-can-equal.scala
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@

//> using options -Werror -Wunused:all
//> using options -Wunused:all

import scala.language.strictEquality

class Box[T](x: T) derives CanEqual:
def y = x

def f[A, B](a: A, b: B)(using CanEqual[A, B]) = a == b // no warn
def z[A, B](a: A, b: B)(using ce: CanEqual[A, B]) = a.toString == b.toString // no warn

def g =
import Box.given // no warn
import Box.given // warn
"42".length

@main def test() = println:
Expand Down
Loading