Skip to content

Commit

Permalink
WIP - info from Typer & Implicits
Browse files Browse the repository at this point in the history
  • Loading branch information
szymon-rd committed Aug 28, 2023
1 parent e25d0fd commit b2b6ab2
Show file tree
Hide file tree
Showing 8 changed files with 153 additions and 151 deletions.
2 changes: 2 additions & 0 deletions compiler/src/dotty/tools/dotc/CompilationUnit.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ class CompilationUnit protected (val source: SourceFile) {

var tpdTree: tpd.Tree = tpd.EmptyTree

val eventLog: EventLog = new EventLog

/** Is this the compilation unit of a Java file */
def isJava: Boolean = source.file.name.endsWith(".java")

Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/Compiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class Compiler {
protected def frontendPhases: List[List[Phase]] =
List(new Parser) :: // Compiler frontend: scanner, parser
List(new TyperPhase) :: // Compiler frontend: namer, typer
List(new CheckUnused.PostTyper) :: // Check for unused elements
List(new CheckUnused.AfterTyper) :: // Check for unused elements
List(new YCheckPositions) :: // YCheck positions
List(new sbt.ExtractDependencies) :: // Sends information on classes' dependencies to sbt via callbacks
List(new semanticdb.ExtractSemanticDB) :: // Extract info into .semanticdb files
Expand All @@ -50,7 +50,7 @@ class Compiler {
List(new Pickler) :: // Generate TASTY info
List(new Inlining) :: // Inline and execute macros
List(new PostInlining) :: // Add mirror support for inlined code
List(new CheckUnused.PostInlining) :: // Check for unused elements
List(new CheckUnused.AfterInlining) :: // Check for unused elements
List(new Staging) :: // Check staging levels and heal staged types
List(new Splicing) :: // Replace level 1 splices with holes
List(new PickleQuotes) :: // Turn quoted trees into explicit run-time data structures
Expand Down
52 changes: 52 additions & 0 deletions compiler/src/dotty/tools/dotc/EventLog.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package dotty.tools.dotc

import dotty.tools.dotc.core.SymDenotations.NoDenotation.exists
import scala.collection.mutable
import core.*
import ast.*
import Symbols.*
import EventLog.*
import Contexts.*
import dotty.tools.dotc.transform.CheckUnused

class EventLog {
private val entries: mutable.ListBuffer[Entry] = mutable.ListBuffer()
private var state = 0

inline def appendEntry(entry: Entry)(using Context): Unit = {
state match {
case Disabled => ()
case Enabled => entries += entry
case Uninitialized =>
val enabled = ctx.base.allPhases.exists { phase =>
logReaderPhasesNames.contains(phase.phaseName) && phase.isRunnable
}
state =
if enabled then
entries += entry
Enabled
else Disabled
}
}

def toSeq: Seq[Entry] = entries.toSeq

}

object EventLog {

private val logReaderPhasesNames = Set(
CheckUnused.phaseNamePrefix + CheckUnused.afterTyperSuffix,
CheckUnused.phaseNamePrefix + CheckUnused.afterInliningSuffix
)

private val Uninitialized: Int = 0
private val Enabled = 1
private val Disabled = 2

sealed trait Entry
case class MatchedImportSelector(importSym: Symbol, selector: untpd.ImportSelector) extends Entry
case class ImplicitIntroducedViaImport(importSym: Symbol, tree: Tree, ref: TermRef) extends Entry
case object FindRefFinished extends Entry

}
4 changes: 3 additions & 1 deletion compiler/src/dotty/tools/dotc/core/Contexts.scala
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ object Contexts {
private var implicitsCache: ContextualImplicits | Null = null
def implicits: ContextualImplicits = {
if (implicitsCache == null)
implicitsCache = {
implicitsCache = {d
val implicitRefs: List[ImplicitRef] =
if (isClassDefContext)
try owner.thisType.implicitMembers
Expand Down Expand Up @@ -357,6 +357,8 @@ object Contexts {
final def isAfterTyper = base.isAfterTyper(phase)
final def isTyper = base.isTyper(phase)

final def eventLog: EventLog = compilationUnit.eventLog

/** Is this a context for the members of a class definition? */
def isClassDefContext: Boolean =
owner.isClass && (owner ne outer.owner)
Expand Down
96 changes: 41 additions & 55 deletions compiler/src/dotty/tools/dotc/transform/CheckUnused.scala
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@ import dotty.tools.dotc.core.Definitions
import dotty.tools.dotc.core.NameKinds.WildcardParamName
import dotty.tools.dotc.core.Symbols.Symbol
import dotty.tools.dotc.core.StdNames.nme
import dotty.tools.dotc.EventLog
import scala.math.Ordering
import scala.annotation.tailrec
import dotty.tools.dotc.util.Spans.Coord
import dotty.tools.dotc.util.Spans.Span


/**
Expand Down Expand Up @@ -55,13 +59,43 @@ class CheckUnused private (phaseMode: CheckUnused.PhaseMode, suffix: String, _ke

override def prepareForUnit(tree: tpd.Tree)(using Context): Context =
val data = UnusedData()
data.usedImports = collectUsedImports
println("Used imports: " + data.usedImports)
tree.getAttachment(_key).foreach(oldData =>
data.unusedAggregate = oldData.unusedAggregate
)
val fresh = ctx.fresh.setProperty(_key, data)
tree.putAttachment(_key, data)
fresh

private def collectUsedImports(using Context): Map[Span, Set[ImportSelector]] = Map.empty
// @tailrec
// def collectUsedImportsFromEventLog(
// acc: Set[(Symbol, ImportSelector)],
// lastSelector: ImportSelector,
// eventLog: Seq[EventLog.Entry]
// ): Set[(Symbol, ImportSelector)] =
// eventLog match
// case EventLog.MatchedImportSelector(_, selector) :: tail =>
// println("Selector")
// collectUsedImportsFromEventLog(acc, selector, tail)
// case EventLog.RefFoundInImport(importSym) :: tail =>
// println("Adding")
// collectUsedImportsFromEventLog(acc + ((importSym, lastSelector)), lastSelector, tail)
// case Nil =>
// acc

// println("Log: " + ctx.eventLog.toSeq)
// val eventLog = ctx.eventLog.toSeq.dropWhile(!_.isInstanceOf[EventLog.MatchedImportSelector])
// println("Reduced: " + eventLog)
// eventLog match
// case EventLog.MatchedImportSelector(_, selector) :: tail =>
// collectUsedImportsFromEventLog(Set.empty, selector, tail)
// .groupBy(_._1.span).view.mapValues(_.map(_._2)).toMap
// case _ => Map.empty



// ========== END + REPORTING ==========

override def transformUnit(tree: tpd.Tree)(using Context): tpd.Tree =
Expand Down Expand Up @@ -305,6 +339,8 @@ end CheckUnused
object CheckUnused:
val phaseNamePrefix: String = "checkUnused"
val description: String = "check for unused elements"
val afterTyperSuffix: String = "AfterTyper"
val afterInliningSuffix: String = "AfterInlining"

enum PhaseMode:
case Aggregate
Expand All @@ -326,9 +362,9 @@ object CheckUnused:
*/
private val _key = Property.StickyKey[UnusedData]

class PostTyper extends CheckUnused(PhaseMode.Aggregate, "PostTyper", _key)
class AfterTyper extends CheckUnused(PhaseMode.Aggregate, afterTyperSuffix, _key)

class PostInlining extends CheckUnused(PhaseMode.Report, "PostInlining", _key)
class AfterInlining extends CheckUnused(PhaseMode.Report, afterInliningSuffix, _key)

/**
* A stateful class gathering the infos on :
Expand All @@ -343,6 +379,7 @@ object CheckUnused:
/** The current scope during the tree traversal */
val currScopeType: MutStack[ScopeType] = MutStack(ScopeType.Other)

var usedImports = Map[Span, Set[ImportSelector]]()
var unusedAggregate: Option[UnusedResult] = None

/* IMPORTS */
Expand Down Expand Up @@ -423,7 +460,8 @@ object CheckUnused:
if !tpd.languageImport(imp.expr).nonEmpty && !imp.isGeneratedByEnum && !isTransparentAndInline(imp) then
impInScope.top += imp
unusedImport ++= imp.selectors.filter { s =>
!shouldSelectorBeReported(imp, s) && !isImportExclusion(s)
!shouldSelectorBeReported(imp, s) && !isImportExclusion(s) &&
usedImports.get(imp.expr.span).forall(!_.contains(s))
}

/** Register (or not) some `val` or `def` according to the context, scope and flags */
Expand Down Expand Up @@ -463,36 +501,6 @@ object CheckUnused:
def popScope()(using Context): Unit =
// used symbol in this scope
val used = usedInScope.pop().toSet
// used imports in this scope
val imports = impInScope.pop()
val kept = used.filterNot { (sym, isAccessible, optName, isDerived) =>
// keep the symbol for outer scope, if it matches **no** import
// This is the first matching wildcard selector
var selWildCard: Option[ImportSelector] = None

val matchedExplicitImport = imports.exists { imp =>
sym.isInImport(imp, isAccessible, optName, isDerived) match
case None => false
case optSel@Some(sel) if sel.isWildcard =>
if selWildCard.isEmpty then selWildCard = optSel
// We keep wildcard symbol for the end as they have the least precedence
false
case Some(sel) =>
unusedImport -= sel
true
}
if !matchedExplicitImport && selWildCard.isDefined then
unusedImport -= selWildCard.get
true // a matching import exists so the symbol won't be kept for outer scope
else
matchedExplicitImport
}

// if there's an outer scope
if usedInScope.nonEmpty then
// we keep the symbols not referencing an import in this scope
// as it can be the only reference to an outer import
usedInScope.top ++= kept
// register usage in this scope for other warnings at the end of the phase
usedDef ++= used.map(_._1)
// retrieve previous scope type
Expand Down Expand Up @@ -654,28 +662,6 @@ object CheckUnused:
&& c.owner.thisType.member(sym.name).alternatives.contains(sym)
}

/** Given an import and accessibility, return selector that matches import<->symbol */
private def isInImport(imp: tpd.Import, isAccessible: Boolean, symName: Option[Name], isDerived: Boolean)(using Context): Option[ImportSelector] =
val tpd.Import(qual, sels) = imp
val dealiasedSym = dealias(sym)
val simpleSelections = qual.tpe.member(sym.name).alternatives
val typeSelections = sels.flatMap(n => qual.tpe.member(n.name.toTypeName).alternatives)
val termSelections = sels.flatMap(n => qual.tpe.member(n.name.toTermName).alternatives)
val selectionsToDealias = typeSelections ::: termSelections
val qualHasSymbol = simpleSelections.map(_.symbol).contains(sym) || (simpleSelections ::: selectionsToDealias).map(_.symbol).map(dealias).contains(dealiasedSym)
def selector = sels.find(sel => (sel.name.toTermName == sym.name || sel.name.toTypeName == sym.name) && symName.map(n => n.toTermName == sel.rename).getOrElse(true))
def dealiasedSelector = if(isDerived) sels.flatMap(sel => selectionsToDealias.map(m => (sel, m.symbol))).collect {
case (sel, sym) if dealias(sym) == dealiasedSym => sel
}.headOption else None
def givenSelector = if sym.is(Given) || sym.is(Implicit)
then sels.filter(sel => sel.isGiven && !sel.bound.isEmpty).find(sel => sel.boundTpe =:= sym.info)
else None
def wildcard = sels.find(sel => sel.isWildcard && ((sym.is(Given) == sel.isGiven && sel.bound.isEmpty) || sym.is(Implicit)))
if qualHasSymbol && (!isAccessible || sym.isRenamedSymbol(symName)) && sym.exists then
selector.orElse(dealiasedSelector).orElse(givenSelector).orElse(wildcard) // selector with name or wildcard (or given)
else
None

private def isRenamedSymbol(symNameInScope: Option[Name])(using Context) =
sym.name != nme.NO_NAME && symNameInScope.exists(_.toSimpleName != sym.name.toSimpleName)

Expand Down
14 changes: 9 additions & 5 deletions compiler/src/dotty/tools/dotc/typer/Implicits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ object Implicits:
/** An implicit definition `implicitRef` that is visible under a different name, `alias`.
* Gets generated if an implicit ref is imported via a renaming import.
*/
class RenamedImplicitRef(val underlyingRef: TermRef, val alias: TermName) extends ImplicitRef {
class RenamedImplicitRef(val underlyingRef: TermRef, val alias: TermName, val fromImport: Option[ImportInfo] = None) extends ImplicitRef {
def implicitName(using Context): TermName = alias
}

Expand Down Expand Up @@ -422,7 +422,7 @@ object Implicits:
* @param isExtension Whether the result is an extension method application
* @param tstate The typer state to be committed if this alternative is chosen
*/
case class SearchSuccess(tree: Tree, ref: TermRef, level: Int, isExtension: Boolean = false)(val tstate: TyperState, val gstate: GadtConstraint)
case class SearchSuccess(tree: Tree, ref: TermRef, level: Int, isExtension: Boolean = false, fromCandidate: Option[Candidate] = None)(val tstate: TyperState, val gstate: GadtConstraint)
extends SearchResult with RefAndLevel with Showable

/** A failed search */
Expand Down Expand Up @@ -877,7 +877,7 @@ trait Implicits:
val inferred = inferImplicit(adjust(to), from, from.span)

inferred match {
case SearchSuccess(_, ref, _, false) if isOldStyleFunctionConversion(ref.underlying) =>
case SearchSuccess(_, ref, _, false, _) if isOldStyleFunctionConversion(ref.underlying) =>
report.migrationWarning(
em"The conversion ${ref} will not be applied implicitly here in Scala 3 because only implicit methods and instances of Conversion class will continue to work as implicit views.",
from
Expand Down Expand Up @@ -1207,7 +1207,7 @@ trait Implicits:
ctx.reporter.removeBufferedMessages
res
else
SearchSuccess(adapted, ref, cand.level, cand.isExtension)(ctx.typerState, ctx.gadt)
SearchSuccess(adapted, ref, cand.level, cand.isExtension, Some(cand))(ctx.typerState, ctx.gadt)
}

/** An implicit search; parameters as in `inferImplicit` */
Expand Down Expand Up @@ -1586,11 +1586,15 @@ trait Implicits:
// effectively in a more inner context than any other definition provided by
// explicit definitions. Consequently these terms have the highest priority and no
// other candidates need to be considered.
recursiveRef match
val result = recursiveRef match
case ref: TermRef =>
SearchSuccess(tpd.ref(ref).withSpan(span.startPos), ref, 0)(ctx.typerState, ctx.gadt)
case _ =>
searchImplicit(contextual = true)
result.fromCandidate.foreach { cand =>
ctx.eventLog.appendLog(cand.)
}
result
end bestImplicit

def implicitScope(tp: Type): OfTypeImplicits = ctx.run.nn.implicitScope(tp)
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/typer/ImportInfo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ class ImportInfo(symf: Context ?=> Symbol,
if isEligible && ref.denot.asSingleDenotation.matchesImportBound(bound) then ref :: Nil
else Nil
else if renamed == ref.name then ref :: Nil
else RenamedImplicitRef(ref, renamed) :: Nil
else RenamedImplicitRef(ref, renamed, Some(this)) :: Nil
}
else
for
Expand All @@ -153,7 +153,7 @@ class ImportInfo(symf: Context ?=> Symbol,
val original = reverseMapping(renamed).nn
val ref = TermRef(pre, original, denot)
if renamed == original then ref
else RenamedImplicitRef(ref, renamed)
else RenamedImplicitRef(ref, renamed, Some(this))

/** The root import symbol hidden by this symbol, or NoSymbol if no such symbol is hidden.
* Note: this computation needs to work even for un-initialized import infos, and
Expand Down
Loading

0 comments on commit b2b6ab2

Please sign in to comment.