Skip to content

Commit ae1d949

Browse files
rochalakubukoz
andauthored
Backport mill 1.x support to series/0.18 (#1899)
* Backport mill 1.x support to series/0.18 * Bump mima to fix deadlock --------- Co-authored-by: Jakub Kozłowski <kubukoz@gmail.com>
1 parent 4644a61 commit ae1d949

File tree

27 files changed

+1268
-94
lines changed

27 files changed

+1268
-94
lines changed

.scalafmt.conf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
version = "3.1.2"
2-
runner.dialect = scala213
2+
runner.dialect = scala213source3
33
project {
44
excludePaths = [
55
"glob:**/generated/**/*.scala",

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,11 @@ When adding entries, please treat them as if they could end up in a release any
55

66
Thank you!
77

8+
89
# 0.18.49
910

11+
* mill codegen plugin: now supports Mill 1.x (tested with 1.1.1), in addition to the existing Mill 0.11.x and 0.12.x support
12+
* codegen: Cross comple codegen module against Scala 3
1013
* codegen: Fix `Long` literal rendering to add `L` suffix, preventing `integer number too large` compilation errors for values exceeding `Int.MaxValue` in [#1898](https://github.com/disneystreaming/smithy4s/pull/1898)
1114

1215
# 0.18.48

build.sbt

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -440,7 +440,10 @@ lazy val codegen = projectMatrix
440440
.in(file("modules/codegen"))
441441
.enablePlugins(BuildInfoPlugin)
442442
.dependsOn(protocol)
443-
.jvmPlatform(buildtimejvmScala2Versions, jvmDimSettings)
443+
.jvmPlatform(
444+
buildtimejvmScala2Versions :+ Scala3,
445+
jvmDimSettings
446+
)
444447
.settings(
445448
buildInfoKeys := Seq[BuildInfoKey](
446449
version,
@@ -464,14 +467,42 @@ lazy val codegen = projectMatrix
464467
Dependencies.Circe.core.value,
465468
Dependencies.Circe.parser.value,
466469
Dependencies.Circe.generic.value,
467-
Dependencies.collectionsCompat.value,
468-
"org.scala-lang" % "scala-reflect" % scalaVersion.value,
469-
"io.get-coursier" %% "coursier" % "2.1.24",
470+
("io.get-coursier" %% "coursier" % "2.1.24")
471+
.cross(CrossVersion.for3Use2_13),
470472
Dependencies.Mima.core % Test
471473
),
474+
libraryDependencies ++= {
475+
if (scalaVersion.value.startsWith("2."))
476+
Seq(
477+
"org.scala-lang" % "scala-reflect" % scalaVersion.value,
478+
Dependencies.collectionsCompat.value
479+
)
480+
else Seq.empty
481+
},
482+
// For Scala 3, exclude transitive Scala 2.13 deps from coursier that conflict with Scala 3 cross versions.
483+
// Note: scala-xml_2.13 is NOT excluded because coursier needs it at runtime.
484+
excludeDependencies ++= {
485+
if (scalaVersion.value.startsWith("3."))
486+
Seq(
487+
ExclusionRule(
488+
"org.scala-lang.modules",
489+
"scala-collection-compat_2.13"
490+
)
491+
)
492+
else Seq.empty
493+
},
472494
libraryDependencies ++= munitDeps.value,
473495
scalacOptions := scalacOptions.value
474-
.filterNot(Seq("-Ywarn-value-discard", "-Wvalue-discard").contains),
496+
.filterNot(Seq("-Ywarn-value-discard", "-Wvalue-discard").contains) ++ {
497+
if (scalaVersion.value.startsWith("3."))
498+
Seq(
499+
"-Wconf:msg=class EnumTrait in package:silent",
500+
"-Wconf:msg=class SetShape in package:silent",
501+
// Scala 2.12 does not support the Scala 3 varargs syntax
502+
"-Wconf:msg=is no longer supported for vararg splices:silent"
503+
)
504+
else Seq.empty
505+
},
475506
bloopEnabled := true,
476507
Compile / sourceGenerators += {
477508
sourceManaged
@@ -564,6 +595,7 @@ lazy val millCodegenPlugin = projectMatrix
564595
(core.jvm(Scala3) / publishLocal).value,
565596
(dynamic.jvm(Scala213) / publishLocal).value,
566597
(codegen.jvm(Scala213) / publishLocal).value,
598+
(codegen.jvm(Scala3) / publishLocal).value,
567599

568600
// for mill
569601
(protocolJvm / publishLocal).value
@@ -573,8 +605,7 @@ lazy val millCodegenPlugin = projectMatrix
573605
Test / test := (Test / test).dependsOn(publishLocal).value,
574606
libraryDependencies ++= munitDeps.value
575607
)
576-
.millPlatforms(Scala213, millVersions)
577-
.dependsOn(codegen)
608+
.millPlatforms(Scala213, millVersions, codegen)
578609

579610
lazy val decline = (projectMatrix in file("modules/decline"))
580611
.settings(

modules/bootstrapped/src/generated/com/amazonaws/dynamodb/DynamoDB.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ object DynamoDBGen extends Service.Mixin[DynamoDBGen, DynamoDBOperation] {
6363
aws.api.Service(sdkId = "DynamoDB", arnNamespace = Some(aws.api.ArnNamespace("dynamodb")), cloudFormationName = Some(aws.api.CloudFormationName("DynamoDB")), cloudTrailEventSource = Some("dynamodb.amazonaws.com"), docId = None, endpointPrefix = Some("dynamodb"), cloudWatchNamespace = None),
6464
aws.auth.Sigv4(name = "dynamodb"),
6565
aws.protocols.AwsJson1_0(http = None, eventStreamHttp = None),
66-
smithy.api.Documentation("<fullname>Amazon DynamoDB</fullname>\n\n\n <p>Amazon DynamoDB is a fully managed NoSQL database service that provides fast and\n predictable performance with seamless scalability. DynamoDB lets you offload the\n administrative burdens of operating and scaling a distributed database, so that you don\'t have\n to worry about hardware provisioning, setup and configuration, replication, software patching,\n or cluster scaling.</p>\n\n <p>With DynamoDB, you can create database tables that can store and retrieve any amount of\n data, and serve any level of request traffic. You can scale up or scale down your tables\'\n throughput capacity without downtime or performance degradation, and use the AWS Management\n Console to monitor resource utilization and performance metrics.</p>\n\n <p>DynamoDB automatically spreads the data and traffic for your tables over a sufficient\n number of servers to handle your throughput and storage requirements, while maintaining\n consistent and fast performance. All of your data is stored on solid state disks (SSDs) and\n automatically replicated across multiple Availability Zones in an AWS region, providing\n built-in high availability and data durability. </p>"),
66+
smithy.api.Documentation("<fullname>Amazon DynamoDB</fullname>\n\n\n <p>Amazon DynamoDB is a fully managed NoSQL database service that provides fast and\n predictable performance with seamless scalability. DynamoDB lets you offload the\n administrative burdens of operating and scaling a distributed database, so that you don't have\n to worry about hardware provisioning, setup and configuration, replication, software patching,\n or cluster scaling.</p>\n\n <p>With DynamoDB, you can create database tables that can store and retrieve any amount of\n data, and serve any level of request traffic. You can scale up or scale down your tables'\n throughput capacity without downtime or performance degradation, and use the AWS Management\n Console to monitor resource utilization and performance metrics.</p>\n\n <p>DynamoDB automatically spreads the data and traffic for your tables over a sufficient\n number of servers to handle your throughput and storage requirements, while maintaining\n consistent and fast performance. All of your data is stored on solid state disks (SSDs) and\n automatically replicated across multiple Availability Zones in an AWS region, providing\n built-in high availability and data durability. </p>"),
6767
smithy.api.Title("Amazon DynamoDB"),
6868
smithy.api.XmlNamespace(uri = smithy.api.NonEmptyString("http://dynamodb.amazonaws.com/doc/2012-08-10/"), prefix = None),
6969
).lazily

modules/bootstrapped/src/generated/smithy4s/example/AnotherString.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import smithy4s.schema.Schema.string
1414
object AnotherString extends Newtype[String] {
1515
val id: ShapeId = ShapeId("smithy4s.example", "AnotherString")
1616
val hints: Hints = Hints(
17-
smithy.api.Documentation("Multiple line doc comment for another string\nContaining a random */ here.\nSeriously, it\'s important to escape special characters."),
17+
smithy.api.Documentation("Multiple line doc comment for another string\nContaining a random */ here.\nSeriously, it's important to escape special characters."),
1818
).lazily
1919
val underlyingSchema: Schema[String] = string.withId(id).addHints(hints)
2020
implicit val schema: Schema[AnotherString] = bijection(underlyingSchema, asBijection)

modules/bootstrapped/src/generated/smithy4s/example/OrderType.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ object OrderType extends ShapeTag.Companion[OrderType] {
3838
val id: ShapeId = ShapeId("smithy4s.example", "OrderType")
3939

4040
val hints: Hints = Hints(
41-
smithy.api.Documentation("Our order types have different ways to identify a product\nExcept for preview orders, these don\'t have an ID"),
41+
smithy.api.Documentation("Our order types have different ways to identify a product\nExcept for preview orders, these don't have an ID"),
4242
).lazily
4343

4444
final case class OnlineCase(online: OrderNumber) extends OrderType { final def $ordinal: Int = 0 }
@@ -51,7 +51,7 @@ object OrderType extends ShapeTag.Companion[OrderType] {
5151
val id: ShapeId = ShapeId("smithy4s.example", "InStoreOrder")
5252

5353
val hints: Hints = Hints(
54-
smithy.api.Documentation("For an InStoreOrder a location ID isn\'t needed"),
54+
smithy.api.Documentation("For an InStoreOrder a location ID isn't needed"),
5555
).lazily
5656

5757
// constructor using the original order from the spec
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* Copyright 2021-2026 Disney Streaming
3+
*
4+
* Licensed under the Tomorrow Open Source Technology License, Version 1.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://disneystreaming.github.io/TOST-1.0.txt
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package smithy4s.codegen.internals
18+
19+
import LineSegment.NameRef
20+
21+
trait MapTypeCompanionPlatform {
22+
val seqMapRef = NameRef("scala.collection.immutable.SeqMap")
23+
}

modules/codegen/src/smithy4s/codegen/internals/LineSyntax.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,12 @@ import WithValue.ToLineWithValue
2222

2323
private[codegen] object LineSyntax {
2424
implicit class LineInterpolator(val sc: StringContext) extends AnyVal {
25-
def line(renderables: ToLineWithValue[_]*): Line = {
25+
def line(renderables: ToLineWithValue[?]*): Line = {
2626
renderAndCombine(renderables.toList)
2727
}
2828

2929
private def renderAndCombine(
30-
renderables: List[ToLineWithValue[_]]
30+
renderables: List[ToLineWithValue[?]]
3131
): Line = {
3232
def aux[A](binding: ToLineWithValue[A]): Line = {
3333
binding.render

modules/codegen/src/smithy4s/codegen/internals/Renderer.scala

Lines changed: 37 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1852,32 +1852,34 @@ private[internals] class Renderer(compilationUnit: CompilationUnit) { self =>
18521852
}
18531853

18541854
private def renderPrimitive[T](prim: Primitive.Aux[T]): T => Line =
1855-
// NOTE: this match doesn't have exhaustivity checking on Scala 2! (due to the Aux pattern's weird interaction with gADTs)
1856-
prim match {
1855+
// NOTE: this match doesn't have exhaustivity checking on Scala 2! (due to the Aux pattern's weird interaction with GADTs)
1856+
// The asInstanceOf is needed for Scala 3 compatibility since it doesn't refine T through the Aux type alias.
1857+
(prim match {
18571858
case Primitive.BigDecimal =>
18581859
(bd: BigDecimal) => line"scala.math.BigDecimal($bd)"
18591860
case Primitive.BigInteger => (bi: BigInt) => line"scala.math.BigInt($bi)"
1860-
case Primitive.Unit => _ => line"()"
1861-
case Primitive.Double => t => line"${t.toString}d"
1862-
case Primitive.Float => t => line"${t.toString}f"
1863-
case Primitive.Long => t => line"${t.toString}L"
1864-
case Primitive.Int => t => line"${t.toString}"
1865-
case Primitive.Short => t => line"${t.toString}"
1866-
case Primitive.Bool => t => line"${t.toString}"
1867-
case Primitive.Uuid => uuid => line"java.util.UUID.fromString(${renderStringLiteral(uuid.toString)})"
1868-
case Primitive.String => renderStringLiteral
1869-
case Primitive.Byte => b => line"${b.toString}"
1861+
case Primitive.Unit => (_: Unit) => line"()"
1862+
case Primitive.Double => (t: Double) => line"${t.toString}d"
1863+
case Primitive.Float => (t: Float) => line"${t.toString}f"
1864+
case Primitive.Long => (t: Long) => line"${t.toString}L"
1865+
case Primitive.Int => (t: Int) => line"${t.toString}"
1866+
case Primitive.Short => (t: Short) => line"${t.toString}"
1867+
case Primitive.Bool => (t: Boolean) => line"${t.toString}"
1868+
case Primitive.Uuid =>
1869+
(uuid: java.util.UUID) => line"java.util.UUID.fromString(${renderStringLiteral(uuid.toString)})"
1870+
case Primitive.String => (s: String) => renderStringLiteral(s)
1871+
case Primitive.Byte => (b: Byte) => line"${b.toString}"
18701872
case Primitive.Blob =>
1871-
ba =>
1873+
(ba: Array[Byte]) =>
18721874
val blob = NameRef("smithy4s", "Blob")
18731875
if (ba.isEmpty) line"$blob.empty"
18741876
else
18751877
line"$blob(Array[Byte](${ba.mkString(", ")}))"
18761878
case Primitive.Timestamp =>
1877-
ts => line"${NameRef("smithy4s", "Timestamp")}(${ts.getEpochSecond()}L, ${ts.getNano()})"
1878-
case Primitive.Document => renderNodeToLine(_)
1879-
case Primitive.Nothing => v => (v: Nothing) // this case can't happen
1880-
}
1879+
(ts: java.time.Instant) => line"${NameRef("smithy4s", "Timestamp")}(${ts.getEpochSecond()}L, ${ts.getNano()})"
1880+
case Primitive.Document => (node: Node) => renderNodeToLine(node)
1881+
case Primitive.Nothing => (_: Any) => sys.error("unreachable") // this case can't happen
1882+
}).asInstanceOf[T => Line]
18811883

18821884
private def renderNodeToLine(node: Node): Line = {
18831885
node.accept(new NodeVisitor[Line] {
@@ -1910,10 +1912,25 @@ private[internals] class Renderer(compilationUnit: CompilationUnit) { self =>
19101912
})
19111913
}
19121914

1915+
private def escapeForStringLiteral(raw: String): String = {
1916+
val sb = new StringBuilder("\"")
1917+
raw.foreach {
1918+
case '\b' => sb.append("\\b")
1919+
case '\t' => sb.append("\\t")
1920+
case '\n' => sb.append("\\n")
1921+
case '\f' => sb.append("\\f")
1922+
case '\r' => sb.append("\\r")
1923+
case '"' => sb.append("\\\"")
1924+
case '\\' => sb.append("\\\\")
1925+
case c if c >= ' ' && c <= '~' => sb.append(c)
1926+
case c => sb.append("\\u%04x".format(c.toInt))
1927+
}
1928+
sb.append('"')
1929+
sb.toString()
1930+
}
1931+
19131932
private def renderStringLiteral(raw: String): Line = {
1914-
import scala.reflect.runtime.universe._
1915-
val str = Literal(Constant(raw))
1916-
.toString()
1933+
val str = escapeForStringLiteral(raw)
19171934
// Replace sequences like "\\uD83D" (how Smithy specs refer to unicode characters)
19181935
// with unicode character escapes like "\uD83D" that can be parsed in the regex implementations on all platforms.
19191936
// See https://github.com/disneystreaming/smithy4s/pull/499

modules/codegen/src/smithy4s/codegen/internals/SmithyToIR.scala

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,11 @@ private[codegen] class SmithyToIR(
132132
.toSet
133133
.flatMap((n: Node) => n.asArrayNode().asScala)
134134
.flatMap(_.getElements().asScala)
135-
.flatMap(_.asStringNode().asScala.map(n => NamespacePattern(n.getValue)))
135+
.flatMap(
136+
_.asStringNode().asScala.map(n =>
137+
NamespacePattern.fromString(n.getValue)
138+
)
139+
)
136140

137141
private def fieldModifier(member: MemberShape): Field.Modifier = {
138142
val hasRequired = member.hasTrait(classOf[RequiredTrait])
@@ -173,7 +177,8 @@ private[codegen] class SmithyToIR(
173177

174178
shape.tpe.flatMap {
175179
case Type.Alias(_, name, tpe: Type.ExternalType, isUnwrapped) =>
176-
val newHints = hints.filterNot(_ sameNativeTrait tpe.refinementHint)
180+
val newHints =
181+
hints.filterNot(_.sameNativeTrait(tpe.refinementHint))
177182
TypeAlias(
178183
shape.getId(),
179184
name,
@@ -701,7 +706,7 @@ private[codegen] class SmithyToIR(
701706
val h = hints(shape)
702707
tpe match {
703708
case e: Type.ExternalType =>
704-
h.filterNot(_ sameNativeTrait e.refinementHint)
709+
h.filterNot(_.sameNativeTrait(e.refinementHint))
705710
case _ => h
706711
}
707712
}
@@ -838,16 +843,18 @@ private[codegen] class SmithyToIR(
838843
def unionShape(x: UnionShape): Option[Type] =
839844
Type.Ref(x.namespace, x.name).some
840845

846+
@SuppressWarnings(Array("all"))
841847
def memberShape(x: MemberShape): Option[Type] =
842848
model.getShape(x.getTarget()).asScala.flatMap { shape =>
843-
val builder =
844-
(Shape.shapeToBuilder(shape: Shape): AbstractShapeBuilder[_, _])
849+
val builder = (Shape.shapeToBuilder(shape: Shape): Any)
850+
.asInstanceOf[AbstractShapeBuilder[?, ?]]
845851

846852
builder
847853
.addTraits(x.getAllTraits().asScala.map(_._2).asJavaCollection)
848854

849855
builder
850856
.build()
857+
.asInstanceOf[Shape]
851858
.accept(this)
852859
}
853860

@@ -1123,7 +1130,8 @@ private[codegen] class SmithyToIR(
11231130
.zipWithIndex
11241131
.collect {
11251132
case ((name, Some(tpe: Type.ExternalType), modifier, hints), index) =>
1126-
val newHints = hints.filterNot(_ sameNativeTrait tpe.refinementHint)
1133+
val newHints =
1134+
hints.filterNot(_.sameNativeTrait(tpe.refinementHint))
11271135
Field(name, tpe, modifier, index, newHints)
11281136
case ((name, Some(tpe), modifier, hints), index) =>
11291137
Field(name, tpe, modifier, index, hints)
@@ -1164,7 +1172,7 @@ private[codegen] class SmithyToIR(
11641172
Alt(
11651173
name,
11661174
UnionMember.TypeCase(tpe),
1167-
h.filterNot(_ sameNativeTrait tpe.refinementHint)
1175+
h.filterNot(_.sameNativeTrait(tpe.refinementHint))
11681176
)
11691177
case (name, Some(Right(tpe)), h) =>
11701178
Alt(name, UnionMember.TypeCase(tpe), h)
@@ -1429,7 +1437,7 @@ private[codegen] class SmithyToIR(
14291437
case (node, IdRefCase()) =>
14301438
val ref = Type.Ref("smithy4s", "ShapeId")
14311439
val namespace :: name :: _ =
1432-
node.asStringNode.get.getValue.split("#").toList
1440+
(node.asStringNode.get.getValue.split("#").toList: @unchecked)
14331441
def toField(value: String) = TypedNode.FieldTN.RequiredTN(
14341442
NodeAndType(
14351443
Node.from(value),

0 commit comments

Comments
 (0)