Skip to content

Commit

Permalink
WIP: render deprecation hints
Browse files Browse the repository at this point in the history
  • Loading branch information
kubukoz committed Nov 12, 2022
1 parent 918154c commit d5b5a81
Show file tree
Hide file tree
Showing 14 changed files with 141 additions and 50 deletions.
6 changes: 4 additions & 2 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -726,7 +726,7 @@ lazy val complianceTests = projectMatrix
* Example application using the custom REST-JSON protocol provided by
* smithy4s.
*
* All Scala code in this module is generated!
* (almost) all Scala code in this module is generated! The ones that aren't should have a note stating so.
*/
lazy val example = projectMatrix
.in(file("modules/example"))
Expand Down Expand Up @@ -769,7 +769,9 @@ lazy val example = projectMatrix
),
genSmithyOutput := ((ThisBuild / baseDirectory).value / "modules" / "example" / "src"),
genSmithyResourcesOutput := (Compile / resourceDirectory).value,
smithy4sSkip := List("resource")
smithy4sSkip := List("resource"),
// Ignore deprecation warnings here - it's all generated code, anyway.
scalacOptions += "-Wconf:cat=deprecation:silent"
)
.jvmPlatform(List(Scala213), jvmDimSettings)
.settings(Smithy4sBuildPlugin.doNotPublishArtifact)
Expand Down
6 changes: 4 additions & 2 deletions modules/codegen/src/smithy4s/codegen/IR.scala
Original file line number Diff line number Diff line change
Expand Up @@ -95,13 +95,13 @@ case class Enumeration(
shapeId: ShapeId,
name: String,
values: List[EnumValue],
hints: List[Hint] = Nil
hints: List[Hint]
) extends Decl
case class EnumValue(
value: String,
intValue: Int,
name: String,
hints: List[Hint] = Nil
hints: List[Hint]
)

