Skip to content

Commit fa7e7a2

Browse files
som-snytttgodzik
authored andcommitted
Gamely resolve CanEqual
[Cherry-picked 88fc3a3]
1 parent 612769d commit fa7e7a2

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, isContextFunction, 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
@@ -196,6 +206,16 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha
196206
refInfos.register(tree)
197207
tree
198208

209+
override def prepareForStats(trees: List[Tree])(using Context): Context =
210+
// gather local implicits while ye may
211+
if !ctx.owner.isClass then
212+
if trees.exists(t => t.isDef && t.symbol.is(Given) && t.symbol.isLocalToBlock) then
213+
val scope = newScope.openForMutations
214+
for tree <- trees if tree.isDef && tree.symbol.is(Given) do
215+
scope.enter(tree.symbol.name, tree.symbol)
216+
return ctx.fresh.setScope(scope)
217+
ctx
218+
199219
override def transformOther(tree: Tree)(using Context): tree.type =
200220
tree match
201221
case imp: Import =>
@@ -374,6 +394,38 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha
374394
if candidate != NoContext && candidate.isImportContext && importer != null then
375395
refInfos.sels.put(importer, ())
376396
end resolveUsage
397+
398+
/** Simulate implicit search for contextual implicits in lexical scope and mark any definitions or imports as used.
399+
* Avoid cached ctx.implicits because it needs the precise import context that introduces the given.
400+
*/
401+
def resolveScoped(tp: Type)(using Context): Unit =
402+
var done = false
403+
val ctxs = ctx.outersIterator
404+
while !done && ctxs.hasNext do
405+
val cur = ctxs.next()
406+
val implicitRefs: List[ImplicitRef] =
407+
if (cur.isClassDefContext) cur.owner.thisType.implicitMembers
408+
else if (cur.isImportContext) cur.importInfo.nn.importedImplicits
409+
else if (cur.isNonEmptyScopeContext) cur.scope.implicitDecls
410+
else Nil
411+
implicitRefs.find(ref => ref.underlyingRef.widen <:< tp) match
412+
case Some(found: TermRef) =>
413+
refInfos.addRef(found.denot.symbol)
414+
if cur.isImportContext then
415+
cur.importInfo.nn.selectors.find(sel => sel.isGiven || sel.rename == found.name) match
416+
case Some(sel) =>
417+
refInfos.sels.put(sel, ())
418+
case _ =>
419+
return
420+
case Some(found: RenamedImplicitRef) if cur.isImportContext =>
421+
refInfos.addRef(found.underlyingRef.denot.symbol)
422+
cur.importInfo.nn.selectors.find(sel => sel.rename == found.implicitName) match
423+
case Some(sel) =>
424+
refInfos.sels.put(sel, ())
425+
case _ =>
426+
return
427+
case _ =>
428+
end resolveScoped
377429
end CheckUnused
378430

379431
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)