Skip to content

Fix #5498: error for postfixOps if language feature not enabled #8388

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

Merged
merged 3 commits into from
Mar 28, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions compiler/src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1675,6 +1675,16 @@ object desugar {
}
else {
assert(ctx.mode.isExpr || ctx.reporter.errorsReported || ctx.mode.is(Mode.Interactive), ctx.mode)
if (!ctx.featureEnabled(nme.postfixOps)) {
ctx.error(
s"""postfix operator `${op.name}` needs to be enabled
|by making the implicit value scala.language.postfixOps visible.
|----
|This can be achieved by adding the import clause 'import scala.language.postfixOps'
|or by setting the compiler option -language:postfixOps.
|See the Scaladoc for value scala.language.postfixOps for a discussion
|why the feature needs to be explicitly enabled.""".stripMargin, t.sourcePos)
}
Select(t, op.name)
}
case PrefixOp(op, t) =>
Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/core/StdNames.scala
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,7 @@ object StdNames {
val ordinalDollar_ : N = "_$ordinal"
val origin: N = "origin"
val parts: N = "parts"
val postfixOps: N = "postfixOps"
val prefix : N = "prefix"
val processEscapes: N = "processEscapes"
val productArity: N = "productArity"
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/TypeComparer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2201,7 +2201,7 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] w

/** Show subtype goal that led to an assertion failure */
def showGoal(tp1: Type, tp2: Type)(implicit ctx: Context): Unit = {
println(i"assertion failure for ${show(tp1)} <:< ${show(tp2)}, frozen = $frozenConstraint")
ctx.echo(i"assertion failure for ${show(tp1)} <:< ${show(tp2)}, frozen = $frozenConstraint")
def explainPoly(tp: Type) = tp match {
case tp: TypeParamRef => ctx.echo(s"TypeParamRef ${tp.show} found in ${tp.binder.show}")
case tp: TypeRef if tp.symbol.exists => ctx.echo(s"typeref ${tp.show} found in ${tp.symbol.owner.show}")
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/TypeOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -500,7 +500,7 @@ trait TypeOps { this: Context => // TODO: Make standalone object.
if (!sym.exists) ""
else toPrefix(sym.owner) + sym.name + "."
val featureName = toPrefix(owner) + feature
ctx.base.settings.language.value exists (s => s == featureName)
ctx.base.settings.language.value contains featureName
}
hasOption || hasImport
}
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/reporting/Reporter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ trait Reporting { this: Context =>
def error(ex: TypeError, pos: SourcePosition): Unit = {
error(ex.toMessage, pos, sticky = true)
if (ctx.settings.YdebugTypeError.value)
ex.printStackTrace
ex.printStackTrace()
}

def errorOrMigrationWarning(msg: Message, pos: SourcePosition = NoSourcePosition): Unit =
Expand Down
10 changes: 6 additions & 4 deletions compiler/test/dotty/tools/dotc/CompilationTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,10 @@ class CompilationTests extends ParallelTesting {
compileFile("tests/pos-special/i7296.scala", defaultOptions.and("-strict", "-deprecation", "-Xfatal-warnings")),
compileFile("tests/pos-special/notNull.scala", defaultOptions.and("-Yexplicit-nulls")),
compileDir("tests/pos-special/adhoc-extension", defaultOptions.and("-strict", "-feature", "-Xfatal-warnings")),
compileFile("tests/pos-special/i7575.scala", defaultOptions.and("-language:dynamics")),
compileFile("tests/pos-special/i7575.scala", defaultOptions.andLanguageFeature("dynamics")),
compileFile("tests/pos-special/kind-projector.scala", defaultOptions.and("-Ykind-projector")),
compileFile("tests/run/i5606.scala", defaultOptions.and("-Yretain-trees")),
compileFile("tests/pos-custom-args/i5498-postfixOps.scala", defaultOptions withoutLanguageFeature "postfixOps"),
).checkCompile()
}

Expand Down Expand Up @@ -128,7 +129,7 @@ class CompilationTests extends ParallelTesting {
compileFile("tests/neg-custom-args/i3246.scala", scala2CompatMode),
compileFile("tests/neg-custom-args/overrideClass.scala", scala2CompatMode),
compileFile("tests/neg-custom-args/ovlazy.scala", scala2CompatMode.and("-migration", "-Xfatal-warnings")),
compileFile("tests/neg-custom-args/autoTuplingTest.scala", defaultOptions.and("-language:noAutoTupling")),
compileFile("tests/neg-custom-args/autoTuplingTest.scala", defaultOptions.andLanguageFeature("noAutoTupling")),
compileFile("tests/neg-custom-args/nopredef.scala", defaultOptions.and("-Yno-predef")),
compileFile("tests/neg-custom-args/noimports.scala", defaultOptions.and("-Yno-imports")),
compileFile("tests/neg-custom-args/noimports2.scala", defaultOptions.and("-Yno-imports")),
Expand All @@ -153,9 +154,10 @@ class CompilationTests extends ParallelTesting {
compileFile("tests/neg-custom-args/indentRight.scala", defaultOptions.and("-noindent", "-Xfatal-warnings")),
compileFile("tests/neg-custom-args/extmethods-tparams.scala", defaultOptions.and("-deprecation", "-Xfatal-warnings")),
compileDir("tests/neg-custom-args/adhoc-extension", defaultOptions.and("-strict", "-feature", "-Xfatal-warnings")),
compileFile("tests/neg/i7575.scala", defaultOptions.and("-language:_")),
compileFile("tests/neg/i7575.scala", defaultOptions.withoutLanguageFeatures.and("-language:_")),
compileFile("tests/neg-custom-args/kind-projector.scala", defaultOptions.and("-Ykind-projector")),
compileFile("tests/neg-custom-args/typeclass-derivation2.scala", defaultOptions.and("-Yerased-terms")),
compileFile("tests/neg-custom-args/i5498-postfixOps.scala", defaultOptions withoutLanguageFeature "postfixOps"),
).checkExpectedErrors()
}

Expand Down Expand Up @@ -223,7 +225,7 @@ class CompilationTests extends ParallelTesting {
Properties.compilerInterface, Properties.scalaLibrary, Properties.scalaAsm,
Properties.dottyInterfaces, Properties.jlineTerminal, Properties.jlineReader,
).mkString(File.pathSeparator),
Array("-Ycheck-reentrant", "-Yemit-tasty-in-class")
Array("-Ycheck-reentrant", "-Yemit-tasty-in-class", "-language:postfixOps")
)

