Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prefer scala-cli pragmas [ci: last-only] #10757

Open
wants to merge 8 commits into
base: 2.13.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1 change: 1 addition & 0 deletions src/compiler/scala/tools/nsc/Reporting.scala
Expand Up @@ -624,6 +624,7 @@ object Reporting {
LintPerformance,
LintIntDivToFloat,
LintUniversalMethods,
LintCloneable,
LintNumericMethods
= lint()

Expand Down
2 changes: 1 addition & 1 deletion src/compiler/scala/tools/nsc/typechecker/RefChecks.scala
Expand Up @@ -902,7 +902,7 @@ abstract class RefChecks extends Transform {
}
}
if (warnCloneable && baseClass.eq(JavaCloneableClass))
reporter.warning(clazz.pos, s"$clazz should not extend Cloneable.")
refchecksWarning(clazz.pos, s"$clazz should not extend Cloneable.", WarningCategory.LintCloneable)
val remaining = tp.parents.filterNot(seenParents)
seenParents ++= remaining
remaining.foreach(register)
Expand Down
146 changes: 83 additions & 63 deletions src/partest/scala/tools/partest/nest/Runner.scala
Expand Up @@ -78,8 +78,12 @@ class Runner(val testInfo: TestInfo, val suiteRunner: AbstractRunner) {

private val _transcript = new TestTranscript

// start log event
def pushTranscript(msg: String) = _transcript.add(msg)

// append to last log in transcript
def appendTranscript(log: String) = _transcript.append(log)

lazy val outDir = { outFile.mkdirs() ; outFile }

// if there is a checkfile, log message for diff; otherwise log stack trace for post-mortem
Expand Down Expand Up @@ -114,7 +118,7 @@ class Runner(val testInfo: TestInfo, val suiteRunner: AbstractRunner) {
joinPaths(outDir :: testClassPath),
"-J-Duser.language=en",
"-J-Duser.country=US"
) ++ (toolArgsFor(files)(ToolName.javac)
) ++ (toolArgsFor(files)(ToolName.javacOpt)
) ++ (files.map(_.getAbsolutePath)
)

Expand Down Expand Up @@ -221,7 +225,7 @@ class Runner(val testInfo: TestInfo, val suiteRunner: AbstractRunner) {
pushTranscript((cmd mkString s" \\$EOL ") + " > " + logFile.getName)
nextTestAction(runCommand(cmd, logFile)) {
case false =>
_transcript append EOL + logFile.fileContents
appendTranscript(EOL + logFile.fileContents)
genFail("non-zero exit code")
}
}
Expand Down Expand Up @@ -363,14 +367,8 @@ class Runner(val testInfo: TestInfo, val suiteRunner: AbstractRunner) {

// no spaces in test file paths below root, because otherwise how to detect end of path string?
val pathFinder = raw"""(?i)\Q${elided}${File.separator}\E([\${File.separator}\S]*)""".r
def canonicalize: String => String = {
val hiders = toolArgs(ToolName.hide).map(_.r)
(s: String) => {
val pathless = pathFinder.replaceAllIn(s, m => quoteReplacement(ellipsis + squashSlashes(m.group(1))))
if (hiders.isEmpty) pathless
else hiders.foldLeft(pathless)((s, r) => r.replaceAllIn(s, m => "***"))
}
}
def canonicalize: String => String =
s => pathFinder.replaceAllIn(s, m => quoteReplacement(ellipsis + squashSlashes(m.group(1))))

def masters = {
val files = List(new File(parentFile, "filters"), new File(suiteRunner.pathSettings.srcDir.path, "filters"))
Expand Down Expand Up @@ -410,7 +408,7 @@ class Runner(val testInfo: TestInfo, val suiteRunner: AbstractRunner) {
else
gitRunner.flatMap(_ => withTempFile(outDir, fileBase, filteredCheck)(f =>
gitDiff(f, logFile))).getOrElse(diff)
_transcript append bestDiff
appendTranscript(bestDiff)
genFail("output differs")
}
}
Expand All @@ -428,23 +426,19 @@ class Runner(val testInfo: TestInfo, val suiteRunner: AbstractRunner) {

def newCompiler = new DirectCompiler(this)

def attemptCompile(sources: List[File]): TestState = {
val badflags = (testFile :: (if (testFile.isDirectory) sources else Nil)).map(_.changeExtension("flags")).find(_.exists)
if (badflags.isDefined) genFail(s"unexpected flags file ${badflags.get} (use source comment // scalac: -Werror)")
else
newCompiler.compile(flagsForCompilation(sources), sources).tap { state =>
if (!state.isOk) _transcript.append("\n" + logFile.fileContents)
}
}
def attemptCompile(sources: List[File], extraFlags: List[String] = Nil): TestState =
newCompiler.compile(flagsForCompilation(sources) ::: extraFlags, sources).tap { state =>
if (!state.isOk) appendTranscript(EOL + logFile.fileContents)
}

// all sources in a round may contribute flags via // scalac: -flags
// under --realeasy, if a javaVersion isn't specified, require the minimum viable using -release 8
// under --realeasy, if a jvm isn't specified, require the minimum viable using -release 8
// to avoid accidentally committing a test that requires a later JVM.
def flagsForCompilation(sources: List[File]): List[String] = {
var perFile = toolArgsFor(sources)(ToolName.scalac)
if (parentFile.getParentFile.getName == "macro-annot")
perFile ::= "-Ymacro-annotations"
if (realeasy && isJavaAtLeast(9) && !perFile.exists(releaseFlag.matches) && toolArgsFor(sources)(ToolName.javaVersion).isEmpty)
if (realeasy && isJavaAtLeast(9) && !perFile.exists(releaseFlag.matches) && toolArgsFor(sources)(ToolName.jvm).isEmpty)
perFile ::= "-release:8"
perFile
}
Expand All @@ -455,42 +449,46 @@ class Runner(val testInfo: TestInfo, val suiteRunner: AbstractRunner) {

// for each file, cache the args for each tool
private val fileToolArgs = new mutable.HashMap[Path, Map[ToolName, List[String]]]
//private val optionsPattern = raw"\s*//>\s*using\s+(?:([^.]+)\.)?option(s)?\s+(.*)".r
private val optionsPattern = raw"\s*//>\s*using\s+(${ToolName.alts})\s+(.*)".r

// Inspect given files for tool args in header line comments of the form `// tool: args`.
// If the line comment starts `//>`, accept `scalac:` options in the form of using pragmas.
// If the line comment starts `//>`, accept `using option` or `using options` pragmas
// to define options to`scalac`. Or take `using test.options`, where test scope is used for test options.
// (`test` scope is not used that way by scala-cli, where framework args are passed on command line.)
// (One could imagine `using test.testOpt` for framework args.)
// If `filter:`, return entire line as if quoted, else parse the args string as command line.
// Currently, we look for scalac, javac, java, javaVersion, filter, hide.
// Currently, we look for scalac, javac, java, jvm, filter, test.
//
def toolArgsFor(files: List[File])(tool: ToolName): List[String] = {
def argsFor(f: File): List[String] = fileToolArgs.getOrElseUpdate(f.toPath, readToolArgs(f)).apply(tool)
def readToolArgs(f: File): Map[ToolName, List[String]] = {
val header = readHeaderFrom(f)
ToolName.values.toList
.map(name => name -> fromHeader(name, header))
.filterNot(_._2.isEmpty)
.toMap[ToolName, List[String]]
.withDefaultValue(List.empty[String])
}
def fromHeader(name: ToolName, header: List[String]) = {
def readToolArgs(f: File): Map[ToolName, List[String]] = optionsFromHeader(readHeaderFrom(f))
def optionsFromHeader(header: List[String]) = {
import scala.sys.process.Parser.tokenize
val namePattern = raw"\s*//\s*$name:\s*(.*)".r
val optionsPattern = raw"\s*//>\s*using\s+option(s)?\s+(.*)".r
def matchLine(line: String): List[String] =
line match {
case namePattern(rest) => if (name == ToolName.filter) List(rest.trim) else tokenize(rest)
case _ if name == ToolName.scalac =>
line match {
case optionsPattern(plural, rest) =>
if (plural == null) List(rest.trim)
else tokenize(rest).filter(_ != ",").map(_.stripSuffix(","))
case _ => Nil
}
case _ => Nil
}
def matchLine(line: String): List[(ToolName, List[String])] = line match {
case optionsPattern(scope, rest) =>
val named = Try {
if (scope == null) ToolName.scalac
else ToolName.named(scope)
}.toOption
named match {
case None =>
suiteRunner.verbose(s"ignoring pragma with unknown scope '$scope': $line")
Nil
case Some(name) =>
val settings = tokenize(rest).filter(_ != ",").map(_.stripSuffix(","))
if (settings.isEmpty) Nil
else (name, settings) :: Nil
}
case _ => Nil
}
header.flatMap(matchLine)
.groupBy(_._1)
.map { case (k, kvs) => (k, kvs.flatMap(_._2)) }
.withDefaultValue(List.empty[String])
}
def readHeaderFrom(f: File): List[String] =
Using.resource(Files.lines(f.toPath, codec.charSet))(stream => stream.limit(10).toArray()).toList.map(_.toString)
Using.resource(Files.lines(f.toPath, codec.charSet))(_.limit(10).toArray()).toList.map(_.toString)
files.flatMap(argsFor)
}

Expand Down Expand Up @@ -526,7 +524,7 @@ class Runner(val testInfo: TestInfo, val suiteRunner: AbstractRunner) {
val Range = """(\d+)(?:(\+)|(?:-(\d+)))?""".r
lazy val currentJavaVersion = javaSpecVersion.stripPrefix("1.").toInt
val allFiles = sources(file)
val skipStates = toolArgsFor(allFiles)(ToolName.javaVersion).flatMap {
val skipStates = toolArgsFor(allFiles)(ToolName.jvm).flatMap {
case v @ Range(from, plus, to) =>
val ok =
if (plus == null)
Expand All @@ -538,7 +536,7 @@ class Runner(val testInfo: TestInfo, val suiteRunner: AbstractRunner) {
else if (ok) None
else Some(genSkip(s"skipped on Java $javaSpecVersion, only running on $v"))
case v =>
Some(genFail(s"invalid javaVersion range in test comment: $v"))
Some(genFail(s"invalid jvm range in test comment: $v"))
}
skipStates.headOption match {
case Some(state) => List(SkipRound(List(file), state))
Expand All @@ -559,14 +557,24 @@ class Runner(val testInfo: TestInfo, val suiteRunner: AbstractRunner) {
else runTestCommon()()

def runNegTest(): TestState = {
// pass if it checks and didn't crash the compiler
// or, OK, we'll let you crash the compiler with a FatalError if you supply a check file
// a "crash test" passes if the error is not FatalError and there is a check file to compare.
// a neg test passes if the log compares same to check file.
// under "//> using retest.option -some-flags", also check pos compilation after adding the extra flags.
def checked(r: TestState) = r match {
case s: Skip => s
case crash @ Crash(_, t, _) if !checkFile.canRead || !t.isInstanceOf[FatalError] => crash
case _ => diffIsOk
case _ =>
val negRes = diffIsOk
toolArgs(ToolName.retest) match {
case extraFlags if extraFlags.nonEmpty && !negRes.isSkipped && negRes.isOk =>
// transcript visible under partest --verbose or after failure
val debug = s"recompile $testIdent with extra flags ${extraFlags.mkString(" ")}"
suiteRunner.verbose(s"% $debug")
pushTranscript(debug)
attemptCompile(sources(testFile), extraFlags = extraFlags)
case _ => negRes
}
}

runTestCommon(checked, expectCompile = false)(identity)
}

Expand Down Expand Up @@ -674,7 +682,7 @@ class Runner(val testInfo: TestInfo, val suiteRunner: AbstractRunner) {
}

private def runRunTest(): TestState = {
val javaopts = toolArgs(ToolName.java)
val javaopts = toolArgs(ToolName.javaOpt)
val execInProcess = PartestDefaults.execInProcess && javaopts.isEmpty && !Set("specialized", "instrumented").contains(testFile.getParentFile.getName)
def exec() = if (execInProcess) execTestInProcess(outDir, logFile) else execTest(outDir, logFile, javaopts)
def noexec() = genSkip("no-exec: tests compiled but not run")
Expand Down Expand Up @@ -714,8 +722,8 @@ class Runner(val testInfo: TestInfo, val suiteRunner: AbstractRunner) {
def pass(s: String) = bold(green("% ")) + s
def fail(s: String) = bold(red("% ")) + s
_transcript.toList match {
case Nil => Nil
case xs => (xs.init map pass) :+ fail(xs.last)
case init :+ last => init.map(pass) :+ fail(last)
case _ => Nil
}
}
}
Expand Down Expand Up @@ -786,15 +794,27 @@ final class TestTranscript {
def toList = buf.toList
}

// Tool names in test file header: scalac, javac, java, javaVersion, filter, hide.
// Tool names in test file header: scalac, javacOpt, javaOpt, jvm, filter, test, retest.
sealed trait ToolName
object ToolName {
case object scalac extends ToolName
case object javac extends ToolName
case object java extends ToolName
case object javaVersion extends ToolName
case object javacOpt extends ToolName
case object javaOpt extends ToolName
case object jvm extends ToolName
case object test extends ToolName
case object retest extends ToolName
case object filter extends ToolName
case object hide extends ToolName
val values = Array(scalac, javac, java, javaVersion, filter, hide)
def named(s: String): ToolName = values.find(_.toString.equalsIgnoreCase(s)).getOrElse(throw new IllegalArgumentException(s))
val values = Array(scalac, javacOpt, javaOpt, jvm, test, retest, filter)
def named(s: String): ToolName = s match {
case "options" => scalac
case "test.options" => test
case "retest.options" => retest
case _ => values.find(_.toString == s).getOrElse(throw new IllegalArgumentException(s))
}
def option(toolName: ToolName): String = toolName match {
case `scalac` => "options"
case `test` | `retest` => s"$toolName.options"
case _ => toolName.toString
}
val alts = values.map(option).mkString("|")
}
2 changes: 1 addition & 1 deletion test/async/jvm/anf.scala
@@ -1,4 +1,4 @@
// scalac: -Xasync
//> using options -Xasync

object Test extends scala.tools.partest.JUnitTest(classOf[scala.async.run.anf.AnfTransformSpec])

Expand Down
2 changes: 1 addition & 1 deletion test/async/jvm/await0.scala
@@ -1,4 +1,4 @@
// scalac: -Xasync
//> using options -Xasync

object Test extends scala.tools.partest.JUnitTest(classOf[scala.async.run.await0.Await0Spec])

Expand Down
2 changes: 1 addition & 1 deletion test/async/jvm/block0.scala
@@ -1,4 +1,4 @@
// scalac: -Xasync
//> using options -Xasync

object Test extends scala.tools.partest.JUnitTest(classOf[scala.async.run.block0.AsyncSpec])

Expand Down
2 changes: 1 addition & 1 deletion test/async/jvm/block1.scala
@@ -1,4 +1,4 @@
// scalac: -Xasync
//> using options -Xasync

object Test extends scala.tools.partest.JUnitTest(classOf[scala.async.run.block1.Block1Spec])

Expand Down
2 changes: 1 addition & 1 deletion test/async/jvm/completable-future.scala
@@ -1,4 +1,4 @@
// scalac: -Xasync
//> using options -Xasync

import java.util.concurrent._
import scala.tools.partest.async.CompletableFutureAwait._
Expand Down
2 changes: 1 addition & 1 deletion test/async/jvm/concurrent_AfterRefchecksIssue.scala
@@ -1,4 +1,4 @@
// scalac: -Xasync
//> using options -Xasync

import scala.concurrent._, ExecutionContext.Implicits.global, scala.tools.testkit.async.Async._

Expand Down
2 changes: 1 addition & 1 deletion test/async/jvm/concurrent_ArrayIndexOutOfBoundIssue.scala
@@ -1,4 +1,4 @@
// scalac: -Xasync
//> using options -Xasync

import scala.concurrent._
import ExecutionContext.Implicits.global
Expand Down
2 changes: 1 addition & 1 deletion test/async/jvm/concurrent_GenericTypeBoundaryIssue.scala
@@ -1,4 +1,4 @@
// scalac: -Xasync
//> using options -Xasync

import Test.test

Expand Down
2 changes: 1 addition & 1 deletion test/async/jvm/concurrent_MatchEndIssue.scala
@@ -1,4 +1,4 @@
// scalac: -Xasync
//> using options -Xasync

import scala.concurrent._
import ExecutionContext.Implicits.global
Expand Down
2 changes: 1 addition & 1 deletion test/async/jvm/concurrent_NegativeArraySizeException.scala
@@ -1,4 +1,4 @@
// scalac: -Xasync
//> using options -Xasync

import scala.concurrent._
import ExecutionContext.Implicits.global
Expand Down
@@ -1,4 +1,4 @@
// scalac: -Xasync
//> using options -Xasync

import scala.concurrent._
import ExecutionContext.Implicits.global
Expand Down
2 changes: 1 addition & 1 deletion test/async/jvm/concurrent_ReturnTupleIssue.scala
@@ -1,4 +1,4 @@
// scalac: -Xasync
//> using options -Xasync

import scala.concurrent._
import ExecutionContext.Implicits.global
Expand Down
2 changes: 1 addition & 1 deletion test/async/jvm/concurrent_fetch.scala
@@ -1,4 +1,4 @@
// scalac: -Xasync
//> using options -Xasync

import scala.concurrent.{Await, Future, duration}
import scala.concurrent.ExecutionContext.Implicits.global
Expand Down
2 changes: 1 addition & 1 deletion test/async/jvm/concurrent_patternAlternative.scala
@@ -1,4 +1,4 @@
// scalac: -Xasync
//> using options -Xasync

import scala.concurrent._
import ExecutionContext.Implicits.global
Expand Down
@@ -1,4 +1,4 @@
// scalac: -Xasync
//> using options -Xasync

import scala.concurrent._
import ExecutionContext.Implicits.global
Expand Down
2 changes: 1 addition & 1 deletion test/async/jvm/concurrent_polymorphicMethod.scala
@@ -1,4 +1,4 @@
// scalac: -Xasync
//> using options -Xasync

import scala.concurrent._
import ExecutionContext.Implicits.global
Expand Down
2 changes: 1 addition & 1 deletion test/async/jvm/concurrent_shadowing.scala
@@ -1,4 +1,4 @@
// scalac: -Xasync
//> using options -Xasync

import scala.concurrent._
import ExecutionContext.Implicits.global
Expand Down
2 changes: 1 addition & 1 deletion test/async/jvm/concurrent_shadowing0.scala
@@ -1,4 +1,4 @@
// scalac: -Xasync
//> using options -Xasync

import scala.concurrent._
import ExecutionContext.Implicits.global
Expand Down
2 changes: 1 addition & 1 deletion test/async/jvm/concurrent_shadowing2.scala
@@ -1,4 +1,4 @@
// scalac: -Xasync
//> using options -Xasync

import scala.concurrent._
import ExecutionContext.Implicits.global
Expand Down
2 changes: 1 addition & 1 deletion test/async/jvm/concurrent_shadowingRefinedTypes.scala
@@ -1,4 +1,4 @@
// scalac: -Xasync
//> using options -Xasync

import scala.concurrent._
import ExecutionContext.Implicits.global
Expand Down