Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14,307 changes: 8,933 additions & 5,374 deletions aws/sdk/aws-models/dynamodb.json

Large diffs are not rendered by default.

11,167 changes: 7,563 additions & 3,604 deletions aws/sdk/aws-models/s3-control.json

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,26 @@ data class Context(
val functionRegistry: FunctionRegistry,
val runtimeConfig: RuntimeConfig,
val isBddMode: Boolean = false,
/**
* Maps each reference's original Smithy name (`Identifier.toString()`) to the
* canonical rust identifier used in generated code. Non-empty only in BDD mode,
* and only when at least one SSA variable's snake_case form collides with a
* parameter name. Call sites that emit a reference in generated code should look
* up through this map so they receive the disambiguated name when applicable;
* other lookups fall back to `id.rustName()`.
*
* See `AnnotatedRefs.from` for how the disambiguation map is built.
*/
val nameByOriginal: Map<String, String> = emptyMap(),
)

/**
* Returns the canonical rust identifier for a reference to [id] in this context.
* Transparent fallback to `id.rustName()` for identifiers that don't need
* disambiguation (the common case).
*/
fun Context.resolveName(id: Identifier): String = nameByOriginal[id.toString()] ?: id.rustName()

/**
* Utility function to convert an [Identifier] into a valid Rust identifier (snake case)
*/
Expand Down

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import software.amazon.smithy.rulesengine.language.syntax.expressions.functions.
import software.amazon.smithy.rulesengine.language.syntax.expressions.literal.Literal
import software.amazon.smithy.rust.codegen.client.smithy.endpoint.Context
import software.amazon.smithy.rust.codegen.client.smithy.endpoint.generators.EndpointResolverGenerator
import software.amazon.smithy.rust.codegen.client.smithy.endpoint.resolveName
import software.amazon.smithy.rust.codegen.client.smithy.endpoint.rustName
import software.amazon.smithy.rust.codegen.core.rustlang.Writable
import software.amazon.smithy.rust.codegen.core.rustlang.join
Expand Down Expand Up @@ -52,35 +53,39 @@ class ExpressionGenerator(

override fun visitRef(ref: Reference) =
writable {
// Resolve the name through the context so BDD-mode SSA variables that
// collide with parameter names get their disambiguated rust-name (e.g.
// `resource_arn_v`). See Context.resolveName / AnnotatedRefs.from.
val name = context.resolveName(ref.name)
if (ownership == Ownership.Owned) {
try {
when (ref.type()) {
is BooleanType -> rust("*${ref.name.rustName()}")
else -> rust("${ref.name.rustName()}.to_owned()")
is BooleanType -> rust("*$name")
else -> rust("$name.to_owned()")
}
} catch (_: RuntimeException) {
// Typechecking was never invoked, default to .to_owned()
rust("${ref.name.rustName()}.to_owned()")
rust("$name.to_owned()")
}
} else {
try {
when (ref.type()) {
// This ensures we obtain a `&str`, regardless of whether `ref.name.rustName()` returns a `String` or a `&str`.
// Typically, we don't know which type will be returned due to code generation.
// In BDD mode, parameters are already bound as references, so the cast is unnecessary and causes errors.
// This ensures we obtain a `&str`, regardless of whether the bound name
// refers to a `String` or a `&str`. In BDD mode, parameters are already
// bound as references, so the `as &str` cast is unnecessary and causes errors.
is StringType -> {
if (context.isBddMode) {
rust("${ref.name.rustName()}.as_ref()")
rust("$name.as_ref()")
} else {
rust("${ref.name.rustName()}.as_ref() as &str")
rust("$name.as_ref() as &str")
}
}
else -> rust(ref.name.rustName())
else -> rust(name)
}
} catch (_: RuntimeException) {
// Because Typechecking was never invoked upon calling `.type()` on Reference for an expression
// like "{ref}: rust". See `generateLiterals2` in ExprGeneratorTest.
rust(ref.name.rustName())
// Typechecking was never invoked upon calling `.type()` on Reference for an
// expression like "{ref}: rust". See `generateLiterals2` in ExprGeneratorTest.
rust(name)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
package software.amazon.smithy.rust.codegen.client.smithy.endpoint.rulesgen

import software.amazon.smithy.rulesengine.language.syntax.Identifier
import software.amazon.smithy.rulesengine.language.syntax.expressions.Expression
import software.amazon.smithy.rulesengine.language.syntax.expressions.Template
import software.amazon.smithy.rulesengine.language.syntax.expressions.literal.Literal
import software.amazon.smithy.rulesengine.language.syntax.expressions.literal.LiteralVisitor
Expand All @@ -23,8 +24,26 @@ import java.util.stream.Stream
* Generator for literals (strings, numbers, bools, lists and objects)
*
* The `Document` type is used to support generating JSON-like documents
*
* [exprGenerator] is an optional callback used to render the dynamic parts of string templates
* (the `{Var}` / `{Var#attr}` placeholders inside a `Template`). BDD-mode callers pass a
* callback that delegates back to [BddExpressionGenerator] so that `getAttr` on optional
* assigned variables correctly generates an `if let Some(inner) = ... { inner.attr() }` unwrap.
* When omitted, the tree-based [ExpressionGenerator] is used, preserving existing behavior for
* callers that do not need BDD-specific optional-ref tracking.
*
* [multipartExprGenerator] is an optional callback used **only** for dynamic elements inside
* a multipart template (the `out.push_str(&...)` parts). When omitted, [exprGenerator] is used.
* BDD-mode callers can use this to apply optional-unwrapping (e.g. `.as_deref().unwrap_or_default()`)
* for `&str`-typed `push_str` arguments while keeping the single-dynamic-template case
* (`"{ref}"`) Option-typed for the outer caller's `if let Some(param) = ...` wrappers.
*/
class LiteralGenerator(private val ownership: Ownership, private val context: Context) :
class LiteralGenerator(
private val ownership: Ownership,
private val context: Context,
private val exprGenerator: ((Expression, Ownership) -> Writable)? = null,
private val multipartExprGenerator: ((Expression, Ownership) -> Writable)? = null,
) :
LiteralVisitor<Writable> {
private val runtimeConfig = context.runtimeConfig
private val codegenScope =
Expand All @@ -33,6 +52,20 @@ class LiteralGenerator(private val ownership: Ownership, private val context: Co
"HashMap" to RuntimeType.HashMap,
)

private fun renderTemplateExpression(
expr: Expression,
exprOwnership: Ownership,
): Writable =
exprGenerator?.invoke(expr, exprOwnership)
?: ExpressionGenerator(exprOwnership, context).generate(expr)

private fun renderMultipartExpression(
expr: Expression,
exprOwnership: Ownership,
): Writable =
multipartExprGenerator?.invoke(expr, exprOwnership)
?: renderTemplateExpression(expr, exprOwnership)

override fun visitBoolean(b: Boolean) =
writable {
rust(b.toString())
Expand All @@ -42,9 +75,15 @@ class LiteralGenerator(private val ownership: Ownership, private val context: Co
writable {
val parts: Stream<Writable> =
value.accept(
TemplateGenerator(ownership) { expr, ownership ->
ExpressionGenerator(ownership, context).generate(expr)
},
TemplateGenerator(
ownership,
{ expr, templateOwnership ->
renderTemplateExpression(expr, templateOwnership)
},
{ expr, templateOwnership ->
renderMultipartExpression(expr, templateOwnership)
},
),
)
parts.forEach { part -> part(this) }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,20 @@ import software.amazon.smithy.rust.codegen.core.util.dq
class TemplateGenerator(
private val ownership: Ownership,
private val exprGenerator: (Expression, Ownership) -> Writable,
/**
* Optional separate generator used **only** for dynamic elements inside a multipart
* template (the `out.push_str(&...)` parts). Defaults to [exprGenerator].
*
* BDD codegen needs this distinction because:
* - In a multipart template, an optional `&Option<String>` reference must be reduced
* to `&str` (e.g. via `.as_deref().unwrap_or_default()`) so it fits `out.push_str(&...)`.
* - In a single-dynamic template (`"{ref}"`), the whole template result is the inner
* expression's value. If the outer caller wraps that in `if let Some(param) = ...`,
* the inner expression must remain `Option`-typed, so the unwrap must NOT be applied.
*
* Tree-mode callers ignore this parameter and pass a single generator for both.
*/
private val multipartElementGenerator: (Expression, Ownership) -> Writable = exprGenerator,
) : TemplateVisitor<Writable> {
override fun visitStaticTemplate(value: String) =
writable {
Expand Down Expand Up @@ -60,7 +74,7 @@ class TemplateGenerator(
writable {
// we don't need to own the argument to push_str
Attribute.AllowClippyNeedlessBorrow.render(this)
rust("out.push_str(&#W);", exprGenerator(expr, Ownership.Borrowed))
rust("out.push_str(&#W);", multipartElementGenerator(expr, Ownership.Borrowed))
}

override fun startMultipartTemplate() =
Expand Down
Loading