Skip to content

fix(python): avoid union case field name collision with Union.name#4647

Merged
dbrattli merged 2 commits into
mainfrom
fix/4645-python-named-union-fields
Jun 10, 2026
Merged

fix(python): avoid union case field name collision with Union.name#4647
dbrattli merged 2 commits into
mainfrom
fix/4645-python-named-union-fields

Conversation

@dbrattli

@dbrattli dbrattli commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator

Problem

Fixes #4645.

A named union case field called name fails at runtime in Python:

type Union =
    | Case of name: string * optional: string

let header1 = Union.Case("v1", "v2")

raises:

TypeError: non-default argument 'optional' follows default argument 'name'

Root cause

The generated case class looked like:

@tagged_union(0)
class Union_Case(_Union):
    name: str
    optional: str

The Union base class (fable_library/union.py) defines a name property. When @dataclass processes the name annotation, it finds that inherited property object via getattr(cls, 'name') and treats it as the field's default value. The next field without a default (optional) then violates Python's "non-default argument follows default argument" rule.

This is the same field-name collision class that records already solve with toFieldSnakeCase (formerly toRecordFieldSnakeCase); union case field generation was the one place still using plain toSnakeCase.

Fix

Use toFieldSnakeCase for union case field annotations, so name becomes name_ and no longer collides:

@tagged_union(0)
class Union_Case(_Union):
    name_: str
    optional: str

This is safe because union construction is positional (Union_Case("v1", "v2")) and field access is by index (self.fields[i]), so the dataclass field name is purely internal.

Rename

Since the toRecordFieldSnakeCase helper is now used for union case fields too — not just records — the "Record" qualifier was misleading. Renamed it to toFieldSnakeCase across the Python transforms and tidied the related comments. (shouldUseRecordFieldNaming stays as-is: it is genuinely record-specific.)

Verification

  • Reproduced the original TypeError, confirmed the fix runs correctly.
  • Added a regression test in tests/Python/TestUnionType.fs.
  • Full Python suite: 2351 passed, no regressions.

🤖 Generated with Claude Code

A named union case field called `name` (e.g. `Case of name: string * optional: string`)
generated a Python dataclass field `name: str`. The `Union` base class defines a `name`
property, which `@dataclass` treats as the field's default value, so any following field
without a default raised "non-default argument follows default argument" at runtime.

Use `toRecordFieldSnakeCase` for union case fields (same convention records already use),
so `name` becomes `name_` and no longer collides. Union construction is positional and
field access is by index, so the dataclass field name is purely internal.

Fixes #4645

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

Python Type Checking Results (Pyright)

Metric Value
Total errors 34
Files with errors 4
Excluded files 4
New errors ✅ No
Excluded files with errors (4 files)

These files have known type errors and are excluded from CI. Remove from pyrightconfig.ci.json as errors are fixed.

File Errors Status
temp/tests/Python/test_hash_set.py 18 Excluded
temp/tests/Python/test_applicative.py 12 Excluded
temp/tests/Python/test_nested_and_recursive_pattern.py 2 Excluded
temp/tests/Python/fable_modules/thoth_json_python/encode.py 2 Excluded

The helper is now used for union case fields too, not just records, so the
"Record" qualifier is misleading. Rename to toFieldSnakeCase and tidy the
related comments.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@dbrattli dbrattli merged commit ff5df24 into main Jun 10, 2026
32 checks passed
@dbrattli dbrattli deleted the fix/4645-python-named-union-fields branch June 10, 2026 22:42
dbrattli added a commit that referenced this pull request Jun 10, 2026
- Replace the 'Reflection Constructor' section with 'Runtime References
  Target the Base Class', documenting the centralized
  redirectUnionToPythonBaseClass helper and both of its call sites,
  including the Python-gated one in the shared FSharp2Fable.Util.fs.
- Add a 'Case Field Naming' section for the toFieldSnakeCase convention
  introduced by #4647 (fixes the Union.name dataclass collision, #4645)
  and update the generated-output examples accordingly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[PYTHON][BUG]: Named union case arguments fail on runtime

1 participant