Skip to content

Commit ec82c3a

Browse files
committed
EnclosingSpan
1 parent 20fc59b commit ec82c3a

File tree

7 files changed

+84
-33
lines changed

7 files changed

+84
-33
lines changed

compiler/src/dotty/tools/dotc/core/Contexts.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ import printing._
2929
import config.{JavaPlatform, SJSPlatform, Platform, ScalaSettings, ScalaRelease}
3030
import classfile.ReusableDataReader
3131
import StdNames.nme
32+
import parsing.Parsers.EnclosingSpan
33+
import util.Spans.NoSpan
3234

3335
import scala.annotation.internal.sharable
3436

@@ -481,7 +483,7 @@ object Contexts:
481483

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

486488
def scalaRelease: ScalaRelease =
487489
val releaseName = base.settings.scalaOutputVersion.value

compiler/src/dotty/tools/dotc/parsing/Parsers.scala

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import scala.language.unsafeNulls
77
import scala.annotation.internal.sharable
88
import scala.collection.mutable.ListBuffer
99
import scala.collection.immutable.BitSet
10-
import util.{ SourceFile, SourcePosition, NoSourcePosition }
10+
import util.{Property, SourceFile, SourcePosition, NoSourcePosition}
1111
import Tokens._
1212
import Scanners._
1313
import xml.MarkupParsers.MarkupParser
@@ -64,6 +64,8 @@ object Parsers {
6464
val QuotedPattern = 1 << 2
6565
}
6666

