forked from smithy-lang/smithy-rs
-
Notifications
You must be signed in to change notification settings - Fork 0
/
SymbolVisitor.kt
319 lines (284 loc) · 13.1 KB
/
SymbolVisitor.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package software.amazon.smithy.rust.codegen.core.smithy
import software.amazon.smithy.codegen.core.Symbol
import software.amazon.smithy.codegen.core.SymbolProvider
import software.amazon.smithy.model.Model
import software.amazon.smithy.model.knowledge.NullableIndex
import software.amazon.smithy.model.knowledge.NullableIndex.CheckMode
import software.amazon.smithy.model.shapes.BigDecimalShape
import software.amazon.smithy.model.shapes.BigIntegerShape
import software.amazon.smithy.model.shapes.BlobShape
import software.amazon.smithy.model.shapes.BooleanShape
import software.amazon.smithy.model.shapes.ByteShape
import software.amazon.smithy.model.shapes.DocumentShape
import software.amazon.smithy.model.shapes.DoubleShape
import software.amazon.smithy.model.shapes.EnumShape
import software.amazon.smithy.model.shapes.FloatShape
import software.amazon.smithy.model.shapes.IntegerShape
import software.amazon.smithy.model.shapes.ListShape
import software.amazon.smithy.model.shapes.LongShape
import software.amazon.smithy.model.shapes.MapShape
import software.amazon.smithy.model.shapes.MemberShape
import software.amazon.smithy.model.shapes.OperationShape
import software.amazon.smithy.model.shapes.ResourceShape
import software.amazon.smithy.model.shapes.ServiceShape
import software.amazon.smithy.model.shapes.SetShape
import software.amazon.smithy.model.shapes.Shape
import software.amazon.smithy.model.shapes.ShapeVisitor
import software.amazon.smithy.model.shapes.ShortShape
import software.amazon.smithy.model.shapes.SimpleShape
import software.amazon.smithy.model.shapes.StringShape
import software.amazon.smithy.model.shapes.StructureShape
import software.amazon.smithy.model.shapes.TimestampShape
import software.amazon.smithy.model.shapes.UnionShape
import software.amazon.smithy.model.traits.EnumTrait
import software.amazon.smithy.model.traits.ErrorTrait
import software.amazon.smithy.rust.codegen.core.rustlang.Attribute
import software.amazon.smithy.rust.codegen.core.rustlang.RustModule
import software.amazon.smithy.rust.codegen.core.rustlang.RustReservedWords
import software.amazon.smithy.rust.codegen.core.rustlang.RustType
import software.amazon.smithy.rust.codegen.core.rustlang.Visibility
import software.amazon.smithy.rust.codegen.core.smithy.traits.RustBoxTrait
import software.amazon.smithy.rust.codegen.core.util.PANIC
import software.amazon.smithy.rust.codegen.core.util.hasTrait
import software.amazon.smithy.rust.codegen.core.util.letIf
import software.amazon.smithy.rust.codegen.core.util.toPascalCase
import software.amazon.smithy.rust.codegen.core.util.toSnakeCase
import kotlin.reflect.KClass
/** Map from Smithy Shapes to Rust Types */
val SimpleShapes: Map<KClass<out Shape>, RustType> = mapOf(
BooleanShape::class to RustType.Bool,
FloatShape::class to RustType.Float(32),
DoubleShape::class to RustType.Float(64),
ByteShape::class to RustType.Integer(8),
ShortShape::class to RustType.Integer(16),
IntegerShape::class to RustType.Integer(32),
LongShape::class to RustType.Integer(64),
StringShape::class to RustType.String,
)
/**
* Track both the past and current name of a symbol
*
* When a symbol name conflicts with another name, we need to rename it. This tracks both names enabling us to generate helpful
* docs that cover both cases.
*
* Note that this is only used for enum shapes an enum variant does not have its own symbol. For structures, the [Symbol.renamedFrom]
* field will be set.
*/
data class MaybeRenamed(val name: String, val renamedFrom: String?)
/**
* Make the return [value] optional if the [member] symbol is as well optional.
*/
fun SymbolProvider.wrapOptional(member: MemberShape, value: String): String = value.letIf(toSymbol(member).isOptional()) {
"Some($value)"
}
/**
* Make the return [value] optional if the [member] symbol is not optional.
*/
fun SymbolProvider.toOptional(member: MemberShape, value: String): String = value.letIf(!toSymbol(member).isOptional()) {
"Some($value)"
}
/**
* Services can rename their contained shapes. See https://awslabs.github.io/smithy/1.0/spec/core/model.html#service
* specifically, `rename`
*/
fun Shape.contextName(serviceShape: ServiceShape?): String {
return if (serviceShape != null) {
id.getName(serviceShape)
} else {
id.name
}
}
/**
* Base converter from `Shape` to `Symbol`. Shapes are the direct contents of the `Smithy` model. `Symbols` carry information
* about Rust types, namespaces, dependencies, metadata as well as other information required to render a symbol.
*
* This is composed with other symbol visitors to handle behavior like Streaming shapes and determining the correct
* derives for a given shape.
*/
open class SymbolVisitor(
override val model: Model,
private val serviceShape: ServiceShape?,
override val config: RustSymbolProviderConfig,
) : RustSymbolProvider, ShapeVisitor<Symbol> {
override val moduleProviderContext = ModuleProviderContext(model, serviceShape)
private val nullableIndex = NullableIndex.of(model)
override fun toSymbol(shape: Shape): Symbol {
return shape.accept(this)
}
override fun symbolForOperationError(operation: OperationShape): Symbol =
toSymbol(operation).let { symbol ->
val module = moduleForOperationError(operation)
module.toType().resolve("${symbol.name}Error").toSymbol().toBuilder().locatedIn(module).build()
}
override fun symbolForEventStreamError(eventStream: UnionShape): Symbol =
toSymbol(eventStream).let { symbol ->
val module = moduleForEventStreamError(eventStream)
module.toType().resolve("${symbol.name}Error").toSymbol().toBuilder().locatedIn(module).build()
}
override fun toMemberName(shape: MemberShape): String {
val container = model.expectShape(shape.container)
return when {
container is StructureShape -> shape.memberName.toSnakeCase()
container is UnionShape || container is EnumShape || container.hasTrait<EnumTrait>() -> shape.memberName.toPascalCase()
else -> error("unexpected container shape: $container")
}
}
override fun blobShape(shape: BlobShape?): Symbol {
return RuntimeType.blob(config.runtimeConfig).toSymbol()
}
/**
* Produce `Box<T>` when the shape has the `RustBoxTrait`
*/
private fun handleRustBoxing(symbol: Symbol, shape: Shape): Symbol {
return if (shape.hasTrait<RustBoxTrait>()) {
val rustType = RustType.Box(symbol.rustType())
with(Symbol.builder()) {
rustType(rustType)
addReference(symbol)
name(rustType.name)
build()
}
} else {
symbol
}
}
private fun simpleShape(shape: SimpleShape): Symbol {
return symbolBuilder(shape, SimpleShapes.getValue(shape::class)).setDefault(Default.RustDefault).build()
}
override fun booleanShape(shape: BooleanShape): Symbol = simpleShape(shape)
override fun byteShape(shape: ByteShape): Symbol = simpleShape(shape)
override fun shortShape(shape: ShortShape): Symbol = simpleShape(shape)
override fun integerShape(shape: IntegerShape): Symbol = simpleShape(shape)
override fun longShape(shape: LongShape): Symbol = simpleShape(shape)
override fun floatShape(shape: FloatShape): Symbol = simpleShape(shape)
override fun doubleShape(shape: DoubleShape): Symbol = simpleShape(shape)
override fun stringShape(shape: StringShape): Symbol {
return if (shape.hasTrait<EnumTrait>()) {
val rustType = RustType.Opaque(shape.contextName(serviceShape).toPascalCase())
symbolBuilder(shape, rustType).locatedIn(moduleForShape(shape)).build()
} else {
simpleShape(shape)
}
}
override fun listShape(shape: ListShape): Symbol {
val inner = this.toSymbol(shape.member)
return symbolBuilder(shape, RustType.Vec(inner.rustType())).addReference(inner).build()
}
override fun setShape(shape: SetShape): Symbol {
val inner = this.toSymbol(shape.member)
val builder = if (model.expectShape(shape.member.target).isStringShape) {
symbolBuilder(shape, RustType.HashSet(inner.rustType()))
} else {
// only strings get put into actual sets because floats are unhashable
symbolBuilder(shape, RustType.Vec(inner.rustType()))
}
return builder.addReference(inner).build()
}
override fun mapShape(shape: MapShape): Symbol {
val target = model.expectShape(shape.key.target)
require(target.isStringShape) { "unexpected key shape: ${shape.key}: $target [keys must be strings]" }
val key = this.toSymbol(shape.key)
val value = this.toSymbol(shape.value)
return symbolBuilder(shape, RustType.HashMap(key.rustType(), value.rustType())).addReference(key)
.addReference(value).build()
}
override fun documentShape(shape: DocumentShape?): Symbol {
return RuntimeType.document(config.runtimeConfig).toSymbol()
}
override fun bigIntegerShape(shape: BigIntegerShape?): Symbol {
TODO("Not yet implemented: https://github.com/awslabs/smithy-rs/issues/312")
}
override fun bigDecimalShape(shape: BigDecimalShape?): Symbol {
TODO("Not yet implemented: https://github.com/awslabs/smithy-rs/issues/312")
}
override fun operationShape(shape: OperationShape): Symbol {
return symbolBuilder(
shape,
RustType.Opaque(
shape.contextName(serviceShape)
.replaceFirstChar { it.uppercase() },
),
)
.locatedIn(moduleForShape(shape))
.build()
}
override fun resourceShape(shape: ResourceShape?): Symbol {
TODO("Not yet implemented: resources are not supported")
}
override fun serviceShape(shape: ServiceShape?): Symbol {
PANIC("symbol visitor should not be invoked in service shapes")
}
override fun structureShape(shape: StructureShape): Symbol {
val isError = shape.hasTrait<ErrorTrait>()
val name = shape.contextName(serviceShape).toPascalCase().letIf(isError && config.renameExceptions) {
it.replace("Exception", "Error")
}
return symbolBuilder(shape, RustType.Opaque(name)).locatedIn(moduleForShape(shape)).build()
}
override fun unionShape(shape: UnionShape): Symbol {
val name = shape.contextName(serviceShape).toPascalCase()
return symbolBuilder(shape, RustType.Opaque(name)).locatedIn(moduleForShape(shape)).build()
}
override fun memberShape(shape: MemberShape): Symbol {
val target = model.expectShape(shape.target)
// Handle boxing first, so we end up with Option<Box<_>>, not Box<Option<_>>.
return handleOptionality(
handleRustBoxing(toSymbol(target), shape),
shape,
nullableIndex,
config.nullabilityCheckMode,
)
}
override fun timestampShape(shape: TimestampShape?): Symbol {
return RuntimeType.dateTime(config.runtimeConfig).toSymbol()
}
}
/**
* Boxes and returns [symbol], the symbol for the target of the member shape [shape], if [shape] is annotated with
* [RustBoxTrait]; otherwise returns [symbol] unchanged.
*
* See `RecursiveShapeBoxer.kt` for the model transformation pass that annotates model shapes with [RustBoxTrait].
*/
fun handleRustBoxing(symbol: Symbol, shape: MemberShape): Symbol =
if (shape.hasTrait<RustBoxTrait>()) {
symbol.makeRustBoxed()
} else {
symbol
}
fun symbolBuilder(shape: Shape?, rustType: RustType): Symbol.Builder =
Symbol.builder().shape(shape).rustType(rustType)
.name(rustType.name)
// Every symbol that actually gets defined somewhere should set a definition file
// If we ever generate a `thisisabug.rs`, there is a bug in our symbol generation
.definitionFile("thisisabug.rs")
fun handleOptionality(symbol: Symbol, member: MemberShape, nullableIndex: NullableIndex, nullabilityCheckMode: CheckMode): Symbol =
symbol.letIf(nullableIndex.isMemberNullable(member, nullabilityCheckMode)) { symbol.makeOptional() }
/**
* Creates a test module for this symbol.
* For example if the symbol represents the name for the struct `struct MyStruct { ... }`,
* this function will create the following inline module:
* ```rust
* #[cfg(test)]
* mod test_my_struct { ... }
* ```
*/
fun SymbolProvider.testModuleForShape(shape: Shape): RustModule.LeafModule {
val symbol = toSymbol(shape)
val rustName = symbol.name.unsafeToRustName()
return RustModule.new(
name = "test_$rustName",
visibility = Visibility.PRIVATE,
inline = true,
parent = symbol.module(),
additionalAttributes = listOf(Attribute.CfgTest),
)
}
/**
* You should rarely need this function, rust names in general should be symbol-aware,
* this is "automatic" if you use things like [software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate].
*/
fun String.unsafeToRustName(): String = RustReservedWords.escapeIfNeeded(this.toSnakeCase())