diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f5e4afd6..f563d45a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ Thank you! # 0.18.25 +* Fixes an issue in which union members targetting Unit would fail to compile when used as traits (see [#1600](https://github.com/disneystreaming/smithy4s/pull/1600)). * Make the `transform` method in generated `*Gen` algebras final. This should make it possible to derive e.g. `FunctorK` instances in cats-tagless automatically (see [#1588](https://github.com/disneystreaming/smithy4s/pull/1588)). # 0.18.24 diff --git a/modules/bootstrapped/src/generated/smithy4s/example/HasUnionUnitCaseTrait.scala b/modules/bootstrapped/src/generated/smithy4s/example/HasUnionUnitCaseTrait.scala new file mode 100644 index 000000000..f96e4fa4c --- /dev/null +++ b/modules/bootstrapped/src/generated/smithy4s/example/HasUnionUnitCaseTrait.scala @@ -0,0 +1,17 @@ +package smithy4s.example + +import smithy4s.Hints +import smithy4s.Newtype +import smithy4s.Schema +import smithy4s.ShapeId +import smithy4s.schema.Schema.bijection +import smithy4s.schema.Schema.string + +object HasUnionUnitCaseTrait extends Newtype[String] { + val id: ShapeId = ShapeId("smithy4s.example", "HasUnionUnitCaseTrait") + val hints: Hints = Hints( + smithy4s.example.UnionTraitWithUnitCase.UCase.widen, + ).lazily + val underlyingSchema: Schema[String] = string.withId(id).addHints(hints) + implicit val schema: Schema[HasUnionUnitCaseTrait] = bijection(underlyingSchema, asBijection) +} diff --git a/modules/bootstrapped/src/generated/smithy4s/example/UnionTraitWithUnitCase.scala b/modules/bootstrapped/src/generated/smithy4s/example/UnionTraitWithUnitCase.scala new file mode 100644 index 000000000..b40bb9c3b --- /dev/null +++ b/modules/bootstrapped/src/generated/smithy4s/example/UnionTraitWithUnitCase.scala @@ -0,0 +1,67 @@ +package smithy4s.example + +import UnionTraitWithUnitCase.UCaseAlt +import smithy4s.Hints +import smithy4s.Schema +import smithy4s.ShapeId +import smithy4s.ShapeTag +import smithy4s.schema.Schema.bijection +import smithy4s.schema.Schema.recursive +import smithy4s.schema.Schema.string +import smithy4s.schema.Schema.union + +sealed trait UnionTraitWithUnitCase extends scala.Product with scala.Serializable { self => + @inline final def widen: UnionTraitWithUnitCase = this + def $ordinal: Int + + object project { + def u: Option[UnionTraitWithUnitCase.UCase.type] = UCaseAlt.project.lift(self) + def s: Option[String] = UnionTraitWithUnitCase.SCase.alt.project.lift(self).map(_.s) + } + + def accept[A](visitor: UnionTraitWithUnitCase.Visitor[A]): A = this match { + case value: UnionTraitWithUnitCase.UCase.type => visitor.u(value) + case value: UnionTraitWithUnitCase.SCase => visitor.s(value.s) + } +} +object UnionTraitWithUnitCase extends ShapeTag.Companion[UnionTraitWithUnitCase] { + + def u(): UnionTraitWithUnitCase = UnionTraitWithUnitCase.UCase + def s(s: String): UnionTraitWithUnitCase = SCase(s) + + val id: ShapeId = ShapeId("smithy4s.example", "unionTraitWithUnitCase") + + val hints: Hints = Hints( + smithy.api.Trait(selector = None, structurallyExclusive = None, conflicts = None, breakingChanges = None), + ).lazily + + case object UCase extends UnionTraitWithUnitCase { final def $ordinal: Int = 0 } + private val UCaseAlt = Schema.constant(UnionTraitWithUnitCase.UCase).oneOf[UnionTraitWithUnitCase]("u").addHints(hints) + final case class SCase(s: String) extends UnionTraitWithUnitCase { final def $ordinal: Int = 1 } + + object SCase { + val hints: Hints = Hints.empty + val schema: Schema[UnionTraitWithUnitCase.SCase] = bijection(string.addHints(hints), UnionTraitWithUnitCase.SCase(_), _.s) + val alt = schema.oneOf[UnionTraitWithUnitCase]("s") + } + + trait Visitor[A] { + def u(value: UnionTraitWithUnitCase.UCase.type): A + def s(value: String): A + } + + object Visitor { + trait Default[A] extends Visitor[A] { + def default: A + def u(value: UnionTraitWithUnitCase.UCase.type): A = default + def s(value: String): A = default + } + } + + implicit val schema: Schema[UnionTraitWithUnitCase] = recursive(union( + UCaseAlt, + UnionTraitWithUnitCase.SCase.alt, + ){ + _.$ordinal + }.withId(id).addHints(hints)) +} diff --git a/modules/bootstrapped/src/generated/smithy4s/example/package.scala b/modules/bootstrapped/src/generated/smithy4s/example/package.scala index a914f9cf4..568c337a0 100644 --- a/modules/bootstrapped/src/generated/smithy4s/example/package.scala +++ b/modules/bootstrapped/src/generated/smithy4s/example/package.scala @@ -66,6 +66,7 @@ package object example { type ExtraData = smithy4s.example.ExtraData.Type type FancyList = smithy4s.example.FancyList.Type type FreeForm = smithy4s.example.FreeForm.Type + type HasUnionUnitCaseTrait = smithy4s.example.HasUnionUnitCaseTrait.Type type Ingredients = smithy4s.example.Ingredients.Type /** @param member * listFoo diff --git a/modules/codegen/src/smithy4s/codegen/internals/IR.scala b/modules/codegen/src/smithy4s/codegen/internals/IR.scala index fbdc20cdc..3519c1d3a 100644 --- a/modules/codegen/src/smithy4s/codegen/internals/IR.scala +++ b/modules/codegen/src/smithy4s/codegen/internals/IR.scala @@ -31,6 +31,7 @@ import TypedNode.FieldTN.OptionalSomeTN import TypedNode.FieldTN.RequiredTN import TypedNode.AltValueTN.ProductAltTN import TypedNode.AltValueTN.TypeAltTN +import TypedNode.AltValueTN.UnitAltTN import UnionMember._ import LineSegment.{NameDef, NameRef} @@ -426,6 +427,7 @@ private[internals] object TypedNode { def map[B](f: A => B): AltValueTN[B] = this match { case ProductAltTN(value) => ProductAltTN(f(value)) case TypeAltTN(value) => TypeAltTN(f(value)) + case UnitAltTN => UnitAltTN } } object AltValueTN { @@ -437,6 +439,7 @@ private[internals] object TypedNode { fa match { case ProductAltTN(value) => f(value).map(ProductAltTN(_)) case TypeAltTN(value) => f(value).map(TypeAltTN(_)) + case UnitAltTN => Applicative[G].pure(UnitAltTN) } def foldLeft[A, B](fa: AltValueTN[A], b: B)(f: (B, A) => B): B = ??? def foldRight[A, B](fa: AltValueTN[A], lb: Eval[B])( @@ -446,6 +449,7 @@ private[internals] object TypedNode { case class ProductAltTN[A](value: A) extends AltValueTN[A] case class TypeAltTN[A](value: A) extends AltValueTN[A] + case object UnitAltTN extends AltValueTN[Nothing] } implicit val typedNodeTraverse: Traverse[TypedNode] = diff --git a/modules/codegen/src/smithy4s/codegen/internals/Renderer.scala b/modules/codegen/src/smithy4s/codegen/internals/Renderer.scala index 912f961a9..ae4de8862 100644 --- a/modules/codegen/src/smithy4s/codegen/internals/Renderer.scala +++ b/modules/codegen/src/smithy4s/codegen/internals/Renderer.scala @@ -1613,6 +1613,9 @@ private[internals] class Renderer(compilationUnit: CompilationUnit) { self => case AltTN(ref, altName, AltValueTN.TypeAltTN(alt)) => line"${ref.show}.${altName.capitalize}Case(${alt.runDefault}).widen".write + case AltTN(ref, altName, AltValueTN.UnitAltTN) => + line"${ref.show}.${altName.capitalize}Case.widen".write + case AltTN(_, _, AltValueTN.ProductAltTN(alt)) => alt.runDefault.write diff --git a/modules/codegen/src/smithy4s/codegen/internals/SmithyToIR.scala b/modules/codegen/src/smithy4s/codegen/internals/SmithyToIR.scala index bee39fb2e..0ccc14bd2 100644 --- a/modules/codegen/src/smithy4s/codegen/internals/SmithyToIR.scala +++ b/modules/codegen/src/smithy4s/codegen/internals/SmithyToIR.scala @@ -1048,7 +1048,9 @@ private[codegen] class SmithyToIR( maybeTypeclassesHint(shape) } - case class AltInfo(name: String, tpe: Type, isAdtMember: Boolean) + case class AltInfo(name: String, tpe: Type, isAdtMember: Boolean) { + def isUnit: Boolean = tpe == Type.unit + } implicit class ShapeExt(shape: Shape) { def name = shape.getId().getName() @@ -1151,21 +1153,17 @@ private[codegen] class SmithyToIR( shape .members() .asScala - .map { member => - val memberTarget = - model.expectShape(member.getTarget) - if (isPartOfAdt(memberTarget)) { - (member.getMemberName(), member.tpe.map(Left(_))) - } else { - (member.getMemberName(), member.tpe.map(Right(_))) + .flatMap { member => + member.tpe.map { tpe => + val memberTarget = model.expectShape(member.getTarget) + + AltInfo( + member.getMemberName(), + tpe, + isAdtMember = isPartOfAdt(memberTarget) + ) } } - .collect { - case (name, Some(Left(tpe))) => - AltInfo(name, tpe, isAdtMember = true) - case (name, Some(Right(tpe))) => - AltInfo(name, tpe, isAdtMember = false) - } .toList } @@ -1302,6 +1300,8 @@ private[codegen] class SmithyToIR( val a = if (alt.isAdtMember) { val t = NodeAndType(node, alt.tpe) TypedNode.AltValueTN.ProductAltTN(t) + } else if (alt.isUnit) { + TypedNode.AltValueTN.UnitAltTN } else { val t = NodeAndType(node, alt.tpe) TypedNode.AltValueTN.TypeAltTN(t) diff --git a/sampleSpecs/misc.smithy b/sampleSpecs/misc.smithy index 27f8f3f9d..a5392c2a8 100644 --- a/sampleSpecs/misc.smithy +++ b/sampleSpecs/misc.smithy @@ -110,3 +110,14 @@ structure RangeCheck { // face with sunglasses emoji @pattern("^\\uD83D\\uDE0E$") string UnicodeRegexString + +@trait +union unionTraitWithUnitCase { + u: Unit + s: String +} + +@unionTraitWithUnitCase(u: { + +}) +string HasUnionUnitCaseTrait