Skip to content

fix(python): keep original F# field names in record reflection#4852

Merged
dbrattli merged 1 commit into
mainfrom
fix/python-record-reflection-field-names
Jul 25, 2026
Merged

fix(python): keep original F# field names in record reflection#4852
dbrattli merged 1 commit into
mainfrom
fix/python-record-reflection-field-names

Conversation

@dbrattli

Copy link
Copy Markdown
Collaborator

Problem

Python is the only Fable target that mangles record field names in the reflection TypeInfo, not just the runtime slots. For type Rec = { FirstName: string; HTTPStatus: int; ID: int }:

Backend FSharpType.GetRecordFields(t).[i].Name
.NET / Fable JS/TS / Fable Beam FirstName, HTTPStatus, ID
Fable Python (before) first_name, httpstatus, id

The transform is lossy (httpstatus could be HttpStatus/HTTPStatus/Httpstatus), so any reflection-driven serializer on Python can't reproduce the wire format that .NET and the JS/TS backend produce. Beam already demonstrates the intended design: snake_case runtime map keys, but a pristine F# name in the reflection metadata (name/erl_name split).

Fix

  • Compiler (Fable2Python.Reflection.fs): emit fi.Name (pristine) as the reflection field name, adding the snake_case slot as an optional 3rd tuple element, only when it differs from the F# name.
  • Runtime (fable-library-py/reflection.py): widen FieldInfo to tuple[str, TypeInfo] | tuple[str, TypeInfo, str]; get_value / get_record_field resolve the attribute via a new _field_attr_name helper (element 2 if present, else 0). Backward-compatible with already-published 2-tuple output.
  • Union reflection already kept the pristine name — this brings records in line.

Tests

  • Collapsed the #if FABLE_COMPILER fork in TestReflection.fs that enshrined the mangled names ("string"/"int", map [(age..)]) down to the single .NET-matching version.
  • Added test Record reflection keeps original F# field names covering { FirstName; HTTPStatus; ID }: reflected names equal the F# names, and GetValue/MakeRecord still resolve the mangled slots.

All 2418 Python tests pass; Pyright clean (0 errors, no # type: ignore).

Notes

src/fable-library-py is PyPI-distributed; this is backward-compatible within the minor version — the library still reads old 2-tuple reflection output, and PropertyInfo.Name semantics only become more correct.

🤖 Generated with Claude Code

Python was the only target that mangled record field names in the
reflection TypeInfo, not just the runtime slots. FSharpType.GetRecordFields
returned snake_case names (first_name, httpstatus, id) instead of the F#
names (FirstName, HTTPStatus, ID) that .NET, JS/TS and Beam all report.
The transform is lossy, so reflection-driven serializers can't reproduce
the .NET/JS wire format.

Emit the pristine F# field name as PropertyInfo.Name, carrying the
snake_case slot as an optional 3rd tuple element (only when it differs)
so value access still resolves the real attribute. fable-library-py reads
that slot for get_value/get_record_field, falling back to element 0 for
already-published 2-tuple output. This mirrors the Beam target's
name/erl_name split. Union reflection already kept the pristine name.

Collapses the FABLE_COMPILER test fork that enshrined the mangled names
and adds coverage for multi-word/acronym fields.

Co-Authored-By: Claude Opus 4.8 <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

@dbrattli
dbrattli merged commit 90c1798 into main Jul 25, 2026
42 checks passed
@dbrattli
dbrattli deleted the fix/python-record-reflection-field-names branch July 25, 2026 08:38
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.

1 participant