Search before asking
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:
#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:
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:
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:
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:
|
- `any` always writes a null flag (same as `nullable`) because values may be empty. |
But the real issue here is that generate_field_accessors() chooses the optional-field accessor path from field.optional, and emits
|
if field.optional: |
|
lines.append(f"{indent} return *{member_name};") |
This expression is valid for std::optional<T>, but invalid for std::any since it is not dereferenceable.
And the generated Rust code:
#[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(" }")
}
}
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:
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.
should be normalized to:
This would fix above edge cases directly.
Are you willing to submit a PR?
Search before asking
Version
Latest Fory
Component(s)
Rust, C++
Minimal reproduce step
Generate C++ and Rust code using this IDL:
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:
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:
cargo compilation error:
Anything Else?
Root cause
Let's analyze the generated code.
The generated C++ code:
We can see Fory generates C++
std::anyfor IDLoptional any. This is correct behavior as defined in the doc:fory/docs/compiler/schema-idl.md
Line 1391 in 4660351
But the real issue here is that
generate_field_accessors()chooses the optional-field accessor path fromfield.optional, and emitsreturn *value_;fory/compiler/fory_compiler/generators/cpp.py
Lines 811 to 812 in 4660351
This expression is valid for
std::optional<T>, but invalid forstd::anysince it is not dereferenceable.And the generated Rust code:
In Rust,
optional anystill generates a bareArc<dyn Any + Send + Sync>field instead of anOption<...>, which is the same as C++.But other Rust generation paths still look only at
field.optional:Defaultis derived because optional fields are assumed to be defaultable.fory/compiler/fory_compiler/generators/rust.py
Lines 1060 to 1061 in 4660351
The custom
Debugimpl emitsself.value.is_some()because optional fields areassumed to be
Option<T>.fory/compiler/fory_compiler/generators/rust.py
Lines 1258 to 1259 in 4660351
Optionhas theis_some()method and implementsDebug, but not forArc<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 anywould trigger edge cases in other languages.The doc already says
anyas writing a null flag, same as anullable field. Therefore,
optional anyappears to be redundant with plainany.So instead of adding language-specific edge-case handling for every generated path,
why won't we canonicalize
optional anydirectly intoanyin the IR?e.g.
should be normalized to:
This would fix above edge cases directly.
Are you willing to submit a PR?