Skip to content
Open
21 changes: 21 additions & 0 deletions library/src/scala/annotation/unchecked/uncheckedOverride.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Scala (https://www.scala-lang.org)
*
* Copyright EPFL and Lightbend, Inc. dba Akka
*
* Licensed under Apache License 2.0
* (http://www.apache.org/licenses/LICENSE-2.0).
*
* See the NOTICE file distributed with this work for
* additional information regarding copyright ownership.
*/

package scala.annotation.unchecked

import scala.annotation.StaticAnnotation

/**
* Marking a definition `@uncheckedOverride` is equivalent to the `override` keyword, except that overriding is not
* enforced. A definition marked `@uncheckedOverride` is allowed to override nothing.
*/
final class uncheckedOverride extends StaticAnnotation
16 changes: 8 additions & 8 deletions library/src/scala/collection/IterableOnce.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1174,12 +1174,12 @@ transparent trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOn
*
* $willNotTerminateInf
*
* @param cmp An ordering to be used for comparing elements.
* @param ord An ordering to be used for comparing elements.
* @tparam B The result type of the function `f`.
* @param f The measuring function.
* @throws UnsupportedOperationException if this $coll is empty.
* @return the first element of this $coll with the largest value measured by function `f`
* with respect to the ordering `cmp`.
* with respect to the ordering `ord`.
*/
def maxBy[B](f: A -> B)(implicit ord: Ordering[B]): A =
knownSize match {
Expand Down Expand Up @@ -1214,11 +1214,11 @@ transparent trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOn
*
* $willNotTerminateInf
*
* @param cmp An ordering to be used for comparing elements.
* @param ord An ordering to be used for comparing elements.
* @tparam B The result type of the function `f`.
* @param f The measuring function.
* @return an option value containing the first element of this $coll with the
* largest value measured by function `f` with respect to the ordering `cmp`.
* largest value measured by function `f` with respect to the ordering `ord`.
*/
def maxByOption[B](f: A -> B)(implicit ord: Ordering[B]): Option[A] =
knownSize match {
Expand All @@ -1230,12 +1230,12 @@ transparent trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOn
*
* $willNotTerminateInf
*
* @param cmp An ordering to be used for comparing elements.
* @param ord An ordering to be used for comparing elements.
* @tparam B The result type of the function `f`.
* @param f The measuring function.
* @throws UnsupportedOperationException if this $coll is empty.
* @return the first element of this $coll with the smallest value measured by function `f`
* with respect to the ordering `cmp`.
* with respect to the ordering `ord`.
*/
def minBy[B](f: A -> B)(implicit ord: Ordering[B]): A =
knownSize match {
Expand All @@ -1247,12 +1247,12 @@ transparent trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOn
*
* $willNotTerminateInf
*
* @param cmp An ordering to be used for comparing elements.
* @param ord An ordering to be used for comparing elements.
* @tparam B The result type of the function `f`.
* @param f The measuring function.
* @return an option value containing the first element of this $coll
* with the smallest value measured by function `f`
* with respect to the ordering `cmp`.
* with respect to the ordering `ord`.
*/
def minByOption[B](f: A -> B)(implicit ord: Ordering[B]): Option[A] =
knownSize match {
Expand Down
2 changes: 1 addition & 1 deletion library/src/scala/collection/mutable/Buffer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ trait IndexedBuffer[A] extends IndexedSeq[A]

/** Replaces the contents of this $coll with the filtered result.
*
* @param f the filtering function
* @param p the filtering function
* @return this $coll
*/
def filterInPlace(p: A => Boolean): this.type = {
Expand Down
5 changes: 3 additions & 2 deletions library/src/scala/math/Ordering.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import java.util.Comparator

import scala.language.implicitConversions
import scala.annotation.migration
import scala.annotation.unchecked.uncheckedOverride

/** Ordering is a trait whose instances each represent a strategy for sorting
* instances of a type.
Expand Down Expand Up @@ -104,10 +105,10 @@ trait Ordering[T] extends Comparator[T] with PartialOrdering[T] with Serializabl
override def equiv(x: T, y: T): Boolean = compare(x, y) == 0

/** Returns `x` if `x` >= `y`, otherwise `y`. */
def max[U <: T](x: U, y: U): U = if (gteq(x, y)) x else y
@uncheckedOverride def max[U <: T](x: U, y: U): U = if (gteq(x, y)) x else y

/** Returns `x` if `x` <= `y`, otherwise `y`. */
def min[U <: T](x: U, y: U): U = if (lteq(x, y)) x else y
@uncheckedOverride def min[U <: T](x: U, y: U): U = if (lteq(x, y)) x else y

/** Returns the opposite ordering of this one.
*
Expand Down
35 changes: 19 additions & 16 deletions library/src/scala/runtime/ScalaRunTime.scala
Original file line number Diff line number Diff line change
Expand Up @@ -289,20 +289,23 @@ object ScalaRunTime {
// Use `null` in places where we want to make sure the reference is cleared.
private[scala] inline def nullForGC[T]: T = null.asInstanceOf[T]

// Convert arrays to immutable.ArraySeq for use with Scala varargs.
// By construction, calls to these methods always receive a fresh (and non-null), non-empty array.
// In cases where an empty array would appear, the compiler uses a direct reference to Nil instead.
// Synthetic Java varargs forwarders (@annotation.varargs or varargs bridges when overriding) may pass
// `null` to these methods; but returning `null` or `ArraySeq(null)` makes little difference in practice.
def genericWrapArray[T](xs: Array[T]): ArraySeq[T] = ArraySeq.unsafeWrapArray(xs)
def wrapRefArray[T <: AnyRef | Null](xs: Array[T]): ArraySeq[T] = new ArraySeq.ofRef[T](xs)
def wrapIntArray(xs: Array[Int]): ArraySeq[Int] = new ArraySeq.ofInt(xs)
def wrapDoubleArray(xs: Array[Double]): ArraySeq[Double] = new ArraySeq.ofDouble(xs)
def wrapLongArray(xs: Array[Long]): ArraySeq[Long] = new ArraySeq.ofLong(xs)
def wrapFloatArray(xs: Array[Float]): ArraySeq[Float] = new ArraySeq.ofFloat(xs)
def wrapCharArray(xs: Array[Char]): ArraySeq[Char] = new ArraySeq.ofChar(xs)
def wrapByteArray(xs: Array[Byte]): ArraySeq[Byte] = new ArraySeq.ofByte(xs)
def wrapShortArray(xs: Array[Short]): ArraySeq[Short] = new ArraySeq.ofShort(xs)
def wrapBooleanArray(xs: Array[Boolean]): ArraySeq[Boolean] = new ArraySeq.ofBoolean(xs)
def wrapUnitArray(xs: Array[Unit]): ArraySeq[Unit] = new ArraySeq.ofUnit(xs)
// Convert an array to an immutable.ArraySeq for use with Scala varargs.
// `foo(x, y)` is compiled to `foo(wrapXArray(Array(x, y))).
// For `foo()`, the Scala 2 compiler uses a reference to `Nil` instead; Scala 3 doesn't have this special case.
//
// The `null` checks are there for backwards compatibility. They were removed in 2.13.17 (scala/scala#11021)
// which led to a ticket in Scala 3 (scala/scala3#24204). The argument may be null:
// - When calling a Scala `@varargs` method from Java
// - When using an array as sequence argument in Scala 3: `foo((null: Array[X])*)`
def genericWrapArray[T](xs: Array[T]): ArraySeq[T] = if (xs ne null) ArraySeq.unsafeWrapArray(xs) else null.asInstanceOf[ArraySeq[T]]
def wrapRefArray[T <: AnyRef | Null](xs: Array[T]): ArraySeq[T] = if (xs ne null) new ArraySeq.ofRef[T](xs) else null.asInstanceOf[ArraySeq[T]]
def wrapIntArray(xs: Array[Int]): ArraySeq[Int] = if (xs ne null) new ArraySeq.ofInt(xs) else null.asInstanceOf[ArraySeq[Int]]
def wrapDoubleArray(xs: Array[Double]): ArraySeq[Double] = if (xs ne null) new ArraySeq.ofDouble(xs) else null.asInstanceOf[ArraySeq[Double]]
def wrapLongArray(xs: Array[Long]): ArraySeq[Long] = if (xs ne null) new ArraySeq.ofLong(xs) else null.asInstanceOf[ArraySeq[Long]]
def wrapFloatArray(xs: Array[Float]): ArraySeq[Float] = if (xs ne null) new ArraySeq.ofFloat(xs) else null.asInstanceOf[ArraySeq[Float]]
def wrapCharArray(xs: Array[Char]): ArraySeq[Char] = if (xs ne null) new ArraySeq.ofChar(xs) else null.asInstanceOf[ArraySeq[Char]]
def wrapByteArray(xs: Array[Byte]): ArraySeq[Byte] = if (xs ne null) new ArraySeq.ofByte(xs) else null.asInstanceOf[ArraySeq[Byte]]
def wrapShortArray(xs: Array[Short]): ArraySeq[Short] = if (xs ne null) new ArraySeq.ofShort(xs) else null.asInstanceOf[ArraySeq[Short]]
def wrapBooleanArray(xs: Array[Boolean]): ArraySeq[Boolean] = if (xs ne null) new ArraySeq.ofBoolean(xs) else null.asInstanceOf[ArraySeq[Boolean]]
def wrapUnitArray(xs: Array[Unit]): ArraySeq[Unit] = if (xs ne null) new ArraySeq.ofUnit(xs) else null.asInstanceOf[ArraySeq[Unit]]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@noti0na1 is it ok? Should we adjust the signature as well? I'm not sure what's the best approach here, should we kept it as it is for backward compatibility, make output nullable, or both input and output

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The signatures look good to me. We can use ScalaRunTime.mapNull for rhs to deal with situations like this.

}
154 changes: 154 additions & 0 deletions project/scripts/syncStdLib.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
//> using scala 3.7
//> using toolkit default

import scala.util.matching.Regex

import scala.util.CommandLineParser.FromString

given FromString[os.Path] with
def fromString(s: String) = os.Path(s, os.pwd)

/** Sync standard library changes from scala/scala to scala/scala3.
*
* Usage: scala run syncStdLib.scala -- <scala2-repo-path> <from-tag> <to-tag>
*
* Example: scala syncStdLib.scala -- ../scala2 v2.13.17 v2.13.18
*/
@main def syncStdLib(
scala2RepoPath: os.Path,
fromTag: String,
toTag: String
): Unit =
val scala2Path = os.Path(scala2RepoPath, os.pwd)
val scala3Path = os.pwd

val workDir = scala3Path / "stdlib-sync-work"
val patchDir = workDir / "patches"

println(s"Scala 2 repo: $scala2Path")
println(s"Scala 3 repo: $scala3Path")
println(s"Syncing: $fromTag..$toTag")
println()

// Clean up and create directories
os.remove.all(workDir)
os.makeDir.all(patchDir)

// -------------------------
// 1) Fetch latest changes in scala2 repo
// -------------------------
println(s"Fetching tags $fromTag and $toTag from scala2...")
os.proc("git", "fetch", "--tags", "origin", fromTag, toTag)
.call(cwd = scala2Path, stdout = os.Inherit, stderr = os.Inherit)

// Show what will be synced
println(s"\nCommits to sync ($fromTag..$toTag):")
val logResult = os
.proc("git", "log", "--oneline", s"$fromTag..$toTag", "--", "src/library")
.call(cwd = scala2Path)
logResult.out.lines().foreach(println)
println()

// -------------------------
// 2) Create patch series for src/library only, paths relative to src/library/
// -------------------------
println("Creating patches...")
os.proc(
"git",
"format-patch",
"--output-directory",
patchDir.toString,
"--relative=src/library",
"--no-signature",
s"$fromTag..$toTag",
"--",
"src/library"
).call(cwd = scala2Path, stdout = os.Inherit, stderr = os.Inherit)

val patchFiles = os.list(patchDir).filter(_.ext == "patch").sorted
println(s"Created ${patchFiles.size} patches")

// -------------------------
// 3) Rewrite patches:
// - qualify #NNNN -> scala/scala#NNNN
// - add footer: "Upstream: scala/scala@<sha>"
// -------------------------
println("\nRewriting patches...")
rewritePatches(patchDir)

// -------------------------
// 4) Apply onto scala3
// -------------------------
val branchName = s"stdlib-sync/${toTag.stripPrefix("v")}"
println(s"\nCreating branch $branchName...")

os.proc("git", "checkout", "-b", branchName, "origin/main")
.call(cwd = scala3Path, stdout = os.Inherit, stderr = os.Inherit)

println(s"Applying ${patchFiles.size} patches...")

val patches = os.list(patchDir).filter(_.ext == "patch").sorted
val amResult = os
.proc("git", "am", "--3way", "--directory=library/src", patches)
.call(
cwd = scala3Path,
check = false,
stdout = os.Inherit,
stderr = os.Inherit
)

if amResult.exitCode != 0 then
println()
println("Conflicts occurred.")
println("Resolve, then run: git am --continue")
println("Abort with: git am --abort")
sys.exit(1)

println("\nPatches applied successfully!")
os.remove.all(workDir)

println(s"\nTo push the branch, run:")
println(s" git push -u origin $branchName")
end syncStdLib

/** Rewrite patches to:
* - Qualify GitHub issue refs: #NNNN -> scala/scala#NNNN
* - Add footer: "Upstream: scala/scala@<sha>"
*/
def rewritePatches(patchDir: os.Path): Unit =
val patchFiles = os.list(patchDir).filter(_.ext == "patch").sorted
val issueRefPattern = """(?<!\w)#(\d+)""".r
val fromLinePattern = """^From ([0-9a-f]{40}) """.r

var rewrittenCount = 0

for patch <- patchFiles do
val raw = os.read(patch)

// Extract upstream commit SHA from the "From <sha> ..." line
val upstream = fromLinePattern.findFirstMatchIn(raw).map(_.group(1))

// Qualify github refs in the patch (commit message is inside)
var rewritten = issueRefPattern.replaceAllIn(raw, "scala/scala#$1")

// Insert "Upstream: ..." footer into the commit message.
// In format-patch output, commit message ends just before "\n---\n"
upstream.foreach { sha =>
val footer = s"Upstream: scala/scala@$sha"
if !rewritten.contains(footer) then
rewritten.split("\n---\n", 2) match
case Array(msg, rest) =>
var newMsg = msg
if !newMsg.endsWith("\n") then newMsg += "\n"
if !newMsg.endsWith("\n\n") then newMsg += "\n"
newMsg += s"$footer\n"
rewritten = newMsg + "\n---\n" + rest
case _ => // No split point found, leave as is
}

if rewritten != raw then
os.write.over(patch, rewritten)
rewrittenCount += 1

println(s"Rewrote $rewrittenCount patches")
end rewritePatches
Loading