diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0c5d0b8eb0..ef276532fb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -28,7 +28,7 @@ jobs: strategy: fail-fast: false matrix: - scala-version: [213, 3] + scala-version: [212, 213, 3] postgres-version: [12, 16] steps: - uses: actions/checkout@v4 @@ -49,6 +49,20 @@ jobs: bleep compile jvm${{ matrix.scala-version }} bleep test jvm${{ matrix.scala-version }} + build-scala212: + runs-on: ubuntu-latest + timeout-minutes: 20 + if: "!contains(github.event.head_commit.message, 'ci skip')" + steps: + - uses: actions/checkout@v4 + - uses: bleep-build/bleep-setup-action@0.0.1 + - uses: coursier/cache-action@v6 + with: + extraFiles: bleep.yaml + + - name: Compile for 2.12 + run: bleep compile typo@jvm212 + build-docs: timeout-minutes: 20 runs-on: ubuntu-latest diff --git a/bleep.yaml b/bleep.yaml index a7b29719d7..918af0ae1d 100644 --- a/bleep.yaml +++ b/bleep.yaml @@ -11,6 +11,12 @@ projects: isTestProject: true # main project typo: + cross: + jvm212: + scala: + strict: false + options: -Xsource:3 -language:higherKinds -language:implicitConversions + version: 2.12.19 dependencies: - com.typesafe.play::play-json:2.10.6 - org.playframework.anorm::anorm:2.7.0 diff --git a/typo/src/scala/typo/ProjectGraph.scala b/typo/src/scala/typo/ProjectGraph.scala index 44725411a4..90f3fea959 100644 --- a/typo/src/scala/typo/ProjectGraph.scala +++ b/typo/src/scala/typo/ProjectGraph.scala @@ -1,5 +1,7 @@ package typo +import typo.internal.compat.* + import java.nio.file.Path import scala.concurrent.{ExecutionContext, Future} @@ -11,7 +13,7 @@ import scala.concurrent.{ExecutionContext, Future} */ final case class ProjectGraph[T, S](name: String, target: Path, testTarget: Option[Path], value: T, scripts: S, downstream: List[ProjectGraph[T, S]]) { def toList: List[ProjectGraph[T, S]] = - (this :: downstream.flatMap(_.toList)).distinctBy(_.target) + (this :: downstream.flatMap(_.toList)).distinctByCompat(_.target) def valueFromProject[TT, SS](f: ProjectGraph[T, S] => (TT, SS)): ProjectGraph[TT, SS] = { val (tt, ss) = f(this) diff --git a/typo/src/scala/typo/internal/ComputedTestInserts.scala b/typo/src/scala/typo/internal/ComputedTestInserts.scala index 435130383a..2372a3125c 100644 --- a/typo/src/scala/typo/internal/ComputedTestInserts.scala +++ b/typo/src/scala/typo/internal/ComputedTestInserts.scala @@ -3,6 +3,7 @@ package internal import typo.db.Type import typo.internal.codegen.* +import typo.internal.compat.* case class ComputedTestInserts(tpe: sc.Type.Qualified, methods: List[ComputedTestInserts.InsertMethod], maybeDomainMethods: Option[ComputedTestInserts.GenerateDomainMethods]) @@ -223,7 +224,7 @@ object ComputedTestInserts { } } yield GenerateDomainMethod(domain) - NonEmptyList.fromList(all.toList.distinctBy(_.dom.tpe).sortBy(_.dom.tpe)) + NonEmptyList.fromList(all.toList.distinctByCompat(_.dom.tpe).sortBy(_.dom.tpe)) } } } diff --git a/typo/src/scala/typo/internal/FkAnalysis.scala b/typo/src/scala/typo/internal/FkAnalysis.scala index 7ef9ad00ad..be061be655 100644 --- a/typo/src/scala/typo/internal/FkAnalysis.scala +++ b/typo/src/scala/typo/internal/FkAnalysis.scala @@ -2,6 +2,7 @@ package typo package internal import typo.internal.codegen.* +import typo.internal.compat.* class FkAnalysis(table: ComputedTable, candidateFks: List[FkAnalysis.CandidateFk]) { lazy val createWithFkIdsRow: Option[FkAnalysis.CreateWithFkIds] = @@ -44,7 +45,8 @@ object FkAnalysis { lazy val exprsForColumn: Map[sc.Ident, List[sc.Code]] = byFks.toList .flatMap(colsFromFk => colsFromFk.colPairs.map { case (_, col) => (col.name, colsFromFk.expr(col.name)) }) - .groupMap { case (colName, _) => colName } { case (_, expr) => expr } + .groupBy { case (colName, _) => colName } + .map { case (k, tuples) => (k, tuples.map { case (_, expr) => expr }) } /** reduce the potentially multiple values in [[exprsForColumn]] down to one expression, with `require` to assert that the others contain the same value */ lazy val exprForColumn: Map[sc.Ident, sc.Code] = @@ -164,7 +166,7 @@ object FkAnalysis { .toList // consider longest first, plus disambiguate for consistency .sortBy(x => (-x.colPairs.length, x.name)) - .distinctBy(_.name) + .distinctByCompat(_.name) } case class CandidateFk(thisFk: db.ForeignKey, otherTable: ComputedTable, otherId: IdComputed.Composite) diff --git a/typo/src/scala/typo/internal/IdComputed.scala b/typo/src/scala/typo/internal/IdComputed.scala index 186eccabca..fa9744a01a 100644 --- a/typo/src/scala/typo/internal/IdComputed.scala +++ b/typo/src/scala/typo/internal/IdComputed.scala @@ -1,6 +1,8 @@ package typo package internal +import typo.internal.compat.* + sealed trait IdComputed { def paramName: sc.Ident def cols: NonEmptyList[ComputedColumn] @@ -8,7 +10,7 @@ sealed trait IdComputed { final def param: sc.Param = sc.Param(paramName, tpe, None) final lazy val userDefinedColTypes: List[sc.Type] = - cols.toList.collect { case x if sc.Type.containsUserDefined(x.tpe) => x }.map(_.tpe).distinctBy(sc.Type.base) + cols.toList.collect { case x if sc.Type.containsUserDefined(x.tpe) => x }.map(_.tpe).distinctByCompat(sc.Type.base) } object IdComputed { diff --git a/typo/src/scala/typo/internal/analysis/DecomposedSql.scala b/typo/src/scala/typo/internal/analysis/DecomposedSql.scala index 10067376cb..853ea083e0 100644 --- a/typo/src/scala/typo/internal/analysis/DecomposedSql.scala +++ b/typo/src/scala/typo/internal/analysis/DecomposedSql.scala @@ -1,6 +1,7 @@ package typo.internal.analysis import typo.internal.codegen.CodeOps +import typo.internal.compat.* import typo.{db, sc} case class DecomposedSql(frags: List[DecomposedSql.Fragment]) { @@ -42,7 +43,7 @@ case class DecomposedSql(frags: List[DecomposedSql.Fragment]) { paramsWithIndex.collect { case (DecomposedSql.NamedParam(parsedName), i) if parsedName.name == name => i } paramsWithIndex - .distinctBy { + .distinctByCompat { case (DecomposedSql.NotNamedParam, idx) => idx.toString case (DecomposedSql.NamedParam(parsedName), _) => parsedName.name } diff --git a/typo/src/scala/typo/internal/codegen/FilesRelation.scala b/typo/src/scala/typo/internal/codegen/FilesRelation.scala index 55e571ae4d..df3ca11350 100644 --- a/typo/src/scala/typo/internal/codegen/FilesRelation.scala +++ b/typo/src/scala/typo/internal/codegen/FilesRelation.scala @@ -3,6 +3,7 @@ package internal package codegen import play.api.libs.json.Json +import typo.internal.compat.* case class FilesRelation( naming: Naming, @@ -33,7 +34,7 @@ case class FilesRelation( | ${args.mkCode(",\n")} |)""".stripMargin - sc.Value(Nil, extractFkId.name.prepended("extract"), Nil, Nil, extractFkId.otherCompositeIdType, body) + sc.Value(Nil, extractFkId.name.prepended("extract"), Nil, Nil, extractFkId.otherCompositeIdType, body).code }, maybeUnsavedRow.map { case (unsaved, defaults) => val (partOfId, rest) = unsaved.defaultCols.toList.partition { case (col, _) => names.isIdColumn(col.dbName) } @@ -182,7 +183,7 @@ case class FilesRelation( | ${columnPairs.mkCode("\n")}""".stripMargin (fkName, body) } - .distinctBy(_._1) + .distinctByCompat(_._1) .map(_._2) case Source.SqlFile(_) => Nil } diff --git a/typo/src/scala/typo/internal/compat.scala b/typo/src/scala/typo/internal/compat.scala new file mode 100644 index 0000000000..a02e4e3599 --- /dev/null +++ b/typo/src/scala/typo/internal/compat.scala @@ -0,0 +1,23 @@ +package typo.internal + +import scala.collection.mutable + +object compat { + // compat with scala 2.12 + implicit class ListOps[A](as: List[A]) { + def distinctByCompat[B, C](f: A => B): List[A] = { + if (as.lengthCompare(1) <= 0) as + else { + val builder = List.newBuilder[A] + val seen = mutable.HashSet.empty[B] + val it = as.iterator + var different = false + while (it.hasNext) { + val next = it.next() + if (seen.add(f(next))) builder += next else different = true + } + if (different) builder.result() else as + } + } + } +} diff --git a/typo/src/scala/typo/internal/findTypeFromFk.scala b/typo/src/scala/typo/internal/findTypeFromFk.scala index 0184fa31b2..fc13bf5ab7 100644 --- a/typo/src/scala/typo/internal/findTypeFromFk.scala +++ b/typo/src/scala/typo/internal/findTypeFromFk.scala @@ -2,6 +2,7 @@ package typo package internal import typo.internal.rewriteDependentData.EvalMaybe +import typo.internal.compat.* // we let types flow through constraints down to this column, the point is to reuse id types downstream object findTypeFromFk { @@ -25,7 +26,7 @@ object findTypeFromFk { } yield Right(otherCol.tpe) } - all.distinctBy { e => sc.Type.base(e.merge) } match { + all.distinctByCompat { e => sc.Type.base(e.merge) } match { case Nil => None case e :: Nil => Some(e.merge) case all => diff --git a/typo/src/scala/typo/internal/forget.scala b/typo/src/scala/typo/internal/forget.scala new file mode 100644 index 0000000000..7fffdc32a8 --- /dev/null +++ b/typo/src/scala/typo/internal/forget.scala @@ -0,0 +1,5 @@ +package typo.internal + +object forget { + def apply[T](t: T): Unit = (t, ())._2 +} diff --git a/typo/src/scala/typo/internal/generate.scala b/typo/src/scala/typo/internal/generate.scala index 7bdcabac6e..24648dbc89 100644 --- a/typo/src/scala/typo/internal/generate.scala +++ b/typo/src/scala/typo/internal/generate.scala @@ -4,7 +4,6 @@ package internal import typo.internal.codegen.* import typo.internal.sqlfiles.SqlFile -import scala.annotation.nowarn import scala.collection.immutable import scala.collection.immutable.SortedMap @@ -77,8 +76,7 @@ object generate { computedLazyRelations.foreach { case (relName, lazyValue) => if (selector.include(relName)) { - lazyValue.get: @nowarn - () + forget(lazyValue.get) } } diff --git a/typo/src/scala/typo/internal/quote.scala b/typo/src/scala/typo/internal/quote.scala index 2e5a65a8da..8ec7b90347 100644 --- a/typo/src/scala/typo/internal/quote.scala +++ b/typo/src/scala/typo/internal/quote.scala @@ -1,6 +1,8 @@ package typo.internal object quote { - def apply(str: String) = s"'$str'" - def double(str: String) = s"\"$str\"" + val Quote = '\'' + val DoubleQuote = '"' + def apply(str: String) = s"$Quote${str}$Quote" + def double(str: String) = s"$DoubleQuote${str}$DoubleQuote" }