val libraryDirs = List(Paths.get("library/src"), Paths.get("library/src-bootstrapped"))
Expand Down
11 changes: 7 additions & 4 deletions compiler/test/dotty/tools/vulpix/ParallelTesting.scala
Original file line number Diff line number Diff line change
Expand Up @@ -626,11 +626,14 @@ trait ParallelTesting extends RunnerOrchestration { self =>
lazy val (errorMap, expectedErrors) = getErrorMapAndExpectedCount(testSource.sourceFiles.toIndexedSeq)
lazy val actualErrors = reporters.foldLeft(0)(_ + _.errorCount)
def hasMissingAnnotations = getMissingExpectedErrors(errorMap, reporters.iterator.flatMap(_.errors))
def showErrors = "-> following the errors:\n" +
reporters.flatMap(_.allErrors.map(e => e.pos.toString + ": " + e.message)).mkString(start = "at ", sep = "\n at ", end = "")

if (compilerCrashed) Some(s"Compiler crashed when compiling: ${testSource.title}")
else if (actualErrors == 0) Some(s"\nNo errors found when compiling neg test $testSource")
else if (expectedErrors != actualErrors) Some(s"\nWrong number of errors encountered when compiling $testSource, expected: $expectedErrors, actual: $actualErrors")
else if (hasMissingAnnotations) Some(s"\nErrors found on incorrect row numbers when compiling $testSource")
else if (expectedErrors == 0) Some(s"\nNo errors expected/defined in $testSource -- use // error or // nopos-error")
else if (expectedErrors != actualErrors) Some(s"\nWrong number of errors encountered when compiling $testSource\nexpected: $expectedErrors, actual: $actualErrors " + showErrors)
else if (hasMissingAnnotations) Some(s"\nErrors found on incorrect row numbers when compiling $testSource\n$showErrors")
else if (!errorMap.isEmpty) Some(s"\nExpected error(s) have {<error position>=<unreported error>}: $errorMap")
else None
}
Expand All @@ -654,7 +657,7 @@ trait ParallelTesting extends RunnerOrchestration { self =>
Source.fromFile(file, "UTF-8").getLines().zipWithIndex.foreach { case (line, lineNbr) =>
val errors = line.toSeq.sliding("// error".length).count(_.unwrap == "// error")
if (errors > 0)
errorMap.put(s"${file.getPath}:${lineNbr}", errors)
errorMap.put(s"${file.getPath}:$lineNbr", errors)

val noposErrors = line.toSeq.sliding("// nopos-error".length).count(_.unwrap == "// nopos-error")
if (noposErrors > 0) {
Expand All @@ -663,7 +666,7 @@ trait ParallelTesting extends RunnerOrchestration { self =>
errorMap.put("nopos", noposErrors + existing)
}

val possibleTypos = List("//error" -> "// error" , "//nopos-error" -> "// nopos-error")
val possibleTypos = List("//error" -> "// error", "//nopos-error" -> "// nopos-error")
for ((possibleTypo, expected) <- possibleTypos) {
if (line.contains(possibleTypo))
echo(s"Warning: Possible typo in error tag in file ${file.getCanonicalPath}:$lineNbr: found `$possibleTypo` but expected `$expected`")
Expand Down
4 changes: 2 additions & 2 deletions compiler/test/dotty/tools/vulpix/TestConfiguration.scala
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ object TestConfiguration {

val yCheckOptions = Array("-Ycheck:all")

val commonOptions = Array("-indent") ++ checkOptions ++ noCheckOptions ++ yCheckOptions
val commonOptions = Array("-indent", "-language:postfixOps") ++ checkOptions ++ noCheckOptions ++ yCheckOptions
val defaultOptions = TestFlags(basicClasspath, commonOptions)
val withCompilerOptions =
defaultOptions.withClasspath(withCompilerClasspath).withRunClasspath(withCompilerClasspath)
Expand All @@ -69,7 +69,7 @@ object TestConfiguration {
)
val picklingWithCompilerOptions =
picklingOptions.withClasspath(withCompilerClasspath).withRunClasspath(withCompilerClasspath)
val scala2CompatMode = defaultOptions and "-language:Scala2Compat"
val scala2CompatMode = defaultOptions.andLanguageFeature("Scala2Compat")
val explicitUTF8 = defaultOptions and ("-encoding", "UTF8")
val explicitUTF16 = defaultOptions and ("-encoding", "UTF16")

Expand Down
23 changes: 23 additions & 0 deletions compiler/test/dotty/tools/vulpix/TestFlags.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,29 @@ final case class TestFlags(

def all: Array[String] = Array("-classpath", defaultClassPath) ++ options

def withoutLanguageFeatures: TestFlags = copy(options = withoutLanguageFeaturesOptions)

private val languageFeatureFlag = "-language:"
private def withoutLanguageFeaturesOptions = options.filterNot(_.startsWith(languageFeatureFlag))

// TODO simplify to add `-language:feature` to `options` once
// https://github.com/lampepfl/dotty-feature-requests/issues/107 is implemented
def andLanguageFeature(feature: String) = {
val (languageFeatures, rest) = options.partition(_.startsWith(languageFeatureFlag))
val existingFeatures = if (languageFeatures.isEmpty) languageFeatures.mkString(",") + "," else ""
copy(options = rest ++ Array(languageFeatureFlag + existingFeatures + feature))
}

def withoutLanguageFeature(feature: String) = {
val (languageFeatures, rest) = options.partition(_.startsWith(languageFeatureFlag))
val filteredFeatures = languageFeatures.filter(_ == feature)
val newOptions =
if (filteredFeatures.isEmpty) rest
else rest ++ Array(languageFeatureFlag + filteredFeatures.mkString(","))

copy(options = newOptions)
}

/** Subset of the flags that should be passed to javac. */
def javacFlags: Array[String] = {
val flags = all
Expand Down
2 changes: 1 addition & 1 deletion project/Build.scala
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ object Build {
"-unchecked",
"-Xfatal-warnings",
"-encoding", "UTF8",
"-language:existentials,higherKinds,implicitConversions"
"-language:existentials,higherKinds,implicitConversions,postfixOps"
),

javacOptions in (Compile, compile) ++= Seq("-Xlint:unchecked", "-Xlint:deprecation"),
Expand Down
20 changes: 20 additions & 0 deletions tests/neg-custom-args/i5498-postfixOps.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
-- Error: tests/neg-custom-args/i5498-postfixOps.scala:4:2 -------------------------------------------------------------
4 | 1 second // error: usage of postfix operator
| ^
| postfix operator `second` needs to be enabled
| by making the implicit value scala.language.postfixOps visible.
| ----
| This can be achieved by adding the import clause 'import scala.language.postfixOps'
| or by setting the compiler option -language:postfixOps.
| See the Scaladoc for value scala.language.postfixOps for a discussion
| why the feature needs to be explicitly enabled.
-- Error: tests/neg-custom-args/i5498-postfixOps.scala:6:23 ------------------------------------------------------------
6 | Seq(1, 2).filter(List(1,2) contains) // error: usage of postfix operator
| ^^^^^^^^^
| postfix operator `contains` needs to be enabled
| by making the implicit value scala.language.postfixOps visible.
| ----
| This can be achieved by adding the import clause 'import scala.language.postfixOps'
| or by setting the compiler option -language:postfixOps.
| See the Scaladoc for value scala.language.postfixOps for a discussion
| why the feature needs to be explicitly enabled.
7 changes: 7 additions & 0 deletions tests/neg-custom-args/i5498-postfixOps.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import scala.concurrent.duration._

def test() = {
1 second // error: usage of postfix operator

Seq(1, 2).filter(List(1,2) contains) // error: usage of postfix operator
}
9 changes: 9 additions & 0 deletions tests/pos-custom-args/i5498-postfixOps.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import scala.concurrent.duration._

import scala.language.postfixOps

def test() = {
1 second

Seq(1, 2) filter (List(1,2) contains)
}
8 changes: 8 additions & 0 deletions tests/pos/i5498-postfixOps.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import scala.concurrent.duration._

def test() = {
// only OK since the defaultOptions in the TestConfiguration includes -language:postfixOps
1 second

Seq(1, 2).filter(List(1,2) contains)
}