case class Field(
Expand Down Expand Up @@ -245,6 +245,8 @@ object Hint {
case class Constraint(tr: Type.Ref, native: Native) extends Hint
case class Protocol(traits: List[Type.Ref]) extends Hint
case class Default(typedNode: Fix[TypedNode]) extends Hint
case class Deprecated(message: Option[String], since: Option[String])
extends Hint
// traits that get rendered generically
case class Native(typedNode: Fix[TypedNode]) extends Hint
case object IntEnum extends Hint
Expand Down
117 changes: 80 additions & 37 deletions modules/codegen/src/smithy4s/codegen/Renderer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -132,13 +132,32 @@ private[codegen] class Renderer(compilationUnit: CompilationUnit) { self =>
renderTypeAlias(shapeId, ta.nameRef, tpe, recursive, hints)
case enumeration @ Enumeration(shapeId, _, values, hints) =>
renderEnum(shapeId, enumeration.nameRef, values, hints)
case _ => Lines.empty
}

private def deprecationAnnotation(hints: List[Hint]): Line = {
hints
.collectFirst { case h: Hint.Deprecated => h }
.foldMap { dep =>
val messagePart = dep.message
.map(msg => line"message = ${renderStringLiteral(msg)}")
val versionPart =
dep.since.map(v => line"since = ${renderStringLiteral(v)}")

val args = List(messagePart, versionPart).flatten.intercalate(comma)

val argListOrEmpty = if (args.nonEmpty) line"($args)" else line""

line"@deprecated$argListOrEmpty"
}
}

def renderPackageContents: Lines = {
val typeAliases = compilationUnit.declarations.collect {
case TypeAlias(_, name, _, _, _, _) =>
line"type $name = ${compilationUnit.namespace}.${name}.Type"
case TypeAlias(_, name, _, _, _, hints) =>
lines(
deprecationAnnotation(hints),
line"type $name = ${compilationUnit.namespace}.${name}.Type"
)
}

val blk =
Expand Down Expand Up @@ -166,6 +185,7 @@ private[codegen] class Renderer(compilationUnit: CompilationUnit) { self =>
val name = s.name
val nameGen = NameRef(s"${name}Gen")
lines(
deprecationAnnotation(s.hints),
line"type ${NameDef(name)}[F[_]] = $FunctorAlgebra_[$nameGen, F]",
line"val ${NameRef(name)} = $nameGen"
)
Expand All @@ -186,12 +206,16 @@ private[codegen] class Renderer(compilationUnit: CompilationUnit) { self =>
val opTraitNameRef = opTraitName.toNameRef

lines(
deprecationAnnotation(hints),
block(line"trait $genName[F[_, _, _, _, _]]")(
line"self =>",
newline,
ops.map { op =>
line"def ${op.methodName}(${op.renderArgs}) : F[${op.renderAlgParams(genNameRef.name)}]"

lines(
deprecationAnnotation(op.hints),
line"def ${op.methodName}(${op.renderArgs}) : F[${op
.renderAlgParams(genNameRef.name)}]"
)
},
newline,
line"def transform : $Transformation.PartiallyApplied[$genName[F]] = new $Transformation.PartiallyApplied[$genName[F]](this)"
Expand Down Expand Up @@ -498,18 +522,24 @@ private[codegen] class Renderer(compilationUnit: CompilationUnit) { self =>
additionalLines: Lines = Lines.empty
): Lines = {
import product._
if (isMixin)
renderProductMixin(
product,
adtParent,
additionalLines
)
else
renderProductNonMixin(
product,
adtParent,
additionalLines
)
val base =
if (isMixin)
renderProductMixin(
product,
adtParent,
additionalLines
)
else
renderProductNonMixin(
product,
adtParent,
additionalLines
)

lines(
deprecationAnnotation(product.hints),
base
)
}

private def renderGetMessage(field: Field) = field match {
Expand Down Expand Up @@ -566,6 +596,7 @@ private[codegen] class Renderer(compilationUnit: CompilationUnit) { self =>
caseNames.zip(alts.map(_.member == UnionMember.UnitCase))

lines(
deprecationAnnotation(hints),
block(
line"sealed trait ${NameDef(name.name)} extends scala.Product with scala.Serializable"
)(
Expand All @@ -581,23 +612,27 @@ private[codegen] class Renderer(compilationUnit: CompilationUnit) { self =>
val cn = caseName(a)
// format: off
lines(
deprecationAnnotation(altHints),
line"case object $cn extends $name",
line"""private val ${cn}Alt = $Schema_.constant($cn)${renderConstraintValidation(altHints)}.oneOf[$name]("$realName").addHints(hints)""",
line"private val ${cn}AltWithValue = ${cn}Alt($cn)"
)
// format: on
case a @ Alt(altName, _, UnionMember.TypeCase(tpe), _) =>
case a @ Alt(altName, _, UnionMember.TypeCase(tpe), altHints) =>
val cn = caseName(a)
lines(
deprecationAnnotation(altHints),
line"case class $cn(${uncapitalise(altName)}: $tpe) extends $name"
)
case Alt(_, realName, UnionMember.ProductCase(struct), _) =>
case Alt(_, realName, UnionMember.ProductCase(struct), altHints) =>
val additionalLines = lines(
newline,
line"""val alt = schema.oneOf[$name]("$realName")"""
)
renderProduct(
struct,
// putting alt hints first should result in higher priority of these.
// might need deduplication (although the Hints type will take care of it, just in case)
struct.copy(hints = altHints ++ struct.hints),
adtParent = Some(name),
additionalLines
)
Expand Down Expand Up @@ -672,7 +707,8 @@ private[codegen] class Renderer(compilationUnit: CompilationUnit) { self =>
)
}

line"$name: " + tpeAndDefault
deprecationAnnotation(hints).appendUnlessEmpty(Line.space) +
line"$name: " + tpeAndDefault
}
}
private def renderArgs(fields: List[Field]): Line = fields
Expand All @@ -685,6 +721,7 @@ private[codegen] class Renderer(compilationUnit: CompilationUnit) { self =>
values: List[EnumValue],
hints: List[Hint]
): Lines = lines(
deprecationAnnotation(hints),
block(
line"sealed abstract class ${name.name}(_value: String, _name: String, _intValue: Int) extends $Enumeration_.Value"
)(
Expand All @@ -699,10 +736,13 @@ private[codegen] class Renderer(compilationUnit: CompilationUnit) { self =>
newline,
renderHintsVal(hints),
newline,
values.map { case e @ EnumValue(value, intValue, _, _) =>
line"""case object ${NameRef(
e.name
)} extends $name("$value", "${e.name}", $intValue)"""
values.map { case e @ EnumValue(value, intValue, _, hints) =>
lines(
deprecationAnnotation(hints),
line"""case object ${NameRef(
e.name
)} extends $name("$value", "${e.name}", $intValue)"""
)
},
newline,
line"val values: $list[$name] = $list".args(
Expand All @@ -712,6 +752,7 @@ private[codegen] class Renderer(compilationUnit: CompilationUnit) { self =>
)
)

// also known as newtypes
private def renderTypeAlias(
shapeId: ShapeId,
name: NameRef,
Expand All @@ -726,6 +767,7 @@ private[codegen] class Renderer(compilationUnit: CompilationUnit) { self =>
line".withId(id).addHints(hints)${renderConstraintValidation(hints)}"
val closing = if (recursive) ")" else ""
lines(
deprecationAnnotation(hints),
obj(name, line"$Newtype_[$tpe]")(
renderId(shapeId),
renderHintsVal(hints),
Expand Down Expand Up @@ -943,18 +985,8 @@ private[codegen] class Renderer(compilationUnit: CompilationUnit) { self =>
case Primitive.Int => t => line"${t.toString}"
case Primitive.Short => t => line"${t.toString}"
case Primitive.Bool => t => line"${t.toString}"
case Primitive.Uuid => uuid => line"java.util.UUID.fromString($uuid)"
case Primitive.String => { raw =>
import scala.reflect.runtime.universe._
val str = Literal(Constant(raw))
.toString()
// Replace sequences like "\\uD83D" (how Smithy specs refer to unicode characters)
// with unicode character escapes like "\uD83D" that can be parsed in the regex implementations on all platforms.
// See https://github.com/disneystreaming/smithy4s/pull/499
.replace("\\\\u", "\\u")

line"$str"
}
case Primitive.Uuid => uuid => line"java.util.UUID.fromString($uuid)"
case Primitive.String => renderStringLiteral
case Primitive.Document => { (node: Node) =>
node.accept(new NodeVisitor[Line] {
def arrayNode(x: ArrayNode): Line = {
Expand Down Expand Up @@ -982,4 +1014,15 @@ private[codegen] class Renderer(compilationUnit: CompilationUnit) { self =>
case _ => _ => line"null"
}

private def renderStringLiteral(raw: String): Line = {
import scala.reflect.runtime.universe._
val str = Literal(Constant(raw))
.toString()
// Replace sequences like "\\uD83D" (how Smithy specs refer to unicode characters)
// with unicode character escapes like "\uD83D" that can be parsed in the regex implementations on all platforms.
// See https://github.com/disneystreaming/smithy4s/pull/499
.replace("\\\\u", "\\u")

line"$str"
}
}
28 changes: 23 additions & 5 deletions modules/codegen/src/smithy4s/codegen/SmithyToIR.scala
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,8 @@ private[codegen] class SmithyToIR(model: Model, namespace: String) {
value.getName().asScala,
value.getValue,
index
)
),
hints = Nil
)
}
.toList
Expand All @@ -287,18 +288,30 @@ private[codegen] class SmithyToIR(model: Model, namespace: String) {
.asScala
.zipWithIndex
.map { case ((name, value), index) =>
EnumValue(value, index, name)
val member = shape.getMember(name).get()

// random note: any other traits that are allowed on enum values? cuz they don't generate hints.
// known: @deprecated, @documentation
EnumValue(value, index, name, hints(member))
}
.toList
Enumeration(shape.getId(), shape.name, values).some

Enumeration(
shape.getId(),
shape.name,
values,
hints = hints(shape)
).some
}

override def intEnumShape(shape: IntEnumShape): Option[Decl] = {
val values = shape
.getEnumValues()
.asScala
.map { case (name, value) =>
EnumValue(name, value, name)
val member = shape.getMember(name).get()

EnumValue(name, value, name, hints(member))
}
.toList
Enumeration(
Expand Down Expand Up @@ -707,6 +720,8 @@ private[codegen] class SmithyToIR(model: Model, namespace: String) {
Hint.Protocol(refs.toList)
case _: PackedInputsTrait =>
Hint.PackedInputs
case d: DeprecatedTrait =>
Hint.Deprecated(d.getMessage.asScala, d.getSince.asScala)
case _: ErrorMessageTrait =>
Hint.ErrorMessage
case _: VectorTrait =>
Expand All @@ -722,7 +737,10 @@ private[codegen] class SmithyToIR(model: Model, namespace: String) {

private def traitsToHints(traits: List[Trait]): List[Hint] = {
val nonMetaTraits =
traits.filterNot(_.toShapeId().getNamespace() == "smithy4s.meta")
traits
.filterNot(_.toShapeId().getNamespace() == "smithy4s.meta")
// not sure about this
.filterNot(_.toShapeId().getNamespace() == "smithy.synthetic")
val nonConstraintNonMetaTraits = nonMetaTraits.collect {
case t if ConstraintTrait.unapply(t).isEmpty => t
}
Expand Down
4 changes: 4 additions & 0 deletions modules/codegen/src/smithy4s/codegen/ToLine.scala
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,9 @@ case class Line(segments: Chain[LineSegment]) {
Lines.empty
}
}

def appendUnlessEmpty(other: Line): Line =
if (nonEmpty) this + other else this
}

object Line {
Expand All @@ -150,6 +153,7 @@ object Line {

val empty: Line = Line(Chain.empty)
val comma: Line = Line(", ")
val space: Line = Line(" ")
val dot: Line = Line(".")
implicit val monoid: Monoid[Line] = Monoid.instance(empty, _ + _)

Expand Down
2 changes: 2 additions & 0 deletions modules/example/src/smithy4s/example/DeprecatedService.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ import smithy4s.kinds.BiFunctorAlgebra
import smithy4s.Hints
import smithy4s.StreamingSchema

@deprecated
trait DeprecatedServiceGen[F[_, _, _, _, _]] {
self =>

@deprecated
def deprecatedOperation() : F[Unit, Nothing, Unit, Nothing, Nothing]

def transform : Transformation.PartiallyApplied[DeprecatedServiceGen[F]] = new Transformation.PartiallyApplied[DeprecatedServiceGen[F]](this)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import smithy4s.ShapeId
import smithy4s.schema.Schema.bijection
import smithy4s.Newtype

@deprecated
object DeprecatedString extends Newtype[String] {
val id: ShapeId = ShapeId("smithy4s.example", "DeprecatedString")
val hints : Hints = Hints(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import smithy4s.ShapeId
import smithy4s.schema.Schema.struct
import smithy4s.ShapeTag

case class DeprecatedStructure(name: Option[String] = None, nameV2: Option[String] = None, strings: Option[List[String]] = None)
@deprecated(message = "A compelling reason", since = "0.0.1")
case class DeprecatedStructure(@deprecated name: Option[String] = None, nameV2: Option[String] = None, strings: Option[List[String]] = None)
object DeprecatedStructure extends ShapeTag.Companion[DeprecatedStructure] {
val id: ShapeId = ShapeId("smithy4s.example", "DeprecatedStructure")

Expand Down
Loading

0 comments on commit d5b5a81

Please sign in to comment.