diff --git a/.gitmodules b/.gitmodules index 1bfc7eaadb3b..0dcadcbfb20f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -37,3 +37,6 @@ [submodule "community-build/community-projects/sourcecode"] path = community-build/community-projects/sourcecode url = https://github.com/dotty-staging/sourcecode +[submodule "community-build/community-projects/scala-xml"] + path = community-build/community-projects/scala-xml + url = https://github.com/scala/scala-xml diff --git a/community-build/community-projects/scala-xml b/community-build/community-projects/scala-xml new file mode 160000 index 000000000000..19f53ad12d73 --- /dev/null +++ b/community-build/community-projects/scala-xml @@ -0,0 +1 @@ +Subproject commit 19f53ad12d7311ba85d8a239e40efed47f4786ab diff --git a/community-build/community-projects/scalatest b/community-build/community-projects/scalatest index 2d3247174e48..421727b30f92 160000 --- a/community-build/community-projects/scalatest +++ b/community-build/community-projects/scalatest @@ -1 +1 @@ -Subproject commit 2d3247174e480bd39c349609a3a2e1233b2abba0 +Subproject commit 421727b30f92853808f0aa7b069af71cd7e84303 diff --git a/community-build/test/scala/dotty/communitybuild/CommunityBuildTest.scala b/community-build/test/scala/dotty/communitybuild/CommunityBuildTest.scala index 7479e68e4f18..6f956bb0d8d3 100644 --- a/community-build/test/scala/dotty/communitybuild/CommunityBuildTest.scala +++ b/community-build/test/scala/dotty/communitybuild/CommunityBuildTest.scala @@ -109,6 +109,12 @@ class CommunityBuildTest { updateCommand = "scalatest/update" ) + @Test def scalaXml = test( + project = "scala-xml", + testCommand = "xml/test", + updateCommand = "xml/update" + ) + @Test def scopt = test( project = "scopt", testCommand = "scoptJVM/compile", diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index ad840ac6c372..be24d065f636 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -223,6 +223,8 @@ class Definitions { lazy val Sys_errorR: TermRef = SysPackage.moduleClass.requiredMethodRef(nme.error) def Sys_error(implicit ctx: Context): Symbol = Sys_errorR.symbol + lazy val ScalaXmlPackageClass: Symbol = ctx.getPackageClassIfDefined("scala.xml") + lazy val CompiletimePackageObjectRef: TermRef = ctx.requiredModuleRef("scala.compiletime.package") lazy val CompiletimePackageObject: Symbol = CompiletimePackageObjectRef.symbol.moduleClass lazy val Compiletime_errorR: TermRef = CompiletimePackageObjectRef.symbol.requiredMethodRef(nme.error) diff --git a/compiler/src/dotty/tools/dotc/core/Symbols.scala b/compiler/src/dotty/tools/dotc/core/Symbols.scala index b3d41754dc87..0b43e9763722 100644 --- a/compiler/src/dotty/tools/dotc/core/Symbols.scala +++ b/compiler/src/dotty/tools/dotc/core/Symbols.scala @@ -383,6 +383,15 @@ trait Symbols { this: Context => .requiredSymbol("class", name, generateStubs = false)(_.isClass) } + /** Get ClassSymbol if package is either defined in current compilation run + * or present on classpath. + * Returns NoSymbol otherwise. */ + def getPackageClassIfDefined(path: PreName): Symbol = { + val name = path.toTypeName + base.staticRef(name, isPackage = true, generateStubs = false) + .requiredSymbol("package", name, generateStubs = false)(_ is PackageClass) + } + def requiredModule(path: PreName): TermSymbol = { val name = path.toTermName base.staticRef(name).requiredSymbol("object", name)(_ is Module).asTerm diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 777421b0b617..524a09f487c8 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -5,7 +5,7 @@ package parsing import scala.annotation.internal.sharable import scala.collection.mutable.ListBuffer import scala.collection.immutable.BitSet -import util.{ SourceFile, SourcePosition } +import util.{ SourceFile, SourcePosition, NoSourcePosition } import Tokens._ import Scanners._ import xml.MarkupParsers.MarkupParser @@ -473,8 +473,21 @@ object Parsers { /* -------------- XML ---------------------------------------------------- */ - /** the markup parser */ - lazy val xmlp: xml.MarkupParsers.MarkupParser = new MarkupParser(this, true) + /** The markup parser. + * The first time this lazy val is accessed, we assume we were trying to parse an XML literal. + * The current position is recorded for later error reporting if it turns out + * that we don't have scala-xml on the compilation classpath. + */ + lazy val xmlp: xml.MarkupParsers.MarkupParser = { + myFirstXmlPos = source.atSpan(Span(in.offset)) + new MarkupParser(this, true) + } + + /** The position of the first XML literal encountered while parsing, + * NoSourcePosition if there were no XML literals. + */ + def firstXmlPos: SourcePosition = myFirstXmlPos + private[this] var myFirstXmlPos: SourcePosition = NoSourcePosition object symbXMLBuilder extends xml.SymbolicXMLBuilder(this, true) // DEBUG choices diff --git a/compiler/src/dotty/tools/dotc/typer/FrontEnd.scala b/compiler/src/dotty/tools/dotc/typer/FrontEnd.scala index 119336950e0d..fa2334633384 100644 --- a/compiler/src/dotty/tools/dotc/typer/FrontEnd.scala +++ b/compiler/src/dotty/tools/dotc/typer/FrontEnd.scala @@ -11,6 +11,7 @@ import parsing.Parsers.Parser import config.Config import config.Printers.{typr, default} import util.Stats._ +import util.{ SourcePosition, NoSourcePosition } import scala.util.control.NonFatal import ast.Trees._ @@ -25,6 +26,11 @@ class FrontEnd extends Phase { /** The contexts for compilation units that are parsed but not yet entered */ private[this] var remaining: List[Context] = Nil + /** The position of the first XML literal encountered while parsing, + * NoSourcePosition if there were no XML literals. + */ + private[this] var firstXmlPos: SourcePosition = NoSourcePosition + /** Does a source file ending with `.scala` belong to a compilation unit * that is parsed but not yet entered? */ @@ -41,9 +47,17 @@ class FrontEnd extends Phase { def parse(implicit ctx: Context): Unit = monitor("parsing") { val unit = ctx.compilationUnit + unit.untpdTree = if (unit.isJava) new JavaParser(unit.source).parse() - else new Parser(unit.source).parse() + else { + val p = new Parser(unit.source) + val tree = p.parse() + if (p.firstXmlPos.exists && !firstXmlPos.exists) + firstXmlPos = p.firstXmlPos + tree + } + val printer = if (ctx.settings.Xprint.value.contains("parser")) default else typr printer.println("parsed:\n" + unit.untpdTree.show) if (Config.checkPositions) @@ -86,6 +100,12 @@ class FrontEnd extends Phase { enterSyms(remaining.head) remaining = remaining.tail } + + if (firstXmlPos.exists && !defn.ScalaXmlPackageClass.exists) + ctx.error("""To support XML literals, your project must depend on scala-xml. + |See https://github.com/scala/scala-xml for more information.""".stripMargin, + firstXmlPos) + unitContexts.foreach(typeCheck(_)) record("total trees after typer", ast.Trees.ntrees) unitContexts.map(_.compilationUnit).filterNot(discardAfterTyper) diff --git a/compiler/test/dotty/Properties.scala b/compiler/test/dotty/Properties.scala index f7cde1f95634..55a5f466fb1d 100644 --- a/compiler/test/dotty/Properties.scala +++ b/compiler/test/dotty/Properties.scala @@ -59,9 +59,6 @@ object Properties { /** scala-asm jar */ def scalaAsm: String = sys.props("dotty.tests.classes.scalaAsm") - /** scala-xml jar */ - def scalaXml: String = sys.props("dotty.tests.classes.scalaXml") - /** jline-terminal jar */ def jlineTerminal: String = sys.props("dotty.tests.classes.jlineTerminal") diff --git a/compiler/test/dotty/tools/vulpix/TestConfiguration.scala b/compiler/test/dotty/tools/vulpix/TestConfiguration.scala index facd9f7ddc75..453ddc5bb895 100644 --- a/compiler/test/dotty/tools/vulpix/TestConfiguration.scala +++ b/compiler/test/dotty/tools/vulpix/TestConfiguration.scala @@ -12,7 +12,7 @@ object TestConfiguration { ) val checkOptions = Array( - // "-Yscala2-unpickler", s"${Properties.scalaLibrary}:${Properties.scalaXml}", + // "-Yscala2-unpickler", s"${Properties.scalaLibrary}", "-Yno-deep-subtypes", "-Yno-double-bindings", "-Yforce-sbt-phases", @@ -21,13 +21,11 @@ object TestConfiguration { val basicClasspath = mkClasspath(List( Properties.scalaLibrary, - Properties.scalaXml, Properties.dottyLibrary )) val withCompilerClasspath = mkClasspath(List( Properties.scalaLibrary, - Properties.scalaXml, Properties.scalaAsm, Properties.jlineTerminal, Properties.jlineReader, diff --git a/project/Build.scala b/project/Build.scala index 1e3f4ec7fc5a..4121c822186e 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -62,14 +62,14 @@ object Build { val referenceVersion = "0.15.0-RC1" val baseVersion = "0.16.0" - val baseSbtDottyVersion = "0.3.3" + val baseSbtDottyVersion = "0.3.4" // Versions used by the vscode extension to create a new project // This should be the latest published releases. // TODO: Have the vscode extension fetch these numbers from the Internet // instead of hardcoding them ? val publishedDottyVersion = referenceVersion - val publishedSbtDottyVersion = "0.3.2" + val publishedSbtDottyVersion = "0.3.3" val dottyOrganization = "ch.epfl.lamp" @@ -294,7 +294,9 @@ object Build { dottyCompiler, allJars ) - } + }, + // sbt-dotty defines `scalaInstance in doc` so we need to override it manually + scalaInstance in doc := scalaInstance.value, ) lazy val commonBenchmarkSettings = Seq( @@ -450,8 +452,6 @@ object Build { // get libraries onboard libraryDependencies ++= Seq( "org.scala-lang.modules" % "scala-asm" % "6.0.0-scala-1", // used by the backend - // FIXME: Not needed, but should be on the compiler CP - ("org.scala-lang.modules" %% "scala-xml" % "1.1.0").withDottyCompat(scalaVersion.value), "org.scala-lang" % "scala-library" % scalacVersion % "test", Dependencies.`compiler-interface`, "org.jline" % "jline-reader" % "3.9.0", // used by the REPL @@ -515,7 +515,6 @@ object Build { "-Ddotty.tests.classes.compilerInterface=" + findLib(attList, "compiler-interface"), "-Ddotty.tests.classes.scalaLibrary=" + findLib(attList, "scala-library-"), "-Ddotty.tests.classes.scalaAsm=" + findLib(attList, "scala-asm"), - "-Ddotty.tests.classes.scalaXml=" + findLib(attList, "scala-xml"), "-Ddotty.tests.classes.jlineTerminal=" + findLib(attList, "jline-terminal"), "-Ddotty.tests.classes.jlineReader=" + findLib(attList, "jline-reader") ) diff --git a/sbt-bridge/src/xsbt/DelegatingReporter.java b/sbt-bridge/src/xsbt/DelegatingReporter.java index 5f72cb2c18d4..0f533a0e0d9a 100644 --- a/sbt-bridge/src/xsbt/DelegatingReporter.java +++ b/sbt-bridge/src/xsbt/DelegatingReporter.java @@ -80,11 +80,11 @@ public void doReport(MessageContainer cont, Context ctx) { SourceFile src = pos.source(); position = new Position() { public Optional sourceFile() { - if (src.exists()) return Optional.empty(); + if (!src.exists()) return Optional.empty(); else return Optional.ofNullable(src.file().file()); } public Optional sourcePath() { - if (src.exists()) return Optional.empty(); + if (!src.exists()) return Optional.empty(); else return Optional.ofNullable(src.file().path()); } public Optional line() { diff --git a/sbt-dotty/src/dotty/tools/sbtplugin/DottyPlugin.scala b/sbt-dotty/src/dotty/tools/sbtplugin/DottyPlugin.scala index 0386075dd116..fed8a1e037d2 100644 --- a/sbt-dotty/src/dotty/tools/sbtplugin/DottyPlugin.scala +++ b/sbt-dotty/src/dotty/tools/sbtplugin/DottyPlugin.scala @@ -1,6 +1,7 @@ package dotty.tools.sbtplugin import sbt._ +import sbt.Def.Initialize import sbt.Keys._ import sbt.librarymanagement.{ ivy, DependencyResolution, ScalaModuleInfo, SemanticSelector, UpdateConfiguration, UnresolvedWarningConfiguration, @@ -171,20 +172,15 @@ object DottyPlugin extends AutoPlugin { scalaCompilerBridgeBinaryJar := Def.settingDyn { if (isDotty.value) Def.task { - val dottyBridgeArtifacts = fetchArtifactsOf( + val updateReport = fetchArtifactsOf( + scalaOrganization.value % "dotty-sbt-bridge" % scalaVersion.value, dependencyResolution.value, scalaModuleInfo.value, updateConfiguration.value, (unresolvedWarningConfiguration in update).value, streams.value.log, - scalaOrganization.value % "dotty-sbt-bridge" % scalaVersion.value).allFiles - val jars = dottyBridgeArtifacts.filter(art => art.getName.startsWith("dotty-sbt-bridge") && art.getName.endsWith(".jar")).toArray - if (jars.size == 0) - throw new MessageOnlyException("No jar found for dotty-sbt-bridge") - else if (jars.size > 1) - throw new MessageOnlyException(s"Multiple jars found for dotty-sbt-bridge: ${jars.toList}") - else - jars.headOption + ) + Option(getJar(updateReport, scalaOrganization.value, "dotty-sbt-bridge", scalaVersion.value)) } else Def.task { None: Option[File] @@ -289,39 +285,20 @@ object DottyPlugin extends AutoPlugin { }, // ... instead, we'll fetch the compiler and its dependencies ourselves. scalaInstance := Def.taskDyn { - if (isDotty.value) Def.task { - val updateReport = - fetchArtifactsOf( - dependencyResolution.value, - scalaModuleInfo.value, - updateConfiguration.value, - (unresolvedWarningConfiguration in update).value, - streams.value.log, - scalaOrganization.value %% "dotty-doc" % scalaVersion.value) - val scalaLibraryJar = getJar(updateReport, - "org.scala-lang", "scala-library", revision = AllPassFilter) - val dottyLibraryJar = getJar(updateReport, - scalaOrganization.value, s"dotty-library_${scalaBinaryVersion.value}", scalaVersion.value) - val compilerJar = getJar(updateReport, - scalaOrganization.value, s"dotty-compiler_${scalaBinaryVersion.value}", scalaVersion.value) - val allJars = - getJars(updateReport, AllPassFilter, AllPassFilter, AllPassFilter) - - makeScalaInstance( - state.value, - scalaVersion.value, - scalaLibraryJar, - dottyLibraryJar, - compilerJar, - allJars - ) - } + if (isDotty.value) + dottyScalaInstanceTask("dotty-compiler") else - // This dereferences the Initialize graph, but keeps the Task unevaluated, - // so its effect gets fired only when isDotty.value evaluates to false. yay monad. Def.valueStrict { scalaInstance.taskValue } }.value, + // We need more stuff on the classpath to run the `doc` task. + scalaInstance in doc := Def.taskDyn { + if (isDotty.value) + dottyScalaInstanceTask("dotty-doc") + else + Def.valueStrict { (scalaInstance in doc).taskValue } + }.value, + // Because managedScalaInstance is false, sbt won't add the standard library to our dependencies for us libraryDependencies ++= { if (isDotty.value && autoScalaLibrary.value) @@ -355,6 +332,7 @@ object DottyPlugin extends AutoPlugin { old } }.value, + scalacOptions ++= { if (isDotty.value) { val projectName = @@ -369,17 +347,17 @@ object DottyPlugin extends AutoPlugin { } else Seq() - } + }, )) /** Fetch artifacts for moduleID */ def fetchArtifactsOf( + moduleID: ModuleID, dependencyRes: DependencyResolution, scalaInfo: Option[ScalaModuleInfo], updateConfig: UpdateConfiguration, warningConfig: UnresolvedWarningConfiguration, - log: Logger, - moduleID: ModuleID): UpdateReport = { + log: Logger): UpdateReport = { val descriptor = dependencyRes.wrapDependencyInModule(moduleID, scalaInfo) dependencyRes.update(descriptor, updateConfig, warningConfig, log) match { @@ -396,18 +374,48 @@ object DottyPlugin extends AutoPlugin { updateReport.select( configurationFilter(Runtime.name), moduleFilter(organization, name, revision), - artifactFilter(extension = "jar") + artifactFilter(extension = "jar", classifier = "") ) } /** Get the single jar in updateReport that match the given filter. - * If zero or more than one jar match, an exception will be thrown. */ + * If zero or more than one jar match, an exception will be thrown. + */ def getJar(updateReport: UpdateReport, organization: NameFilter, name: NameFilter, revision: NameFilter): File = { val jars = getJars(updateReport, organization, name, revision) assert(jars.size == 1, s"There should only be one $name jar but found: $jars") jars.head } + /** Create a scalaInstance task that uses Dotty based on `moduleName`. */ + def dottyScalaInstanceTask(moduleName: String): Initialize[Task[ScalaInstance]] = Def.task { + val updateReport = + fetchArtifactsOf( + scalaOrganization.value %% moduleName % scalaVersion.value, + dependencyResolution.value, + scalaModuleInfo.value, + updateConfiguration.value, + (unresolvedWarningConfiguration in update).value, + streams.value.log) + val scalaLibraryJar = getJar(updateReport, + "org.scala-lang", "scala-library", revision = AllPassFilter) + val dottyLibraryJar = getJar(updateReport, + scalaOrganization.value, s"dotty-library_${scalaBinaryVersion.value}", scalaVersion.value) + val compilerJar = getJar(updateReport, + scalaOrganization.value, s"dotty-compiler_${scalaBinaryVersion.value}", scalaVersion.value) + val allJars = + getJars(updateReport, AllPassFilter, AllPassFilter, AllPassFilter) + + makeScalaInstance( + state.value, + scalaVersion.value, + scalaLibraryJar, + dottyLibraryJar, + compilerJar, + allJars + ) + } + def makeScalaInstance( state: State, dottyVersion: String, scalaLibrary: File, dottyLibrary: File, compiler: File, all: Seq[File] ): ScalaInstance = { diff --git a/tests/neg/xml.check b/tests/neg/xml.check new file mode 100644 index 000000000000..05b56c4e8136 --- /dev/null +++ b/tests/neg/xml.check @@ -0,0 +1,9 @@ +-- Error: tests/neg/xml.scala:2:10 ------------------------------------------------------------------------------------- +2 | val a = bla // error // error + | ^ + | To support XML literals, your project must depend on scala-xml. + | See https://github.com/scala/scala-xml for more information. +-- [E008] Member Not Found Error: tests/neg/xml.scala:2:11 ------------------------------------------------------------- +2 | val a = bla // error // error + | ^ + | value xml is not a member of scala - did you mean scala.&? diff --git a/tests/pos/i1976.scala b/tests/pos/i1976.scala deleted file mode 100644 index 32967977dbe6..000000000000 --- a/tests/pos/i1976.scala +++ /dev/null @@ -1,4 +0,0 @@ -object Test { - import scala.xml._ - val node = { "whatever " } -} diff --git a/tests/pos/xml-attribute-block.scala b/tests/pos/xml-attribute-block.scala deleted file mode 100644 index c782d511d390..000000000000 --- a/tests/pos/xml-attribute-block.scala +++ /dev/null @@ -1,3 +0,0 @@ -class Foo { - -} diff --git a/tests/pos/xml-pos.scala b/tests/pos/xml-pos.scala deleted file mode 100644 index 953b3a90c9d4..000000000000 --- a/tests/pos/xml-pos.scala +++ /dev/null @@ -1,10 +0,0 @@ -object Test { - val a = - - - val b = - - - val c = - -}