Repository: oxidecomputer/typify
Pull request #650 (2024-08-12 14:01:02)

Bump syn from 2.0.72 to 2.0.74
Bumps [syn](https://github.com/dtolnay/syn) from 2.0.72 to 2.0.74.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/dtolnay/syn/releases">syn's releases</a>.</em></p>
<blockquote>
<h2>2.0.74</h2>
<ul>
<li>Fix <em>&quot;temporary is dropped and runs the destructor for type `impl Iterator`&quot;</em> regression affecting certain use of <code>Generics</code> iterator methods (<a href="https://redirect.github.com/dtolnay/syn/issues/1719">#1719</a>)</li>
</ul>
<h2>2.0.73</h2>
<ul>
<li>Support parsing unnamed C varargs within function pointer types (<a href="https://redirect.github.com/dtolnay/syn/issues/1711">#1711</a>)</li>
<li>Improve synthesized error message on unexpected tokens at the end of the expected contents of a delimited group (<a href="https://redirect.github.com/dtolnay/syn/issues/1713">#1713</a>)</li>
<li>Support parsing unstable tail call syntax (<a href="https://redirect.github.com/dtolnay/syn/issues/1714">#1714</a>, <a href="https://redirect.github.com/rust-lang/rust/issues/112788">rust-lang/rust#112788</a>)</li>
<li>Add <a href="https://docs.rs/syn/2.0.73/syn/enum.Fields.html#method.members"><code>Fields::members</code></a> iterator (<a href="https://redirect.github.com/dtolnay/syn/issues/1716">#1716</a>, thanks <a href="https://github.com/Fancyflame"><code>@​Fancyflame</code></a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/dtolnay/syn/commit/b15ae2840910cb6102d4d23d8b1e7bcb91304b23"><code>b15ae28</code></a> Release 2.0.74</li>
<li><a href="https://github.com/dtolnay/syn/commit/6cf234444021eb753af7a26f8122c28ce9d84935"><code>6cf2344</code></a> Merge pull request <a href="https://redirect.github.com/dtolnay/syn/issues/1719">#1719</a> from dtolnay/iterators</li>
<li><a href="https://github.com/dtolnay/syn/commit/2955ac55b70952e2114e559d1cd61267225152e8"><code>2955ac5</code></a> Expose non-impl-Trait iterator return types</li>
<li><a href="https://github.com/dtolnay/syn/commit/7dc05a564398eabc552a2d46bb580f9a60d410fb"><code>7dc05a5</code></a> Add regression test for issue 1718</li>
<li><a href="https://github.com/dtolnay/syn/commit/857942e411d71d3c0787e14e220c8c7d9d491051"><code>857942e</code></a> Revert &quot;Define Fields::members iterator type privately inside method&quot;</li>
<li><a href="https://github.com/dtolnay/syn/commit/65ec30d65913e2368febc1f00a68324ec9a9ecd4"><code>65ec30d</code></a> Revert &quot;Embed Generics iterator implementations into method bodies&quot;</li>
<li><a href="https://github.com/dtolnay/syn/commit/b5a5a8c17737ac7a7b3553ec202626035bfa779c"><code>b5a5a8c</code></a> Release 2.0.73</li>
<li><a href="https://github.com/dtolnay/syn/commit/8cdb5c734dab211eef10bbd0c47a251d38dca9e1"><code>8cdb5c7</code></a> Add example of using Fields::members iterator</li>
<li><a href="https://github.com/dtolnay/syn/commit/2afdc1267471c9813739a500f9bd2c0b19aa99af"><code>2afdc12</code></a> Remove redundancy of Generics::split_for_impl cfg with type's cfg</li>
<li><a href="https://github.com/dtolnay/syn/commit/64b0dc8d0d5fa6b3bf996c60fdff054944f1c993"><code>64b0dc8</code></a> Combine Generics impl blocks into one</li>
<li>Additional commits viewable in <a href="https://github.com/dtolnay/syn/compare/2.0.72...2.0.74">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=syn&package-manager=cargo&previous-version=2.0.72&new-version=2.0.74)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 394e393..791732e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -75,9 +75,9 @@ dependencies = [
 
 [[package]]
 name = "anstyle"
-version = "1.0.4"
+version = "1.0.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87"
+checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1"
 
 [[package]]
 name = "anstyle-parse"
@@ -222,9 +222,9 @@ dependencies = [
 
 [[package]]
 name = "clap"
-version = "4.5.13"
+version = "4.5.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0fbb260a053428790f3de475e304ff84cdbc4face759ea7a3e64c1edd938a7fc"
+checksum = "11d8838454fda655dafd3accb2b6e2bea645b9e4078abe84a22ceb947235c5cc"
 dependencies = [
  "clap_builder",
  "clap_derive",
@@ -232,9 +232,9 @@ dependencies = [
 
 [[package]]
 name = "clap_builder"
-version = "4.5.13"
+version = "4.5.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "64b17d7ea74e9f833c7dbf2cbe4fb12ff26783eda4782a8975b72f895c9b4d99"
+checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6"
 dependencies = [
  "anstream",
  "anstyle",
@@ -990,9 +990,9 @@ checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01"
 
 [[package]]
 name = "syn"
-version = "2.0.72"
+version = "2.0.74"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af"
+checksum = "1fceb41e3d546d0bd83421d3409b1460cc7444cd389341a4c880fe7a042cb3d7"
 dependencies = [
  "proc-macro2",
  "quote",
diff --git a/cargo-typify/Cargo.toml b/cargo-typify/Cargo.toml
index 22d1ca4..9076426 100644
--- a/cargo-typify/Cargo.toml
+++ b/cargo-typify/Cargo.toml
@@ -14,7 +14,7 @@ default-run = "cargo-typify"
 [dependencies]
 typify = { version = "0.1.0", path = "../typify" }
 
-clap = { version = "4.5.13", features = ["derive"] }
+clap = { version = "4.5.15", features = ["derive"] }
 color-eyre = "0.6"
 env_logger = "0.10"
 rustfmt-wrapper = "0.2.1"
diff --git a/typify-impl/Cargo.toml b/typify-impl/Cargo.toml
index 6ddc551..c39ca2c 100644
--- a/typify-impl/Cargo.toml
+++ b/typify-impl/Cargo.toml
@@ -17,7 +17,7 @@ schemars = "0.8.21"
 semver = "1.0.23"
 serde = "1.0.204"
 serde_json = "1.0.122"
-syn = { version = "2.0.72", features = ["full"] }
+syn = { version = "2.0.74", features = ["full"] }
 thiserror = "1.0.63"
 unicode-ident = "1.0.12"
 
@@ -29,5 +29,5 @@ paste = "1.0.15"
 rustfmt-wrapper = "0.2.1"
 schema = "0.1.0"
 schemars = { version = "0.8.21", features = ["uuid1", "impl_json_schema"] }
-syn = { version = "2.0.72", features = ["full", "extra-traits"] }
+syn = { version = "2.0.74", features = ["full", "extra-traits"] }
 uuid = "1.10.0"
diff --git a/typify-impl/src/lib.rs b/typify-impl/src/lib.rs
index ffcd692..b9a156c 100644
--- a/typify-impl/src/lib.rs
+++ b/typify-impl/src/lib.rs
@@ -562,7 +562,7 @@ impl TypeSpace {
                     } else {
                         Name::Unknown
                     };
-                    self.convert_ref_type(type_name, schema, type_id)?
+                    self.convert_ref_type(type_name, schema, type_id)?;
                 }
 
                 Some(replace_type) => {
@@ -570,6 +570,7 @@ impl TypeSpace {
                         replace_type.replace_type.clone(),
                         &replace_type.impls.clone(),
                     );
+
                     self.id_to_entry.insert(type_id, type_entry);
                 }
             }
@@ -597,7 +598,7 @@ impl TypeSpace {
             .and_then(|m| m.default.as_ref())
             .cloned()
             .map(WrappedValue::new);
-        let type_entry = match &mut type_entry.details {
+        let mut type_entry = match &mut type_entry.details {
             // The types that are already named are good to go.
             TypeEntryDetails::Enum(details) => {
                 details.default = default;
@@ -645,9 +646,9 @@ impl TypeSpace {
                 )
             }
         };
-        // TODO need a type alias?
-        if let Some(entry_name) = type_entry.name() {
-            self.name_to_id.insert(entry_name.clone(), type_id.clone());
+        if let Some(entry_name) = type_entry.name().cloned() {
+            let name = type_entry.ensure_unique_name(self).unwrap_or(entry_name);
+            self.name_to_id.insert(name, type_id.clone());
         }
         self.id_to_entry.insert(type_id, type_entry);
         Ok(())
@@ -824,10 +825,10 @@ impl TypeSpace {
     /// checking for duplicate type definitions (e.g. to make sure there aren't
     /// two conflicting types of the same name), and deduplicates various
     /// flavors of built-in types.
-    fn assign_type(&mut self, ty: TypeEntry) -> TypeId {
+    fn assign_type(&mut self, mut ty: TypeEntry) -> TypeId {
         if let TypeEntryDetails::Reference(type_id) = ty.details {
             type_id
-        } else if let Some(name) = ty.name() {
+        } else if let Some(name) = ty.name().cloned() {
             // If there's already a type of this name, we make sure it's
             // identical. Note that this covers all user-defined types.
 
@@ -837,7 +838,7 @@ impl TypeSpace {
             // bunch of places and if that were the case we might expect
             // them to be different and resolve that by renaming or scoping
             // them in some way.
-            if let Some(type_id) = self.name_to_id.get(name) {
+            if let Some(type_id) = self.name_to_id.get(&name) {
                 // TODO we'd like to verify that the type is structurally the
                 // same, but the types may not be functionally equal. This is a
                 // consequence of types being "finalized" after each type
@@ -846,7 +847,8 @@ impl TypeSpace {
                 type_id.clone()
             } else {
                 let type_id = self.assign();
-                self.name_to_id.insert(name.clone(), type_id.clone());
+                let name = ty.ensure_unique_name(self).unwrap_or(name);
+                self.name_to_id.insert(name, type_id.clone());
                 self.id_to_entry.insert(type_id.clone(), ty);
                 type_id
             }
diff --git a/typify-impl/src/type_entry.rs b/typify-impl/src/type_entry.rs
index 3f28853..febbf39 100644
--- a/typify-impl/src/type_entry.rs
+++ b/typify-impl/src/type_entry.rs
@@ -514,6 +514,40 @@ impl TypeEntry {
         self.check_defaults(type_space)
     }
 
+    // Ensures that the name is unique within the provided `TypeSpace`.
+    //
+    // This function checks if the current object's name already exists in the
+    // `TypeSpace`. If it does, the function modifies the name by appending
+    // numbers until a unique name is found (Name2, Name3 ...).
+    //
+    // The unique name is then inserted into the `TypeSpace` and the object's
+    // name is updated accordingly.
+    pub(crate) fn ensure_unique_name(&mut self, type_space: &mut TypeSpace) -> Option<String> {
+        if let Some(name) = self.name() {
+            let mut postfix = 2;
+            let mut new_name = name.clone();
+            while type_space.name_to_id.contains_key(&new_name) {
+                new_name = format!("{name}{postfix}");
+                postfix += 1;
+            }
+            self.rename(new_name.clone());
+            Some(new_name)
+        } else {
+            None
+        }
+    }
+
+    pub(crate) fn rename(&mut self, new_name: String) {
+        match &mut self.details {
+            TypeEntryDetails::Enum(TypeEntryEnum { name, .. })
+            | TypeEntryDetails::Struct(TypeEntryStruct { name, .. })
+            | TypeEntryDetails::Newtype(TypeEntryNewtype { name, .. }) => {
+                *name = new_name;
+            }
+            _ => {}
+        }
+    }
+
     pub(crate) fn name(&self) -> Option<&String> {
         match &self.details {
             TypeEntryDetails::Enum(TypeEntryEnum { name, .. })


Test patch:
diff --git a/typify-test/Cargo.toml b/typify-test/Cargo.toml
index 7313806..68fdebe 100644
--- a/typify-test/Cargo.toml
+++ b/typify-test/Cargo.toml
@@ -13,5 +13,5 @@ ipnetwork = { version = "0.20.0", features = ["schemars"] }
 prettyplease = "0.2.20"
 schemars = "0.8.21"
 serde = "1.0.204"
-syn = "2.0.72"
+syn = "2.0.74"
 typify = { path = "../typify" }
diff --git a/typify/tests/schemas/property-name-collision.json b/typify/tests/schemas/property-name-collision.json
new file mode 100644
index 0000000..76bbd25
--- /dev/null
+++ b/typify/tests/schemas/property-name-collision.json
@@ -0,0 +1,26 @@
+{
+    "$schema": "http://json-schema.org/draft-04/schema#",
+    "definitions": {
+        "Foo": {
+            "properties": {
+                "Bar": {
+                    "oneOf": [
+                        {
+                            "$ref": "#/definitions/FooBar"
+                        },
+                        {
+                            "$ref": "#/definitions/Baz"
+                        }
+                    ]
+                }
+            },
+            "type": "object"
+        },
+        "FooBar": {
+            "type": "string"
+        },
+        "Baz": {
+            "type": "string"
+        }
+    }
+}
diff --git a/typify/tests/schemas/property-name-collision.rs b/typify/tests/schemas/property-name-collision.rs
new file mode 100644
index 0000000..80e5413
--- /dev/null
+++ b/typify/tests/schemas/property-name-collision.rs
@@ -0,0 +1,227 @@
+#[doc = r" Error types."]
+pub mod error {
+    #[doc = r" Error from a TryFrom or FromStr implementation."]
+    pub struct ConversionError(std::borrow::Cow<'static, str>);
+    impl std::error::Error for ConversionError {}
+    impl std::fmt::Display for ConversionError {
+        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
+            std::fmt::Display::fmt(&self.0, f)
+        }
+    }
+    impl std::fmt::Debug for ConversionError {
+        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
+            std::fmt::Debug::fmt(&self.0, f)
+        }
+    }
+    impl From<&'static str> for ConversionError {
+        fn from(value: &'static str) -> Self {
+            Self(value.into())
+        }
+    }
+    impl From<String> for ConversionError {
+        fn from(value: String) -> Self {
+            Self(value.into())
+        }
+    }
+}
+#[doc = "Baz"]
+#[doc = r""]
+#[doc = r" <details><summary>JSON schema</summary>"]
+#[doc = r""]
+#[doc = r" ```json"]
+#[doc = "{"]
+#[doc = "  \"type\": \"string\""]
+#[doc = "}"]
+#[doc = r" ```"]
+#[doc = r" </details>"]
+#[derive(
+    Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, serde :: Deserialize, serde :: Serialize,
+)]
+pub struct Baz(pub String);
+impl std::ops::Deref for Baz {
+    type Target = String;
+    fn deref(&self) -> &String {
+        &self.0
+    }
+}
+impl From<Baz> for String {
+    fn from(value: Baz) -> Self {
+        value.0
+    }
+}
+impl From<&Baz> for Baz {
+    fn from(value: &Baz) -> Self {
+        value.clone()
+    }
+}
+impl From<String> for Baz {
+    fn from(value: String) -> Self {
+        Self(value)
+    }
+}
+impl std::str::FromStr for Baz {
+    type Err = std::convert::Infallible;
+    fn from_str(value: &str) -> Result<Self, Self::Err> {
+        Ok(Self(value.to_string()))
+    }
+}
+impl ToString for Baz {
+    fn to_string(&self) -> String {
+        self.0.to_string()
+    }
+}
+#[doc = "Foo"]
+#[doc = r""]
+#[doc = r" <details><summary>JSON schema</summary>"]
+#[doc = r""]
+#[doc = r" ```json"]
+#[doc = "{"]
+#[doc = "  \"type\": \"object\","]
+#[doc = "  \"properties\": {"]
+#[doc = "    \"Bar\": {"]
+#[doc = "      \"oneOf\": ["]
+#[doc = "        {"]
+#[doc = "          \"$ref\": \"#/definitions/FooBar\""]
+#[doc = "        },"]
+#[doc = "        {"]
+#[doc = "          \"$ref\": \"#/definitions/Baz\""]
+#[doc = "        }"]
+#[doc = "      ]"]
+#[doc = "    }"]
+#[doc = "  }"]
+#[doc = "}"]
+#[doc = r" ```"]
+#[doc = r" </details>"]
+#[derive(Clone, Debug, serde :: Deserialize, serde :: Serialize)]
+pub struct Foo {
+    #[serde(rename = "Bar", default, skip_serializing_if = "Option::is_none")]
+    pub bar: Option<FooBar>,
+}
+impl From<&Foo> for Foo {
+    fn from(value: &Foo) -> Self {
+        value.clone()
+    }
+}
+#[doc = "FooBar"]
+#[doc = r""]
+#[doc = r" <details><summary>JSON schema</summary>"]
+#[doc = r""]
+#[doc = r" ```json"]
+#[doc = "{"]
+#[doc = "  \"oneOf\": ["]
+#[doc = "    {"]
+#[doc = "      \"$ref\": \"#/definitions/FooBar\""]
+#[doc = "    },"]
+#[doc = "    {"]
+#[doc = "      \"$ref\": \"#/definitions/Baz\""]
+#[doc = "    }"]
+#[doc = "  ]"]
+#[doc = "}"]
+#[doc = r" ```"]
+#[doc = r" </details>"]
+#[derive(Clone, Debug, serde :: Deserialize, serde :: Serialize)]
+#[serde(untagged)]
+pub enum FooBar {
+    FooBar(FooBar2),
+    Baz(Baz),
+}
+impl From<&FooBar> for FooBar {
+    fn from(value: &FooBar) -> Self {
+        value.clone()
+    }
+}
+impl std::str::FromStr for FooBar {
+    type Err = self::error::ConversionError;
+    fn from_str(value: &str) -> Result<Self, self::error::ConversionError> {
+        if let Ok(v) = value.parse() {
+            Ok(Self::FooBar(v))
+        } else if let Ok(v) = value.parse() {
+            Ok(Self::Baz(v))
+        } else {
+            Err("string conversion failed for all variants".into())
+        }
+    }
+}
+impl std::convert::TryFrom<&str> for FooBar {
+    type Error = self::error::ConversionError;
+    fn try_from(value: &str) -> Result<Self, self::error::ConversionError> {
+        value.parse()
+    }
+}
+impl std::convert::TryFrom<&String> for FooBar {
+    type Error = self::error::ConversionError;
+    fn try_from(value: &String) -> Result<Self, self::error::ConversionError> {
+        value.parse()
+    }
+}
+impl std::convert::TryFrom<String> for FooBar {
+    type Error = self::error::ConversionError;
+    fn try_from(value: String) -> Result<Self, self::error::ConversionError> {
+        value.parse()
+    }
+}
+impl ToString for FooBar {
+    fn to_string(&self) -> String {
+        match self {
+            Self::FooBar(x) => x.to_string(),
+            Self::Baz(x) => x.to_string(),
+        }
+    }
+}
+impl From<FooBar2> for FooBar {
+    fn from(value: FooBar2) -> Self {
+        Self::FooBar(value)
+    }
+}
+impl From<Baz> for FooBar {
+    fn from(value: Baz) -> Self {
+        Self::Baz(value)
+    }
+}
+#[doc = "FooBar2"]
+#[doc = r""]
+#[doc = r" <details><summary>JSON schema</summary>"]
+#[doc = r""]
+#[doc = r" ```json"]
+#[doc = "{"]
+#[doc = "  \"type\": \"string\""]
+#[doc = "}"]
+#[doc = r" ```"]
+#[doc = r" </details>"]
+#[derive(
+    Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, serde :: Deserialize, serde :: Serialize,
+)]
+pub struct FooBar2(pub String);
+impl std::ops::Deref for FooBar2 {
+    type Target = String;
+    fn deref(&self) -> &String {
+        &self.0
+    }
+}
+impl From<FooBar2> for String {
+    fn from(value: FooBar2) -> Self {
+        value.0
+    }
+}
+impl From<&FooBar2> for FooBar2 {
+    fn from(value: &FooBar2) -> Self {
+        value.clone()
+    }
+}
+impl From<String> for FooBar2 {
+    fn from(value: String) -> Self {
+        Self(value)
+    }
+}
+impl std::str::FromStr for FooBar2 {
+    type Err = std::convert::Infallible;
+    fn from_str(value: &str) -> Result<Self, Self::Err> {
+        Ok(Self(value.to_string()))
+    }
+}
+impl ToString for FooBar2 {
+    fn to_string(&self) -> String {
+        self.0.to_string()
+    }
+}
+fn main() {}