From 9fd40bb7292e9a49cd23f4de3765797a44309a31 Mon Sep 17 00:00:00 2001 From: pierscin <30854591+pierscin@users.noreply.github.com> Date: Tue, 6 Oct 2026 09:57:25 +0200 Subject: [PATCH 1/2] Migrate to Scala 3.9.0 LTS - Scala 3: 3.3.8 -> 3.9.0 - Scala 3 JVM: -java-output-version 11 -> 17 (required by Scala 3.9) - remove -Yfuture-lazy-vals (rejected by 3.9 as a bad option) - fix new 3.9 migration warnings in a Scala 2 compatible way (`_` type wildcards, some `: _*` splices) - suppress remaining `xs: _*` vararg-splice warnings on Scala 3 only (needed while cross-building for 2.12) - Scala Steward: pin scala3-library(_sjs1) to 3.9. Co-Authored-By: Claude Opus 5.5 --- .scala-steward.conf | 4 ++-- build.sbt | 12 +++++++++--- core/src/main/scala/sttp/model/Uri.scala | 2 +- core/src/main/scala/sttp/model/UriInterpolator.scala | 8 ++++---- .../scalanative/sttp/model/internal/idn/CIdn.scala | 2 +- 5 files changed, 17 insertions(+), 11 deletions(-) diff --git a/.scala-steward.conf b/.scala-steward.conf index 0c8bf58e..b6b6f7d3 100644 --- a/.scala-steward.conf +++ b/.scala-steward.conf @@ -4,6 +4,6 @@ updates.ignore = [ {groupId = "org.scala-lang", artifactId = "scala-compiler", version = "3."} ] updates.pin = [ - {groupId = "org.scala-lang", artifactId = "scala3-library", version = "3.3."}, - {groupId = "org.scala-lang", artifactId = "scala3-library_sjs1", version = "3.3."} + {groupId = "org.scala-lang", artifactId = "scala3-library", version = "3.9."}, + {groupId = "org.scala-lang", artifactId = "scala3-library_sjs1", version = "3.9."} ] diff --git a/build.sbt b/build.sbt index 3dd5bb38..3d75e833 100644 --- a/build.sbt +++ b/build.sbt @@ -5,7 +5,7 @@ import com.softwaremill.Publish.ossPublishSettings val scala2_12 = "2.12.21" val scala2_13 = "2.13.18" val scala2 = List(scala2_12, scala2_13) -val scala3 = List("3.3.8") +val scala3 = List("3.9.0") val scalaTestVersion = "3.2.20" val scalaTestPlusScalaCheckVersion = "3.2.20.0" @@ -18,12 +18,18 @@ def dependenciesFor(version: String)(deps: (Option[(Long, Long)] => ModuleID)*): val commonSettings = commonSmlBuildSettings ++ ossPublishSettings ++ Seq( organization := "com.softwaremill.sttp.model", mimaPreviousArtifacts := Set.empty, - versionScheme := Some("semver-spec") + versionScheme := Some("semver-spec"), + // TODO(scala-3.9): `xs: _*` vararg splices are still needed while cross-building for Scala 2.12 (no `-Xsource:3`); + // replace with `xs*` and drop this once Scala 2.12 support is removed + scalacOptions ++= { + if (ScalaArtifacts.isScala3(scalaVersion.value)) Seq("-Wconf:msg=is no longer supported for vararg splices:s") + else Seq.empty + } ) val commonJvmSettings = commonSettings ++ Seq( scalacOptions ++= - (if (ScalaArtifacts.isScala3(scalaVersion.value)) Seq("-Yfuture-lazy-vals", "-java-output-version", "11") + (if (ScalaArtifacts.isScala3(scalaVersion.value)) Seq("-java-output-version", "17") else Seq("-release", "8")), ideSkipProject := (scalaVersion.value != scala2_13), libraryDependencies ++= Seq( diff --git a/core/src/main/scala/sttp/model/Uri.scala b/core/src/main/scala/sttp/model/Uri.scala index 8c5e0f1b..a6661da1 100644 --- a/core/src/main/scala/sttp/model/Uri.scala +++ b/core/src/main/scala/sttp/model/Uri.scala @@ -124,7 +124,7 @@ case class Uri( /** Replace path with the given path segment. */ @deprecated(message = "Use addPath, withPath or withWholePath", since = "1.2.0") - def pathSegments(s1: Segment, s2: Segment, ss: Segment*): Uri = withPathSegments(s1, s2, ss: _*) + def pathSegments(s1: Segment, s2: Segment, ss: Segment*): Uri = withPathSegments(s1 :: s2 :: ss.toList) /** Replace path with the given path segments. */ @deprecated(message = "Use addPath, withPath or withWholePath", since = "1.2.0") diff --git a/core/src/main/scala/sttp/model/UriInterpolator.scala b/core/src/main/scala/sttp/model/UriInterpolator.scala index 58096758..17050f8e 100644 --- a/core/src/main/scala/sttp/model/UriInterpolator.scala +++ b/core/src/main/scala/sttp/model/UriInterpolator.scala @@ -178,9 +178,9 @@ object UriInterpolator { private object Tokenizer { object Scheme extends Tokenizer { - private val alphabet = Set(('a' to 'z'): _*) ++ Set(('A' to 'Z'): _*) + private val alphabet = ('a' to 'z').toSet ++ ('A' to 'Z').toSet private val firstChar = FastCharSet(alphabet) - private val nonFirstChars = FastCharSet(alphabet ++ Set(('0' to '9'): _*) ++ Set('+', '.', '-')) + private val nonFirstChars = FastCharSet(alphabet ++ ('0' to '9').toSet ++ Set('+', '.', '-')) // This is equivalent to such regular expression "[A-Za-z][A-Za-z0-9+.-]*" private def findPrefix(s: String): Option[String] = { @@ -252,7 +252,7 @@ object UriInterpolator { } object Authority extends Tokenizer { - private val HexChars = FastCharSet(Set(('0' to '9'): _*) ++ Set(('a' to 'f'): _*) ++ Set(('A' to 'F'): _*) + ':') + private val HexChars = FastCharSet(('0' to '9').toSet ++ ('a' to 'f').toSet ++ ('A' to 'F').toSet + ':') private def isIpV6Like(str: String): Boolean = { val len = str.length() @@ -626,7 +626,7 @@ object UriInterpolator { v <- anyToStringOpt(ve) } yield QF.KeyValue(k, v) - def seqToQueryFragments(s: Seq[_]): Vector[QF] = { + def seqToQueryFragments(s: Seq[Any]): Vector[QF] = { s.flatMap { case (ke, ve) => expressionPairToQueryFragment(ke, ve) case ve => anyToStringOpt(ve).map(QF.Value(_)) diff --git a/core/src/main/scalanative/sttp/model/internal/idn/CIdn.scala b/core/src/main/scalanative/sttp/model/internal/idn/CIdn.scala index ac0d0625..1dba40c8 100644 --- a/core/src/main/scalanative/sttp/model/internal/idn/CIdn.scala +++ b/core/src/main/scalanative/sttp/model/internal/idn/CIdn.scala @@ -12,5 +12,5 @@ private[idn] object CIdn { def errorMsg(rc: CInt): CString = extern @name("idn2_free") - def free(ptr: Ptr[_]): Unit = extern + def free(ptr: Ptr[Byte]): Unit = extern } From 4f8feb317aad29e08b26c55be43a1f0449b321ae Mon Sep 17 00:00:00 2001 From: pierscin <30854591+pierscin@users.noreply.github.com> Date: Wed, 7 Oct 2026 10:22:24 +0200 Subject: [PATCH 2/2] Fix the new Scala 3.9 vararg splice warnings instead of suppressing them Enable -Xsource:3 in the Scala 2 builds, so the shared sources can use `xs*`; switch the scalafmt dialect to scala213source3 so that it formats `xs*` correctly. Co-Authored-By: Claude Opus 5.5 --- .scalafmt.conf | 2 +- build.sbt | 7 +------ core/src/main/scala/sttp/model/Uri.scala | 6 +++--- core/src/main/scala/sttp/model/UriInterpolator.scala | 4 ++-- 4 files changed, 7 insertions(+), 12 deletions(-) diff --git a/.scalafmt.conf b/.scalafmt.conf index e6196f88..7a43d8b3 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -1,4 +1,4 @@ -runner.dialect = scala213 +runner.dialect = scala213source3 version = 3.11.5 maxColumn = 120 fileOverride { diff --git a/build.sbt b/build.sbt index 3d75e833..e37c94ce 100644 --- a/build.sbt +++ b/build.sbt @@ -19,12 +19,7 @@ val commonSettings = commonSmlBuildSettings ++ ossPublishSettings ++ Seq( organization := "com.softwaremill.sttp.model", mimaPreviousArtifacts := Set.empty, versionScheme := Some("semver-spec"), - // TODO(scala-3.9): `xs: _*` vararg splices are still needed while cross-building for Scala 2.12 (no `-Xsource:3`); - // replace with `xs*` and drop this once Scala 2.12 support is removed - scalacOptions ++= { - if (ScalaArtifacts.isScala3(scalaVersion.value)) Seq("-Wconf:msg=is no longer supported for vararg splices:s") - else Seq.empty - } + scalacOptions ++= (if (ScalaArtifacts.isScala3(scalaVersion.value)) Seq.empty else Seq("-Xsource:3")) ) val commonJvmSettings = commonSettings ++ Seq( diff --git a/core/src/main/scala/sttp/model/Uri.scala b/core/src/main/scala/sttp/model/Uri.scala index a6661da1..4c067080 100644 --- a/core/src/main/scala/sttp/model/Uri.scala +++ b/core/src/main/scala/sttp/model/Uri.scala @@ -174,11 +174,11 @@ case class Uri( /** Adds the given parameters to the query. */ @deprecated(message = "Use addParam or withParam", since = "1.2.0") - def params(ps: (String, String)*): Uri = addParams(ps: _*) + def params(ps: (String, String)*): Uri = addParams(ps*) def addParam(k: String, v: String): Uri = addParams(k -> v) def addParam(k: String, v: Option[String]): Uri = v.map(addParam(k, _)).getOrElse(this) - def addParams(ps: Map[String, String]): Uri = addParams(ps.toSeq: _*) + def addParams(ps: Map[String, String]): Uri = addParams(ps.toSeq*) def addParams(mqp: QueryParams): Uri = { this.copy(querySegments = querySegments ++ QuerySegment.fromQueryParams(mqp)) } @@ -195,7 +195,7 @@ case class Uri( def withParam(k: String, v: Option[String]): Uri = v.map(withParam(k, _)).getOrElse(this) /** Replace query with the given parameters. */ - def withParams(ps: Map[String, String]): Uri = withParams(ps.toSeq: _*) + def withParams(ps: Map[String, String]): Uri = withParams(ps.toSeq*) /** Replace query with the given parameters. */ def withParams(mqp: QueryParams): Uri = this.copy(querySegments = QuerySegment.fromQueryParams(mqp).toList) diff --git a/core/src/main/scala/sttp/model/UriInterpolator.scala b/core/src/main/scala/sttp/model/UriInterpolator.scala index 17050f8e..5f62dc7a 100644 --- a/core/src/main/scala/sttp/model/UriInterpolator.scala +++ b/core/src/main/scala/sttp/model/UriInterpolator.scala @@ -36,7 +36,7 @@ trait UriInterpolator { * @throws IllegalArgumentException * In case of a validation error. For a safe version, see [[Uri.parse()]]. */ - def uri(args: Any*): Uri = UriInterpolator.interpolate(sc, args: _*) + def uri(args: Any*): Uri = UriInterpolator.interpolate(sc, args*) } } @@ -65,7 +65,7 @@ object UriInterpolator { if (isScEmptyString && areArgsEmptyString) { throw new IllegalArgumentException("empty string is not valid uri") } - val tokens = tokenize(sc, args: _*) + val tokens = tokenize(sc, args*) val (uri, leftTokens) = builders.foldLeft((startingUri, filterNulls(tokens))) { case ((u, t), builder) =>