Unverified Commit 63fcc795 authored by Russell Cohen's avatar Russell Cohen Committed by GitHub
Browse files

Fix a number of hygiene issues around namespacing and reserved words (#78)

* Fix a number of hygiene issues around namespacing and reserved words

* Enable opting out of Exception => Error
parent 0af416fc
Loading
Loading
Loading
Loading
+8 −1
Original line number Diff line number Diff line
@@ -22,7 +22,7 @@ dependencies {
    implementation("software.amazon.smithy:smithy-aws-traits:$smithyVersion")
}

data class CodegenTest(val service: String, val module: String)
data class CodegenTest(val service: String, val module: String, val extraConfig: String? = null)

val CodgenTests = listOf(
    CodegenTest("com.amazonaws.dynamodb#DynamoDB_20120810", "dynamo"),
@@ -35,6 +35,12 @@ val CodgenTests = listOf(
    CodegenTest(
        "aws.protocoltests.restjson#RestJson",
        "rest_json"
    ),
    CodegenTest(
        "crate#Config",
        "naming_test", """
            , "codegen": { "renameErrors": false }
        """.trimIndent()
    )
)

@@ -53,6 +59,7 @@ fun generateSmithyBuild(tests: List<CodegenTest>): String {
                      "build": {
                        "rootProject": true
                      }
                      ${it.extraConfig ?: ""}
                 }
               }
            }
+97 −0
Original line number Diff line number Diff line
$version: "1.0"
namespace crate

use smithy.test#httpRequestTests
use smithy.test#httpResponseTests
use aws.protocols#awsJson1_1

/// Confounds model generation machinery with lots of problematic names
@awsJson1_1
service Config {
    version: "2006-03-01",
    operations: [
       ReservedWordsAsMembers,
       StructureNamePunning,
       ErrCollisions
    ]
}

@httpRequestTests([
    {
        id: "reserved_words",
        protocol: awsJson1_1,
        params: {
            "as": 5,
            "async": true,
        },
        method: "POST",
        uri: "/",
        body: "{\"as\": 5, \"async\": true}",
        bodyMediaType: "application/json"
    }
])
operation ReservedWordsAsMembers {
    input: ReservedWords
}


structure ReservedWords {
    as: Integer,
    async: Boolean
}

@httpRequestTests([
    {
        id: "structure_punning",
        protocol: awsJson1_1,
        params: {
            "regular_string": "hello!",
            "punned_string": { "ps_member": true },
        },
        method: "POST",
        uri: "/",
        body: "{\"regular_string\": \"hello!\", \"punned_string\": { \"ps_member\": true }}",
        bodyMediaType: "application/json"
    }
])
operation StructureNamePunning {
    input: StructureNamePunningInput
}

structure StructureNamePunningInput {
    regular_string: smithy.api#String,
    punned_string: crate#String,
    punned_vec: Vec
}

structure Vec {
    pv_member: Boolean
}

structure String {
    ps_member: Boolean
}

operation ErrCollisions {
    errors: [
        CollidingError,
        CollidingException
        // , ErrCollisionsException
    ]
}

@error("client")
structure CollidingError {

}

/// This will be renamed to CollidingError
@error("client")
structure CollidingException {

}

// This will conflict with the name of the top-level error declaration
// Fixing this is more refactoring than I want to get into right now
// @error("client")
// structure ErrCollisionsException { }
+82 −0
Original line number Diff line number Diff line
package software.amazon.smithy.rust.codegen.lang

import software.amazon.smithy.codegen.core.ReservedWordSymbolProvider
import software.amazon.smithy.codegen.core.ReservedWords
import software.amazon.smithy.codegen.core.Symbol
import software.amazon.smithy.model.shapes.MemberShape
import software.amazon.smithy.model.shapes.Shape
import software.amazon.smithy.rust.codegen.smithy.RustSymbolProvider
import software.amazon.smithy.rust.codegen.smithy.WrappingSymbolProvider

class RustReservedWordSymbolProvider(base: RustSymbolProvider) : WrappingSymbolProvider(base) {
    private val internal = ReservedWordSymbolProvider.builder().symbolProvider(base).memberReservedWords(RustReservedWords).build()
    override fun toMemberName(shape: MemberShape): String {
        return internal.toMemberName(shape)
    }

    override fun toSymbol(shape: Shape): Symbol {
        return internal.toSymbol(shape)
    }
}

