diff --git a/library/src-bootstrapped/scala/annotation/experimental.scala b/library/src-bootstrapped/scala/annotation/experimental.scala index 185db51c07c1..634cfe12db7f 100644 --- a/library/src-bootstrapped/scala/annotation/experimental.scala +++ b/library/src-bootstrapped/scala/annotation/experimental.scala @@ -5,6 +5,5 @@ package scala.annotation * @see [[https://dotty.epfl.ch/docs/reference/other-new-features/experimental-defs]] * @syntax markdown */ -@deprecatedInheritance("Scheduled for being final in the future", "3.4.0") -class experimental(message: String) extends StaticAnnotation: +final class experimental(message: String) extends StaticAnnotation: def this() = this("") diff --git a/library/src-non-bootstrapped/scala/annotation/experimental.scala b/library/src-non-bootstrapped/scala/annotation/experimental.scala index dbc3296aa1ab..e879b47e12ff 100644 --- a/library/src-non-bootstrapped/scala/annotation/experimental.scala +++ b/library/src-non-bootstrapped/scala/annotation/experimental.scala @@ -1,4 +1,3 @@ package scala.annotation -@deprecatedInheritance("Scheduled for being final in the future", "3.4.0") -class experimental extends StaticAnnotation +final class experimental extends StaticAnnotation diff --git a/library/src/scala/runtime/stdLibPatches/Predef.scala b/library/src/scala/runtime/stdLibPatches/Predef.scala index 2146254a9467..7abd92e408f8 100644 --- a/library/src/scala/runtime/stdLibPatches/Predef.scala +++ b/library/src/scala/runtime/stdLibPatches/Predef.scala @@ -62,4 +62,8 @@ object Predef: * `eq` or `ne` methods, only `==` and `!=` inherited from `Any`. */ inline def ne(inline y: AnyRef | Null): Boolean = !(x eq y) + + extension (opt: Option.type) + @experimental + inline def fromNullable[T](t: T | Null): Option[T] = Option(t).asInstanceOf[Option[T]] end Predef diff --git a/project/MiMaFilters.scala b/project/MiMaFilters.scala index 904367d15ecd..5ccb70ad6fdf 100644 --- a/project/MiMaFilters.scala +++ b/project/MiMaFilters.scala @@ -9,6 +9,7 @@ object MiMaFilters { // Additions that require a new minor version of the library Build.mimaPreviousDottyVersion -> Seq( ProblemFilters.exclude[DirectMissingMethodProblem]("scala.annotation.experimental.this"), + ProblemFilters.exclude[FinalClassProblem]("scala.annotation.experimental"), ), // Additions since last LTS @@ -56,6 +57,8 @@ object MiMaFilters { ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#MethodTypeModule.apply"), ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#MethodTypeMethods.methodTypeKind"), ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#MethodTypeMethods.isContextual"), + // Change `experimental` annotation to a final class + ProblemFilters.exclude[FinalClassProblem]("scala.annotation.experimental"), ), // Breaking changes since last LTS diff --git a/tests/explicit-nulls/neg/from-nullable.scala b/tests/explicit-nulls/neg/from-nullable.scala new file mode 100644 index 000000000000..ab4ab7f63e8e --- /dev/null +++ b/tests/explicit-nulls/neg/from-nullable.scala @@ -0,0 +1,6 @@ +import scala.annotation.experimental + +@experimental def testFromNullable = + val s: String | Null = "abc" + val sopt1: Option[String] = Option(s) // error + val sopt2: Option[String] = Option.fromNullable(s) // ok \ No newline at end of file diff --git a/tests/explicit-nulls/run/from-nullable.check b/tests/explicit-nulls/run/from-nullable.check new file mode 100644 index 000000000000..43d418e64a03 --- /dev/null +++ b/tests/explicit-nulls/run/from-nullable.check @@ -0,0 +1,2 @@ +hello +None diff --git a/tests/explicit-nulls/run/from-nullable.scala b/tests/explicit-nulls/run/from-nullable.scala new file mode 100644 index 000000000000..6f01e402e790 --- /dev/null +++ b/tests/explicit-nulls/run/from-nullable.scala @@ -0,0 +1,17 @@ +object Test: + import scala.annotation.experimental + + @experimental def main(args: Array[String]): Unit = + val s1: String | Null = "hello" + val s2: String | Null = null + + val opts1: Option[String] = Option.fromNullable(s1) + val opts2: Option[String] = Option.fromNullable(s2) + + opts1 match + case Some(s) => println(s) + case None => println("None") + + opts2 match + case Some(s) => println(s) + case None => println("None") diff --git a/tests/neg/experimentalExperimental.scala b/tests/neg/experimentalExperimental.scala new file mode 100644 index 000000000000..9011a3e49225 --- /dev/null +++ b/tests/neg/experimentalExperimental.scala @@ -0,0 +1 @@ +class MyExperimentalAnnot extends scala.annotation.experimental // error diff --git a/tests/pos/experimentalExperimental.scala b/tests/pos/experimentalExperimental.scala deleted file mode 100644 index 4b57e5b94346..000000000000 --- a/tests/pos/experimentalExperimental.scala +++ /dev/null @@ -1 +0,0 @@ -class MyExperimentalAnnot extends scala.annotation.experimental diff --git a/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala b/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala index 5e6e5700b719..76c08fa24213 100644 --- a/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala +++ b/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala @@ -76,6 +76,9 @@ val experimentalDefinitionInLibrary = Set( "scala.Tuple$.Reverse", // can be stabilized in 3.5 "scala.Tuple$.ReverseOnto", // can be stabilized in 3.5 "scala.runtime.Tuples$.reverse", // can be stabilized in 3.5 + + // New feature: fromNullable for explicit nulls + "scala.Predef$.fromNullable", )