Skip to content

Commit 4e76a36

Browse files
authored
Merge pull request #1701 from disneystreaming/fix-1700
Add cache for matching mixins, solve some mixin bugs
1 parent ad2b7c2 commit 4e76a36

7 files changed

Lines changed: 106 additions & 15 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ Thank you!
88
# 0.18.34
99

1010
* codegen-cli: Ensure the command returns a failing exit code if command line arguments aren't valid in [#1694](https://github.com/disneystreaming/smithy4s/pull/1694).
11+
* codegen: Mixins - fix several bugs and a performance regression in [#1701](https://github.com/disneystreaming/smithy4s/pull/1701).
1112

1213
# 0.18.33
1314

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package smithy4s.example
2+
3+
4+
trait MixinRequiredMember {
5+
def description: String
6+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package smithy4s.example
2+
3+
import smithy4s.Hints
4+
import smithy4s.Schema
5+
import smithy4s.ShapeId
6+
import smithy4s.ShapeTag
7+
import smithy4s.schema.Schema.string
8+
import smithy4s.schema.Schema.struct
9+
10+
final case class MixinRequiredMemberDefaultAdded(description: String = "different description") extends MixinRequiredMember
11+
12+
object MixinRequiredMemberDefaultAdded extends ShapeTag.Companion[MixinRequiredMemberDefaultAdded] {
13+
val id: ShapeId = ShapeId("smithy4s.example", "MixinRequiredMemberDefaultAdded")
14+
15+
val hints: Hints = Hints.empty
16+
17+
// constructor using the original order from the spec
18+
private def make(description: String): MixinRequiredMemberDefaultAdded = MixinRequiredMemberDefaultAdded(description)
19+
20+
implicit val schema: Schema[MixinRequiredMemberDefaultAdded] = struct(
21+
string.required[MixinRequiredMemberDefaultAdded]("description", _.description).addHints(smithy.api.Default(smithy4s.Document.fromString("different description"))),
22+
)(make).withId(id).addHints(hints)
23+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package smithy4s.example
2+
3+
4+
trait MixinRequiredMemberIntermediate extends MixinRequiredMember {
5+
def description: String
6+
def extraField: Option[String]
7+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package smithy4s.example
2+
3+
import smithy4s.Hints
4+
import smithy4s.Schema
5+
import smithy4s.ShapeId
6+
import smithy4s.ShapeTag
7+
import smithy4s.schema.Schema.string
8+
import smithy4s.schema.Schema.struct
9+
10+
final case class StructUsingMixinRequiredMember(description: String, extraField: String) extends MixinRequiredMember
11+
12+
object StructUsingMixinRequiredMember extends ShapeTag.Companion[StructUsingMixinRequiredMember] {
13+
val id: ShapeId = ShapeId("smithy4s.example", "StructUsingMixinRequiredMember")
14+
15+
val hints: Hints = Hints.empty
16+
17+
// constructor using the original order from the spec
18+
private def make(description: String, extraField: String): StructUsingMixinRequiredMember = StructUsingMixinRequiredMember(description, extraField)
19+
20+
implicit val schema: Schema[StructUsingMixinRequiredMember] = struct(
21+
string.required[StructUsingMixinRequiredMember]("description", _.description),
22+
string.required[StructUsingMixinRequiredMember]("extraField", _.extraField),
23+
)(make).withId(id).addHints(hints)
24+
}

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

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ import java.time.temporal.ChronoField
5353
import java.time.format.DateTimeFormatterBuilder
5454
import scala.util.Try
5555
import java.util.UUID
56+
import java.util.concurrent.ConcurrentHashMap
5657

5758
private[codegen] object SmithyToIR {
5859

@@ -82,7 +83,10 @@ private[codegen] class SmithyToIR(
8283
namespace: String
8384
) {
8485

85-
val finder = PathFinder.create(model)
86+
private val finder = PathFinder.create(model)
87+
88+
// Contains mixins of the given shape that have matching fields.
89+
private val mixinsOfCache = new ConcurrentHashMap[ShapeId, Set[ShapeId]]()
8690

8791
val allShapes =
8892
model
@@ -213,7 +217,7 @@ private[codegen] class SmithyToIR(
213217
fields
214218
.find(_.name == memberName)
215219
.forall { field =>
216-
field.modifier == fieldModifier(member)
220+
field.modifier.typeMod == fieldModifier(member).typeMod
217221
}
218222
}
219223
}
@@ -227,10 +231,7 @@ private[codegen] class SmithyToIR(
227231
val rec = isRecursive(shape.getId()) || isTrait
228232

229233
val fields = shape.fields
230-
val filteredMixins = shape
231-
.getMixins()
232-
.asScala
233-
.filter(mixinId => doFieldsMatch(mixinId, fields))
234+
val filteredMixins = getMixinsMatchingFields(shape)
234235
val mixins = filterMixinsExistOnParentAdt(filteredMixins.toSet, shape)
235236
.flatMap(_.tpe)
236237
.toList
@@ -263,22 +264,30 @@ private[codegen] class SmithyToIR(
263264
.map(mem => model.expectShape(mem.getTarget))
264265

265266
val mixins: List[Set[ShapeId]] = memberTargets
266-
.map { targetShape =>
267-
def allMixinsOf(s: Shape): Set[ShapeId] =
268-
s.getMixins.asScala.toSet[ShapeId].flatMap { m =>
269-
allMixinsOf(model.expectShape(m)) + m
270-
}
271-
272-
allMixinsOf(targetShape)
273-
.filter(mixinId => doFieldsMatch(mixinId, targetShape.fields))
274-
}
267+
.map(getMixinsMatchingFields)
275268

276269
val result =
277270
if (mixins.isEmpty) Set.empty else mixins.reduce(_ intersect _)
278271

279272
result.toList
280273
}
281274

275+
private def getMixinsMatchingFields(shape: Shape): Set[ShapeId] = {
276+
def allMixinsOf(s: Shape): Set[ShapeId] =
277+
s.getMixins.asScala.toSet[ShapeId].flatMap { m =>
278+
allMixinsOf(model.expectShape(m)) + m
279+
}
280+
281+
mixinsOfCache
282+
.computeIfAbsent(
283+
shape.getId,
284+
_ =>
285+
allMixinsOf(shape)
286+
// This filter is the more intensive part worth caching
287+
.filter(doFieldsMatch(_, shape.fields))
288+
)
289+
}
290+
282291
// Filters out any mixins which exist on the parent ADT (if it is part of an ADT)
283292
// This is so the case classes in the ADT won't also extend the same mixins
284293
// as the parent sealed trait. This leads to cleaner generated code (no redundancy).

sampleSpecs/mixins.smithy

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,24 @@ structure MixinOptionalMemberOverride with [MixinOptionalMember] {
5454
structure MixinOptionalMemberDefaultAdded with [MixinOptionalMember] {
5555
a: String = "test"
5656
}
57+
58+
@mixin
59+
structure MixinRequiredMember {
60+
@required description: String
61+
}
62+
63+
@mixin
64+
structure MixinRequiredMemberIntermediate with [MixinRequiredMember] {
65+
extraField: String
66+
}
67+
68+
// regression test for https://github.com/disneystreaming/smithy4s/issues/1702
69+
structure StructUsingMixinRequiredMember with [MixinRequiredMemberIntermediate] {
70+
@required
71+
$extraField
72+
}
73+
74+
// regression test for https://github.com/disneystreaming/smithy4s/issues/1699
75+
structure MixinRequiredMemberDefaultAdded with [MixinRequiredMember] {
76+
$description = "different description"
77+
}

0 commit comments

Comments
 (0)