Skip to content

Commit

Permalink
EnclosingSpan
Browse files Browse the repository at this point in the history
  • Loading branch information
som-snytt committed Jul 24, 2022
1 parent 8880b3d commit 55f2e25
Show file tree
Hide file tree
Showing 7 changed files with 84 additions and 33 deletions.
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 @@ -30,6 +30,8 @@ import printing._
import config.{JavaPlatform, SJSPlatform, Platform, ScalaSettings, ScalaRelease}
import classfile.ReusableDataReader
import StdNames.nme
import parsing.Parsers.EnclosingSpan
import util.Spans.NoSpan

import scala.annotation.internal.sharable

Expand Down Expand Up @@ -482,7 +484,7 @@ object Contexts:

/** A new context that summarizes an import statement */
def importContext(imp: Import[?], sym: Symbol, enteringSyms: Boolean = false): FreshContext =
fresh.setImportInfo(ImportInfo(sym, imp.selectors, imp.expr).tap(ii => if enteringSyms && ctx.settings.WunusedHas.imports then usages += ii))
fresh.setImportInfo(ImportInfo(sym, imp.selectors, imp.expr, imp.attachmentOrElse(EnclosingSpan, NoSpan)).tap(ii => if enteringSyms && ctx.settings.WunusedHas.imports then usages += ii))

/** Is the debug option set? */
def debug: Boolean = base.settings.Ydebug.value
Expand Down
12 changes: 8 additions & 4 deletions compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import scala.language.unsafeNulls
import scala.annotation.internal.sharable
import scala.collection.mutable.ListBuffer
import scala.collection.immutable.BitSet
import util.{ SourceFile, SourcePosition, NoSourcePosition }
import util.{Property, SourceFile, SourcePosition, NoSourcePosition}
import Tokens._
import Scanners._
import xml.MarkupParsers.MarkupParser
Expand Down Expand Up @@ -64,6 +64,8 @@ object Parsers {
val QuotedPattern = 1 << 2
}

val EnclosingSpan: Property.Key[Span] = Property.Key()

