diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 90c26e279d01..1c44d0c34e76 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -1071,12 +1071,19 @@ trait Checking { def checkValidInfix(tree: untpd.InfixOp, meth: Symbol)(using Context): Unit = { tree.op match { case id @ Ident(name: Name) => + def methCompiledBeforeDeprecation = + meth.compilationUnitInfo != null && { + meth.compilationUnitInfo.nn.tastyVersion match + case Some(version) => version.minor < 4 // compiled before 3.4 + case _ => false // compiled with the current compiler + } name.toTermName match { case name: SimpleName if !untpd.isBackquoted(id) && !name.isOperatorName && !meth.isDeclaredInfix && !meth.maybeOwner.is(Scala2x) && + !methCompiledBeforeDeprecation && !infixOKSinceFollowedBy(tree.right) => val (kind, alternative) = if (ctx.mode.is(Mode.Type)) @@ -1085,13 +1092,14 @@ trait Checking { ("extractor", (n: Name) => s"prefix syntax $n(...)") else ("method", (n: Name) => s"method syntax .$n(...)") - def rewriteMsg = Message.rewriteNotice("The latter", version = `future-migration`) - report.errorOrMigrationWarning( + def rewriteMsg = Message.rewriteNotice("The latter", version = `3.4-migration`) + report.gradualErrorOrMigrationWarning( em"""Alphanumeric $kind $name is not declared ${hlAsKeyword("infix")}; it should not be used as infix operator. |Instead, use ${alternative(name)} or backticked identifier `$name`.$rewriteMsg""", tree.op.srcPos, - from = future) - if sourceVersion == `future-migration` then { + warnFrom = `3.4`, + errorFrom = future) + if sourceVersion.isMigrating && sourceVersion.isAtLeast(`3.4-migration`) then { patch(Span(tree.op.span.start, tree.op.span.start), "`") patch(Span(tree.op.span.end, tree.op.span.end), "`") } diff --git a/compiler/test-resources/repl/i1374 b/compiler/test-resources/repl/i1374 index 3d117fdb4ff9..2e0b5be900af 100644 --- a/compiler/test-resources/repl/i1374 +++ b/compiler/test-resources/repl/i1374 @@ -1,4 +1,4 @@ -scala> implicit class Padder(val sb: StringBuilder) extends AnyVal { def pad2(width: Int) = { 1 to width - sb.length foreach { sb append '*' }; sb } } +scala> implicit class Padder(val sb: StringBuilder) extends AnyVal { infix def pad2(width: Int) = { 1 to width - sb.length foreach { sb append '*' }; sb } } // defined class Padder def Padder(sb: StringBuilder): Padder scala> val greeting = new StringBuilder("Hello, kitteh!") diff --git a/compiler/test-resources/type-printer/infix b/compiler/test-resources/type-printer/infix index 2fe2864ad9fe..bedb7071e7f2 100644 --- a/compiler/test-resources/type-printer/infix +++ b/compiler/test-resources/type-printer/infix @@ -29,7 +29,7 @@ def foo: (Int &: String) & Boolean scala> def foo: Int &: (Boolean & String) = ??? def foo: Int &: (Boolean & String) scala> import scala.annotation.showAsInfix -scala> @scala.annotation.showAsInfix class Mappy[T,U] +scala> @scala.annotation.showAsInfix infix class Mappy[T,U] // defined class Mappy scala> def foo: (Int Mappy Boolean) && String = ??? def foo: (Int Mappy Boolean) && String diff --git a/language-server/test/dotty/tools/languageserver/util/embedded/CodeMarker.scala b/language-server/test/dotty/tools/languageserver/util/embedded/CodeMarker.scala index 416730dc0f4e..33e6a875750a 100644 --- a/language-server/test/dotty/tools/languageserver/util/embedded/CodeMarker.scala +++ b/language-server/test/dotty/tools/languageserver/util/embedded/CodeMarker.scala @@ -13,7 +13,7 @@ import PositionContext.PosCtx class CodeMarker(val name: String) extends Embedded { /** A range of positions between this marker and `other`. */ - def to(other: CodeMarker): CodeRange = CodeRange(this, other) + infix def to(other: CodeMarker): CodeRange = CodeRange(this, other) /** The file containing this marker. */ def file: PosCtx[TestFile] = posCtx.positionOf(this)._1 diff --git a/scaladoc-testcases/src/tests/infixTypes.scala b/scaladoc-testcases/src/tests/infixTypes.scala index acfc9044e2eb..30fc982f2bca 100644 --- a/scaladoc-testcases/src/tests/infixTypes.scala +++ b/scaladoc-testcases/src/tests/infixTypes.scala @@ -4,9 +4,9 @@ package infixTypes import annotation.showAsInfix @showAsInfix -trait SomeTrait[A, B] +infix trait SomeTrait[A, B] -trait SomeTrait2[A, B] +infix trait SomeTrait2[A, B] def someTrait1[C, D]: C SomeTrait D = ??? diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/MemberLookup.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/MemberLookup.scala index b2c4e1bdcac4..26c4fb06dfdf 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/MemberLookup.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/MemberLookup.scala @@ -236,9 +236,9 @@ object MemberLookup extends MemberLookup { // Scaladoc overloading support allows terminal * (and they're meaningless) val cleanStr = str.stripSuffix("*") - if cleanStr endsWith "$" then + if cleanStr.endsWith("$") then Selector(cleanStr.init, SelectorKind.ForceTerm) - else if cleanStr endsWith "!" then + else if cleanStr.endsWith("!") then Selector(cleanStr.init, SelectorKind.ForceType) else Selector(cleanStr, SelectorKind.NoForce) diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/wiki/Parser.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/wiki/Parser.scala index dd3187fb5346..125bca102fba 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/comments/wiki/Parser.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/comments/wiki/Parser.scala @@ -675,7 +675,7 @@ sealed class CharReader(buffer: String) { reader => var offset: Int = 0 def char: Char = - if (offset >= buffer.length) endOfText else buffer charAt offset + if (offset >= buffer.length) endOfText else buffer.charAt(offset) final def nextChar() = offset += 1 @@ -712,7 +712,7 @@ sealed class CharReader(buffer: String) { reader => jumpWhitespace() val (ok0, chars0) = if (chars.charAt(0) == ' ') - (offset > poff, chars substring 1) + (offset > poff, chars.substring(1)) else (true, chars) val ok = ok0 && jump(chars0) diff --git a/scaladoc/test/dotty/tools/scaladoc/testUtils.scala b/scaladoc/test/dotty/tools/scaladoc/testUtils.scala index 2ba78c321eab..d8b0e179c5f1 100644 --- a/scaladoc/test/dotty/tools/scaladoc/testUtils.scala +++ b/scaladoc/test/dotty/tools/scaladoc/testUtils.scala @@ -71,7 +71,7 @@ def tastyFiles(name: String, allowEmpty: Boolean = false, rootPck: String = "tes } def collectFiles(dir: File): List[File] = listFilesSafe(dir).toList.flatMap { case f if f.isDirectory => collectFiles(f) - case f if f.getName endsWith ".tasty" => f :: Nil + case f if f.getName.endsWith(".tasty") => f :: Nil case _ => Nil } val outputDir = BuildInfo.test_testcasesOutputDir diff --git a/tests/init-global/pos/i18628.scala b/tests/init-global/pos/i18628.scala index 71d9a1ed30a8..e9fd8f359356 100644 --- a/tests/init-global/pos/i18628.scala +++ b/tests/init-global/pos/i18628.scala @@ -55,10 +55,10 @@ trait Parsers { } def flatMap[U](f: T => Parser[U]): Parser[U] - = Parser{ in => this(in) flatMapWithNext(f)} + = Parser{ in => this(in).flatMapWithNext(f)} def map[U](f: T => U): Parser[U] //= flatMap{x => success(f(x))} - = Parser{ in => this(in) map(f)} + = Parser{ in => this(in).map(f)} def ^^ [U](f: T => U): Parser[U] = map(f) } diff --git a/tests/init/pos/Properties.scala b/tests/init/pos/Properties.scala index ba92dd54dc83..8dd2fa052c0b 100644 --- a/tests/init/pos/Properties.scala +++ b/tests/init/pos/Properties.scala @@ -23,9 +23,9 @@ private[scala] trait PropertiesTrait { /** The loaded properties */ protected lazy val scalaProps: java.util.Properties = { val props = new java.util.Properties - val stream = pickJarBasedOn getResourceAsStream propFilename + val stream = pickJarBasedOn.getResourceAsStream(propFilename) if (stream ne null) - quietlyDispose(props load stream, stream.close) + quietlyDispose(props.load(stream), stream.close) props } @@ -47,8 +47,8 @@ private[scala] trait PropertiesTrait { final def setProp(name: String, value: String) = System.setProperty(name, value) final def clearProp(name: String) = System.clearProperty(name) - final def envOrElse(name: String, alt: String) = Option(System getenv name) getOrElse alt - final def envOrNone(name: String) = Option(System getenv name) + final def envOrElse(name: String, alt: String) = Option(System.getenv(name)) getOrElse alt + final def envOrNone(name: String) = Option(System.getenv(name)) final def envOrSome(name: String, alt: Option[String]) = envOrNone(name) orElse alt @@ -68,7 +68,7 @@ private[scala] trait PropertiesTrait { val releaseVersion = for { v <- scalaPropOrNone("maven.version.number") - if !(v endsWith "-SNAPSHOT") + if !(v.endsWith("-SNAPSHOT")) } yield v /** The development Scala version, if this is not a final release. @@ -82,7 +82,7 @@ private[scala] trait PropertiesTrait { val developmentVersion = for { v <- scalaPropOrNone("maven.version.number") - if v endsWith "-SNAPSHOT" + if v.endsWith("-SNAPSHOT") ov <- scalaPropOrNone("version.number") } yield ov diff --git a/tests/init/pos/i15465.scala b/tests/init/pos/i15465.scala index 5b99670e9027..1b0c9e1fead3 100644 --- a/tests/init/pos/i15465.scala +++ b/tests/init/pos/i15465.scala @@ -2,7 +2,7 @@ class TestSuite: protected val it = new ItWord protected final class ItWord: - def should(string: String) = new ItVerbString("should", string) + infix def should(string: String) = new ItVerbString("should", string) private def registerTestToRun(fun: => Any): Unit = () diff --git a/tests/neg/alphanumeric-infix-operator-3.4.scala b/tests/neg/alphanumeric-infix-operator-3.4.scala new file mode 100644 index 000000000000..14d3358127ca --- /dev/null +++ b/tests/neg/alphanumeric-infix-operator-3.4.scala @@ -0,0 +1,11 @@ +//> using options -Werror + +import language.`3.4` + +class Foo: + def x(i: Int) = i + infix def y(i: Int) = i + +def test(foo: Foo): Unit = + foo x 1 // error (because it was compiled with 3.4+) + foo y 2 // ok: is marked as infix diff --git a/tests/neg/alphanumeric-infix-operator.check b/tests/neg/alphanumeric-infix-operator.check new file mode 100644 index 000000000000..52b08f16b88c --- /dev/null +++ b/tests/neg/alphanumeric-infix-operator.check @@ -0,0 +1,6 @@ +-- Error: tests/neg/alphanumeric-infix-operator.scala:8:6 -------------------------------------------------------------- +8 | foo x 1 // error (because it was compiled with 3.4+) + | ^ + | Alphanumeric method x is not declared infix; it should not be used as infix operator. + | Instead, use method syntax .x(...) or backticked identifier `x`. + | The latter can be rewritten automatically under -rewrite -source 3.4-migration. diff --git a/tests/neg/alphanumeric-infix-operator.scala b/tests/neg/alphanumeric-infix-operator.scala new file mode 100644 index 000000000000..1f2233dda6ce --- /dev/null +++ b/tests/neg/alphanumeric-infix-operator.scala @@ -0,0 +1,9 @@ +//> using options -Werror + +class Foo: + def x(i: Int) = i + infix def y(i: Int) = i + +def test(foo: Foo): Unit = + foo x 1 // error (because it was compiled with 3.4+) + foo y 2 // ok: is marked as infix diff --git a/tests/neg/i10901.scala b/tests/neg/i10901.scala index 19a2e3023c85..dc1ea6e6eef6 100644 --- a/tests/neg/i10901.scala +++ b/tests/neg/i10901.scala @@ -13,23 +13,23 @@ object BugExp4Point2D { // N - N @targetName("point2DConstant") - def º(y: T2): Point2D[T1,T2] = ??? + infix def º(y: T2): Point2D[T1,T2] = ??? // N - C @targetName("point2DConstantData") - def º(y: ColumnType[T2]): Point2D[T1,T2] = ??? + infix def º(y: ColumnType[T2]): Point2D[T1,T2] = ??? extension [T1:Numeric, T2:Numeric](x: ColumnType[T1]) // C - C @targetName("point2DData") - def º(y: ColumnType[T2]): Point2D[T1,T2] = ??? + infix def º(y: ColumnType[T2]): Point2D[T1,T2] = ??? // C - N @targetName("point2DDataConstant") - def º(y: T2): Point2D[T1,T2] = ??? + infix def º(y: T2): Point2D[T1,T2] = ??? } diff --git a/tests/neg/i2033.check b/tests/neg/i2033.check index 5751d91f4f3a..7737bba96a5e 100644 --- a/tests/neg/i2033.check +++ b/tests/neg/i2033.check @@ -13,3 +13,9 @@ 6 | val out = new ObjectOutputStream(println) | ^^^^^^^ |method println is eta-expanded even though java.io.OutputStream does not have the @FunctionalInterface annotation. +-- Warning: tests/neg/i2033.scala:7:18 --------------------------------------------------------------------------------- +7 | val arr = bos toByteArray () // error + | ^^^^^^^^^^^ + | Alphanumeric method toByteArray is not declared infix; it should not be used as infix operator. + | Instead, use method syntax .toByteArray(...) or backticked identifier `toByteArray`. + | The latter can be rewritten automatically under -rewrite -source 3.4-migration. diff --git a/tests/neg/rewrite-messages.check b/tests/neg/rewrite-messages.check index f368c2dc8997..b062ab2bc732 100644 --- a/tests/neg/rewrite-messages.check +++ b/tests/neg/rewrite-messages.check @@ -8,4 +8,4 @@ | ^^^ | Alphanumeric method foo is not declared infix; it should not be used as infix operator. | Instead, use method syntax .foo(...) or backticked identifier `foo`. - | The latter can be rewritten automatically under -rewrite -source future-migration. + | The latter can be rewritten automatically under -rewrite -source 3.4-migration. diff --git a/tests/neg/syntax-error-recovery.check b/tests/neg/syntax-error-recovery.check index 0cf550c74d09..d93a939b0c72 100644 --- a/tests/neg/syntax-error-recovery.check +++ b/tests/neg/syntax-error-recovery.check @@ -136,3 +136,9 @@ | Discarded non-Unit value of type Null. You may want to use `()`. | | longer explanation available when compiling with `-explain` +-- Warning: tests/neg/syntax-error-recovery.scala:61:2 ----------------------------------------------------------------- +61 | println(bam) + | ^^^^^^^ + | Alphanumeric method println is not declared infix; it should not be used as infix operator. + | Instead, use method syntax .println(...) or backticked identifier `println`. + | The latter can be rewritten automatically under -rewrite -source 3.4-migration. diff --git a/tests/pos/alphanumeric-infix-operator-compat/A_1_c3.0.0.scala b/tests/pos/alphanumeric-infix-operator-compat/A_1_c3.0.0.scala new file mode 100644 index 000000000000..51ea7ec45de6 --- /dev/null +++ b/tests/pos/alphanumeric-infix-operator-compat/A_1_c3.0.0.scala @@ -0,0 +1,3 @@ +class A: + def x(i: Int) = i + infix def y(i: Int) = i diff --git a/tests/pos/alphanumeric-infix-operator-compat/B_1_c3.1.0.scala b/tests/pos/alphanumeric-infix-operator-compat/B_1_c3.1.0.scala new file mode 100644 index 000000000000..2377b01f7fec --- /dev/null +++ b/tests/pos/alphanumeric-infix-operator-compat/B_1_c3.1.0.scala @@ -0,0 +1,3 @@ +class B: + def x(i: Int) = i + infix def y(i: Int) = i diff --git a/tests/pos/alphanumeric-infix-operator-compat/C_1_c3.2.0.scala b/tests/pos/alphanumeric-infix-operator-compat/C_1_c3.2.0.scala new file mode 100644 index 000000000000..c1e11d2547ee --- /dev/null +++ b/tests/pos/alphanumeric-infix-operator-compat/C_1_c3.2.0.scala @@ -0,0 +1,3 @@ +class C: + def x(i: Int) = i + infix def y(i: Int) = i diff --git a/tests/pos/alphanumeric-infix-operator-compat/D_1_c3.3.0.scala b/tests/pos/alphanumeric-infix-operator-compat/D_1_c3.3.0.scala new file mode 100644 index 000000000000..0e05a819694c --- /dev/null +++ b/tests/pos/alphanumeric-infix-operator-compat/D_1_c3.3.0.scala @@ -0,0 +1,3 @@ +class D: + def x(i: Int) = i + infix def y(i: Int) = i diff --git a/tests/pos/alphanumeric-infix-operator-compat/Test3.4_2.scala b/tests/pos/alphanumeric-infix-operator-compat/Test3.4_2.scala new file mode 100644 index 000000000000..dd6367518fb4 --- /dev/null +++ b/tests/pos/alphanumeric-infix-operator-compat/Test3.4_2.scala @@ -0,0 +1,15 @@ +//> using options -Werror + +import language.`3.4` + +def test1(a: A, b: B, c: C, d: D): Unit = + a x 1 // ok: was compiled with 3.0 + b x 1 // ok: was compiled with 3.1 + c x 1 // ok: was compiled with 3.2 + d x 1 // ok: was compiled with 3.3 + + // ok: is marked as infix + a y 2 + b y 2 + c y 2 + d y 2 diff --git a/tests/pos/alphanumeric-infix-operator-compat/TestFuture_2.scala b/tests/pos/alphanumeric-infix-operator-compat/TestFuture_2.scala new file mode 100644 index 000000000000..cf6a9d0adfc2 --- /dev/null +++ b/tests/pos/alphanumeric-infix-operator-compat/TestFuture_2.scala @@ -0,0 +1,13 @@ +import language.future + +def test2(a: A, b: B, c: C, d: D): Unit = + a x 1 // ok: was compiled with 3.0 + b x 1 // ok: was compiled with 3.1 + c x 1 // ok: was compiled with 3.2 + d x 1 // ok: was compiled with 3.3 + + // ok: is marked as infix + a y 2 + b y 2 + c y 2 + d y 2 diff --git a/tests/pos/i7424c.scala b/tests/pos/i7424c.scala index 9a02328f82a2..b40ef00a48dd 100644 --- a/tests/pos/i7424c.scala +++ b/tests/pos/i7424c.scala @@ -1,6 +1,6 @@ //> using options -Werror object Main extends App: - enum Extends[A, B]: + infix enum Extends[A, B]: case Ev[B, A <: B]() extends (A Extends B) def cast(a: A): B = this match { diff --git a/tests/semanticdb/metac.expect b/tests/semanticdb/metac.expect index 717bfb0505fb..2fd8eca47a7b 100644 --- a/tests/semanticdb/metac.expect +++ b/tests/semanticdb/metac.expect @@ -3460,6 +3460,7 @@ Text => empty Language => Scala Symbols => 12 entries Occurrences => 33 entries +Diagnostics => 1 entries Synthetics => 4 entries Symbols: @@ -3511,6 +3512,11 @@ Occurrences: [20:4..20:13): scalameta -> scala/reflect/Selectable#selectDynamic(). [21:4..21:19): StructuralTypes -> example/StructuralTypes. +Diagnostics: +[14:20..14:23): [warning] Alphanumeric method foo is not declared infix; it should not be used as infix operator. +Instead, use method syntax .foo(...) or backticked identifier `foo`. +The latter can be rewritten automatically under -rewrite -source 3.4-migration. + Synthetics: [12:2..12:6):user => reflectiveSelectable(*) [13:2..13:6):user => reflectiveSelectable(*)