Summary
Backend-generated wrapper helpers currently erase typed Dictionary[K, V] differences when building helper names for ordinary bound methods. If the same compiled class contains multiple methods whose signatures differ only by typed Dictionary generic arguments, but share the same arity and return type, the generated wrapper layer can emit duplicate C helper names and fail with redefinition errors.
This is a backend helper naming / symbol-identity bug, not a String <-> StringName semantic conversion failure.
How this was exposed
The problem was surfaced while extending the String / StringName test suite, especially the typed-dictionary key roundtrip fixture:
src/test/test_suite/unit_test/script/subscript/string_stringname_dictionary_key_roundtrip.gd
src/test/test_suite/unit_test/validation/subscript/string_stringname_dictionary_key_roundtrip.gd
doc/test_error/string_stringname_test_suite_exposed_limits.md
That fixture family exercises typed Dictionary[StringName, ...] and Dictionary[String, ...] boundaries through method calls, keyed access, writable payload mutation, and caller-visible writeback. During that work, helper-name collisions became visible as a separate backend limitation.
Expected behavior
Two methods that differ in typed Dictionary generic arguments should remain distinct throughout backend wrapper generation.
For example, methods like these should not share wrapper helper symbols:
func use_named(values: Dictionary[StringName, int]) -> int:
return int(values[&"score"])
func use_keyed(values: Dictionary[String, int]) -> int:
return int(values["score"])
Expected backend property:
- typed
Dictionary leaf information stays part of helper symbol identity,
- generated
call_func, ptrcall, and bind-registration helpers remain unique,
- distinct typed signatures do not collide inside the same generated module.
Actual behavior
Generated helper names collapse all GdDictionaryType inputs to the same Dictionary fragment, so multiple distinct typed signatures can map to the same helper suffix.
Typical failure shape:
call_1_arg_Dictionary_ret_int redefinition
ptrcall_1_arg_Dictionary_ret_int redefinition
gdcc_bind_method_1_arg_Dictionary_ret_int redefinition
Cause chain
BindingData still preserves the real GdType signature for parameters and return types, including typed Dictionary leaf information.
CGenHelper.renderGdTypeName(...) erases every GdDictionaryType to the same string literal Dictionary.
CGenHelper.renderFuncBindName(...) builds helper suffixes from that erased type-name rendering.
src/main/c/codegen/template_451/entry.h.ftl uses that suffix directly to emit:
call${helper.renderFuncBindName(bindingData)}
ptrcall${helper.renderFuncBindName(bindingData)}
gdcc_bind_method${helper.renderFuncBindName(bindingData)}
bindingDataList is collected from real signatures, but the template does not re-check uniqueness after helper-name rendering.
- Distinct typed
Dictionary signatures can therefore generate identical static C helper names and fail during C compilation.
Why this is a contract violation
The current backend docs already describe a stronger identity contract than the implementation follows here:
doc/module_impl/backend/godot_binding_implementation.md says generated binding symbol collisions are only legal when the full ABI signature is identical.
- The same doc defines
Dictionary[K, V] ABI encoding as carrying both key and value descriptors.
doc/module_impl/backend/typed_dictionary_abi_contract.md treats typed Dictionary outward ABI as a maintained backend contract rather than a plain Dictionary alias.
In other words, typed Dictionary generic information is still part of backend identity and metadata contracts, but helper-name generation currently drops that distinction too early.
Relevant implementation points
src/main/java/gd/script/gdcc/backend/c/gen/CGenHelper.java
renderGdTypeName(...)
renderFuncBindName(...)
renderBoundMetadata(...)
src/main/java/gd/script/gdcc/backend/c/gen/binding/BindingData.java
src/main/java/gd/script/gdcc/backend/c/gen/binding/GodotBindingSymbol.java
src/main/c/codegen/template_451/entry.h.ftl
Notes
This is separate from another current limitation where typed Dictionary literals in compiled source are still blocked by frontend compile-check and must be constructed in validation scripts. That fixture limitation is documented, but it is not the root cause of this helper collision.
Suggested acceptance coverage
Add focused backend/codegen coverage for at least these cases:
- two methods in one compiled class that differ only by typed
Dictionary generic arguments,
- same arity and same return type, to prove helper-name uniqueness depends on typed signature details,
- positive codegen assertion that generated
call / ptrcall / bind helper symbols are distinct,
- fail-fast coverage if helper-name rendering ever collapses distinct signatures again.
A minimal regression should assert that the backend either:
- emits distinct helper names for distinct typed
Dictionary signatures, or
- rejects non-unique helper symbol generation before C compilation.
Summary
Backend-generated wrapper helpers currently erase typed
Dictionary[K, V]differences when building helper names for ordinary bound methods. If the same compiled class contains multiple methods whose signatures differ only by typedDictionarygeneric arguments, but share the same arity and return type, the generated wrapper layer can emit duplicate C helper names and fail with redefinition errors.This is a backend helper naming / symbol-identity bug, not a
String <-> StringNamesemantic conversion failure.How this was exposed
The problem was surfaced while extending the
String/StringNametest suite, especially the typed-dictionary key roundtrip fixture:src/test/test_suite/unit_test/script/subscript/string_stringname_dictionary_key_roundtrip.gdsrc/test/test_suite/unit_test/validation/subscript/string_stringname_dictionary_key_roundtrip.gddoc/test_error/string_stringname_test_suite_exposed_limits.mdThat fixture family exercises typed
Dictionary[StringName, ...]andDictionary[String, ...]boundaries through method calls, keyed access, writable payload mutation, and caller-visible writeback. During that work, helper-name collisions became visible as a separate backend limitation.Expected behavior
Two methods that differ in typed
Dictionarygeneric arguments should remain distinct throughout backend wrapper generation.For example, methods like these should not share wrapper helper symbols:
Expected backend property:
Dictionaryleaf information stays part of helper symbol identity,call_func,ptrcall, and bind-registration helpers remain unique,Actual behavior
Generated helper names collapse all
GdDictionaryTypeinputs to the sameDictionaryfragment, so multiple distinct typed signatures can map to the same helper suffix.Typical failure shape:
call_1_arg_Dictionary_ret_intredefinitionptrcall_1_arg_Dictionary_ret_intredefinitiongdcc_bind_method_1_arg_Dictionary_ret_intredefinitionCause chain
BindingDatastill preserves the realGdTypesignature for parameters and return types, including typedDictionaryleaf information.CGenHelper.renderGdTypeName(...)erases everyGdDictionaryTypeto the same string literalDictionary.CGenHelper.renderFuncBindName(...)builds helper suffixes from that erased type-name rendering.src/main/c/codegen/template_451/entry.h.ftluses that suffix directly to emit:call${helper.renderFuncBindName(bindingData)}ptrcall${helper.renderFuncBindName(bindingData)}gdcc_bind_method${helper.renderFuncBindName(bindingData)}bindingDataListis collected from real signatures, but the template does not re-check uniqueness after helper-name rendering.Dictionarysignatures can therefore generate identical static C helper names and fail during C compilation.Why this is a contract violation
The current backend docs already describe a stronger identity contract than the implementation follows here:
doc/module_impl/backend/godot_binding_implementation.mdsays generated binding symbol collisions are only legal when the full ABI signature is identical.Dictionary[K, V]ABI encoding as carrying both key and value descriptors.doc/module_impl/backend/typed_dictionary_abi_contract.mdtreats typedDictionaryoutward ABI as a maintained backend contract rather than a plainDictionaryalias.In other words, typed
Dictionarygeneric information is still part of backend identity and metadata contracts, but helper-name generation currently drops that distinction too early.Relevant implementation points
src/main/java/gd/script/gdcc/backend/c/gen/CGenHelper.javarenderGdTypeName(...)renderFuncBindName(...)renderBoundMetadata(...)src/main/java/gd/script/gdcc/backend/c/gen/binding/BindingData.javasrc/main/java/gd/script/gdcc/backend/c/gen/binding/GodotBindingSymbol.javasrc/main/c/codegen/template_451/entry.h.ftlNotes
This is separate from another current limitation where typed
Dictionaryliterals in compiled source are still blocked by frontend compile-check and must be constructed in validation scripts. That fixture limitation is documented, but it is not the root cause of this helper collision.Suggested acceptance coverage
Add focused backend/codegen coverage for at least these cases:
Dictionarygeneric arguments,call/ptrcall/ bind helper symbols are distinct,A minimal regression should assert that the backend either:
Dictionarysignatures, or