Skip to content

Commit 55f2e25

Browse files
committed
EnclosingSpan
1 parent 8880b3d commit 55f2e25

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
@@ -30,6 +30,8 @@ import printing._
3030
import config.{JavaPlatform, SJSPlatform, Platform, ScalaSettings, ScalaRelease}
3131
import classfile.ReusableDataReader
3232
import StdNames.nme
33+
import parsing.Parsers.EnclosingSpan
34+
import util.Spans.NoSpan
3335

3436
import scala.annotation.internal.sharable
3537

@@ -482,7 +484,7 @@ object Contexts:
482484

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

487489
/** Is the debug option set? */
488490
def debug: Boolean = base.settings.Ydebug.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._
@@ -57,18 +56,55 @@ class TyperPhase(addRootImports: Boolean = true) extends Phase {
5756
JavaChecks.check(unit.tpdTree)
5857
}
5958

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

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
@@ -83,6 +83,11 @@ object Build {
8383
val referenceVersion = "3.2.0-RC2"
8484

8585
val baseVersion = "3.2.1-RC1"
86+
//val referenceVersion = "3.1.3-RC2"
87+
//val referenceVersion = "3.2.0-RC1-bin-SNAPSHOT"
88+
89+
//val baseVersion = "3.2.0-RC1"
90+
//val baseVersion = "3.2.0-RC2"
8691

8792
// Versions used by the vscode extension to create a new project
8893
// This should be the latest published releases.
@@ -790,10 +795,11 @@ object Build {
790795
"-Ddotty.tests.classes.dottyTastyInspector=" + jars("scala3-tasty-inspector"),
791796
)
792797
},
793-
//scalacOptions ++= Seq(
794-
// "-Wunused:imports",
795-
// "-rewrite",
796-
//),
798+
scalacOptions ++= Seq(
799+
//"-Wunused:imports",
800+
//"-rewrite",
801+
//"-Yrewrite-imports",
802+
),
797803
packageAll := {
798804
(`scala3-compiler` / packageAll).value ++ Seq(
799805
"scala3-compiler" -> (Compile / packageBin).value.getAbsolutePath,

0 commit comments

Comments
 (0)