Skip to content

Backend wrapper helper names collapse typed Dictionary signatures and trigger C redefinition #39

Description

@Iridium-Zero

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

  1. BindingData still preserves the real GdType signature for parameters and return types, including typed Dictionary leaf information.
  2. CGenHelper.renderGdTypeName(...) erases every GdDictionaryType to the same string literal Dictionary.
  3. CGenHelper.renderFuncBindName(...) builds helper suffixes from that erased type-name rendering.
  4. 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)}
  5. bindingDataList is collected from real signatures, but the template does not re-check uniqueness after helper-name rendering.
  6. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions