Skip to content

Commit ca19213

Browse files
committed
Gamely resolve CanEqual
1 parent 254cd7d commit ca19213

File tree

2 files changed

+66
-1
lines changed

2 files changed

+66
-1
lines changed

compiler/src/dotty/tools/dotc/transform/CheckUnused.scala

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import dotty.tools.dotc.core.Names.{Name, SimpleName, DerivedName, TermName, ter
1010
import dotty.tools.dotc.core.NameOps.{isAnonymousFunctionName, isReplWrapperName, setterName}
1111
import dotty.tools.dotc.core.NameKinds.{
1212
BodyRetainerName, ContextBoundParamName, ContextFunctionParamName, DefaultGetterName, WildcardParamName}
13+
import dotty.tools.dotc.core.Scopes.newScope
1314
import dotty.tools.dotc.core.StdNames.nme
1415
import dotty.tools.dotc.core.Symbols.{ClassSymbol, NoSymbol, Symbol, defn, isDeprecated, requiredClass, requiredModule}
1516
import dotty.tools.dotc.core.Types.*
@@ -19,6 +20,7 @@ import dotty.tools.dotc.rewrites.Rewrites
1920
import dotty.tools.dotc.transform.MegaPhase.MiniPhase
2021
import dotty.tools.dotc.typer.{ImportInfo, Typer}
2122
import dotty.tools.dotc.typer.Deriving.OriginalTypeClass
23+
import dotty.tools.dotc.typer.Implicits.{ContextualImplicits, RenamedImplicitRef}
2224
import dotty.tools.dotc.util.{Property, Spans, SrcPos}, Spans.Span
2325
import dotty.tools.dotc.util.Chars.{isLineBreakChar, isWhitespace}
2426
import dotty.tools.dotc.util.chaining.*
@@ -116,6 +118,14 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha
116118
args.foreach(_.withAttachment(ForArtifact, ()))
117119
case _ =>
118120
ctx
121+
override def transformApply(tree: Apply)(using Context): tree.type =
122+
// check for multiversal equals
123+
tree match
124+
case Apply(Select(left, nme.Equals | nme.NotEquals), right :: Nil) =>
125+
val caneq = defn.CanEqualClass.typeRef.appliedTo(left.tpe.widen :: right.tpe.widen :: Nil)
126+
resolveScoped(caneq)
127+
case _ =>
128+
tree
119129

120130
override def prepareForAssign(tree: Assign)(using Context): Context =
121131
tree.lhs.putAttachment(AssignmentTarget, ()) // don't take LHS reference as a read
@@ -213,6 +223,16 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha
213223
refInfos.register(tree)
214224
tree
215225

226+
override def prepareForStats(trees: List[Tree])(using Context): Context =
227+
// gather local implicits while ye may
228+
if !ctx.owner.isClass then
229+
if trees.exists(t => t.isDef && t.symbol.is(Given) && t.symbol.isLocalToBlock) then
230+
val scope = newScope.openForMutations
231+
for tree <- trees if tree.isDef && tree.symbol.is(Given) do
232+
scope.enter(tree.symbol.name, tree.symbol)
233+
return ctx.fresh.setScope(scope)
234+
ctx
235+
216236
override def transformOther(tree: Tree)(using Context): tree.type =
217237
tree match
218238
case imp: Import =>
@@ -406,6 +426,38 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha
406426
if candidate != NoContext && candidate.isImportContext && importer != null then
407427
refInfos.sels.put(importer, ())
408428
end resolveUsage
429+
430+
/** Simulate implicit search for contextual implicits in lexical scope and mark any definitions or imports as used.
431+
* Avoid cached ctx.implicits because it needs the precise import context that introduces the given.
432+
*/
433+
def resolveScoped(tp: Type)(using Context): Unit =
434+
var done = false
435+
val ctxs = ctx.outersIterator
436+
while !done && ctxs.hasNext do
437+
val cur = ctxs.next()
438+
val implicitRefs: List[ImplicitRef] =
439+
if (cur.isClassDefContext) cur.owner.thisType.implicitMembers
440+
else if (cur.isImportContext) cur.importInfo.nn.importedImplicits
441+
else if (cur.isNonEmptyScopeContext) cur.scope.implicitDecls
442+
else Nil
443+
implicitRefs.find(ref => ref.underlyingRef.widen <:< tp) match
444+
case Some(found: TermRef) =>
445+
refInfos.addRef(found.denot.symbol)
446+
if cur.isImportContext then
447+
cur.importInfo.nn.selectors.find(sel => sel.isGiven || sel.rename == found.name) match
448+
case Some(sel) =>
449+
refInfos.sels.put(sel, ())
450+
case _ =>
451+
return
452+
case Some(found: RenamedImplicitRef) if cur.isImportContext =>
453+
refInfos.addRef(found.underlyingRef.denot.symbol)
454+
cur.importInfo.nn.selectors.find(sel => sel.rename == found.implicitName) match
455+
case Some(sel) =>
456+
refInfos.sels.put(sel, ())
457+
case _ =>
458+
return
459+
case _ =>
460+
end resolveScoped
409461
end CheckUnused
410462

411463
object CheckUnused:

tests/warn/i17762.scala

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//> using options -Werror -Wunused:all
1+
//> using options -Wunused:all
22

33
class SomeType
44

@@ -18,3 +18,16 @@ object UsesCanEqual2:
1818
import HasCanEqual.f
1919
def testIt(st1: SomeType, st2: SomeType): Boolean =
2020
st1 != st2
21+
22+
object UsesCanEqual3:
23+
import HasCanEqual.f as g
24+
def testIt(st1: SomeType, st2: SomeType): Boolean =
25+
st1 != st2
26+
27+
def warnable(st1: SomeType, st2: SomeType): Boolean =
28+
given CanEqual[SomeType, SomeType] = CanEqual.derived // warn
29+
st1.toString == st2.toString
30+
31+
def importable(st1: SomeType, st2: SomeType): Boolean =
32+
import HasCanEqual.given // warn
33+
st1.toString == st2.toString

0 commit comments

Comments
 (0)