-
Notifications
You must be signed in to change notification settings - Fork 87
Description
Description
The BorshSchema derive macro generates incorrect code that causes infinite recursion errors when an enum variant contains a type with a similar name to the variant itself. This occurs even when there is no actual circular dependency in the user's type definitions.
Steps to Reproduce
use borsh::BorshSchema;
#[derive(BorshSchema)]
pub struct AuthDataV3 {
pub field: u32,
}
#[derive(BorshSchema)]
pub struct AuthDataV4 {
pub field: u32,
}
#[derive(BorshSchema)]
pub enum AuthData {
V3(AuthDataV3), // Variant V3 contains type AuthDataV3
V4(AuthDataV4), // Variant V4 contains type AuthDataV4
}Expected Behavior
The code should compile successfully since there are no actual circular dependencies between the types.
Actual Behavior
Compilation fails with the following errors:
error[E0072]: recursive type `<AuthData as BorshSchema>::add_definitions_recursively::AuthDataV3` has infinite size
--> src/lib.rs:13:10
|
| #[derive(BorshSchema)]
| ^^^^^^^^^^^
| pub enum AuthData {
| V3(AuthDataV3),
| ---------- recursive without indirection
error[E0072]: recursive type `<AuthData as BorshSchema>::add_definitions_recursively::AuthDataV4` has infinite size
--> src/lib.rs:13:10
|
| #[derive(BorshSchema)]
| ^^^^^^^^^^^
| pub enum AuthData {
| V4(AuthDataV4),
| ---------- recursive without indirection
Root Cause
When expanding the macro with cargo expand, the issue becomes clear. The BorshSchema implementation for the enum creates local wrapper structs with the same names as the contained types:
impl borsh::BorshSchema for AuthData {
fn add_definitions_recursively(...) {
// Creates a LOCAL struct that shadows the actual AuthDataV3 type
struct AuthDataV3(AuthDataV3);
impl borsh::BorshSchema for AuthDataV3 {
fn add_definitions_recursively(...) {
// This references itself recursively!
let fields = borsh::schema::Fields::UnnamedFields(
vec![<AuthDataV3 as borsh::BorshSchema>::declaration()]
);
// ...
<AuthDataV3 as borsh::BorshSchema>::add_definitions_recursively(definitions);
}
}
// Same issue for V4
struct AuthDataV4(AuthDataV4);
// ...
}
}The macro creates wrapper structs struct AuthDataV3(AuthDataV3) and struct AuthDataV4(AuthDataV4) inside the add_definitions_recursively method. These wrapper structs shadow the actual types and create infinite recursion because they reference themselves.
Workaround
Users can work around this issue by:
- Renaming the struct types to avoid the naming collision:
#[derive(BorshSchema)]
pub struct AuthDataV3Struct { // Renamed
pub field: u32,
}
#[derive(BorshSchema)]
pub enum AuthData {
V3(AuthDataV3Struct), // No naming collision
}- Using type aliases:
pub type AuthV3 = AuthDataV3;
#[derive(BorshSchema)]
pub enum AuthData {
V3(AuthV3), // Uses alias instead
}- Not deriving BorshSchema for the enum (only using BorshSerialize/BorshDeserialize)
Suggested Fix
The macro should generate wrapper structs with unique names that don't shadow user-defined types. For example, it could prefix or suffix the generated struct names:
// Instead of: struct AuthDataV3(AuthDataV3);
// Generate: struct __BorshSchemaAuthDataV3Wrapper(AuthDataV3);Additional Context
This issue only affects the BorshSchema derive macro. The BorshSerialize and BorshDeserialize derive macros work correctly with the same type definitions.