67+
val EnclosingSpan: Property.Key[Span] = Property.Key()
68+
6769
extension (buf: ListBuffer[Tree])
6870
def +++=(x: Tree) = x match {
6971
case x: Thicket => buf ++= x.trees
@@ -3182,18 +3184,20 @@ object Parsers {
31823184
/** Import ::= `import' ImportExpr {‘,’ ImportExpr}
31833185
* Export ::= `export' ImportExpr {‘,’ ImportExpr}
31843186
*/
3185-
def importOrExportClause(leading: Token, mkTree: ImportConstr): List[Tree] = {
3187+
def importOrExportClause(leading: Token, mkTree: ImportConstr): List[Tree] =
31863188
val offset = accept(leading)
31873189
commaSeparated(importExpr(mkTree)) match {
31883190
case t :: rest =>
31893191
// The first import should start at the start offset of the keyword.
31903192
val firstPos =
31913193
if (t.span.exists) t.span.withStart(offset)
31923194
else Span(offset, in.lastOffset)
3193-
t.withSpan(firstPos) :: rest
3195+
val imports = t.withSpan(firstPos) :: rest
3196+
val enclosing = imports.head.span union imports.last.span
3197+
imports.foreach(_.putAttachment(EnclosingSpan, enclosing))
3198+
imports
31943199
case nil => nil
31953200
}
3196-
}
31973201

31983202
def exportClause() =
31993203
importOrExportClause(EXPORT, Export(_,_))

compiler/src/dotty/tools/dotc/typer/ImportInfo.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import ast.{tpd, untpd}
66
import core._
77
import printing.{Printer, Showable}
88
import util.SimpleIdentityMap
9+
import util.Spans.*
910
import Symbols._, Names._, Types._, Contexts._, StdNames._, Flags._
1011
import Implicits.{ImportedImplicitRef, RenamedImplicitRef}
1112
import StdNames.nme
@@ -32,7 +33,7 @@ object ImportInfo {
3233
val expr = tpd.Ident(ref.refFn()) // refFn must be called in the context of ImportInfo.sym
3334
tpd.Import(expr, selectors).symbol
3435

35-
ImportInfo(sym, selectors, untpd.EmptyTree, isRootImport = true)
36+
ImportInfo(sym, selectors, untpd.EmptyTree, NoSpan, isRootImport = true)
3637

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

5962
private def symNameOpt = qualifier match {

compiler/src/dotty/tools/dotc/typer/Namer.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import core._
66
import ast._
77
import Trees._, StdNames._, Scopes._, Denotations._, NamerOps._, ContextOps._
88
import Contexts._, Symbols._, Types._, SymDenotations._, Names._, NameOps._, Flags._
9-
import Decorators._, Comments.{_, given}
9+
import Decorators._
10+
import Comments.{_, given}
1011
import NameKinds.DefaultGetterName
1112
import ast.desugar, ast.desugar._
1213
import ProtoTypes._

compiler/src/dotty/tools/dotc/typer/TyperPhase.scala

Lines changed: 51 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package dotty.tools
22
package dotc
33
package typer
44

5-
import ast.untpd
65
import core._
76
import Phases._
87
import Contexts._
@@ -56,18 +55,55 @@ class TyperPhase(addRootImports: Boolean = true) extends Phase {
5655
JavaChecks.check(unit.tpdTree)
5756
}
5857

58+
/** Report unused imports.
59+
*
60+
* If `-Yrewrite-imports`, emit patches instead.
61+
* Patches are applied if `-rewrite` and no errors.
62+
*/
5963
def emitDiagnostics(using Context): Unit =
60-
ctx.usages.unused.foreach { (info, owner, selectors) =>
61-
import rewrites.Rewrites.patch
62-
import parsing.Parsers
63-
import util.SourceFile
64-
import ast.Trees.*
65-
def reportSelectors() = selectors.foreach(selector => report.warning(s"Unused import", pos = selector.srcPos))
66-
if ctx.settings.YrewriteImports.value then
64+
import ast.NavigateAST.untypedPath
65+
import ast.Trees.*
66+
import ast.untpd
67+
import parsing.Parsers
68+
import rewrites.Rewrites.patch
69+
import util.SourceFile
70+
def reportSelectors(sels: List[untpd.ImportSelector]) = sels.foreach(sel => report.warning(s"Unused import", pos = sel.srcPos))
71+
// format the selectors without braces, as replacement text
72+
def toText(sels: List[untpd.ImportSelector]): String =
73+
def selected(sel: untpd.ImportSelector) =
74+
if sel.isGiven then "given"
75+
else if sel.isWildcard then "*"
76+
else if sel.name == sel.rename then sel.name.show
77+
else s"${sel.name.show} as ${sel.rename.show}"
78+
sels.map(selected).mkString(", ")
79+
// begin
80+
val unused = ctx.usages.unused
81+
if ctx.settings.YrewriteImports.value then
82+
val byLocation = unused.groupBy((info, owner, selectors) => info.qualifier.sourcePos.withSpan(info.enclosingSpan))
83+
byLocation.foreach { (enclosingPos, grouped) =>
84+
val importText = enclosingPos.spanText
85+
val lineSource = SourceFile.virtual(name = "import-line.scala", content = importText)
86+
val PackageDef(_, pieces) = Parsers.Parser(lineSource).parse(): @unchecked
87+
println(s"pieces are $pieces")
88+
grouped match {
89+
case (info, owners, selectors) :: rest =>
90+
println(s"info enclosing ${info.enclosingSpan} has qual ${info.qualifier.sourcePos}")
91+
println(s"untyped path from qual\n${ untypedPath(info.qualifier.sourcePos.span).mkString("\n") }")
92+
//println(s"untyped path\n${ untypedPath(info.enclosingSpan).mkString("\n") }")
93+
case _ =>
94+
println(s"I got nothing")
95+
}
96+
}
97+
/*
98+
ctx.usages.unused.foreach { (info, owner, selectors) =>
99+
println(s"PATCH enclosing ${info.enclosingSpan} in ${info.qualifier.sourcePos.withSpan(info.enclosingSpan).spanText}")
67100
val src = ctx.compilationUnit.source
68101
val infoPos = info.qualifier.sourcePos
69-
val lineSource = SourceFile.virtual(name = "import-line.scala", content = infoPos.lineContent)
102+
//val importText = infoPos.lineContent
103+
val importText = infoPos.withSpan(info.enclosingSpan).spanText
104+
val lineSource = SourceFile.virtual(name = "import-line.scala", content = importText)
70105
val PackageDef(_, pieces) = Parsers.Parser(lineSource).parse(): @unchecked
106+
println(s"ENCLOSING has ${pieces.length} parts")
71107
// patch if there's just one import on the line, i.e., not import a.b, c.d
72108
if pieces.length == 1 then
73109
val retained = info.selectors.filterNot(selectors.contains)
@@ -85,19 +121,12 @@ class TyperPhase(addRootImports: Boolean = true) extends Phase {
85121
patch(src, widened, toText(retained)) // try to remove braces
86122
else
87123
patch(src, selectorSpan, toText(retained))
88-
else
89-
reportSelectors()
90-
else
91-
reportSelectors()
92-
}
93-
// just the selectors, no need to add braces
94-
private def toText(retained: List[untpd.ImportSelector])(using Context): String =
95-
def selected(sel: untpd.ImportSelector) =
96-
if sel.isGiven then "given"
97-
else if sel.isWildcard then "*"
98-
else if sel.name == sel.rename then sel.name.show
99-
else s"${sel.name.show} as ${sel.rename.show}"
100-
retained.map(selected).mkString(", ")
124+
}
125+
*/
126+
else
127+
ctx.usages.unused.foreach { (info, owner, selectors) => reportSelectors(selectors) }
128+
end emitDiagnostics
129+
101130
def clearDiagnostics()(using Context): Unit =
102131
ctx.usages.clear()
103132

compiler/src/dotty/tools/dotc/util/SourcePosition.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ extends SrcPos, interfaces.SourcePosition, Showable {
3333
def linesSlice: Array[Char] =
3434
source.content.slice(source.startOfLine(start), source.nextLine(end))
3535

36+
/** Extract exactly the span from the source file. */
37+
def spanSlice: Array[Char] = source.content.slice(start, end)
38+
39+
/** Extract exactly the span from the source file as a String. */
40+
def spanText: String = String(spanSlice)
41+
3642
/** The lines of the position */
3743
def lines: Range = {
3844
val startOffset = source.offsetToLine(start)

project/Build.scala

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,11 @@ object Build {
6060
val referenceVersion = "3.2.0-RC1"
6161

6262
val baseVersion = "3.2.1-RC1"
63+
//val referenceVersion = "3.1.3-RC2"
64+
//val referenceVersion = "3.2.0-RC1-bin-SNAPSHOT"
65+
66+
//val baseVersion = "3.2.0-RC1"
67+
//val baseVersion = "3.2.0-RC2"
6368

6469
// Versions used by the vscode extension to create a new project
6570
// This should be the latest published releases.
@@ -766,10 +771,11 @@ object Build {
766771
"-Ddotty.tests.classes.dottyTastyInspector=" + jars("scala3-tasty-inspector"),
767772
)
768773
},
769-
//scalacOptions ++= Seq(
770-
// "-Wunused:imports",
771-
// "-rewrite",
772-
//),
774+
scalacOptions ++= Seq(
775+
//"-Wunused:imports",
776+
//"-rewrite",
777+
//"-Yrewrite-imports",
778+
),
773779
packageAll := {
774780
(`scala3-compiler` / packageAll).value ++ Seq(
775781
"scala3-compiler" -> (Compile / packageBin).value.getAbsolutePath,

0 commit comments

Comments
 (0)