Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Objects fields named Result generate code that (generally) won't compile #568

Closed
colmanhumphrey opened this issue Apr 24, 2024 · 1 comment · Fixed by #705
Closed

Objects fields named Result generate code that (generally) won't compile #568

colmanhumphrey opened this issue Apr 24, 2024 · 1 comment · Fixed by #705
Labels
compile-fail generated code doesn't compile

Comments

@colmanhumphrey
Copy link

colmanhumphrey commented Apr 24, 2024

Having a Result definition seems to create code that won't compile, if std::result::Result is needed.

Reproduction

A simplified schema that shows the issue (note: if you don't have the enum, or something that needs a result, it won't give the error):

{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "title": "Test",
  "type": "object",
  "properties": {
    "results": {
      "$ref": "#/definitions/MultipleResult"
    }
  },
  "definitions": {
    "MultipleResult": {
      "type": "object",
      "properties": {
        "results": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/Result"
          }
        }
      }
    },
    "Result": {
      "type": "object",
      "properties": {
        "id": {
          "type": "string"
        }
      }
    },
    "SomeEnum": {
      "type": "string",
      "description": "",
      "enum": [
        "Arm1",
        "Arm2",
        "Arm3"
      ]
    }
  }
}
Gives:
error[E0107]: struct takes 0 generic arguments but 2 generic arguments were supplied
   --> src/lib.rs:136:33
    |
