BaldDemian opened a new issue, #3805:
URL: https://github.com/apache/fory/issues/3805

   ### Search before asking
   
   - [x] I had searched in the [issues](https://github.com/apache/fory/issues) 
and found no similar issues.
   
   
   ### Version
   
   Latest Fory
   
   ### Component(s)
   
   Rust, C++
   
   ### Minimal reproduce step
   
   Generate C++ and Rust code using this IDL:
   
   ```
   package issue.optional_any;
   
   message Holder {
     optional any value = 1;
   }
   ```
   and compile the generated code with g++ and cargo.
   
   Note: since Fory C++ compiler only generate header files for IDL, we should 
define and compile the simple source file like this:
   
   ```c++
   #include "issue_optional_any.h"
   
   int main() {
     issue::optional_any::Holder holder;
     return holder.has_value() ? 1 : 0;
   }
   ```
   
   ### What did you expect to see?
   
   Code should be compiled.
   
   ### What did you see instead?
   
   Compilation failed for both C++ and Rust.
   
   g++ compilation error:
   
   ```text
   In file included from /tmp/fory-cpp-optional-any.3A0fEE/compile.cc:1:
   /tmp/fory-cpp-optional-any.3A0fEE/issue_optional_any.h: In member function 
‘const std::any& issue::optional_any::Holder::value() const’:
   /tmp/fory-cpp-optional-any.3A0fEE/issue_optional_any.h:52:12: error: no 
match for ‘operator*’ (operand type is ‘const std::any’)
      52 |     return *value_;
         |            ^~~~~~~
   ```
   
   cargo compilation error:
   
   ```text
   error[E0277]: the trait bound `dyn Any + Send + Sync: Default` is not 
satisfied
       --> src/issue_optional_any.rs:24:5
        |
     21 | #[derive(::fory::ForyStruct, Default)]
        |                              ------- in this derive macro expansion
   ...
     24 |     pub value: ::std::sync::Arc<dyn ::std::any::Any + Send + Sync>,
        |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 
the trait `Default` is not implemented for `dyn Any + Send + Sync`
        |
   help: the following other types implement trait `Default`
       --> 
/home/hpy/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/alloc/src/sync.rs:3715:1
        |
   3715 | impl<T: Default> Default for Arc<T> {
        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `Arc<T>`
   ...
   3767 | impl Default for Arc<str> {
        | ^^^^^^^^^^^^^^^^^^^^^^^^^ `Arc<str>`
   ...
   3782 | impl Default for Arc<core::ffi::CStr> {
        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `Arc<CStr>`
   ...
   3801 | impl<T> Default for Arc<[T]> {
        | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `Arc<[T]>`
        = note: required for `Arc<dyn Any + Send + Sync>` to implement `Default`
   
   error[E0599]: no method named `is_some` found for struct `Arc<(dyn Any + 
Send + Sync + 'static)>` in the current scope
     --> src/issue_optional_any.rs:31:23
      |
   31 |         if self.value.is_some() {
      |                       ^^^^^^^ method not found in `Arc<(dyn Any + Send 
+ Sync + 'static)>`
   
   Some errors have detailed explanations: E0277, E0599.
   For more information about an error, try `rustc --explain E0277`.
   error: could not compile `optional_any_repro` (lib) due to 2 previous errors
   ```
   
   
   
   ### Anything Else?
   
   ## Root cause
   
   Let's analyze the generated code.
   
   The generated C++ code:
   
   ```c++
   class Holder final {
     public:
   
     bool has_value() const {
       return value_.has_value();
     }
   
     const std::any& value() const {
       return *value_;
     }
   
     void set_value(std::any value) {
       value_ = std::move(value);
     }
   
     void clear_value() {
       value_.reset();
     }
   
     bool operator==(const Holder& other) const {
       return ((!value_.has_value() && !other.value_.has_value()) || 
(value_.type() == other.value_.type()));
     }
   
     private:
       std::any value_;
   
     public:
     FORY_STRUCT(Holder, (value_, fory::F(1).nullable()));
   };
   ```
   
   We can see Fory generates C++ `std::any` for IDL `optional any`. This is 
correct behavior as defined in the doc:
   
   
https://github.com/apache/fory/blob/4660351b625da3222f7c4665454cdec2c552f419/docs/compiler/schema-idl.md?plain=1#L1391
   
   But the real issue here is that `generate_field_accessors()` chooses the 
optional-field accessor path from `field.optional`, and emits
   
   ```cpp
   return *value_;
   ```
   
https://github.com/apache/fory/blob/4660351b625da3222f7c4665454cdec2c552f419/compiler/fory_compiler/generators/cpp.py#L811-L812
   
   This expression is valid for `std::optional<T>`, but invalid for `std::any` 
since it is not not dereferenceable.
   
   And the generated Rust code:
   
   ```rust
   #[derive(::fory::ForyStruct, Default)]
   pub struct Holder {
       #[fory(id = 1, nullable = true)]
       pub value: ::std::sync::Arc<dyn ::std::any::Any + Send + Sync>,
   }
   
   impl ::std::fmt::Debug for Holder {
       fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
           f.write_str("Holder { ")?;
           f.write_str("value: ")?;
           if self.value.is_some() {
               f.write_str("any(...)")?;
           } else {
               f.write_str("None")?;
           }
           f.write_str(" }")
       }
   }
   ```
   
   The Rust generator maps primitive `any` to:
   
   ```rust
   ::std::sync::Arc<dyn ::std::any::Any + Send + Sync>
   ```
   
   In Rust, `optional any` still generates a bare `Arc<dyn Any + Send + Sync>` 
field instead of an `Option<...>`, which is the same as C++.
   
   But other Rust generation paths still look only at `field.optional`:
   
   - `Default` is derived because optional fields are assumed to be defaultable.
     
https://github.com/apache/fory/blob/4660351b625da3222f7c4665454cdec2c552f419/compiler/fory_compiler/generators/rust.py#L1060-L1061
   
   - The custom `Debug` impl emits `self.value.is_some()` because optional 
fields are
     assumed to be `Option<T>`.
     
https://github.com/apache/fory/blob/4660351b625da3222f7c4665454cdec2c552f419/compiler/fory_compiler/generators/rust.py#L1258-L1259
   
   `Option` has the `is_some()` method and implements `Debug`, but not for 
`Arc<dyn Any + Send + Sync>`.
   
   
   ## Suggested fix
   
   At first I though of fixing each edge cases in both Rust and C++ compiler 
case by case.
   But I didn't know whether `optional any` would trigger edge cases in other 
languages.
   
   The doc already says `any` as writing a null flag, same as a
   nullable field. Therefore, `optional any` appears to be redundant with plain
   `any`. 
   
   So instead of adding language-specific edge-case handling for every 
generated path,
   why won't we canonicalize `optional any` directly into `any` in the IR?
   
   e.g. 
   
   ```
   optional any value = 1;
   ```
   
   should be normalized to:
   
   ```
   any value = 1;
   ```
   
   This would fix above edge cases directly.
   
   
   ### Are you willing to submit a PR?
   
   - [x] I'm willing to submit a PR!


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to