diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0c45e14..ea15160 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -90,11 +90,11 @@ jobs: - name: Make target directories if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') - run: mkdir -p target .js/target .jvm/target .native/target lib/.js/target lib/.jvm/target project/target + run: mkdir -p target macros/.jvm/target testkit/.js/target .js/target core/.js/target macros/.js/target core/.jvm/target .jvm/target .native/target lib/.js/target testkit/.jvm/target lib/.jvm/target project/target - name: Compress target directories if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') - run: tar cf targets.tar target .js/target .jvm/target .native/target lib/.js/target lib/.jvm/target project/target + run: tar cf targets.tar target macros/.jvm/target testkit/.js/target .js/target core/.js/target macros/.js/target core/.jvm/target .jvm/target .native/target lib/.js/target testkit/.jvm/target lib/.jvm/target project/target - name: Upload target directories if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') diff --git a/.scalafmt.conf b/.scalafmt.conf index 7cf6d67..f9fb0f0 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -1,6 +1,11 @@ version = 3.6.0 -runner.dialect = scala213 +runner.dialect = Scala213Source3 +fileOverride { + "glob:**/src/{main,test}/scala-3/**" { + runner.dialect = Scala3 + } +} preset = defaultWithAlign @@ -10,4 +15,4 @@ indent { callSite = 2 defnSite = 2 extendSite = 2 -} \ No newline at end of file +} diff --git a/build.sbt b/build.sbt index 10ff199..2eaabbb 100644 --- a/build.sbt +++ b/build.sbt @@ -2,6 +2,10 @@ name := "scalac-options-root" ThisBuild / tlBaseVersion := "0.1" +// Mima validations against the prevous releases are intentionally disabled +// since the current changes are not (yet) supposed to be binary-compatible. +ThisBuild / tlMimaPreviousVersions := Set.empty + ThisBuild / organization := "org.typelevel" ThisBuild / organizationName := "Typelevel" ThisBuild / startYear := Some(2022) @@ -24,19 +28,75 @@ ThisBuild / crossScalaVersions := Seq( Scala3 ) // There's no reason not to cross-publish -lazy val root = tlCrossRootProject.aggregate(lib) +lazy val root = tlCrossRootProject.aggregate(core, macros, lib, testkit) +lazy val literallyVersion = "1.1.0" lazy val munitVersion = "0.7.29" lazy val scalacheckVersion = "1.17.0" +lazy val core = crossProject(JVMPlatform, JSPlatform) + .crossType(CrossType.Pure) + .in(file("core")) + .enablePlugins(NoPublishPlugin) + .settings( + name := "scalac-options-core" + ) + +lazy val macros = crossProject(JVMPlatform, JSPlatform) + .crossType(CrossType.Pure) + .in(file("macros")) + .dependsOn(core) + .enablePlugins(NoPublishPlugin) + .settings( + name := "scalac-options-macros", + scalacOptions := { + if (tlIsScala3.value) + scalacOptions.value.filterNot(_ == "-source:3.0-migration") :+ "-source:3.1" + else + scalacOptions.value + }, + libraryDependencies ++= Seq( + "org.typelevel" %%% "literally" % literallyVersion + ) ++ { + if (tlIsScala3.value) Nil + else + List("org.scala-lang" % "scala-reflect" % scalaVersion.value % Provided) + } + ) + lazy val lib = crossProject(JVMPlatform, JSPlatform) .crossType(CrossType.Pure) .in(file("lib")) + .dependsOn(core, macros) + .settings( + name := "scalac-options" + ) + .settings { + def projectMappings(cproj: sbtcrossproject.CrossProject) = Def.taskDyn { + cproj.projects(crossProjectPlatform.value) / Compile / packageBin / mappings + } + + Compile / packageBin / mappings ++= + projectMappings(core).value ++ projectMappings(macros).value + } + .settings { + def projectMappings(cproj: sbtcrossproject.CrossProject) = Def.taskDyn { + cproj.projects(crossProjectPlatform.value) / Compile / packageSrc / mappings + } + + Compile / packageSrc / mappings ++= + projectMappings(core).value ++ projectMappings(macros).value + } + +lazy val testkit = crossProject(JVMPlatform, JSPlatform) + .crossType(CrossType.Pure) + .in(file("testkit")) + .dependsOn(lib) .settings( - name := "scalac-options", + name := "scalac-options-testkit", libraryDependencies ++= Seq( - "org.scalameta" %%% "munit" % munitVersion % Test, - "org.scalacheck" %%% "scalacheck" % scalacheckVersion % Test, - "org.scalameta" %%% "munit-scalacheck" % munitVersion % Test + "org.scalacheck" %%% "scalacheck" % scalacheckVersion, + "org.scalameta" %%% "munit" % munitVersion % Test, + "org.scalameta" %%% "munit-scalacheck" % munitVersion % Test ) ) diff --git a/core/src/main/scala/org/typelevel/scalacoptions/ScalaVersion.scala b/core/src/main/scala/org/typelevel/scalacoptions/ScalaVersion.scala new file mode 100644 index 0000000..6542a3f --- /dev/null +++ b/core/src/main/scala/org/typelevel/scalacoptions/ScalaVersion.scala @@ -0,0 +1,101 @@ +/* + * Copyright 2022 Typelevel + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.typelevel.scalacoptions + +import org.typelevel.scalacoptions.internal.Parser + +import scala.Ordering.Implicits._ +import scala.collection.immutable + +abstract sealed case class ScalaVersion private (major: Long, minor: Long, patch: Long) { + + def isBetween(addedVersion: ScalaVersion, removedVersion: ScalaVersion): Boolean = + this >= addedVersion && this < removedVersion +} + +object ScalaVersion { + private def apply(major: Long, minor: Long, patch: Long): ScalaVersion = + new ScalaVersion(major, minor, patch) {} + + lazy val knownVersions: immutable.SortedSet[ScalaVersion] = { + val bld = immutable.SortedSet.newBuilder[ScalaVersion] + + def bldPatches(major: Long, minor: Long, lastPatch: Long): Unit = + for (patch <- 0L to lastPatch) + bld += ScalaVersion(major, minor, patch) + + bldPatches(2, 11, 12) + bldPatches(2, 12, 17) + bldPatches(2, 13, 9) + bldPatches(3, 0, 2) + bldPatches(3, 1, 3) + bldPatches(3, 2, 0) + + bld.result() + } + + def from(major: Long, minor: Long, patch: Long): Option[ScalaVersion] = { + val candidate = ScalaVersion(major, minor, patch) + if (knownVersions.contains(candidate)) Some(candidate) else None + } + + def unsafeFrom(major: Long, minor: Long, patch: Long): ScalaVersion = + from(major, minor, patch).getOrElse(throw invalidScalaVersionError(s"$major.$minor.$patch")) + + object fromString { + def apply(s: String): Option[ScalaVersion] = + Parser.parseGenericVersion(s).flatMap(Function.tupled(from _)) + def unapply(s: String): Option[ScalaVersion] = apply(s) + + def unsafe(s: String): ScalaVersion = apply(s).getOrElse(throw invalidScalaVersionError(s)) + } + + @deprecated("use literal `sver\"2.11.0\"` instead", "") + val V2_11_0 = ScalaVersion(2, 11, 0) + @deprecated("use literal `sver\"2.11.11\"` instead", "") + val V2_11_11 = ScalaVersion(2, 11, 11) + @deprecated("use literal `sver\"2.12.0\"` instead", "") + val V2_12_0 = ScalaVersion(2, 12, 0) + @deprecated("use literal `sver\"2.12.2\"` instead", "") + val V2_12_2 = ScalaVersion(2, 12, 2) + @deprecated("use literal `sver\"2.12.5\"` instead", "") + val V2_12_5 = ScalaVersion(2, 12, 5) + @deprecated("use literal `sver\"2.12.13\"` instead", "") + val V2_12_13 = ScalaVersion(2, 12, 13) + @deprecated("use literal `sver\"2.13.0\"` instead", "") + val V2_13_0 = ScalaVersion(2, 13, 0) + @deprecated("use literal `sver\"2.13.2\"` instead", "") + val V2_13_2 = ScalaVersion(2, 13, 2) + @deprecated("use literal `sver\"2.13.3\"` instead", "") + val V2_13_3 = ScalaVersion(2, 13, 3) + @deprecated("use literal `sver\"2.13.4\"` instead", "") + val V2_13_4 = ScalaVersion(2, 13, 4) + @deprecated("use literal `sver\"2.13.5\"` instead", "") + val V2_13_5 = ScalaVersion(2, 13, 5) + @deprecated("use literal `sver\"2.13.6\"` instead", "") + val V2_13_6 = ScalaVersion(2, 13, 6) + @deprecated("use literal `sver\"3.0.0\"` instead", "") + val V3_0_0 = ScalaVersion(3, 0, 0) + @deprecated("use literal `sver\"3.1.0\"` instead", "") + val V3_1_0 = ScalaVersion(3, 1, 0) + + implicit lazy val scalaVersionOrdering: Ordering[ScalaVersion] = + Ordering.by(version => (version.major, version.minor, version.patch)) + + private def invalidScalaVersionError(s: String) = + new IllegalArgumentException(s"invalid Scala version: $s") +} diff --git a/core/src/main/scala/org/typelevel/scalacoptions/internal/Parser.scala b/core/src/main/scala/org/typelevel/scalacoptions/internal/Parser.scala new file mode 100644 index 0000000..10f782a --- /dev/null +++ b/core/src/main/scala/org/typelevel/scalacoptions/internal/Parser.scala @@ -0,0 +1,38 @@ +/* + * Copyright 2022 Typelevel + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.typelevel.scalacoptions.internal + +private[scalacoptions] object Parser { + private[internal] lazy val genericVersionRe = """^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$""".r + + private object asLong { + def unapply(s: String): Option[Long] = { + try { + Some(java.lang.Long.parseUnsignedLong(s)) + } catch { + case _: NumberFormatException => None + } + } + } + + def parseGenericVersion(s: String): Option[(Long, Long, Long)] = s match { + case genericVersionRe(asLong(major), asLong(minor), asLong(patch)) => + Some((major, minor, patch)) + case _ => + None + } +} diff --git a/lib/src/main/scala/org/typelevel/scalacoptions/ScalaVersion.scala b/lib/src/main/scala/org/typelevel/scalacoptions/ScalaVersion.scala deleted file mode 100644 index 1c7f17d..0000000 --- a/lib/src/main/scala/org/typelevel/scalacoptions/ScalaVersion.scala +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2022 Typelevel - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.typelevel.scalacoptions - -import scala.Ordering.Implicits._ - -case class ScalaVersion(major: Long, minor: Long, patch: Long) { - def isBetween(addedVersion: ScalaVersion, removedVersion: ScalaVersion) = - this >= addedVersion && this < removedVersion -} - -object ScalaVersion { - val V2_11_0 = ScalaVersion(2, 11, 0) - val V2_11_11 = ScalaVersion(2, 11, 11) - val V2_12_0 = ScalaVersion(2, 12, 0) - val V2_12_2 = ScalaVersion(2, 12, 2) - val V2_12_5 = ScalaVersion(2, 12, 5) - val V2_12_13 = ScalaVersion(2, 12, 13) - val V2_13_0 = ScalaVersion(2, 13, 0) - val V2_13_2 = ScalaVersion(2, 13, 2) - val V2_13_3 = ScalaVersion(2, 13, 3) - val V2_13_4 = ScalaVersion(2, 13, 4) - val V2_13_5 = ScalaVersion(2, 13, 5) - val V2_13_6 = ScalaVersion(2, 13, 6) - val V3_0_0 = ScalaVersion(3, 0, 0) - val V3_1_0 = ScalaVersion(3, 1, 0) - - implicit val scalaVersionOrdering: Ordering[ScalaVersion] = - Ordering.by(version => (version.major, version.minor, version.patch)) -} diff --git a/lib/src/main/scala/org/typelevel/scalacoptions/ScalacOptions.scala b/lib/src/main/scala/org/typelevel/scalacoptions/ScalacOptions.scala index c1d1511..919189f 100644 --- a/lib/src/main/scala/org/typelevel/scalacoptions/ScalacOptions.scala +++ b/lib/src/main/scala/org/typelevel/scalacoptions/ScalacOptions.scala @@ -16,6 +16,8 @@ package org.typelevel.scalacoptions +import org.typelevel.scalacoptions.literals._ + import scala.Ordering.Implicits._ import scala.collection.immutable.ListSet @@ -64,7 +66,7 @@ private[scalacoptions] trait ScalacOptions { ScalacOption( "-release", List(version), - version => JavaMajorVersion.javaMajorVersion >= 9 && version >= V2_12_5 + version => JavaMajorVersion.javaMajorVersion >= 9 && version >= sver"2.12.5" ) /** Enable features that will be available in a future version of Scala, for purposes of early @@ -72,7 +74,7 @@ private[scalacoptions] trait ScalacOptions { */ def scala3Source( version: String, - isSupported: ScalaVersion => Boolean = _ >= V3_0_0 + isSupported: ScalaVersion => Boolean = _ >= sver"3.0.0" ) = ScalacOption("-source", List(version), isSupported) @@ -89,12 +91,12 @@ private[scalacoptions] trait ScalacOptions { * - in conjunction with -rewrite, offer code rewrites from Scala 2.13 to 3.0 */ val source3Migration = - scala3Source("3.0-migration", version => version >= V3_0_0) + scala3Source("3.0-migration", version => version >= sver"3.0.0") /** Enable features that will be available in future versions of Scala 3.x, for purposes of early * migration and alpha testing. */ - val sourceFuture = scala3Source("future", version => version >= V3_0_0) + val sourceFuture = scala3Source("future", version => version >= sver"3.0.0") /** Enable features that will be available in future versions of Scala 3.x with Scala 2.x * compatibility mode, for purposes of early migration and alpha testing. @@ -105,12 +107,12 @@ private[scalacoptions] trait ScalacOptions { * optional rewrites. */ val sourceFutureMigration = - scala3Source("future-migration", version => version >= V3_0_0) + scala3Source("future-migration", version => version >= sver"3.0.0") /** Enable features that will be available in Scala 3.1.x, for purposes of early migration and * alpha testing. */ - val source31 = scala3Source("3.1", version => version >= V3_1_0) + val source31 = scala3Source("3.1", version => version >= sver"3.1.0") /** Enable or disable language features */ @@ -126,7 +128,7 @@ private[scalacoptions] trait ScalacOptions { /** Existential types (besides wildcard types) can be written and inferred. */ val languageExistentials = - languageFeatureOption("existentials", version => version < V3_0_0) + languageFeatureOption("existentials", version => version < sver"3.0.0") /** Allow macro definition (besides implementation and application). */ @@ -176,7 +178,7 @@ private[scalacoptions] trait ScalacOptions { /** Wrap field accessors to throw an exception on uninitialized access. */ val checkInit = - advancedOption("checkinit", version => version < V3_0_0) + advancedOption("checkinit", version => version < sver"3.0.0") /** Fail the compilation if there are any warnings. */ @@ -186,12 +188,12 @@ private[scalacoptions] trait ScalacOptions { /** Enable recommended warnings. */ val lint = - advancedOption("lint", version => version < V2_11_0) + advancedOption("lint", version => version < sver"2.11.0") /** Enable SIP-22 async/await constructs */ val async = - advancedOption("async", version => version.isBetween(V2_13_3, V3_0_0)) + advancedOption("async", version => version.isBetween(sver"2.13.0", sver"3.0.0")) /** Enable recommended warnings. */ @@ -212,7 +214,7 @@ private[scalacoptions] trait ScalacOptions { /** Warn if an argument list is modified to match the receiver. */ val lintAdaptedArgs = - lintOption("adapted-args", version => version.isBetween(V2_11_0, V3_0_0)) + lintOption("adapted-args", version => version.isBetween(sver"2.11.0", sver"3.0.0")) /** Warn if a right-associative operator taking a by-name parameter is used. * @@ -224,38 +226,38 @@ private[scalacoptions] trait ScalacOptions { val lintByNameRightAssociative = lintOption( "by-name-right-associative", - version => version.isBetween(V2_11_0, V2_13_0) + version => version.isBetween(sver"2.11.0", sver"2.13.0") ) /** Warn if evaluation of a constant arithmetic expression results in an error. */ val lintConstant = - lintOption("constant", version => version.isBetween(V2_12_0, V3_0_0)) + lintOption("constant", version => version.isBetween(sver"2.12.0", sver"3.0.0")) /** Warn when selecting a member of DelayedInit. */ val lintDelayedInitSelect = lintOption( "delayedinit-select", - version => version.isBetween(V2_11_0, V3_0_0) + version => version.isBetween(sver"2.11.0", sver"3.0.0") ) /** Enable linted deprecations. */ val lintDeprecation = - lintOption("deprecation", version => version.isBetween(V2_12_13, V3_0_0)) + lintOption("deprecation", version => version.isBetween(sver"2.12.13", sver"3.0.0")) /** Warn when a Scaladoc comment appears to be detached from its element. */ val lintDocDetached = - lintOption("doc-detached", version => version.isBetween(V2_11_0, V3_0_0)) + lintOption("doc-detached", version => version.isBetween(sver"2.11.0", sver"3.0.0")) /** Warn when an implicit resolves to an enclosing self-definition. */ val lintImplicitRecursion = lintOption( "implicit-recursion", - version => version.isBetween(V2_13_3, V3_0_0) + version => version.isBetween(sver"2.13.0", sver"3.0.0") ) /** Warn when an @implicitNotFound or @implicitAmbigous annotation references an invalid type @@ -264,25 +266,25 @@ private[scalacoptions] trait ScalacOptions { val lintImplicitNotFound = lintOption( "implicit-not-found", - version => version.isBetween(V2_13_0, V3_0_0) + version => version.isBetween(sver"2.13.0", sver"3.0.0") ) /** Warn about inaccessible types in method signatures. */ val lintInaccessible = - lintOption("inaccessible", version => version.isBetween(V2_11_0, V3_0_0)) + lintOption("inaccessible", version => version.isBetween(sver"2.11.0", sver"3.0.0")) /** Warn when a type argument is inferred to be Any. */ val lintInferAny = - lintOption("infer-any", version => version.isBetween(V2_11_0, V3_0_0)) + lintOption("infer-any", version => version.isBetween(sver"2.11.0", sver"3.0.0")) /** Warn when a string literal appears to be missing an interpolator id. */ val lintMissingInterpolator = lintOption( "missing-interpolator", - version => version.isBetween(V2_11_0, V3_0_0) + version => version.isBetween(sver"2.11.0", sver"3.0.0") ) /** Warn when non-nullary def f() overrides nullary def f. @@ -290,25 +292,25 @@ private[scalacoptions] trait ScalacOptions { val lintNullaryOverride = lintOption( "nullary-override", - version => version.isBetween(V2_11_0, V2_13_0) + version => version.isBetween(sver"2.11.0", sver"2.13.0") ) /** Warn when nullary methods return Unit. */ val lintNullaryUnit = - lintOption("nullary-unit", version => version.isBetween(V2_11_0, V3_0_0)) + lintOption("nullary-unit", version => version.isBetween(sver"2.11.0", sver"3.0.0")) /** Warn when Option.apply uses an implicit view. */ val lintOptionImplicit = - lintOption("option-implicit", version => version.isBetween(V2_11_0, V3_0_0)) + lintOption("option-implicit", version => version.isBetween(sver"2.11.0", sver"3.0.0")) /** Warn when a class or object is defined in a package object. */ val lintPackageObjectClasses = lintOption( "package-object-classes", - version => version.isBetween(V2_11_0, V3_0_0) + version => version.isBetween(sver"2.11.0", sver"3.0.0") ) /** Warn when a parameterized overloaded implicit methods is used in a view bound. @@ -316,26 +318,26 @@ private[scalacoptions] trait ScalacOptions { val lintPolyImplicitOverload = lintOption( "poly-implicit-overload", - version => version.isBetween(V2_11_0, V3_0_0) + version => version.isBetween(sver"2.11.0", sver"3.0.0") ) /** Warn when a private field (or class parameter) shadows a superclass field. */ val lintPrivateShadow = - lintOption("private-shadow", version => version.isBetween(V2_11_0, V3_0_0)) + lintOption("private-shadow", version => version.isBetween(sver"2.11.0", sver"3.0.0")) /** Warn when a pattern sequence wildcard does not align with the sequence component of the data * being matched. */ val lintStarsAlign = - lintOption("stars-align", version => version.isBetween(V2_11_0, V3_0_0)) + lintOption("stars-align", version => version.isBetween(sver"2.11.0", sver"3.0.0")) /** Warn when matching on an unsealed class without a catch-all patttern. */ val lintStrictUnsealedPatmat = lintOption( "strict-unsealed-patmat", - version => version.isBetween(V2_13_4, V3_0_0) + version => version.isBetween(sver"2.13.4", sver"3.0.0") ) /** Warn when a local type parameter shadows a type already in scope. @@ -343,13 +345,13 @@ private[scalacoptions] trait ScalacOptions { val lintTypeParameterShadow = lintOption( "type-parameter-shadow", - version => version.isBetween(V2_11_0, V3_0_0) + version => version.isBetween(sver"2.11.0", sver"3.0.0") ) /** Warn when a pattern matches using `.equals` and therefore may be unsound. */ val lintUnsoundMatch = - lintOption("unsound-match", version => version.isBetween(V2_11_0, V2_13_0)) + lintOption("unsound-match", version => version.isBetween(sver"2.11.0", sver"2.13.0")) /** Warn when a by-name implicit conversion is applied to the result of a block. * @@ -358,7 +360,7 @@ private[scalacoptions] trait ScalacOptions { val disableLintBynameImplicit = disableLintOption( "byname-implicit", - version => version.isBetween(V2_13_3, V3_0_0) + version => version.isBetween(sver"2.13.0", sver"3.0.0") ) /** Advanced linting options (-Xlint:) @@ -392,25 +394,25 @@ private[scalacoptions] trait ScalacOptions { */ def source( version: String, - isSupported: ScalaVersion => Boolean = _ >= V3_0_0 + isSupported: ScalaVersion => Boolean = _ >= sver"3.0.0" ) = advancedOption(s"source:$version", isSupported) /** Treat compiler input as Scala source for version 2.10. */ - val source210 = source("2.10", version => version < V2_13_2) + val source210 = source("2.10", version => version < sver"2.13.2") /** Treat compiler input as Scala source for version 2.11. */ - val source211 = source("2.11", version => version < V2_13_2) + val source211 = source("2.11", version => version < sver"2.13.2") /** Treat compiler input as Scala source for version 2.12. */ - val source212 = source("2.12", version => version < V2_13_2) + val source212 = source("2.12", version => version < sver"2.13.2") /** Treat compiler input as Scala source for version 2.13. */ - val source213 = source("2.13", version => version.isBetween(V2_12_2, V3_0_0)) + val source213 = source("2.13", version => version.isBetween(sver"2.12.2", sver"3.0.0")) /** Treat compiler input as Scala source for version 3.x: * @@ -419,7 +421,7 @@ private[scalacoptions] trait ScalacOptions { * - Implicit search and overload resolution follow Scala 3 handling of contravariance when * checking specificity. */ - val source3 = source("3", version => version.isBetween(V2_12_2, V3_0_0)) + val source3 = source("3", version => version.isBetween(sver"2.12.2", sver"3.0.0")) /** Advanced options (-X) */ @@ -446,13 +448,13 @@ private[scalacoptions] trait ScalacOptions { /** Produce an error if an argument list is modified to match the receiver. */ val privateNoAdaptedArgs = - privateOption("no-adapted-args", version => version < V2_13_0) + privateOption("no-adapted-args", version => version < sver"2.13.0") /** Enables support for a subset of [[https://github.com/typelevel/kind-projector kind-projector]] * syntax. */ val privateKindProjector = - privateOption("kind-projector", version => version >= V3_0_0) + privateOption("kind-projector", version => version >= sver"3.0.0") /** Enables support for higher order unification in type constructor inference. * @@ -464,7 +466,7 @@ private[scalacoptions] trait ScalacOptions { val privatePartialUnification = privateOption( "partial-unification", - version => version.isBetween(V2_11_11, V2_13_0) + version => version.isBetween(sver"2.11.11", sver"2.13.0") ) /** Configures the number of worker threads for the compiler backend. @@ -481,7 +483,7 @@ private[scalacoptions] trait ScalacOptions { ) = privateOption( "backend-parallelism", List(threads.toString), - version => version.isBetween(V2_12_5, V3_0_0) + version => version.isBetween(sver"2.12.5", sver"3.0.0") ) /** Private warning options (-Ywarn) @@ -495,53 +497,53 @@ private[scalacoptions] trait ScalacOptions { /** Warn when dead code is identified. */ val privateWarnDeadCode = - privateWarnOption("dead-code", version => version < V2_13_0) + privateWarnOption("dead-code", version => version < sver"2.13.0") /** Warn when more than one implicit parameter section is defined. */ val privateWarnExtraImplicit = privateWarnOption( "extra-implicit", - version => version.isBetween(V2_12_0, V2_13_0) + version => version.isBetween(sver"2.12.0", sver"2.13.0") ) /** Warn about inaccessible types in method signatures. */ val privateWarnInaccessible = - privateWarnOption("inaccessible", version => version < V2_11_0) + privateWarnOption("inaccessible", version => version < sver"2.11.0") /** Warn when non-nullary def f() overrides nullary def f. */ val privateWarnNullaryOverride = - privateWarnOption("nullary-override", version => version < V2_13_0) + privateWarnOption("nullary-override", version => version < sver"2.13.0") /** Warn when nullary methods return Unit. */ val privateWarnNullaryUnit = - privateWarnOption("nullary-unit", version => version < V2_13_0) + privateWarnOption("nullary-unit", version => version < sver"2.13.0") /** Warn when numerics are widened. */ val privateWarnNumericWiden = - privateWarnOption("numeric-widen", version => version < V2_13_0) + privateWarnOption("numeric-widen", version => version < sver"2.13.0") /** Warn when local and private vals, vars, defs and types are unused. */ val privateWarnUnused = - privateWarnOption("unused", version => version.isBetween(V2_11_0, V2_12_0)) + privateWarnOption("unused", version => version.isBetween(sver"2.11.0", sver"2.12.0")) /** Warn if an import selector is not referenced. */ val privateWarnUnusedImport = privateWarnOption( "unused-import", - version => version.isBetween(V2_11_0, V2_12_0) + version => version.isBetween(sver"2.11.0", sver"2.12.0") ) /** Warn when non-Unit expression results are unused. */ val privateWarnValueDiscard = - privateWarnOption("value-discard", version => version < V2_13_0) + privateWarnOption("value-discard", version => version < sver"2.13.0") /** Private unused warning options (-Ywarn-unused:) */ @@ -556,7 +558,7 @@ private[scalacoptions] trait ScalacOptions { val privateWarnUnusedImplicits = privateWarnUnusedOption( "implicits", - version => version.isBetween(V2_12_0, V2_13_0) + version => version.isBetween(sver"2.12.0", sver"2.13.0") ) /** Warn if an import selector is not referenced. @@ -564,7 +566,7 @@ private[scalacoptions] trait ScalacOptions { val privateWarnUnusedImports = privateWarnUnusedOption( "imports", - version => version.isBetween(V2_12_0, V2_13_0) + version => version.isBetween(sver"2.12.0", sver"2.13.0") ) /** Warn if a local definition is unused. @@ -572,7 +574,7 @@ private[scalacoptions] trait ScalacOptions { val privateWarnUnusedLocals = privateWarnUnusedOption( "locals", - version => version.isBetween(V2_12_0, V2_13_0) + version => version.isBetween(sver"2.12.0", sver"2.13.0") ) /** Warn if an explicit parameter is unused. @@ -580,7 +582,7 @@ private[scalacoptions] trait ScalacOptions { val privateWarnUnusedParams = privateWarnUnusedOption( "params", - version => version.isBetween(V2_12_0, V2_13_0) + version => version.isBetween(sver"2.12.0", sver"2.13.0") ) /** Warn if a variable bound in a pattern is unused. @@ -588,7 +590,7 @@ private[scalacoptions] trait ScalacOptions { val privateWarnUnusedPatVars = privateWarnUnusedOption( "patvars", - version => version.isBetween(V2_12_0, V2_13_0) + version => version.isBetween(sver"2.12.0", sver"2.13.0") ) /** Warn if a private member is unused. @@ -596,7 +598,7 @@ private[scalacoptions] trait ScalacOptions { val privateWarnUnusedPrivates = privateWarnUnusedOption( "privates", - version => version.isBetween(V2_12_0, V2_13_0) + version => version.isBetween(sver"2.12.0", sver"2.13.0") ) /** Private unused warning options (-Ywarn-unused:) @@ -643,27 +645,27 @@ private[scalacoptions] trait ScalacOptions { /** Warn when dead code is identified. */ val warnDeadCode = - warnOption("dead-code", version => version.isBetween(V2_13_0, V3_0_0)) + warnOption("dead-code", version => version.isBetween(sver"2.13.0", sver"3.0.0")) /** Warn when more than one implicit parameter section is defined. */ val warnExtraImplicit = - warnOption("extra-implicit", version => version.isBetween(V2_13_0, V3_0_0)) + warnOption("extra-implicit", version => version.isBetween(sver"2.13.0", sver"3.0.0")) /** Warn when numerics are widened. */ val warnNumericWiden = - warnOption("numeric-widen", version => version.isBetween(V2_13_0, V3_0_0)) + warnOption("numeric-widen", version => version.isBetween(sver"2.13.0", sver"3.0.0")) /** Warn when non-Unit expression results are unused. */ val warnValueDiscard = - warnOption("value-discard", version => version.isBetween(V2_13_0, V3_0_0)) + warnOption("value-discard", version => version.isBetween(sver"2.13.0", sver"3.0.0")) /** Fail the compilation if there are any warnings. */ val warnError = - warnOption("error", version => version.isBetween(V2_13_0, V3_0_0)) + warnOption("error", version => version.isBetween(sver"2.13.0", sver"3.0.0")) /** Unused warning options (-Wunused:) */ @@ -676,44 +678,44 @@ private[scalacoptions] trait ScalacOptions { /** Warn if a @nowarn annotation did not suppress at least one warning. */ val warnUnusedNoWarn = - warnUnusedOption("nowarn", version => version.isBetween(V2_13_0, V3_0_0)) + warnUnusedOption("nowarn", version => version.isBetween(sver"2.13.0", sver"3.0.0")) /** Warn if an implicit parameter is unused. */ val warnUnusedImplicits = - warnUnusedOption("implicits", version => version.isBetween(V2_13_0, V3_0_0)) + warnUnusedOption("implicits", version => version.isBetween(sver"2.13.0", sver"3.0.0")) /** Warn if an explicit parameter is unused. */ val warnUnusedExplicits = - warnUnusedOption("explicits", version => version.isBetween(V2_13_0, V3_0_0)) + warnUnusedOption("explicits", version => version.isBetween(sver"2.13.0", sver"3.0.0")) /** Warn if an import selector is not referenced. */ val warnUnusedImports = - warnUnusedOption("imports", version => version.isBetween(V2_13_0, V3_0_0)) + warnUnusedOption("imports", version => version.isBetween(sver"2.13.0", sver"3.0.0")) /** Warn if a local definition is unused. */ val warnUnusedLocals = - warnUnusedOption("locals", version => version.isBetween(V2_13_0, V3_0_0)) + warnUnusedOption("locals", version => version.isBetween(sver"2.13.0", sver"3.0.0")) /** Warn if either explicit or implicit parameters are unused. * * Equivalent to -Wunused:explicits,implicits. */ val warnUnusedParams = - warnUnusedOption("params", version => version.isBetween(V2_13_0, V3_0_0)) + warnUnusedOption("params", version => version.isBetween(sver"2.13.0", sver"3.0.0")) /** Warn if a variable bound in a pattern is unused. */ val warnUnusedPatVars = - warnUnusedOption("patvars", version => version.isBetween(V2_13_0, V3_0_0)) + warnUnusedOption("patvars", version => version.isBetween(sver"2.13.0", sver"3.0.0")) /** Warn if a private member is unused. */ val warnUnusedPrivates = - warnUnusedOption("privates", version => version.isBetween(V2_13_0, V3_0_0)) + warnUnusedOption("privates", version => version.isBetween(sver"2.13.0", sver"3.0.0")) /** Unused warning options (-Wunused:) */ @@ -765,19 +767,19 @@ private[scalacoptions] trait ScalacOptions { * unreachable-code,simplify-jumps,compact-locals,copy-propagation,redundant-casts,box-unbox,nullness-tracking,closure-invocations,allow-skip-core-module-init,assume-modules-non-null,allow-skip-class-loading. */ val optimizerMethodLocal = - optimizerOption(":l:method", version => version.isBetween(V2_12_0, V3_0_0)) + optimizerOption(":l:method", version => version.isBetween(sver"2.12.0", sver"3.0.0")) /** Enable cross-method optimizations. * * Note: inlining requires -opt-inline-from and -opt:l:method to be provided. */ val optimizerInline = - optimizerOption(":l:inline", version => version.isBetween(V2_12_0, V3_0_0)) + optimizerOption(":l:inline", version => version.isBetween(sver"2.12.0", sver"3.0.0")) /** Enable optimizer warnings */ val optimizerWarnings = - optimizerOption("-warnings", version => version.isBetween(V2_12_0, V3_0_0)) + optimizerOption("-warnings", version => version.isBetween(sver"2.12.0", sver"3.0.0")) /** Patterns for classfile names from which to allow inlining. * @@ -789,7 +791,7 @@ private[scalacoptions] trait ScalacOptions { def optimizerInlineFrom(inlineFromPackages: String*) = optimizerOption( s"-inline-from:${inlineFromPackages.mkString(":")}", - version => version.isBetween(V2_12_0, V3_0_0) + version => version.isBetween(sver"2.12.0", sver"3.0.0") ) /** Enable cross-method optimizations. diff --git a/lib/src/test/scala/org/typelevel/scalacoptions/ScalacOptionSuite.scala b/lib/src/test/scala/org/typelevel/scalacoptions/ScalacOptionSuite.scala deleted file mode 100644 index 113af6e..0000000 --- a/lib/src/test/scala/org/typelevel/scalacoptions/ScalacOptionSuite.scala +++ /dev/null @@ -1,218 +0,0 @@ -/* - * Copyright 2022 Typelevel - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.typelevel.scalacoptions - -import org.scalacheck.Gen -import org.scalacheck.Prop.forAll - -import scala.Ordering.Implicits._ - -class ScalacOptionSuite extends munit.ScalaCheckSuite { - val versionGen = Gen.chooseNum(0L, 20L) - - property("valid when no version predicate") { - forAll(versionGen, versionGen, versionGen) { - (currentMaj: Long, currentMin: Long, currentPatch: Long) => - val currentVersion = ScalaVersion(currentMaj, currentMin, currentPatch) - val scalacOption = ScalacOption("-some-opt", _ => true) - assert( - scalacOption.isSupported(currentVersion), - "Should be valid when neither addedIn nor removedIn" - ) - } - } - - property("valid when added in past major release") { - forAll(versionGen, versionGen, versionGen) { - (currentMaj: Long, currentMin: Long, currentPatch: Long) => - val currentVersion = ScalaVersion(currentMaj, currentMin, currentPatch) - val addedVersion = ScalaVersion(currentMaj - 1, 0, 0) - val scalacOption = ScalacOption("-some-opt", version => version >= addedVersion) - assert( - scalacOption.isSupported(currentVersion), - "Should be valid when addedIn matches past major release" - ) - } - } - - property("valid when added in past minor release") { - forAll(versionGen, versionGen, versionGen) { - (currentMaj: Long, currentMin: Long, currentPatch: Long) => - val currentVersion = ScalaVersion(currentMaj, currentMin, currentPatch) - val addedVersion = ScalaVersion(currentMaj, currentMin - 1, 0) - val scalacOption = ScalacOption("-some-opt", version => version >= addedVersion) - assert( - scalacOption.isSupported(currentVersion), - "Should be valid when addedIn matches past minor release" - ) - } - } - - property("valid when added in past patch release") { - forAll(versionGen, versionGen, versionGen) { - (currentMaj: Long, currentMin: Long, currentPatch: Long) => - val currentVersion = ScalaVersion(currentMaj, currentMin, currentPatch) - val addedVersion = ScalaVersion(currentMaj, currentMin, currentPatch - 1) - val scalacOption = ScalacOption("-some-opt", version => version >= addedVersion) - assert( - scalacOption.isSupported(currentVersion), - "Should be valid when addedIn matches past patch release" - ) - } - } - - property("valid when added in this minor/patch release") { - forAll(versionGen, versionGen, versionGen) { - (currentMaj: Long, currentMin: Long, currentPatch: Long) => - val currentVersion = ScalaVersion(currentMaj, currentMin, currentPatch) - val scalacOption = ScalacOption("-some-opt", version => version >= currentVersion) - assert( - scalacOption.isSupported(currentVersion), - "Should be valid when addedIn matches this minor/patch release" - ) - } - } - - property("not valid when added in a future major release") { - forAll(versionGen, versionGen, versionGen) { - (currentMaj: Long, currentMin: Long, currentPatch: Long) => - val currentVersion = ScalaVersion(currentMaj, currentMin, currentPatch) - val addedVersion = ScalaVersion(currentMaj + 1, currentMin, currentPatch) - val scalacOption = ScalacOption("-some-opt", version => version >= addedVersion) - assert( - !scalacOption.isSupported(currentVersion), - "Should not be valid when addedIn matches a future major release" - ) - } - } - - property("not valid when added in a future minor release") { - forAll(versionGen, versionGen, versionGen) { - (currentMaj: Long, currentMin: Long, currentPatch: Long) => - val currentVersion = ScalaVersion(currentMaj, currentMin, currentPatch) - val addedVersion = ScalaVersion(currentMaj, currentMin + 1, currentPatch) - val scalacOption = ScalacOption("-some-opt", version => version >= addedVersion) - assert( - !scalacOption.isSupported(currentVersion), - "Should not be valid when addedIn matches a future minor release" - ) - } - } - - property("not valid when added in a future patch release") { - forAll(versionGen, versionGen, versionGen) { - (currentMaj: Long, currentMin: Long, currentPatch: Long) => - val currentVersion = ScalaVersion(currentMaj, currentMin, currentPatch) - val addedVersion = ScalaVersion(currentMaj, currentMin, currentPatch + 1) - val scalacOption = ScalacOption("-some-opt", version => version >= addedVersion) - assert( - !scalacOption.isSupported(currentVersion), - "Should not be valid when addedIn matches a future patch release" - ) - } - } - - property("valid when removed in next major release") { - forAll(versionGen, versionGen, versionGen) { - (currentMaj: Long, currentMin: Long, currentPatch: Long) => - val currentVersion = ScalaVersion(currentMaj, currentMin, currentPatch) - val removedVersion = ScalaVersion(currentMaj + 1, 0, 0) - val scalacOption = ScalacOption("-some-opt", version => version < removedVersion) - assert( - scalacOption.isSupported(currentVersion), - "Should be valid when removedIn matches next major release" - ) - } - } - - property("valid when removed in next minor release") { - forAll(versionGen, versionGen, versionGen) { - (currentMaj: Long, currentMin: Long, currentPatch: Long) => - val currentVersion = ScalaVersion(currentMaj, currentMin, currentPatch) - val removedVersion = ScalaVersion(currentMaj, currentMin + 1, currentPatch) - val scalacOption = ScalacOption("-some-opt", version => version < removedVersion) - assert( - scalacOption.isSupported(currentVersion), - "Should be valid when removedIn matches next minor release" - ) - } - } - - property("valid when removed in next patch release") { - forAll(versionGen, versionGen, versionGen) { - (currentMaj: Long, currentMin: Long, currentPatch: Long) => - val currentVersion = ScalaVersion(currentMaj, currentMin, currentPatch) - val removedVersion = ScalaVersion(currentMaj, currentMin, currentPatch + 1) - val scalacOption = ScalacOption("-some-opt", version => version < removedVersion) - assert( - scalacOption.isSupported(currentVersion), - "Should be valid when removedIn matches next patch release" - ) - } - } - - property("not valid when removed in this minor/patch release") { - forAll(versionGen, versionGen, versionGen) { - (currentMaj: Long, currentMin: Long, currentPatch: Long) => - val currentVersion = ScalaVersion(currentMaj, currentMin, currentPatch) - val scalacOption = ScalacOption("-some-opt", version => version < currentVersion) - assert( - !scalacOption.isSupported(currentVersion), - "Should not be valid when removedIn matches this minor/patch release" - ) - } - } - - property("not valid when removed in an old major release") { - forAll(versionGen, versionGen, versionGen) { - (currentMaj: Long, currentMin: Long, currentPatch: Long) => - val currentVersion = ScalaVersion(currentMaj, currentMin, currentPatch) - val removedVersion = ScalaVersion(currentMaj - 1, currentMin, currentPatch) - val scalacOption = ScalacOption("-some-opt", version => version < removedVersion) - assert( - !scalacOption.isSupported(currentVersion), - "Should not be valid when removedIn matches an old major release" - ) - } - } - - property("not valid when removed in an old minor release") { - forAll(versionGen, versionGen, versionGen) { - (currentMaj: Long, currentMin: Long, currentPatch: Long) => - val currentVersion = ScalaVersion(currentMaj, currentMin, currentPatch) - val removedVersion = ScalaVersion(currentMaj, currentMin - 1, currentPatch) - val scalacOption = ScalacOption("-some-opt", version => version < removedVersion) - assert( - !scalacOption.isSupported(currentVersion), - "Should not be valid when removedIn matches an old minor release" - ) - } - } - - property("not valid when removed in an old patch release") { - forAll(versionGen, versionGen, versionGen) { - (currentMaj: Long, currentMin: Long, currentPatch: Long) => - val currentVersion = ScalaVersion(currentMaj, currentMin, currentPatch) - val removedVersion = ScalaVersion(currentMaj, currentMin, currentPatch - 1) - val scalacOption = ScalacOption("-some-opt", version => version < removedVersion) - assert( - !scalacOption.isSupported(currentVersion), - "Should not be valid when removedIn matches an old patch release" - ) - } - } -} diff --git a/macros/src/main/scala-2/org/typelevel/scalacoptions/literals.scala b/macros/src/main/scala-2/org/typelevel/scalacoptions/literals.scala new file mode 100644 index 0000000..c9aab8c --- /dev/null +++ b/macros/src/main/scala-2/org/typelevel/scalacoptions/literals.scala @@ -0,0 +1,38 @@ +/* + * Copyright 2022 Typelevel + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.typelevel.scalacoptions + +import org.typelevel.literally.Literally + +object literals { + implicit class ScalaVersionContext(val sc: StringContext) extends AnyVal { + def sver(args: Any*): ScalaVersion = macro ScalaVersionLiteral.make + } + + object ScalaVersionLiteral extends Literally[ScalaVersion] { + def validate(c: Context)(s: String) = { + import c.universe._ + + if (ScalaVersion.fromString(s).isEmpty) + Left(s"incorrect or unsupported Scala version") + else + Right(c.Expr(q"ScalaVersion.fromString.unsafe($s)")) + } + + def make(c: Context)(args: c.Expr[Any]*): c.Expr[ScalaVersion] = apply(c)(args: _*) + } +} diff --git a/macros/src/main/scala-3/org/typelevel/scalacoptions/literals.scala b/macros/src/main/scala-3/org/typelevel/scalacoptions/literals.scala new file mode 100644 index 0000000..618d40b --- /dev/null +++ b/macros/src/main/scala-3/org/typelevel/scalacoptions/literals.scala @@ -0,0 +1,32 @@ +/* + * Copyright 2022 Typelevel + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.typelevel.scalacoptions + +import org.typelevel.literally.Literally + +object literals: + extension (inline ctx: StringContext) + inline def sver(inline args: Any*): ScalaVersion = + ${ ScalaVersionLiteral('ctx, 'args) } + + object ScalaVersionLiteral extends Literally[ScalaVersion]: + def validate(s: String)(using Quotes) = + if (ScalaVersion.fromString(s).isEmpty) + Left("incorrect or unsupported Scala version") + else { + Right('{ ScalaVersion.fromString.unsafe(${ Expr(s) }) }) + } diff --git a/testkit/src/main/scala/org/typelevel/scalacoptions/testkit/package.scala b/testkit/src/main/scala/org/typelevel/scalacoptions/testkit/package.scala new file mode 100644 index 0000000..c89b908 --- /dev/null +++ b/testkit/src/main/scala/org/typelevel/scalacoptions/testkit/package.scala @@ -0,0 +1,35 @@ +/* + * Copyright 2022 Typelevel + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.typelevel.scalacoptions + +import org.scalacheck.Arbitrary +import org.scalacheck.Gen +import org.scalacheck.Shrink + +package object testkit { + + /** Emits one of the known Scala versions. + */ + lazy val knownScalaVersionGen: Gen[ScalaVersion] = Gen.oneOf(ScalaVersion.knownVersions) + + implicit lazy val scalaVersionArbitrary: Arbitrary[ScalaVersion] = + Arbitrary(knownScalaVersionGen) + + // TODO: Shrinking is currently suppressed. + // Consider implementing by shrinking the `patch` version down to 0. + implicit lazy val scalaVersionShrink: Shrink[ScalaVersion] = Shrink.shrinkAny +} diff --git a/testkit/src/test/scala-2.12/org/typelevel/scalacoptions/internal/ScalaCollectionCompat.scala b/testkit/src/test/scala-2.12/org/typelevel/scalacoptions/internal/ScalaCollectionCompat.scala new file mode 100644 index 0000000..d978ee3 --- /dev/null +++ b/testkit/src/test/scala-2.12/org/typelevel/scalacoptions/internal/ScalaCollectionCompat.scala @@ -0,0 +1,44 @@ +/* + * Copyright 2022 Typelevel + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.typelevel.scalacoptions.internal + +import scala.collection.SortedSet +import scala.collection.SortedSetLike +import scala.language.implicitConversions + +private[scalacoptions] trait ScalaCollectionCompat { + import ScalaCollectionCompat._ + + implicit def sortedSetCompatOps[A, C[a] <: SortedSet[a] with SortedSetLike[a, C[a]]]( + ca: C[A] + ): SortedSetCompatOps[A, C[A]] = + new SortedSetCompatOps(ca) +} + +private[internal] object ScalaCollectionCompat { + final class SortedSetCompatOps[A, C <: SortedSet[A] with SortedSetLike[A, C]](val ca: C) + extends AnyVal { + + /** `SortedSet#from` is deprecated since Scala 2.13 and `rangeFrom` is recommended instead. + */ + def rangeFrom(a: A): C = ca.from(a) + + /** `SortedSet#until` is deprecated since Scala 2.13 and `rangeUntil` is recommended instead. + */ + def rangeUntil(a: A): C = ca.until(a) + } +} diff --git a/testkit/src/test/scala-2.13+/org/typelevel/scalacoptions/internal/ScalaCollectionCompat.scala b/testkit/src/test/scala-2.13+/org/typelevel/scalacoptions/internal/ScalaCollectionCompat.scala new file mode 100644 index 0000000..86ace3d --- /dev/null +++ b/testkit/src/test/scala-2.13+/org/typelevel/scalacoptions/internal/ScalaCollectionCompat.scala @@ -0,0 +1,19 @@ +/* + * Copyright 2022 Typelevel + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.typelevel.scalacoptions.internal + +private[scalacoptions] trait ScalaCollectionCompat diff --git a/testkit/src/test/scala/org/typelevel/scalacoptions/ScalaVersionSuite.scala b/testkit/src/test/scala/org/typelevel/scalacoptions/ScalaVersionSuite.scala new file mode 100644 index 0000000..1250148 --- /dev/null +++ b/testkit/src/test/scala/org/typelevel/scalacoptions/ScalaVersionSuite.scala @@ -0,0 +1,54 @@ +/* + * Copyright 2022 Typelevel + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.typelevel.scalacoptions + +import org.typelevel.scalacoptions.testkit._ +import org.scalacheck.Prop + +class ScalaVersionSuite extends munit.ScalaCheckSuite { + property("ScalaVersion.from/unsafeFrom can create a known version") { + Prop.forAll(knownScalaVersionGen) { case expected @ ScalaVersion(major, minor, patch) => + assertEquals(ScalaVersion.from(major, minor, patch), Some(expected)) + assertEquals(ScalaVersion.unsafeFrom(major, minor, patch), expected) + } + } + property("ScalaVersion.from/unsafeFrom are consistent with each other") { + Prop.forAll { (major: Long, minor: Long, patch: Long) => + ScalaVersion.from(major, minor, patch) match { + case Some(expected) => + assertEquals(ScalaVersion.unsafeFrom(major, minor, patch), expected) + case None => + val expectedMessage = s"invalid Scala version: $major.$minor.$patch" + interceptMessage[IllegalArgumentException](expectedMessage) { + ScalaVersion.unsafeFrom(major, minor, patch) + } + () + } + } + } + property("ScalaVersion.fromString.apply/unsafe can create a known version") { + Prop.forAll(knownScalaVersionGen) { case expected @ ScalaVersion(major, minor, patch) => + val s = s"$major.$minor.$patch" + assertEquals(ScalaVersion.fromString(s), Some(expected)) + assertEquals(ScalaVersion.fromString.unsafe(s), expected) + s match { + case ScalaVersion.fromString(obtained) => assertEquals(obtained, expected) + case _ => fail("must not happen") + } + } + } +} diff --git a/testkit/src/test/scala/org/typelevel/scalacoptions/ScalacOptionSuite.scala b/testkit/src/test/scala/org/typelevel/scalacoptions/ScalacOptionSuite.scala new file mode 100644 index 0000000..0f9eddf --- /dev/null +++ b/testkit/src/test/scala/org/typelevel/scalacoptions/ScalacOptionSuite.scala @@ -0,0 +1,107 @@ +/* + * Copyright 2022 Typelevel + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.typelevel.scalacoptions + +import org.scalacheck.Arbitrary +import org.scalacheck.Gen +import org.scalacheck.Prop.forAll +import org.typelevel.scalacoptions.testkit._ + +import scala.Ordering.Implicits._ + +class ScalacOptionSuite extends munit.ScalaCheckSuite with internal.ScalaCollectionCompat { + + private val optionNameGen = Arbitrary.arbitrary[String] + private val versionsPairGen = { + // Make sure there will be at least one older version available. + val secondVersion = ScalaVersion.knownVersions.tail.head // we know for sure it is there + for { + newerVersion <- Gen.oneOf(ScalaVersion.knownVersions.rangeFrom(secondVersion)) + olderVersion <- Gen.oneOf(ScalaVersion.knownVersions.rangeUntil(newerVersion)) + } yield (olderVersion, newerVersion) + } + + property("valid when no version predicate") { + forAll { (optionName: String, currentVersion: ScalaVersion) => + val scalacOption = ScalacOption(optionName, _ => true) + assert( + scalacOption.isSupported(currentVersion), + "Should be valid when neither addedIn nor removedIn" + ) + } + } + + property("valid when added in a past release") { + forAll(optionNameGen, versionsPairGen) { case (optionName, (addedVersion, currentVersion)) => + val scalacOption = ScalacOption(optionName, version => version >= addedVersion) + assert( + scalacOption.isSupported(currentVersion), + "Should be valid when addedIn matches past major release" + ) + } + } + + property("valid when added in this minor/patch release") { + forAll { (optionName: String, currentVersion: ScalaVersion) => + val scalacOption = ScalacOption(optionName, version => version >= currentVersion) + assert( + scalacOption.isSupported(currentVersion), + "Should be valid when addedIn matches this minor/patch release" + ) + } + } + + property("not valid when added in a future release") { + forAll(optionNameGen, versionsPairGen) { case (optionName, (currentVersion, addedVersion)) => + val scalacOption = ScalacOption(optionName, version => version >= addedVersion) + assert( + !scalacOption.isSupported(currentVersion), + "Should not be valid when addedIn matches a future major release" + ) + } + } + + property("valid when removed in a future release") { + forAll(optionNameGen, versionsPairGen) { case (optionName, (currentVersion, removedVersion)) => + val scalacOption = ScalacOption(optionName, version => version < removedVersion) + assert( + scalacOption.isSupported(currentVersion), + "Should be valid when removedIn matches next major release" + ) + } + } + + property("not valid when removed in this release") { + forAll { (optionName: String, currentVersion: ScalaVersion) => + val scalacOption = ScalacOption(optionName, version => version < currentVersion) + assert( + !scalacOption.isSupported(currentVersion), + "Should not be valid when removedIn matches this minor/patch release" + ) + } + } + + property("not valid when removed in a past release") { + forAll(optionNameGen, versionsPairGen) { case (optionName, (removedVersion, currentVersion)) => + val scalacOption = ScalacOption(optionName, version => version < removedVersion) + assert( + !scalacOption.isSupported(currentVersion), + "Should not be valid when removedIn matches an old major release" + ) + } + } +} diff --git a/testkit/src/test/scala/org/typelevel/scalacoptions/internal/ParserSuite.scala b/testkit/src/test/scala/org/typelevel/scalacoptions/internal/ParserSuite.scala new file mode 100644 index 0000000..bc23cec --- /dev/null +++ b/testkit/src/test/scala/org/typelevel/scalacoptions/internal/ParserSuite.scala @@ -0,0 +1,43 @@ +/* + * Copyright 2022 Typelevel + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.typelevel.scalacoptions.internal + +import org.scalacheck.Arbitrary +import org.scalacheck.Gen +import org.scalacheck.Prop + +class ParserSuite extends munit.ScalaCheckSuite { + property("Parser.parseGenericVersion should succeed for valid version strings") { + val ulongGen: Gen[Long] = Gen.chooseNum[Long](0L, Long.MaxValue) + Prop.forAll(ulongGen, ulongGen, ulongGen) { (major, minor, patch) => + assertEquals( + Parser.parseGenericVersion(s"$major.$minor.$patch"), + Some((major, minor, patch)) + ) + } + } + property("Parser.parseGenericVersion should None for invalid version strings") { + val gen = + Arbitrary + .arbitrary[String] + .filterNot(Parser.genericVersionRe.pattern.matcher(_).matches()) + + Prop.forAll(gen) { s => + assert(Parser.parseGenericVersion(s).isEmpty) + } + } +} diff --git a/testkit/src/test/scala/org/typelevel/scalacoptions/literalsSuite.scala b/testkit/src/test/scala/org/typelevel/scalacoptions/literalsSuite.scala new file mode 100644 index 0000000..20b9cd1 --- /dev/null +++ b/testkit/src/test/scala/org/typelevel/scalacoptions/literalsSuite.scala @@ -0,0 +1,43 @@ +/* + * Copyright 2022 Typelevel + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.typelevel.scalacoptions + +import literals._ + +class literalsSuite extends munit.FunSuite { + + // Compile-time checks are only possible. + test("""literals.sver: correct literals should compile successfully""") { + assertEquals(sver"2.12.1", ScalaVersion.unsafeFrom(2, 12, 1)) + assertEquals(sver"2.12.17", ScalaVersion.unsafeFrom(2, 12, 17)) + assertEquals(sver"2.13.1", ScalaVersion.unsafeFrom(2, 13, 1)) + assertEquals(sver"2.13.8", ScalaVersion.unsafeFrom(2, 13, 8)) + assertEquals(sver"3.0.0", ScalaVersion.unsafeFrom(3, 0, 0)) + assertEquals(sver"3.1.3", ScalaVersion.unsafeFrom(3, 1, 3)) + } + test("literals.sver: incorrect literals should fail to compile") { + def check(error: String) = + // assertNoDiff does not work for Scala3. + assert(error.contains("error: incorrect or unsupported Scala version")) + + check(compileErrors("""sver""""")) + check(compileErrors("""sver"1"""")) + check(compileErrors("""sver"1.2"""")) + check(compileErrors("""sver"1.2.3"""")) + check(compileErrors("""sver"1.2.3.4"""")) + } +} diff --git a/testkit/src/test/scala/org/typelevel/scalacoptions/package.scala b/testkit/src/test/scala/org/typelevel/scalacoptions/package.scala new file mode 100644 index 0000000..b6ddadf --- /dev/null +++ b/testkit/src/test/scala/org/typelevel/scalacoptions/package.scala @@ -0,0 +1,19 @@ +/* + * Copyright 2022 Typelevel + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.typelevel + +package object scalacoptions extends internal.ScalaCollectionCompat