136 |     fn try_from(value: &str) -> Result<Self, self::error::ConversionError> {
    |                                 ^^^^^^------------------------------------ help: remove these generics
    |                                 |
    |                                 expected 0 generic arguments
    |
note: struct defined here, with 0 generic parameters
   --> src/lib.rs:78:12
    |
78  | pub struct Result {
    |            ^^^^^^

error[E0107]: struct takes 0 generic arguments but 2 generic arguments were supplied
   --> src/lib.rs:142:36
    |
142 |     fn try_from(value: &String) -> Result<Self, self::error::ConversionError> {
    |                                    ^^^^^^------------------------------------ help: remove these generics
    |                                    |
    |                                    expected 0 generic arguments
    |
note: struct defined here, with 0 generic parameters
   --> src/lib.rs:78:12
    |
78  | pub struct Result {
    |            ^^^^^^

error[E0107]: struct takes 0 generic arguments but 2 generic arguments were supplied
   --> src/lib.rs:125:33
    |
125 |     fn from_str(value: &str) -> Result<Self, self::error::ConversionError> {
    |                                 ^^^^^^------------------------------------ help: remove these generics
    |                                 |
    |                                 expected 0 generic arguments
    |
note: struct defined here, with 0 generic parameters
   --> src/lib.rs:78:12
    |
78  | pub struct Result {
    |            ^^^^^^

error[E0053]: method `from_str` has an incompatible type for trait
   --> src/lib.rs:125:33
    |
125 |     fn from_str(value: &str) -> Result<Self, self::error::ConversionError> {
    |                                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |                                 |
    |                                 expected `Result<SomeEnum, ConversionError>`, found `Result`
    |                                 help: change the output type to match the trait: `std::result::Result<SomeEnum, ConversionError>`
    |
    = note: expected signature `fn(&_) -> std::result::Result<SomeEnum, ConversionError>`
               found signature `fn(&_) -> Result`

error[E0053]: method `try_from` has an incompatible type for trait
   --> src/lib.rs:136:33
    |
136 |     fn try_from(value: &str) -> Result<Self, self::error::ConversionError> {
    |                                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |                                 |
    |                                 expected `Result<SomeEnum, ConversionError>`, found `Result`
    |                                 help: change the output type to match the trait: `std::result::Result<SomeEnum, ConversionError>`
    |
    = note: expected signature `fn(&_) -> std::result::Result<SomeEnum, ConversionError>`
               found signature `fn(&_) -> Result`

error[E0053]: method `try_from` has an incompatible type for trait
   --> src/lib.rs:142:36
    |
142 |     fn try_from(value: &String) -> Result<Self, self::error::ConversionError> {
    |                                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |                                    |
    |                                    expected `Result<SomeEnum, ConversionError>`, found `Result`
    |                                    help: change the output type to match the trait: `std::result::Result<SomeEnum, ConversionError>`
    |
    = note: expected signature `fn(&std::string::String) -> std::result::Result<SomeEnum, ConversionError>`
               found signature `fn(&std::string::String) -> Result`

error[E0107]: struct takes 0 generic arguments but 2 generic arguments were supplied
   --> src/lib.rs:148:35
    |
148 |     fn try_from(value: String) -> Result<Self, self::error::ConversionError> {
    |                                   ^^^^^^------------------------------------ help: remove these generics
    |                                   |
    |                                   expected 0 generic arguments
    |
note: struct defined here, with 0 generic parameters
   --> src/lib.rs:78:12
    |
78  | pub struct Result {
    |            ^^^^^^

error[E0053]: method `try_from` has an incompatible type for trait
   --> src/lib.rs:148:35
    |
148 |     fn try_from(value: String) -> Result<Self, self::error::ConversionError> {
    |                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |                                   |
    |                                   expected `Result<SomeEnum, ConversionError>`, found `Result`
    |                                   help: change the output type to match the trait: `std::result::Result<SomeEnum, ConversionError>`
    |
    = note: expected signature `fn(std::string::String) -> std::result::Result<SomeEnum, ConversionError>`
               found signature `fn(std::string::String) -> Result`

error[E0308]: mismatched types
   --> src/lib.rs:127:23
    |
125 |     fn from_str(value: &str) -> Result<Self, self::error::ConversionError> {
    |                                 ------------------------------------------ expected `Result` because of return type
126 |         match value {
127 |             "Arm1" => Ok(Self::Arm1),
    |                       ^^^^^^^^^^^^^^ expected `Result`, found `Result<SomeEnum, _>`
    |
    = note: `std::result::Result<SomeEnum, _>` and `Result` have similar names, but are actually distinct types
note: `std::result::Result<SomeEnum, _>` is defined in crate `core`
   --> /Users/colmanhumphrey/.rustup/toolchains/nightly-aarch64-apple-darwin/lib/rustlib/src/rust/library/core/src/result.rs:502:1
    |
502 | pub enum Result<T, E> {
    | ^^^^^^^^^^^^^^^^^^^^^
note: `Result` is defined in the current crate
   --> src/lib.rs:78:1
    |
78  | pub struct Result {
    | ^^^^^^^^^^^^^^^^^

error[E0308]: mismatched types
   --> src/lib.rs:137:9
    |
136 |     fn try_from(value: &str) -> Result<Self, self::error::ConversionError> {
    |                                 ------------------------------------------ expected `Result` because of return type
137 |         value.parse()
    |         ^^^^^^^^^^^^^ expected `Result`, found `Result<_, _>`
    |
    = note: `std::result::Result<_, _>` and `Result` have similar names, but are actually distinct types
note: `std::result::Result<_, _>` is defined in crate `core`
   --> /Users/colmanhumphrey/.rustup/toolchains/nightly-aarch64-apple-darwin/lib/rustlib/src/rust/library/core/src/result.rs:502:1
    |
502 | pub enum Result<T, E> {
    | ^^^^^^^^^^^^^^^^^^^^^
note: `Result` is defined in the current crate
   --> src/lib.rs:78:1
    |
78  | pub struct Result {
    | ^^^^^^^^^^^^^^^^^
help: consider using `Result::expect` to unwrap the `std::result::Result<_, _>` value, panicking if the value is a `Result::Err`
    |
137 |         value.parse().expect("REASON")
    |                      +++++++++++++++++

error[E0308]: mismatched types
   --> src/lib.rs:143:9
    |
142 |     fn try_from(value: &String) -> Result<Self, self::error::ConversionError> {
    |                                    ------------------------------------------ expected `Result` because of return type
143 |         value.parse()
    |         ^^^^^^^^^^^^^ expected `Result`, found `Result<_, _>`
    |
    = note: `std::result::Result<_, _>` and `Result` have similar names, but are actually distinct types
note: `std::result::Result<_, _>` is defined in crate `core`
   --> /Users/colmanhumphrey/.rustup/toolchains/nightly-aarch64-apple-darwin/lib/rustlib/src/rust/library/core/src/result.rs:502:1
    |
502 | pub enum Result<T, E> {
    | ^^^^^^^^^^^^^^^^^^^^^
note: `Result` is defined in the current crate
   --> src/lib.rs:78:1
    |
78  | pub struct Result {
    | ^^^^^^^^^^^^^^^^^
help: consider using `Result::expect` to unwrap the `std::result::Result<_, _>` value, panicking if the value is a `Result::Err`
    |
143 |         value.parse().expect("REASON")
    |                      +++++++++++++++++

error[E0308]: mismatched types
   --> src/lib.rs:149:9
    |
148 |     fn try_from(value: String) -> Result<Self, self::error::ConversionError> {
    |                                   ------------------------------------------ expected `Result` because of return type
149 |         value.parse()
    |         ^^^^^^^^^^^^^ expected `Result`, found `Result<_, _>`
    |
    = note: `std::result::Result<_, _>` and `Result` have similar names, but are actually distinct types
note: `std::result::Result<_, _>` is defined in crate `core`
   --> /Users/colmanhumphrey/.rustup/toolchains/nightly-aarch64-apple-darwin/lib/rustlib/src/rust/library/core/src/result.rs:502:1
    |
502 | pub enum Result<T, E> {
    | ^^^^^^^^^^^^^^^^^^^^^
note: `Result` is defined in the current crate
   --> src/lib.rs:78:1
    |
78  | pub struct Result {
    | ^^^^^^^^^^^^^^^^^
help: consider using `Result::expect` to unwrap the `std::result::Result<_, _>` value, panicking if the value is a `Result::Err`
    |
149 |         value.parse().expect("REASON")
    |                      +++++++++++++++++

Some errors have detailed explanations: E0053, E0107, E0308.
For more information about an error, try `rustc --explain E0053`.
error: could not compile `inspection-schema` (lib) due to 12 previous errors

Solve

I think an explicit Result, like #458, should hopefully solve it.

(meanwhile, I will probably change my field name...)

Thanks!

@ahl
Copy link
Collaborator

ahl commented May 2, 2024

Thanks for the bug report. It sounds like we should fully qualify our use of std::result::Result to avoid ambiguity.

@ahl ahl added the compile-fail generated code doesn't compile label May 2, 2024
anelson added a commit to anelson/typify that referenced this issue Nov 30, 2024
I ran into oxidecomputer#568 when trying to use typify to
generate Rust bindings for the [Model Context
Protocol](https://spec.modelcontextprotocol.io).  The Anthropic have
published a (JSON Schema specification for this
protocol)[https://github.com/modelcontextprotocol/specification/blob/main/schema/schema.json],
but as soon as I tried to codegen Rust bindings for it, I ran into the
aforementioned issue.

I added the entire MCP JSON schema to
`typify/tests/schemas/model-context-protocol.json`, which as expected
immediately caused the `schemas` test to fail.  Fixing this was a
relatively simple matter of finding every place in the codegen that
referred to the Rust `std::result::Result` type as simply `Result`.
That was enough to make the MCP schema work right.

But then I looked a bit closer and realized there were some other Rust
built-in types that were being referenced in such a way as to cause
either errors during codegen or generation of code that doesn't compile.

So I made a diabolical test schema `rust-collisions.json` that is truly
cursed.  It defines a bunch of types that collide with Rust built-in
types and keywords, and made the `schemas` test explode with such
force that there may have been inquiries from the neighbors.  Upon
reassuring them that that's just how the Rust compiler expresses
affection, I made some more codegen changes to resolve all of the
issues that my cursed schema exposed.

I've tried to make the changes minimal, and to my knowledge none of the
changes I have made would be breaking from the perspective of any user
of the generated Rust code.

I had to modify several existing tests in `typify-impl` that broke
because they were expecting the non-canonicalized versions of Rust
standard types.  Pay particular attention to the hack I put in
`test_util::validate_output_impl` which is not something I'm
particularly proud of but I think it retains the spirit of the test.

Closes oxidecomputer#568
anelson added a commit to anelson/typify that referenced this issue Dec 2, 2024
I ran into oxidecomputer#568 when trying to use typify to
generate Rust bindings for the [Model Context
Protocol](https://spec.modelcontextprotocol.io).  The Anthropic have
published a (JSON Schema specification for this
protocol)[https://github.com/modelcontextprotocol/specification/blob/main/schema/schema.json],
but as soon as I tried to codegen Rust bindings for it, I ran into the
aforementioned issue.

I added the entire MCP JSON schema to
`typify/tests/schemas/model-context-protocol.json`, which as expected
immediately caused the `schemas` test to fail.  Fixing this was a
relatively simple matter of finding every place in the codegen that
referred to the Rust `std::result::Result` type as simply `Result`.
That was enough to make the MCP schema work right.

But then I looked a bit closer and realized there were some other Rust
built-in types that were being referenced in such a way as to cause
either errors during codegen or generation of code that doesn't compile.

So I made a diabolical test schema `rust-collisions.json` that is truly
cursed.  It defines a bunch of types that collide with Rust built-in
types and keywords, and made the `schemas` test explode with such
force that there may have been inquiries from the neighbors.  Upon
reassuring them that that's just how the Rust compiler expresses
affection, I made some more codegen changes to resolve all of the
issues that my cursed schema exposed.

I've tried to make the changes minimal, and to my knowledge none of the
changes I have made would be breaking from the perspective of any user
of the generated Rust code.

I had to modify several existing tests in `typify-impl` that broke
because they were expecting the non-canonicalized versions of Rust
standard types.  Pay particular attention to the hack I put in
`test_util::validate_output_impl` which is not something I'm
particularly proud of but I think it retains the spirit of the test.

Closes oxidecomputer#568
@ahl ahl closed this as completed in #705 Dec 2, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
compile-fail generated code doesn't compile
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants