Skip to content

Commit 7cbc5cc

Browse files
authored
Scala 3 support (#11)
Closes #2 ## Macros Just like @KuceraMartin in #3, I ran into scala/scala3#19493 and scala/scala3#19436 when trying to resolve a `TypeMapper` by importing from a `DialectTypeMappers`. As a workaround, I introduced [additional `implicit def`s in the `TableMapper` companion object](https://github.com/com-lihaoyi/scalasql/blob/a7d6c531bf7b9cc2f5e2c175906d2a1e961de206/scalasql/core/src/TypeMapper.scala#L58-L121) that instead rely on an implicit instance of `DialectTypeMappers`, i.e. in a macro: ```scala // bad, causes a compiler crash // TableMacro.scala (dialect: DialectTypeMappers) => { import dialect.* summonInline[TypeMapper[t]] } // good // TypeMapper.scala implicit def stringFromDialectTypeMappers(implicit d: DialectTypeMappers): TypeMapper[String] = d.StringType // TableMacro.scala (dialect: DialectTypeMappers) => { given d: DialectTypeMappers = dialect summonInline[TypeMapper[t]] } ``` ## Supporting changes In addition to building out the macros in Scala 3, the following changes were necessary: 1. Update the generated code to ensure `def`s aren't too far to the left -- this is to silence Scala 3 warnings 2. Convert `CharSequence`s to `String`s explicitly -- see the [error the Scala 3 compiler reported here](9ffeb06) 3. Remove `try` block without a corresponding `catch` -- see the [warning the Scala 3 compiler reported here](011c3f6) 4. Add types to implicit definitions 5. Mark `renderSql` as `private[scalasql]` instead of `protected` -- see the [error the Scala 3 compiler reported here](8e767e3) 6. Use Scala 3.4 -- this is a little unfortunate since it's not the LTS but it's necessary for the Scala 3 macros to [match on higher kinded types like this](https://github.com/com-lihaoyi/scalasql/blob/a7d6c531bf7b9cc2f5e2c175906d2a1e961de206/scalasql/query/src-3/TableMacro.scala#L48-L52). This type of match doesn't work in Scala 3.3 7. Replace `_` wildcards with `?` -- this is to silence Scala 3 warnings 8. Replace `Foo with Bar` in types with `Foo & Bar` -- this is to silence Scala 3 warnings 9. Add the `-Xsource:3` compiler option for Scala 2 -- this is necessary to use the language features mentioned in points 7 and 8 10. Add a number of type annotations to method overrides -- this is to silence warnings reported by the Scala 2 compiler as a result of enabling `-Xsource:3`. All of the warnings relate to the inferred type of the method changing between Scala 2 and 3
1 parent 7318f00 commit 7cbc5cc

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+572
-251
lines changed

.scalafmt.conf

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
version = "3.7.15"
1+
version = "3.8.1"
22

33
align.preset = none
44
align.openParenCallSite = false
@@ -15,5 +15,10 @@ docstrings.wrap = no
1515

1616
maxColumn = 100
1717
newlines.implicitParamListModifierPrefer = before
18-
runner.dialect = scala213
18+
runner.dialect = scala3
1919

20+
fileOverride {
21+
"glob:**/src-2/**" {
22+
runner.dialect = scala213source3
23+
}
24+
}

build.sc

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import $file.docs.generateDocs
22
import $ivy.`de.tototec::de.tobiasroeser.mill.vcs.version::0.4.0`
33
import $ivy.`com.github.lolgab::mill-mima::0.1.0`
4-
import $ivy.`com.goyeau::mill-scalafix::0.3.1`
4+
import $ivy.`com.goyeau::mill-scalafix::0.4.0`
55
import de.tobiasroeser.mill.vcs.version.VcsVersion
66
import com.goyeau.mill.scalafix.ScalafixModule
77
import mill._, scalalib._, publish._
88

9-
val scalaVersions = Seq("2.13.12"/*, "3.3.1"*/)
9+
val scalaVersions = Seq("2.13.12", "3.4.2")
1010

1111
trait Common extends CrossScalaModule with PublishModule with ScalafixModule{
1212
def scalaVersion = crossScalaVersion
@@ -27,12 +27,15 @@ trait Common extends CrossScalaModule with PublishModule with ScalafixModule{
2727
)
2828
)
2929

30-
def scalacOptions = Seq("-Xlint:unused")
30+
def scalacOptions = T {
31+
Seq("-Wunused:privates,locals,explicits,implicits,params") ++
32+
Option.when(scalaVersion().startsWith("2."))("-Xsource:3")
33+
}
3134
}
3235