extension (buf: ListBuffer[Tree])
def +++=(x: Tree) = x match {
case x: Thicket => buf ++= x.trees
Expand Down Expand Up @@ -3182,18 +3184,20 @@ object Parsers {
/** Import ::= `import' ImportExpr {‘,’ ImportExpr}
* Export ::= `export' ImportExpr {‘,’ ImportExpr}
*/
def importOrExportClause(leading: Token, mkTree: ImportConstr): List[Tree] = {
def importOrExportClause(leading: Token, mkTree: ImportConstr): List[Tree] =
val offset = accept(leading)
commaSeparated(importExpr(mkTree)) match {
case t :: rest =>
// The first import should start at the start offset of the keyword.
val firstPos =
if (t.span.exists) t.span.withStart(offset)
else Span(offset, in.lastOffset)
t.withSpan(firstPos) :: rest
val imports = t.withSpan(firstPos) :: rest
val enclosing = imports.head.span union imports.last.span
imports.foreach(_.putAttachment(EnclosingSpan, enclosing))
imports
case nil => nil
}
}

def exportClause() =
importOrExportClause(EXPORT, Export(_,_))
Expand Down
5 changes: 4 additions & 1 deletion compiler/src/dotty/tools/dotc/typer/ImportInfo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import ast.{tpd, untpd}
import core._
import printing.{Printer, Showable}
import util.SimpleIdentityMap
import util.Spans.*
import Symbols._, Names._, Types._, Contexts._, StdNames._, Flags._
import Implicits.{ImportedImplicitRef, RenamedImplicitRef}
import StdNames.nme
Expand All @@ -32,7 +33,7 @@ object ImportInfo {
val expr = tpd.Ident(ref.refFn()) // refFn must be called in the context of ImportInfo.sym
tpd.Import(expr, selectors).symbol

ImportInfo(sym, selectors, untpd.EmptyTree, isRootImport = true)
ImportInfo(sym, selectors, untpd.EmptyTree, NoSpan, isRootImport = true)

extension (c: Context)
def withRootImports(rootRefs: List[RootRef])(using Context): Context =
Expand All @@ -48,12 +49,14 @@ object ImportInfo {
* @param selectors The selector clauses
* @param qualifier The import qualifier, or EmptyTree for root imports.
* Defined for all explicit imports from ident or select nodes.
* @param enclosingSpan Span of the enclosing import statement
* @param isRootImport true if this is one of the implicit imports of scala, java.lang,
* scala.Predef in the start context, false otherwise.
*/
class ImportInfo(symf: Context ?=> Symbol,
val selectors: List[untpd.ImportSelector],
val qualifier: untpd.Tree,
val enclosingSpan: Span,
val isRootImport: Boolean = false) extends Showable {

private def symNameOpt = qualifier match {
Expand Down
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/dotc/typer/Namer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import core._
import ast._
import Trees._, StdNames._, Scopes._, Denotations._, NamerOps._, ContextOps._
import Contexts._, Symbols._, Types._, SymDenotations._, Names._, NameOps._, Flags._
import Decorators._, Comments.{_, given}
import Decorators._
import Comments.{_, given}
import NameKinds.DefaultGetterName
import ast.desugar, ast.desugar._
import ProtoTypes._
Expand Down
73 changes: 51 additions & 22 deletions compiler/src/dotty/tools/dotc/typer/TyperPhase.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package dotty.tools
package dotc
package typer

import ast.untpd
import core._
import Phases._
import Contexts._
Expand Down Expand Up @@ -57,18 +56,55 @@ class TyperPhase(addRootImports: Boolean = true) extends Phase {
JavaChecks.check(unit.tpdTree)
}

/** Report unused imports.
*
* If `-Yrewrite-imports`, emit patches instead.
* Patches are applied if `-rewrite` and no errors.
*/
def emitDiagnostics(using Context): Unit =
ctx.usages.unused.foreach { (info, owner, selectors) =>
import rewrites.Rewrites.patch
import parsing.Parsers
import util.SourceFile
import ast.Trees.*
def reportSelectors() = selectors.foreach(selector => report.warning(s"Unused import", pos = selector.srcPos))
if ctx.settings.YrewriteImports.value then
import ast.NavigateAST.untypedPath
import ast.Trees.*
import ast.untpd
import parsing.Parsers
import rewrites.Rewrites.patch
import util.SourceFile
def reportSelectors(sels: List[untpd.ImportSelector]) = sels.foreach(sel => report.warning(s"Unused import", pos = sel.srcPos))
// format the selectors without braces, as replacement text
def toText(sels: List[untpd.ImportSelector]): String =
def selected(sel: untpd.ImportSelector) =
if sel.isGiven then "given"
else if sel.isWildcard then "*"
else if sel.name == sel.rename then sel.name.show
else s"${sel.name.show} as ${sel.rename.show}"
sels.map(selected).mkString(", ")
// begin
val unused = ctx.usages.unused
if ctx.settings.YrewriteImports.value then
val byLocation = unused.groupBy((info, owner, selectors) => info.qualifier.sourcePos.withSpan(info.enclosingSpan))
byLocation.foreach { (enclosingPos, grouped) =>
val importText = enclosingPos.spanText
val lineSource = SourceFile.virtual(name = "import-line.scala", content = importText)
val PackageDef(_, pieces) = Parsers.Parser(lineSource).parse(): @unchecked
println(s"pieces are $pieces")
grouped match {
case (info, owners, selectors) :: rest =>
println(s"info enclosing ${info.enclosingSpan} has qual ${info.qualifier.sourcePos}")
println(s"untyped path from qual\n${ untypedPath(info.qualifier.sourcePos.span).mkString("\n") }")
//println(s"untyped path\n${ untypedPath(info.enclosingSpan).mkString("\n") }")
case _ =>
println(s"I got nothing")
}
}
/*
ctx.usages.unused.foreach { (info, owner, selectors) =>
println(s"PATCH enclosing ${info.enclosingSpan} in ${info.qualifier.sourcePos.withSpan(info.enclosingSpan).spanText}")
val src = ctx.compilationUnit.source
val infoPos = info.qualifier.sourcePos
val lineSource = SourceFile.virtual(name = "import-line.scala", content = infoPos.lineContent)
//val importText = infoPos.lineContent
val importText = infoPos.withSpan(info.enclosingSpan).spanText
val lineSource = SourceFile.virtual(name = "import-line.scala", content = importText)
val PackageDef(_, pieces) = Parsers.Parser(lineSource).parse(): @unchecked
println(s"ENCLOSING has ${pieces.length} parts")
// patch if there's just one import on the line, i.e., not import a.b, c.d
if pieces.length == 1 then
val retained = info.selectors.filterNot(selectors.contains)
Expand All @@ -86,19 +122,12 @@ class TyperPhase(addRootImports: Boolean = true) extends Phase {
patch(src, widened, toText(retained)) // try to remove braces
else
patch(src, selectorSpan, toText(retained))
else
reportSelectors()
else
reportSelectors()
}
// just the selectors, no need to add braces
private def toText(retained: List[untpd.ImportSelector])(using Context): String =
def selected(sel: untpd.ImportSelector) =
if sel.isGiven then "given"
else if sel.isWildcard then "*"
else if sel.name == sel.rename then sel.name.show
else s"${sel.name.show} as ${sel.rename.show}"
retained.map(selected).mkString(", ")
}
*/
else
ctx.usages.unused.foreach { (info, owner, selectors) => reportSelectors(selectors) }
end emitDiagnostics

def clearDiagnostics()(using Context): Unit =
ctx.usages.clear()

Expand Down
6 changes: 6 additions & 0 deletions compiler/src/dotty/tools/dotc/util/SourcePosition.scala
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ extends SrcPos, interfaces.SourcePosition, Showable {
def linesSlice: Array[Char] =
source.content.slice(source.startOfLine(start), source.nextLine(end))

/** Extract exactly the span from the source file. */
def spanSlice: Array[Char] = source.content.slice(start, end)

/** Extract exactly the span from the source file as a String. */
def spanText: String = String(spanSlice)

/** The lines of the position */
def lines: Range = {
val startOffset = source.offsetToLine(start)
Expand Down
14 changes: 10 additions & 4 deletions project/Build.scala
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ object Build {
val referenceVersion = "3.2.0-RC2"

val baseVersion = "3.2.1-RC1"
//val referenceVersion = "3.1.3-RC2"
//val referenceVersion = "3.2.0-RC1-bin-SNAPSHOT"

//val baseVersion = "3.2.0-RC1"
//val baseVersion = "3.2.0-RC2"

// Versions used by the vscode extension to create a new project
// This should be the latest published releases.
Expand Down Expand Up @@ -790,10 +795,11 @@ object Build {
"-Ddotty.tests.classes.dottyTastyInspector=" + jars("scala3-tasty-inspector"),
)
},
//scalacOptions ++= Seq(
// "-Wunused:imports",
// "-rewrite",
//),
scalacOptions ++= Seq(
//"-Wunused:imports",
//"-rewrite",
//"-Yrewrite-imports",
),
packageAll := {
(`scala3-compiler` / packageAll).value ++ Seq(
"scala3-compiler" -> (Compile / packageBin).value.getAbsolutePath,
Expand Down

0 comments on commit 55f2e25

Please sign in to comment.