# Generator Fix: Recursive Type Detection **Date:** 2025-11-18 **Issue:** Recursive struct compilation error **Status:** Fixed ## Problem The code generator was creating all Kubernetes types as `struct`, but some types like `JSONSchemaProps` are recursive (they reference themselves). Crystal structs cannot be recursive because they need a fixed size in memory. ### Error Message ``` Error: recursive struct Kubernetes::JSONSchemaProps detected `@not : (Kubernetes::JSONSchemaProps | Nil)` -> `(Kubernetes::JSONSchemaProps | Nil)` -> `Kubernetes::JSONSchemaProps` The struct Kubernetes::JSONSchemaProps has, either directly or indirectly, an instance variable whose type is, eventually, this same struct. This makes it impossible to represent the struct in memory, because the size of this instance variable depends on the size of this struct, which depends on the size of this instance variable, causing an infinite cycle. You should probably be using classes here, as classes instance variables are always behind a pointer, which makes it possible to always compute a size for them. ``` ## Root Cause `JSONSchemaProps` has properties that reference itself: - `all_of : Array(JSONSchemaProps)?` - `any_of : Array(JSONSchemaProps)?` - `definitions : Hash(String, JSONSchemaProps)?` - `not : JSONSchemaProps?` - `one_of : Array(JSONSchemaProps)?` - `pattern_properties : Hash(String, JSONSchemaProps)?` - `properties : Hash(String, JSONSchemaProps)?` This creates a recursive structure that cannot be represented as a `struct`. ## Solution Modified the code generator (`src/generator/main.cr`) to: 1. **Detect recursive types** before generating the type definition 2. **Use `class` for recursive types** instead of `struct` 3. **Keep `struct` for non-recursive types** for better performance ### Implementation Added `recursive_type?` method that checks if a type references itself: ```crystal # Check if a type references itself (directly or in arrays/hashes) private def recursive_type?(type_name : String, properties : Hash(String, JSON::Any)) : Bool properties.each do |_, prop_def| prop_hash = prop_def.as_h # Check direct reference if ref = prop_hash["$ref"]?.try(&.as_s) ref_type = ref.split("/").last.split(".").last return true if ref_type == type_name end # Check array items if items = prop_hash["items"]?.try(&.as_h) if ref = items["$ref"]?.try(&.as_s) ref_type = ref.split("/").last.split(".").last return true if ref_type == type_name end end # Check additionalProperties (for Hash types) if additional = prop_hash["additionalProperties"]?.try(&.as_h) if ref = additional["$ref"]?.try(&.as_s) ref_type = ref.split("/").last.split(".").last return true if ref_type == type_name end end end false end ``` Updated `generate_model` to use this detection: ```crystal # Detect if this type is recursive (references itself) is_recursive = recursive_type?(simple_name, properties) # Use class for recursive types, struct for non-recursive if is_recursive io.puts " class #{simple_name}" else io.puts " struct #{simple_name}" end ``` ## Results **Before:** - All 718 types generated as `struct` - `JSONSchemaProps` caused compilation error - `shards build` failed **After:** - 1 type generated as `class` (JSONSchemaProps) - 717 types generated as `struct` - `shards build` succeeds - All 78 tests passing - Linter passes (0 failures) ## Performance Impact **Classes vs Structs:** - **Structs**: Stack-allocated, passed by value, slightly faster - **Classes**: Heap-allocated, passed by reference, required for recursion **Impact on crystal-kubernetes-client:** - 99.86% of types remain as structs (717/718) - Only 1 recursive type uses class (JSONSchemaProps) - Minimal performance impact - Correct memory representation ## Files Modified **src/generator/main.cr:** - Added `recursive_type?` method (29 lines) - Modified `generate_model` to detect and handle recursive types **Generated code:** - `src/generated/models/v1.cr` - JSONSchemaProps now a class ## Verification ```bash # Build succeeds shards build # I: Building: kubernetes-client ✓ # Tests pass crystal spec # 78 examples, 0 failures ✓ # Linter passes make lint # 115 inspected, 0 failures ✓ # Type breakdown grep "^ class " src/generated/models/*.cr | wc -l # 1 (JSONSchemaProps) grep "^ struct " src/generated/models/*.cr | wc -l # 717 (all other types) ``` ## Future Considerations If additional recursive types are added to the Kubernetes API: - Generator will automatically detect them - They will be generated as classes - No manual intervention required ## Related Issues This fix ensures: - Kubernetes API spec updates won't break compilation - Generated code follows Crystal best practices - Type safety is maintained - Performance is optimal for non-recursive types