From 526f6c3934f31902fc3f5a0e690a472e86ecfc48 Mon Sep 17 00:00:00 2001 From: Grzegorz Kossakowski Date: Sat, 27 Jul 2013 01:38:45 -0700 Subject: Add example of SymbolTable unit test. Add a SymbolTableTest which contains all the code needed to initialize a SymbolTable in JUnit environment. It shows that initialization of definitions works and one can easily lookup some symbols and perform tests like subtyping tests. --- .../nsc/symtab/SymbolTableForUnitTesting.scala | 89 ++++++++++++++++++++++ .../scala/tools/nsc/symtab/SymbolTableTest.scala | 29 +++++++ 2 files changed, 118 insertions(+) create mode 100644 test/junit/scala/tools/nsc/symtab/SymbolTableForUnitTesting.scala create mode 100644 test/junit/scala/tools/nsc/symtab/SymbolTableTest.scala (limited to 'test/junit') diff --git a/test/junit/scala/tools/nsc/symtab/SymbolTableForUnitTesting.scala b/test/junit/scala/tools/nsc/symtab/SymbolTableForUnitTesting.scala new file mode 100644 index 0000000000..209d93adb2 --- /dev/null +++ b/test/junit/scala/tools/nsc/symtab/SymbolTableForUnitTesting.scala @@ -0,0 +1,89 @@ +package scala.tools.nsc +package symtab + +import scala.reflect.internal.{Phase, NoPhase, SomePhase} +import scala.tools.util.PathResolver +import util.ClassPath +import io.AbstractFile + +/** + * A complete SymbolTable implementation designed to be used in JUnit tests. + * + * It enables `usejavacp` setting so classpath of JUnit runner is being used + * for symbol table's classpath. + * + * This class contains enough of logic implemented to make it possible to + * initialize definitions and inspect symbols. + */ +class SymbolTableForUnitTesting extends SymbolTable { + // Members declared in scala.reflect.api.Trees + override def newStrictTreeCopier: TreeCopier = new StrictTreeCopier + override def newLazyTreeCopier: TreeCopier = new LazyTreeCopier + trait TreeCopier extends InternalTreeCopierOps + // these should be mocks + class StrictTreeCopier extends super.StrictTreeCopier with TreeCopier + class LazyTreeCopier extends super.LazyTreeCopier with TreeCopier + + override def isCompilerUniverse: Boolean = true + def classPath = new PathResolver(settings).result + + object platform extends backend.Platform { + val symbolTable: SymbolTableForUnitTesting.this.type = SymbolTableForUnitTesting.this + lazy val loaders: SymbolTableForUnitTesting.this.loaders.type = SymbolTableForUnitTesting.this.loaders + def platformPhases: List[SubComponent] = Nil + val classPath: ClassPath[AbstractFile] = new PathResolver(settings).result + def doLoad(cls: ClassPath[AbstractFile]#ClassRep): Boolean = true + def isMaybeBoxed(sym: Symbol): Boolean = ??? + def needCompile(bin: AbstractFile, src: AbstractFile): Boolean = ??? + def externalEquals: Symbol = ??? + def updateClassPath(subst: Map[ClassPath[AbstractFile], ClassPath[AbstractFile]]): Unit = ??? + } + + object loaders extends symtab.SymbolLoaders { + val symbolTable: SymbolTableForUnitTesting.this.type = SymbolTableForUnitTesting.this + lazy val platform: symbolTable.platform.type = symbolTable.platform + protected def lookupMemberAtTyperPhaseIfPossible(sym: Symbol, name: Name): Symbol = + sym.info.member(name) + protected override def compileLate(srcfile: AbstractFile): Unit = + sys.error(s"We do not expect compileLate to be called in SymbolTableTest. The srcfile passed in is $srcfile") + } + + class GlobalMirror extends Roots(NoSymbol) { + val universe: SymbolTableForUnitTesting.this.type = SymbolTableForUnitTesting.this + def rootLoader: LazyType = new loaders.PackageLoader(classPath) + override def toString = "compiler mirror" + } + + lazy val rootMirror: Mirror = { + val rm = new GlobalMirror + rm.init() + rm.asInstanceOf[Mirror] + } + + def settings: Settings = { + val s = new Settings + // initialize classpath using java classpath + s.usejavacp.value = true + s + } + + // Members declared in scala.reflect.internal.Required + def picklerPhase: scala.reflect.internal.Phase = SomePhase + + // Members declared in scala.reflect.internal.SymbolTable + def currentRunId: Int = 1 + def log(msg: => AnyRef): Unit = println(msg) + def mirrorThatLoaded(sym: Symbol): Mirror = rootMirror + val phases: Seq[Phase] = List(NoPhase, SomePhase) + val phaseWithId: Array[Phase] = { + val maxId = phases.map(_.id).max + val phasesArray = Array.ofDim[Phase](maxId+1) + phases foreach { phase => + phasesArray(phase.id) = phase + } + phasesArray + } + lazy val treeInfo: scala.reflect.internal.TreeInfo{val global: SymbolTableForUnitTesting.this.type} = ??? + + phase = SomePhase +} diff --git a/test/junit/scala/tools/nsc/symtab/SymbolTableTest.scala b/test/junit/scala/tools/nsc/symtab/SymbolTableTest.scala new file mode 100644 index 0000000000..166e0382f7 --- /dev/null +++ b/test/junit/scala/tools/nsc/symtab/SymbolTableTest.scala @@ -0,0 +1,29 @@ +package scala.tools.nsc +package symtab + +import org.junit.Assert._ +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.runners.JUnit4 + +@RunWith(classOf[JUnit4]) +class SymbolTableTest { + private def createSymbolTable: SymbolTable = new SymbolTableForUnitTesting + + @Test + def initDefinitions = { + val symbolTable = createSymbolTable + symbolTable.definitions.init() + } + + @Test + def basicSubTypeCheck = { + val symbolTable = createSymbolTable + symbolTable.definitions.init() + val listClassTpe = symbolTable.definitions.ListClass.tpe + val seqClassTpe = symbolTable.definitions.SeqClass.tpe + assertTrue("List should be subclass of Seq", listClassTpe <:< seqClassTpe) + } + +} -- cgit v1.2.3 From 3cb3c8e35c3df31a7f661903eca48cca557d5a49 Mon Sep 17 00:00:00 2001 From: Grzegorz Kossakowski Date: Tue, 30 Jul 2013 22:26:38 -0700 Subject: Address TODOs around SymbolLoaders and SymbolTable. SymbolTable refactoring introduced some TODOs that were supposed to be addressed after M5 release. The reason I couldn't address those problems right away was a conflict with our plans to modularize Scaladoc and interactive. However, we decided to delay that work until after M5 is released so addressing TODOs is not blocked anymore. This commit introduces the following changes: * Eclipse project definitions for interactive and scaladoc depend on scala-compiler project so they are builded against latest version of the compiler (quick) instead of STARR. This aligns our Eclipse project definitions with build.xml structure. * Introduce GlobalSymbolLoaders class which wires dependencies of SymbolLoaders with assumption of dependency on Global. * Switch to GlobalSymbolLoaders in BrowsingLoaders, interactive Global and ScaladocGlobal; this eliminates all TODO comments introduced before --- src/compiler/scala/tools/nsc/Global.scala | 17 ++---------- .../scala/tools/nsc/GlobalSymbolLoaders.scala | 30 ++++++++++++++++++++++ .../scala/tools/nsc/backend/icode/ICodes.scala | 11 ++------ .../scala/tools/nsc/symtab/BrowsingLoaders.scala | 18 +------------ .../scala/tools/nsc/symtab/SymbolLoaders.scala | 2 +- src/eclipse/interactive/.classpath | 4 +-- src/eclipse/scaladoc/.classpath | 6 +++-- .../scala/tools/nsc/interactive/Global.scala | 12 +++------ .../scala/tools/nsc/doc/ScaladocGlobal.scala | 22 ++-------------- .../nsc/symtab/SymbolTableForUnitTesting.scala | 2 +- 10 files changed, 49 insertions(+), 75 deletions(-) create mode 100644 src/compiler/scala/tools/nsc/GlobalSymbolLoaders.scala (limited to 'test/junit') diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala index ceab0625f8..296ba08eef 100644 --- a/src/compiler/scala/tools/nsc/Global.scala +++ b/src/compiler/scala/tools/nsc/Global.scala @@ -353,22 +353,9 @@ class Global(var currentSettings: Settings, var reporter: Reporter) } lazy val loaders = new { - val symbolTable: Global.this.type = Global.this + val global: Global.this.type = Global.this val platform: Global.this.platform.type = Global.this.platform - } with SymbolLoaders { - protected override def lookupMemberAtTyperPhaseIfPossible(sym: Symbol, name: Name): Symbol = { - def lookup = sym.info.member(name) - // if loading during initialization of `definitions` typerPhase is not yet set. - // in that case we simply load the member at the current phase - if (currentRun.typerPhase eq null) - lookup - else - enteringTyper { lookup } - } - protected override def compileLate(srcfile: AbstractFile): Unit = - currentRun.compileLate(srcfile) - - } + } with GlobalSymbolLoaders /** Returns the mirror that loaded given symbol */ def mirrorThatLoaded(sym: Symbol): Mirror = rootMirror diff --git a/src/compiler/scala/tools/nsc/GlobalSymbolLoaders.scala b/src/compiler/scala/tools/nsc/GlobalSymbolLoaders.scala new file mode 100644 index 0000000000..6921548230 --- /dev/null +++ b/src/compiler/scala/tools/nsc/GlobalSymbolLoaders.scala @@ -0,0 +1,30 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2013 LAMP/EPFL + * @author Martin Odersky + */ + +package scala +package tools +package nsc + +/** + * Symbol loaders implementation that wires dependencies using Global. + */ +abstract class GlobalSymbolLoaders extends symtab.SymbolLoaders { + val global: Global + val symbolTable: global.type = global + val platform: symbolTable.platform.type + import global._ + def lookupMemberAtTyperPhaseIfPossible(sym: Symbol, name: Name): Symbol = { + def lookup = sym.info.member(name) + // if loading during initialization of `definitions` typerPhase is not yet set. + // in that case we simply load the member at the current phase + if (currentRun.typerPhase eq null) + lookup + else + enteringTyper { lookup } + } + + protected def compileLate(srcfile: io.AbstractFile): Unit = + currentRun.compileLate(srcfile) +} diff --git a/src/compiler/scala/tools/nsc/backend/icode/ICodes.scala b/src/compiler/scala/tools/nsc/backend/icode/ICodes.scala index fca18eb09e..b9eb8f8aac 100644 --- a/src/compiler/scala/tools/nsc/backend/icode/ICodes.scala +++ b/src/compiler/scala/tools/nsc/backend/icode/ICodes.scala @@ -110,15 +110,8 @@ abstract class ICodes extends AnyRef object icodeReader extends ICodeReader { lazy val global: ICodes.this.global.type = ICodes.this.global import global._ - def lookupMemberAtTyperPhaseIfPossible(sym: Symbol, name: Name): Symbol = { - def lookup = sym.info.member(name) - // if loading during initialization of `definitions` typerPhase is not yet set. - // in that case we simply load the member at the current phase - if (currentRun.typerPhase eq null) - lookup - else - enteringTyper { lookup } - } + def lookupMemberAtTyperPhaseIfPossible(sym: Symbol, name: Name): Symbol = + global.loaders.lookupMemberAtTyperPhaseIfPossible(sym, name) lazy val symbolTable: global.type = global lazy val loaders: global.loaders.type = global.loaders def classPath: util.ClassPath[AbstractFile] = ICodes.this.global.platform.classPath diff --git a/src/compiler/scala/tools/nsc/symtab/BrowsingLoaders.scala b/src/compiler/scala/tools/nsc/symtab/BrowsingLoaders.scala index 0d756fd309..4b9e056df3 100644 --- a/src/compiler/scala/tools/nsc/symtab/BrowsingLoaders.scala +++ b/src/compiler/scala/tools/nsc/symtab/BrowsingLoaders.scala @@ -12,28 +12,12 @@ import scala.tools.nsc.io.AbstractFile * This class should be used whenever file dependencies and recompile sets * are managed automatically. */ -abstract class BrowsingLoaders extends SymbolLoaders { +abstract class BrowsingLoaders extends GlobalSymbolLoaders { val global: Global - val symbolTable: global.type - protected def compileLate(srcfile: AbstractFile) = global.currentRun.compileLate(srcfile) import global._ import syntaxAnalyzer.{OutlineParser, MalformedInput} - /* - * BrowsingLoaders has dependency on Global so we can implement this method here instead of forcing subclasses - * of BrowsingLoaders (e.g. in interactive) to implement it. - */ - override def lookupMemberAtTyperPhaseIfPossible(sym: Symbol, name: Name): Symbol = { - def lookup = sym.info.member(name) - // if loading during initialization of `definitions` typerPhase is not yet set. - // in that case we simply load the member at the current phase - if (currentRun.typerPhase eq null) - lookup - else - enteringTyper { lookup } - } - /** In browse mode, it can happen that an encountered symbol is already * present. For instance, if the source file has a name different from * the classes and objects it contains, the symbol loader will always diff --git a/src/compiler/scala/tools/nsc/symtab/SymbolLoaders.scala b/src/compiler/scala/tools/nsc/symtab/SymbolLoaders.scala index d22352154b..6f27eb8660 100644 --- a/src/compiler/scala/tools/nsc/symtab/SymbolLoaders.scala +++ b/src/compiler/scala/tools/nsc/symtab/SymbolLoaders.scala @@ -30,7 +30,7 @@ abstract class SymbolLoaders { /** * Required by ClassfileParser. Check documentation in that class for details. */ - protected def lookupMemberAtTyperPhaseIfPossible(sym: Symbol, name: Name): Symbol + def lookupMemberAtTyperPhaseIfPossible(sym: Symbol, name: Name): Symbol /** * Should forward to `Run.compileLate`. The more principled fix would be to * determine why this functionality is needed and extract it into a separate diff --git a/src/eclipse/interactive/.classpath b/src/eclipse/interactive/.classpath index 73a67e45ed..9e773a39d2 100644 --- a/src/eclipse/interactive/.classpath +++ b/src/eclipse/interactive/.classpath @@ -3,7 +3,7 @@ - - + + diff --git a/src/eclipse/scaladoc/.classpath b/src/eclipse/scaladoc/.classpath index caafcf33b0..8e03c97657 100644 --- a/src/eclipse/scaladoc/.classpath +++ b/src/eclipse/scaladoc/.classpath @@ -3,8 +3,10 @@ - - + + + + diff --git a/src/interactive/scala/tools/nsc/interactive/Global.scala b/src/interactive/scala/tools/nsc/interactive/Global.scala index cfe6d57eb8..492f0f4fb4 100644 --- a/src/interactive/scala/tools/nsc/interactive/Global.scala +++ b/src/interactive/scala/tools/nsc/interactive/Global.scala @@ -365,20 +365,16 @@ class Global(settings: Settings, _reporter: Reporter, projectName: String = "") */ override def registerTopLevelSym(sym: Symbol) { currentTopLevelSyms += sym } + protected type SymbolLoadersInInteractive = GlobalSymbolLoaders { + val global: Global.this.type + val platform: Global.this.platform.type + } /** Symbol loaders in the IDE parse all source files loaded from a package for * top-level idents. Therefore, we can detect top-level symbols that have a name * different from their source file */ - protected type SymbolLoadersInInteractive = SymbolLoaders { - // `global` val is needed so we conform to loaders type in Global in Scala 2.11.0-M4 - // TODO: remove once 2.11.0-M5 is used to build interactive - val global: Global.this.type - val symbolTable: Global.this.type - val platform: Global.this.platform.type - } override lazy val loaders: SymbolLoadersInInteractive = new { val global: Global.this.type = Global.this - val symbolTable: Global.this.type = Global.this val platform: Global.this.platform.type = Global.this.platform } with BrowsingLoaders diff --git a/src/scaladoc/scala/tools/nsc/doc/ScaladocGlobal.scala b/src/scaladoc/scala/tools/nsc/doc/ScaladocGlobal.scala index 94d3809fc3..723f8b1dc8 100644 --- a/src/scaladoc/scala/tools/nsc/doc/ScaladocGlobal.scala +++ b/src/scaladoc/scala/tools/nsc/doc/ScaladocGlobal.scala @@ -25,33 +25,15 @@ trait ScaladocGlobalTrait extends Global { } override lazy val loaders = new { - val symbolTable: outer.type = outer - val platform: outer.platform.type = outer.platform - // `global` val is needed so we conform to loaders type in Global in Scala 2.11.0-M4 - // TODO: remove once 2.11.0-M5 is used to build Scaladoc val global: outer.type = outer - } with SymbolLoaders { + val platform: outer.platform.type = outer.platform + } with GlobalSymbolLoaders { // SI-5593 Scaladoc's current strategy is to visit all packages in search of user code that can be documented // therefore, it will rummage through the classpath triggering errors whenever it encounters package objects // that are not in their correct place (see bug for details) override protected def signalError(root: Symbol, ex: Throwable) { log(s"Suppressing error involving $root: $ex") } - - // TODO: Add `override` modifier once Scala 2.11.0-M5 is used to build Scaladoc - protected /*override*/ def compileLate(srcfile: io.AbstractFile): Unit = - currentRun.compileLate(srcfile) - - // TODO: Add `override` modifier once Scala 2.11.0-M5 is used to build Scaladoc - protected def /*override*/ lookupMemberAtTyperPhaseIfPossible(sym: Symbol, name: Name): Symbol = { - def lookup = sym.info.member(name) - // if loading during initialization of `definitions` typerPhase is not yet set. - // in that case we simply load the member at the current phase - if (currentRun.typerPhase eq null) - lookup - else - enteringTyper { lookup } - } } } diff --git a/test/junit/scala/tools/nsc/symtab/SymbolTableForUnitTesting.scala b/test/junit/scala/tools/nsc/symtab/SymbolTableForUnitTesting.scala index 209d93adb2..285e87e3b2 100644 --- a/test/junit/scala/tools/nsc/symtab/SymbolTableForUnitTesting.scala +++ b/test/junit/scala/tools/nsc/symtab/SymbolTableForUnitTesting.scala @@ -42,7 +42,7 @@ class SymbolTableForUnitTesting extends SymbolTable { object loaders extends symtab.SymbolLoaders { val symbolTable: SymbolTableForUnitTesting.this.type = SymbolTableForUnitTesting.this lazy val platform: symbolTable.platform.type = symbolTable.platform - protected def lookupMemberAtTyperPhaseIfPossible(sym: Symbol, name: Name): Symbol = + def lookupMemberAtTyperPhaseIfPossible(sym: Symbol, name: Name): Symbol = sym.info.member(name) protected override def compileLate(srcfile: AbstractFile): Unit = sys.error(s"We do not expect compileLate to be called in SymbolTableTest. The srcfile passed in is $srcfile") -- cgit v1.2.3 From d5e0f728c322a3beec5c62a9f4e28b3a3f86e84d Mon Sep 17 00:00:00 2001 From: Grzegorz Kossakowski Date: Fri, 2 Aug 2013 11:22:04 -0700 Subject: Add sample to SymbolTableTest using custom symbols and type. Add a test which demonstrates how one can create symbols and types from scratch and perform sub type check using them. --- .../scala/tools/nsc/symtab/SymbolTableTest.scala | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) (limited to 'test/junit') diff --git a/test/junit/scala/tools/nsc/symtab/SymbolTableTest.scala b/test/junit/scala/tools/nsc/symtab/SymbolTableTest.scala index 166e0382f7..537cb93ef3 100644 --- a/test/junit/scala/tools/nsc/symtab/SymbolTableTest.scala +++ b/test/junit/scala/tools/nsc/symtab/SymbolTableTest.scala @@ -26,4 +26,25 @@ class SymbolTableTest { assertTrue("List should be subclass of Seq", listClassTpe <:< seqClassTpe) } + /** + * Demonstrates how one can create symbols and type completely + * from scratch and perform sub type check. + */ + @Test + def customClassesSubTypeCheck: Unit = { + val symbolTable = createSymbolTable + import symbolTable._ + symbolTable.definitions.init() + val rootClass = symbolTable.rootMirror.RootClass + val fooSymbol = rootClass.newClassSymbol("Foo": TypeName, NoPosition, 0) + val fooType = new ClassInfoType(Nil, EmptyScope, fooSymbol) + fooSymbol.info = fooType + val barSymbol = rootClass.newClassSymbol("Bar": TypeName, NoPosition, 0) + val fooTypeRef = TypeRef(fooSymbol.owner.tpe, fooSymbol, Nil) + val barType = new ClassInfoType(List(fooTypeRef), EmptyScope, barSymbol) + barSymbol.info = barType + assertTrue("Bar should be subclass of Foo", barSymbol.tpe <:< fooSymbol.tpe) + assertFalse("Foo should be a superclass of Foo", fooSymbol.tpe <:< barSymbol.tpe) + } + } -- cgit v1.2.3