Skip to content

refactor(python): split FSharpRef.__init__ into overloads#4638

Merged
dbrattli merged 1 commit into
mainfrom
refactor/py-fsharpref-init-overloads
Jun 9, 2026
Merged

refactor(python): split FSharpRef.__init__ into overloads#4638
dbrattli merged 1 commit into
mainfrom
refactor/py-fsharpref-init-overloads

Conversation

@dbrattli

@dbrattli dbrattli commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator

Summary

The FSharpRef stub declared a single constructor with a union parameter:

def __init__(self, contents_or_getter: T | Callable[[], T],
             setter: Callable[[T], None] | None = None) -> None: ...

This splits it into two overloads matching the two real construction modes — wrap a value, or wrap a getter/setter pair:

@overload
def __init__(self, contents_or_getter: T) -> None: ...
@overload
def __init__(self, contents_or_getter: Callable[[], T], setter: Callable[[T], None]) -> None: ...

Motivation

Pyright infers T from the union form fine, but ty cannot solve T through T | Callable[[], T] for the getter/setter form — it binds T to the getter callable itself, yielding FSharpRef[T] that it then rejects at every out-parameter call site (try_parse, byref helpers, etc.). The overloads make T unambiguously solvable from each mode. Runtime behaviour is identical and the intent is clearer.

This came out of an investigation into whether ty could replace Pyright for this repo; the single-constructor union was the largest single source of ty false positives.

Verification

ty (tests) ty (library) Pyright (tests) Pyright (library) runtime
before 292 72 34 0 2350 ✓
after 118 68 34 0 2350 ✓
  • Removes 178 false-positive ty diagnostics across the generated output.
  • No change to Pyright (the gating checker): still 0 library / 34 tests.
  • All 2350 Python tests pass (./build.sh test python).

🤖 Generated with Claude Code

The FSharpRef stub declared a single constructor with a union parameter:

    def __init__(self, contents_or_getter: T | Callable[[], T],
                 setter: Callable[[T], None] | None = None) -> None

Pyright infers `T` from this fine, but Astral's `ty` cannot solve `T`
through the `T | Callable[[], T]` union for the getter/setter form: it
binds `T` to the getter callable itself, producing `FSharpRef[T]` that
it then rejects at every out-parameter call site (try_parse, byref
helpers, etc.).

Split the constructor into two overloads that mirror the two actual
construction modes — wrap a value, or wrap a getter/setter pair — so `T`
is unambiguously solvable from each. Runtime behaviour is unchanged and
the form is clearer.

This removes 178 false-positive `ty` diagnostics across the generated
Python output (tests 292 -> 118, library 72 -> 68) with no change to
Pyright (0 library / 34 tests, both unchanged) and all 2350 Python
tests passing.

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

github-actions Bot commented Jun 9, 2026

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 7e175dc into main Jun 9, 2026
32 checks passed
@dbrattli dbrattli deleted the refactor/py-fsharpref-init-overloads branch June 9, 2026 21:40
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