3336

3437
object scalasql extends Cross[ScalaSql](scalaVersions)
35-
trait ScalaSql extends Common{
38+
trait ScalaSql extends Common{ common =>
3639
def moduleDeps = Seq(query, operations)
3740
def ivyDeps = Agg(
3841
ivy"org.apache.logging.log4j:log4j-api:2.20.0",
@@ -45,7 +48,7 @@ trait ScalaSql extends Common{
4548

4649

4750
object test extends ScalaTests with ScalafixModule{
48-
def scalacOptions = Seq("-Xlint:unused")
51+
def scalacOptions = common.scalacOptions
4952
def ivyDeps = Agg(
5053
ivy"com.github.vertical-blank:sql-formatter:2.0.4",
5154
ivy"com.lihaoyi::mainargs:0.4.0",
@@ -66,6 +69,9 @@ trait ScalaSql extends Common{
6669
def forkArgs = Seq("-Duser.timezone=Asia/Singapore")
6770
}
6871

72+
private def indent(code: Iterable[String]): String =
73+
code.map(_.split("\n").map(" " + _).mkString("\n")).mkString("\n")
74+
6975
object core extends Common with CrossValue {
7076
def ivyDeps = Agg(
7177
ivy"com.lihaoyi::geny:1.0.0",
@@ -100,7 +106,7 @@ trait ScalaSql extends Common{
100106
s"""package scalasql.core.generated
101107
|import scalasql.core.Queryable
102108
|trait QueryableRow{
103-
| ${queryableRowDefs.mkString("\n")}
109+
|${indent(queryableRowDefs)}
104110
|}
105111
|""".stripMargin
106112
)
@@ -147,7 +153,6 @@ trait ScalaSql extends Common{
147153
| implicit
148154
| ${commaSep(j => s"q$j: Queryable.Row[Q$j, R$j]")}
149155
|): Queryable.Row[(${commaSep(j => s"Q$j")}), (${commaSep(j => s"R$j")})] = {
150-
| import scalasql.core.SqlStr.SqlStringSyntax
151156
| new Queryable.Row.TupleNQueryable(
152157
| Seq(${commaSep(j => s"q$j.walkLabels()")}),
153158
| t => Seq(${commaSep(j => s"q$j.walkExprs(t._$j)")}),
@@ -166,7 +171,7 @@ trait ScalaSql extends Common{
166171
s"""
167172
|implicit def append$i[$commaSepQ, QA, $commaSepR, RA](
168173
| implicit qr0: Queryable.Row[($commaSepQ, QA), ($commaSepR, RA)],
169-
| qr20: Queryable.Row[QA, RA]): $joinAppendType = new $joinAppendType {
174+
| @annotation.nowarn("msg=never used") qr20: Queryable.Row[QA, RA]): $joinAppendType = new $joinAppendType {
170175
| override def appendTuple(t: ($commaSepQ), v: QA): ($commaSepQ, QA) = (${commaSep(j => s"t._$j")}, v)
171176
|
172177
| def qr: Queryable.Row[($commaSepQ, QA), ($commaSepR, RA)] = qr0
@@ -179,23 +184,23 @@ trait ScalaSql extends Common{
179184
|import scalasql.core.{Queryable, Expr}
180185
|import scalasql.query.Column
181186
|trait Insert[V[_[_]], R]{
182-
| ${defs(false).mkString("\n")}
187+
|${indent(defs(false))}
183188
|}
184189
|trait InsertImpl[V[_[_]], R] extends Insert[V, R]{ this: scalasql.query.Insert[V, R] =>
185190
| def newInsertValues[R](
186191
| insert: scalasql.query.Insert[V, R],
187-
| columns: Seq[Column[_]],
188-
| valuesLists: Seq[Seq[Expr[_]]]
192+
| columns: Seq[Column[?]],
193+
| valuesLists: Seq[Seq[Expr[?]]]
189194
| )(implicit qr: Queryable[V[Column], R]): scalasql.query.InsertColumns[V, R]
190-
| ${defs(true).mkString("\n")}
195+
|${indent(defs(true))}
191196
|}
192197
|
193198
|trait QueryableRow{
194-
| ${queryableRowDefs.mkString("\n")}
199+
|${indent(queryableRowDefs)}
195200
|}
196201
|
197202
|trait JoinAppend extends scalasql.query.JoinAppendLowPriority{
198-
| ${joinAppendDefs.mkString("\n")}
203+
|${indent(joinAppendDefs)}
199204
|}
200205
|""".stripMargin
201206
)

docs/reference.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -440,7 +440,7 @@ dbClient.transaction { implicit db =>
440440
db.run(Purchase.delete(_.id <= 3)) ==> 3
441441
db.run(Purchase.select.size) ==> 4
442442

443-
db.savepoint { sp =>
443+
db.savepoint { _ =>
444444
db.run(Purchase.delete(_ => true)) ==> 4
445445
db.run(Purchase.select.size) ==> 0
446446
}
@@ -499,7 +499,7 @@ dbClient.transaction { implicit db =>
499499
db.run(Purchase.select.size) ==> 4
500500

501501
try {
502-
db.savepoint { sp =>
502+
db.savepoint { _ =>
503503
db.run(Purchase.delete(_ => true)) ==> 4
504504
db.run(Purchase.select.size) ==> 0
505505
throw new FooException
@@ -533,11 +533,11 @@ dbClient.transaction { implicit db =>
533533
db.run(Purchase.delete(_.id <= 2)) ==> 2
534534
db.run(Purchase.select.size) ==> 5
535535

536-
db.savepoint { sp1 =>
536+
db.savepoint { _ =>
537537
db.run(Purchase.delete(_.id <= 4)) ==> 2
538538
db.run(Purchase.select.size) ==> 3
539539

540-
db.savepoint { sp2 =>
540+
db.savepoint { _ =>
541541
db.run(Purchase.delete(_.id <= 6)) ==> 2
542542
db.run(Purchase.select.size) ==> 1
543543
}

scalasql/core/src/DbApi.scala

Lines changed: 26 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ trait DbApi extends AutoCloseable {
2727
* Runs the given [[SqlStr]] of the form `sql"..."` and returns a value of type [[R]]
2828
*/
2929
def runSql[R](query: SqlStr, fetchSize: Int = -1, queryTimeoutSeconds: Int = -1)(
30-
implicit qr: Queryable.Row[_, R],
30+
implicit qr: Queryable.Row[?, R],
3131
fileName: sourcecode.FileName,
3232
lineNum: sourcecode.Line
3333
): IndexedSeq[R]
@@ -48,7 +48,7 @@ trait DbApi extends AutoCloseable {
4848
* arbitrary [[SqlStr]] of the form `sql"..."` and streams the results back to you
4949
*/
5050
def streamSql[R](sql: SqlStr, fetchSize: Int = -1, queryTimeoutSeconds: Int = -1)(
51-
implicit qr: Queryable.Row[_, R],
51+
implicit qr: Queryable.Row[?, R],
5252
fileName: sourcecode.FileName,
5353
lineNum: sourcecode.Line
5454
): Generator[R]
@@ -63,7 +63,7 @@ trait DbApi extends AutoCloseable {
6363
fetchSize: Int = -1,
6464
queryTimeoutSeconds: Int = -1
6565
)(
66-
implicit qr: Queryable.Row[_, R],
66+
implicit qr: Queryable.Row[?, R],
6767
fileName: sourcecode.FileName,
6868
lineNum: sourcecode.Line
6969
): IndexedSeq[R]
@@ -78,7 +78,7 @@ trait DbApi extends AutoCloseable {
7878
fetchSize: Int = -1,
7979
queryTimeoutSeconds: Int = -1
8080
)(
81-
implicit qr: Queryable.Row[_, R],
81+
implicit qr: Queryable.Row[?, R],
8282
fileName: sourcecode.FileName,
8383
lineNum: sourcecode.Line
8484
): Generator[R]
@@ -104,7 +104,7 @@ trait DbApi extends AutoCloseable {
104104
)(implicit fileName: sourcecode.FileName, lineNum: sourcecode.Line): Int
105105

106106
def updateGetGeneratedKeysSql[R](sql: SqlStr, fetchSize: Int = -1, queryTimeoutSeconds: Int = -1)(
107-
implicit qr: Queryable.Row[_, R],
107+
implicit qr: Queryable.Row[?, R],
108108
fileName: sourcecode.FileName,
109109
lineNum: sourcecode.Line
110110
): IndexedSeq[R]
@@ -115,7 +115,7 @@ trait DbApi extends AutoCloseable {
115115
fetchSize: Int = -1,
116116
queryTimeoutSeconds: Int = -1
117117
)(
118-
implicit qr: Queryable.Row[_, R],
118+
implicit qr: Queryable.Row[?, R],
119119
fileName: sourcecode.FileName,
120120
lineNum: sourcecode.Line
121121
): IndexedSeq[R]
@@ -202,22 +202,20 @@ object DbApi {
202202
.asInstanceOf[R]
203203
else if (qr.isExecuteUpdate(query)) updateSql(flattened).asInstanceOf[R]
204204
else {
205-
try {
206-
val res = stream(query, fetchSize, queryTimeoutSeconds)(
207-
qr.asInstanceOf[Queryable[Q, Seq[_]]],
208-
fileName,
209-
lineNum
205+
val res = stream(query, fetchSize, queryTimeoutSeconds)(
206+
qr.asInstanceOf[Queryable[Q, Seq[?]]],
207+
fileName,
208+
lineNum
209+
)
210+
if (qr.isSingleRow(query)) {
211+
val results = res.take(2).toVector
212+
assert(
213+
results.size == 1,
214+
s"Single row query must return 1 result, not ${results.size}"
210215
)
211-
if (qr.isSingleRow(query)) {
212-
val results = res.take(2).toVector
213-
assert(
214-
results.size == 1,
215-
s"Single row query must return 1 result, not ${results.size}"
216-
)
217-
results.head.asInstanceOf[R]
218-
} else {
219-
res.toVector.asInstanceOf[R]
220-
}
216+
results.head.asInstanceOf[R]
217+
} else {
218+
res.toVector.asInstanceOf[R]
221219
}
222220
}
223221
}
@@ -248,7 +246,7 @@ object DbApi {
248246
fetchSize: Int = -1,
249247
queryTimeoutSeconds: Int = -1
250248
)(
251-
implicit qr: Queryable.Row[_, R],
249+
implicit qr: Queryable.Row[?, R],
252250
fileName: sourcecode.FileName,
253251
lineNum: sourcecode.Line
254252
): IndexedSeq[R] = streamSql(sql, fetchSize, queryTimeoutSeconds).toVector
@@ -258,7 +256,7 @@ object DbApi {
258256
fetchSize: Int = -1,
259257
queryTimeoutSeconds: Int = -1
260258
)(
261-
implicit qr: Queryable.Row[_, R],
259+
implicit qr: Queryable.Row[?, R],
262260
fileName: sourcecode.FileName,
263261
lineNum: sourcecode.Line
264262
): Generator[R] = {
@@ -292,7 +290,7 @@ object DbApi {
292290
fetchSize: Int = -1,
293291
queryTimeoutSeconds: Int = -1
294292
)(
295-
implicit qr: Queryable.Row[_, R],
293+
implicit qr: Queryable.Row[?, R],
296294
fileName: sourcecode.FileName,
297295
lineNum: sourcecode.Line
298296
): IndexedSeq[R] = {
@@ -314,7 +312,7 @@ object DbApi {
314312
fetchSize: Int = -1,
315313
queryTimeoutSeconds: Int = -1
316314
)(
317-
implicit qr: Queryable.Row[_, R],
315+
implicit qr: Queryable.Row[?, R],
318316
fileName: sourcecode.FileName,
319317
lineNum: sourcecode.Line
320318
): IndexedSeq[R] = {
@@ -327,7 +325,7 @@ object DbApi {
327325
fetchSize: Int = -1,
328326
queryTimeoutSeconds: Int = -1
329327
)(
330-
implicit qr: Queryable.Row[_, R],
328+
implicit qr: Queryable.Row[?, R],
331329
fileName: sourcecode.FileName,
332330
lineNum: sourcecode.Line
333331
): Generator[R] = {
@@ -362,7 +360,7 @@ object DbApi {
362360
fetchSize: Int = -1,
363361
queryTimeoutSeconds: Int = -1
364362
)(
365-
implicit qr: Queryable.Row[_, R],
363+
implicit qr: Queryable.Row[?, R],
366364
fileName: sourcecode.FileName,
367365
lineNum: sourcecode.Line
368366
): IndexedSeq[R] = runRawUpdateGetGeneratedKeys0(
@@ -466,7 +464,7 @@ object DbApi {
466464
queryTimeoutSeconds: Int,
467465
fileName: sourcecode.FileName,
468466
lineNum: sourcecode.Line,
469-
qr: Queryable.Row[_, R]
467+
qr: Queryable.Row[?, R]
470468
): IndexedSeq[R] = {
471469
val statement = connection.prepareStatement(sql, java.sql.Statement.RETURN_GENERATED_KEYS)
472470
for ((v, i) <- variables.iterator.zipWithIndex) v(statement, i + 1)

scalasql/core/src/Expr.scala

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import scalasql.core.SqlStr.SqlStringSyntax
77
* a Scala value of a particular type [[T]]
88
*/
99
trait Expr[T] extends SqlStr.Renderable {
10-
protected final def renderSql(ctx: Context): SqlStr = {
10+
private[scalasql] final def renderSql(ctx: Context): SqlStr = {
1111
ctx.exprNaming.get(this.exprIdentity).getOrElse(renderToSql0(ctx))
1212
}
1313

@@ -37,15 +37,15 @@ object Expr {
3737
def identity[T](e: Expr[T]): Identity = e.exprIdentity
3838
class Identity()
3939

40-
implicit def ExprQueryable[E[_] <: Expr[_], T](
40+
implicit def ExprQueryable[E[_] <: Expr[?], T](
4141
implicit mt: TypeMapper[T]
4242
): Queryable.Row[E[T], T] = new ExprQueryable[E, T]()
4343

44-
class ExprQueryable[E[_] <: Expr[_], T](
44+
class ExprQueryable[E[_] <: Expr[?], T](
4545
implicit tm: TypeMapper[T]
4646
) extends Queryable.Row[E[T], T] {
47-
def walkLabels() = Seq(Nil)
48-
def walkExprs(q: E[T]) = Seq(q)
47+
def walkLabels(): Seq[List[String]] = Seq(Nil)
48+
def walkExprs(q: E[T]): Seq[Expr[?]] = Seq(q)
4949

5050
override def construct(args: Queryable.ResultSetIterator): T = args.get(tm)
5151

scalasql/core/src/ExprsToSql.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ object ExprsToSql {
3939
}
4040
}
4141

42-
def booleanExprs(prefix: SqlStr, exprs: Seq[Expr[_]])(implicit ctx: Context) = {
42+
def booleanExprs(prefix: SqlStr, exprs: Seq[Expr[?]])(implicit ctx: Context) = {
4343
SqlStr.optSeq(exprs.filter(!Expr.isLiteralTrue(_))) { having =>
4444
prefix + SqlStr.join(having.map(Renderable.renderSql(_)), sql" AND ")
4545
}

scalasql/core/src/JoinNullable.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ import scalasql.core.SqlStr.SqlStringSyntax
88
*/
99
trait JoinNullable[Q] {
1010
def get: Q
11-
def isEmpty[T](f: Q => Expr[T])(implicit qr: Queryable[Q, _]): Expr[Boolean]
12-
def nonEmpty[T](f: Q => Expr[T])(implicit qr: Queryable[Q, _]): Expr[Boolean]
11+
def isEmpty[T](f: Q => Expr[T])(implicit qr: Queryable[Q, ?]): Expr[Boolean]
12+
def nonEmpty[T](f: Q => Expr[T])(implicit qr: Queryable[Q, ?]): Expr[Boolean]
1313
def map[V](f: Q => V): JoinNullable[V]
1414

1515
}
@@ -19,11 +19,11 @@ object JoinNullable {
1919

2020
def apply[Q](t: Q): JoinNullable[Q] = new JoinNullable[Q] {
2121
def get: Q = t
22-
def isEmpty[T](f: Q => Expr[T])(implicit qr: Queryable[Q, _]): Expr[Boolean] = Expr {
22+
def isEmpty[T](f: Q => Expr[T])(implicit qr: Queryable[Q, ?]): Expr[Boolean] = Expr {
2323
implicit ctx =>
2424
sql"(${f(t)} IS NULL)"
2525
}
26-
def nonEmpty[T](f: Q => Expr[T])(implicit qr: Queryable[Q, _]): Expr[Boolean] = Expr {
26+
def nonEmpty[T](f: Q => Expr[T])(implicit qr: Queryable[Q, ?]): Expr[Boolean] = Expr {
2727
implicit ctx =>
2828
sql"(${f(t)} IS NOT NULL)"
2929
}

0 commit comments

Comments
 (0)