Skip to content

Commit bf92598

Browse files
committed
Scan for classes under -scansource
This lets us find a class for completion even if its name is not the filename of the containing class.
1 parent e0c89cd commit bf92598

File tree

8 files changed

+120
-31
lines changed

8 files changed

+120
-31
lines changed

compiler/src/dotty/tools/dotc/Run.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint
8888
myFiles
8989
}
9090

91-
private def getSource(fileName: String): SourceFile = {
91+
def getSource(fileName: String): SourceFile = {
9292
val f = new PlainFile(io.Path(fileName))
9393
if (f.isDirectory) {
9494
ctx.error(s"expected file, received directory '$fileName'")
@@ -170,7 +170,7 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint
170170
runPhases(runCtx)
171171
if (!ctx.reporter.hasErrors) Rewrites.writeBack()
172172
}
173-
173+
174174
/** Enter top-level definitions of classes and objects contain in Scala source file `file`.
175175
* The newly added symbols replace any previously entered symbols.
176176
*/

compiler/src/dotty/tools/dotc/config/ScalaSettings.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ class ScalaSettings extends Settings.SettingGroup {
1616
val extdirs = PathSetting("-extdirs", "Override location of installed extensions.", Defaults.scalaExtDirs)
1717
val javabootclasspath = PathSetting("-javabootclasspath", "Override java boot classpath.", Defaults.javaBootClassPath)
1818
val javaextdirs = PathSetting("-javaextdirs", "Override java extdirs classpath.", Defaults.javaExtDirs)
19-
val sourcepath = PathSetting("-sourcepath", "Specify location(s) of source files.", "") // Defaults.scalaSourcePath
19+
val sourcepath = PathSetting("-sourcepath", "Specify location(s) of source files.", Defaults.scalaSourcePath)
20+
val scansource = BooleanSetting("-scansource", "Scan source files to locate classes for which class-name != file-name")
21+
2022
val classpath = PathSetting("-classpath", "Specify where to find user class files.", defaultClasspath) withAbbreviation "-cp"
2123
val outputDir = PathSetting("-d", "directory|jar", "destination for generated classfiles.", ".")
2224
val priorityclasspath = PathSetting("-priorityclasspath", "class path that takes precedence over all other paths (or testing only)", "")
@@ -131,6 +133,7 @@ class ScalaSettings extends Settings.SettingGroup {
131133
sys.props("user.dir")
132134
)
133135

136+
134137
val projectName = StringSetting (
135138
"-project",
136139
"project title",

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,7 @@ object SymDenotations {
275275
final def privateWithin(implicit ctx: Context): Symbol = { ensureCompleted(); myPrivateWithin }
276276

277277
/** Set privateWithin. */
278-
protected[core] final def privateWithin_=(sym: Symbol): Unit =
278+
protected[dotc] final def privateWithin_=(sym: Symbol): Unit =
279279
myPrivateWithin = sym
280280

281281
/** The annotations of this denotation */

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

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

5-
import java.io.IOException
5+
import java.io.{IOException, File}
66
import scala.compat.Platform.currentTime
77
import dotty.tools.io.{ ClassPath, ClassRepresentation, AbstractFile }
88
import classpath._
@@ -11,8 +11,10 @@ import StdNames._, NameOps._
1111
import Decorators.{PreNamedString, StringInterpolators}
1212
import classfile.ClassfileParser
1313
import util.Stats
14+
import Decorators._
1415
import scala.util.control.NonFatal
1516
import ast.Trees._
17+
import parsing.Parsers.OutlineParser
1618
import reporting.trace
1719

1820
object SymbolLoaders {
@@ -24,11 +26,15 @@ object SymbolLoaders {
2426

2527
/** A base class for Symbol loaders with some overridable behavior */
2628
class SymbolLoaders {
29+
import ast.untpd._
2730

2831
protected def enterNew(
2932
owner: Symbol, member: Symbol,
3033
completer: SymbolLoader, scope: Scope = EmptyScope)(implicit ctx: Context): Symbol = {
31-
assert(scope.lookup(member.name) == NoSymbol, s"${owner.fullName}.${member.name} already has a symbol")
34+
val comesFromScan =
35+
completer.isInstanceOf[SourcefileLoader] && ctx.settings.scansource.value
36+
assert(comesFromScan || scope.lookup(member.name) == NoSymbol,
37+
s"${owner.fullName}.${member.name} already has a symbol")
3238
owner.asClass.enter(member, scope)
3339
member
3440
}
@@ -98,16 +104,65 @@ class SymbolLoaders {
98104
scope = scope)
99105
}
100106

101-
/** In batch mode: Enter class and module with given `name` into scope of `owner`
102-
* and give them a source completer for given `src` as type.
103-
* In IDE mode: Find all toplevel definitions in `src` and enter then into scope of `owner`
104-
* with source completer for given `src` as type.
105-
* (overridden in interactive.Global).
107+
/** If setting -scansource is set:
108+
* Enter all toplevel classes and objects in file `src` into package `owner`, provided
109+
* they are in the right package. Issue a warning if a class or object is in the wrong
110+
* package, i.e. if the file path differs from the declared package clause.
111+
* If -scansource is not set:
112+
* Enter class and module with given `name` into scope of `owner`.
113+
*
114+
* All entered symbols are given a source completer of `src` as info.
106115
*/
107116
def enterToplevelsFromSource(
108117
owner: Symbol, name: PreName, src: AbstractFile,
109118
scope: Scope = EmptyScope)(implicit ctx: Context): Unit = {
110-
enterClassAndModule(owner, name, new SourcefileLoader(src), scope = scope)
119+
120+
val completer = new SourcefileLoader(src)
121+
if (ctx.settings.scansource.value) {
122+
if (src.exists && !src.isDirectory) {
123+
val filePath = owner.ownersIterator.takeWhile(!_.isRoot).map(_.name.toTermName).toList
124+
125+
def addPrefix(pid: RefTree, path: List[TermName]): List[TermName] = pid match {
126+
case Ident(name: TermName) => name :: path
127+
case Select(qual: RefTree, name: TermName) => name :: addPrefix(qual, path)
128+
case _ => path
129+
}
130+
131+
def enterScanned(unit: CompilationUnit)(implicit ctx: Context) = {
132+
133+
def checkPathMatches(path: List[TermName], what: String, tree: MemberDef): Boolean = {
134+
val ok = filePath == path
135+
if (!ok)
136+
ctx.warning(i"""$what ${tree.name} is in wrong directory.
137+
|It was declared to be in package ${path.reverse.mkString(".")}
138+
|But it is found in directory ${filePath.reverse.mkString(File.separator)}""",
139+
tree.pos)
140+
ok
141+
}
142+
143+
def traverse(tree: Tree, path: List[TermName]): Unit = tree match {
144+
case PackageDef(pid, body) =>
145+
val path1 = addPrefix(pid, path)
146+
for (stat <- body) traverse(stat, path1)
147+
case tree: TypeDef if tree.isClassDef =>
148+
if (checkPathMatches(path, "class", tree))
149+
enterClassAndModule(owner, tree.name, completer, scope = scope)
150+
// It might be a case class or implicit class,
151+
// so enter class and module to be on the safe side
152+
case tree: ModuleDef =>
153+
if (checkPathMatches(path, "object", tree))
154+
enterModule(owner, tree.name, completer, scope = scope)
155+
case _ =>
156+
}
157+
158+
traverse(new OutlineParser(unit.source).parse(), Nil)
159+
}
160+
161+
val unit = new CompilationUnit(ctx.run.getSource(src.path))
162+
enterScanned(unit)(ctx.run.runContext.fresh.setCompilationUnit(unit))
163+
}
164+
}
165+
else enterClassAndModule(owner, name, completer, scope = scope)
111166
}
112167

113168
/** The package objects of scala and scala.reflect should always

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

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -284,14 +284,32 @@ class Namer { typer: Typer =>
284284
else name
285285
}
286286

287+
/** Create new symbol or redefine existing symbol under lateCompile. */
288+
def createOrRefine[S <: Symbol](
289+
tree: MemberDef, name: Name, flags: FlagSet, infoFn: S => Type,
290+
symFn: (FlagSet, S => Type, Symbol) => S): Symbol = {
291+
val prev =
292+
if (lateCompile && ctx.owner.is(Package)) ctx.effectiveScope.lookup(name)
293+
else NoSymbol
294+
val sym =
295+
if (prev.exists) {
296+
prev.flags = flags
297+
prev.info = infoFn(prev.asInstanceOf[S])
298+
prev.privateWithin = privateWithinClass(tree.mods)
299+
prev
300+
}
301+
else symFn(flags, infoFn, privateWithinClass(tree.mods))
302+
recordSym(sym, tree)
303+
}
304+
287305
tree match {
288306
case tree: TypeDef if tree.isClassDef =>
289307
val name = checkNoConflict(tree.name).asTypeName
290308
val flags = checkFlags(tree.mods.flags &~ Implicit)
291-
val cls = recordSym(ctx.newClassSymbol(
292-
ctx.owner, name, flags,
293-
cls => adjustIfModule(new ClassCompleter(cls, tree)(ctx), tree),
294-
privateWithinClass(tree.mods), tree.namePos, ctx.source.file), tree)
309+
val cls =
310+
createOrRefine[ClassSymbol](tree, name, flags,
311+
cls => adjustIfModule(new ClassCompleter(cls, tree)(ctx), tree),
312+
ctx.newClassSymbol(ctx.owner, name, _, _, _, tree.namePos, ctx.source.file))
295313
cls.completer.asInstanceOf[ClassCompleter].init()
296314
cls
297315
case tree: MemberDef =>
@@ -320,11 +338,10 @@ class Namer { typer: Typer =>
320338
case tree: TypeDef => new TypeDefCompleter(tree)(cctx)
321339
case _ => new Completer(tree)(cctx)
322340
}
323-
324-
recordSym(ctx.newSymbol(
325-
ctx.owner, name, flags | deferred | method | higherKinded,
326-
adjustIfModule(completer, tree),
327-
privateWithinClass(tree.mods), tree.namePos), tree)
341+
val info = adjustIfModule(completer, tree)
342+
createOrRefine[Symbol](tree, name, flags | deferred | method | higherKinded,
343+
_ => info,
344+
(fs, _, pwithin) => ctx.newSymbol(ctx.owner, name, fs, info, pwithin, tree.namePos))
328345
case tree: Import =>
329346
recordSym(ctx.newImportSymbol(ctx.owner, new Completer(tree), tree.pos), tree)
330347
case _ =>
@@ -337,15 +354,6 @@ class Namer { typer: Typer =>
337354
*/
338355
def enterSymbol(sym: Symbol)(implicit ctx: Context) = {
339356
if (sym.exists) {
340-
if (lateCompile && sym.owner.is(Package)) {
341-
val preExisting = ctx.effectiveScope.lookup(sym.name)
342-
if (preExisting.exists) {
343-
typr.println(i"overwriting $preExisting to late loaded $sym")
344-
val old = preExisting.denot
345-
old.replaceWith(sym.denot)
346-
old.info = sym.info
347-
}
348-
}
349357
typr.println(s"entered: $sym in ${ctx.owner}")
350358
ctx.enter(sym)
351359
}
@@ -745,7 +753,7 @@ class Namer { typer: Typer =>
745753
}
746754

747755
/** The completer of a symbol defined by a member def or import (except ClassSymbols) */
748-
class Completer(val original: Tree)(implicit ctx: Context) extends LazyType {
756+
class Completer(val original: Tree)(implicit ctx: Context) extends LazyType with SymbolLoaders.SecondCompleter {
749757

750758
protected def localContext(owner: Symbol) = ctx.fresh.setOwner(owner).setTree(original)
751759

compiler/test/dotty/tools/dotc/CompilationTests.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ class CompilationTests extends ParallelTesting {
6060
compileFile("../tests/pos-special/i3589-b.scala", defaultOptions.and("-Xfatal-warnings")) +
6161
compileFile("../tests/pos-special/completeFromSource/Test.scala", defaultOptions.and("-sourcepath", "../tests/pos-special")) +
6262
compileFile("../tests/pos-special/completeFromSource/Test2.scala", defaultOptions.and("-sourcepath", "../tests/pos-special")) +
63+
compileFile("../tests/pos-special/completeFromSource/Test3.scala", defaultOptions.and("-sourcepath", "../tests/pos-special", "-scansource")) +
6364
compileList(
6465
"compileMixed",
6566
List(
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package completeFromSource
2+
import nested._
3+
4+
class Test3 {
5+
6+
val x = if (true) new B(1) else new C("xx")
7+
8+
x match {
9+
case B(n) => println(s"B($n)")
10+
case C(s) => println(s"C($s)")
11+
}
12+
}
13+
14+
15+
16+
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package completeFromSource.nested
2+
3+
4+
case class B(x: Int) extends A(x)
5+
6+
case class C(s: String) extends A(s.length)

0 commit comments

Comments
 (0)