object RustReservedWords : ReservedWords {
    private val RustKeywords = setOf(
        "as",
        "break",
        "const",
        "continue",
        "crate",
        "else",
        "enum",
        "extern",
        "false",
        "fn",
        "for",
        "if",
        "impl",
        "in",
        "let",
        "loop",
        "match",
        "mod",
        "move",
        "mut",
        "pub",
        "ref",
        "return",
        "self",
        "Self",
        "static",
        "struct",
        "super",
        "trait",
        "true",
        "type",
        "unsafe",
        "use",
        "where",
        "while",

        "async",
        "await",
        "dyn",

        "abstract",
        "become",
        "box",
        "do",
        "final",
        "macro",
        "override",
        "priv",
        "typeof",
        "unsized",
        "virtual",
        "yield",
        "try"
    )

    override fun escape(word: String): String = "r##$word"

    override fun isReserved(word: String): Boolean = RustKeywords.contains(word)
}
+31 −17
Original line number Diff line number Diff line
@@ -21,12 +21,15 @@ sealed class RustType {
     */
    abstract val name: kotlin.String

    open val namespace: kotlin.String? = null

    object Bool : RustType() {
        override val name: kotlin.String = "bool"
    }

    object String : RustType() {
        override val name: kotlin.String = "String"
        override val namespace = "::std::string"
    }

    data class Float(val precision: Int) : RustType() {
@@ -39,21 +42,23 @@ sealed class RustType {

    data class Vec(val member: RustType) : RustType() {
        override val name: kotlin.String = "Vec"
        override val namespace = "::std::vec"
    }

    data class Slice(val member: RustType) : RustType() {
        override val name: kotlin.String
            get() = ""
        override val name: kotlin.String = ""
    }

    data class HashMap(val key: RustType, val value: RustType) : RustType() {
        // TODO: assert that underneath, the member is a String
        override val name: kotlin.String = "HashMap"
        override val namespace = "::std::collections"
    }

    data class HashSet(val member: RustType) : RustType() {
        // TODO: assert that underneath, the member is a String
        override val name: kotlin.String = SetType
        override val namespace = SetNamespace
    }

    data class Reference(val lifetime: kotlin.String?, override val value: RustType) : RustType(), Container {
@@ -62,33 +67,42 @@ sealed class RustType {

    data class Option(override val value: RustType) : RustType(), Container {
        override val name: kotlin.String = "Option"
        override val namespace = "::std::option"
    }

    data class Box(override val value: RustType) : RustType(), Container {
        override val name: kotlin.String = "Box"
        override val namespace = "::std::boxed"
    }

    data class Opaque(override val name: kotlin.String) : RustType()
    data class Opaque(override val name: kotlin.String, override val namespace: kotlin.String? = null) : RustType()

    companion object {
        val SetType = "BTreeSet"
        const val SetType = "BTreeSet"
        const val SetNamespace = "::std::collections"
    }
}

fun RustType.render(): String = when (this) {
fun RustType.render(fullyQualified: Boolean): String {
    val namespace = if (fullyQualified) {
        this.namespace?.let { "$it::" } ?: ""
    } else ""
    val base = when (this) {
        is RustType.Bool -> this.name
        is RustType.Float -> this.name
        is RustType.Integer -> this.name
        is RustType.String -> this.name
    is RustType.Vec -> "${this.name}<${this.member.render()}>"
    is RustType.Slice -> "[${this.member.render()}]"
    is RustType.HashMap -> "${this.name}<${this.key.render()}, ${this.value.render()}>"
    is RustType.HashSet -> "${this.name}<${this.member.render()}>"
    is RustType.Reference -> "&${this.lifetime?.let { "'$it" } ?: ""} ${this.value.render()}"
    is RustType.Option -> "${this.name}<${this.value.render()}>"
    is RustType.Box -> "${this.name}<${this.value.render()}>"
        is RustType.Vec -> "${this.name}<${this.member.render(fullyQualified)}>"
        is RustType.Slice -> "[${this.member.render(fullyQualified)}]"
        is RustType.HashMap -> "${this.name}<${this.key.render(fullyQualified)}, ${this.value.render(fullyQualified)}>"
        is RustType.HashSet -> "${this.name}<${this.member.render(fullyQualified)}>"
        is RustType.Reference -> "&${this.lifetime?.let { "'$it" } ?: ""} ${this.value.render(fullyQualified)}"
        is RustType.Option -> "${this.name}<${this.value.render(fullyQualified)}>"
        is RustType.Box -> "${this.name}<${this.value.render(fullyQualified)}>"
        is RustType.Opaque -> this.name
    }
    return "$namespace$base"
}

/**
 * Returns true if [this] contains [t] anywhere within it's tree. For example,
+2 −4
Original line number Diff line number Diff line
@@ -269,10 +269,8 @@ class RustWriter private constructor(
                    t.fullyQualifiedName()
                }
                is Symbol -> {
                    if (t.namespace != namespace) {
                    addImport(t, null)
                    }
                    t.rustType().render()
                    t.rustType().render(fullyQualified = true)
                }
                else -> throw CodegenException("Invalid type provided to RustSymbolFormatter")
            }
Loading