Skip to content

Commit ed16114

Browse files
authored
[Python] Fix type var scoping for PEP 695 annotations (#4362)
1 parent ad6884a commit ed16114

5 files changed

Lines changed: 43 additions & 9 deletions

File tree

pyrightconfig.ci.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
"**/node_modules/**",
66
"temp/tests/Python/test_applicative.py",
77
"temp/tests/Python/test_hash_set.py",
8-
"temp/tests/Python/test_mailbox_processor.py",
98
"temp/tests/Python/test_nested_and_recursive_pattern.py",
109
"temp/tests/Python/fable_modules/thoth_json_python/encode.py"
1110
]

src/Fable.Cli/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111

1212
* [Beam] Add Erlang/BEAM target (`--lang beam`). Compiles F# to `.erl` source files. 2086 tests passing. (by @dbrattli)
1313

14+
### Fixed
15+
16+
* [Python] Fix type var scoping for PEP 695 annotations: emit `Any` for type vars outside function scope and prevent non-repeated generic params from leaking into `ScopedTypeParams` (by @dbrattli)
17+
1418
## 5.0.0-alpha.24 - 2026-02-13
1519

1620
### Fixed

src/Fable.Compiler/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111

1212
* [Beam] Add Erlang/BEAM compilation support: Fable2Beam transform, Beam Replacements, ErlangPrinter, and 31 runtime `.erl` modules. 2086 tests passing. (by @dbrattli)
1313

14+
### Fixed
15+
16+
* [Python] Fix type var scoping for PEP 695 annotations: emit `Any` for type vars outside function scope and prevent non-repeated generic params from leaking into `ScopedTypeParams` (by @dbrattli)
17+
1418
## 5.0.0-alpha.23 - 2026-02-13
1519

1620
### Fixed

src/Fable.Transforms/Python/Fable2Python.Annotation.fs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -424,8 +424,10 @@ let rec typeAnnotation
424424
com.AddTypeVar(ctx, name), []
425425
| Some _ -> stdlibModuleTypeHint com ctx "typing" "Any" [] repeatedGenerics
426426
| None ->
427-
let name = Helpers.clean name
428-
com.AddTypeVar(ctx, name), []
427+
// No repeatedGenerics info means we're outside a function type param scope
428+
// (e.g., class fields, variable annotations). With PEP 695, type vars are
429+
// lexically scoped, so emit Any instead of an undefined type var reference.
430+
stdlibModuleTypeHint com ctx "typing" "Any" [] repeatedGenerics
429431
| Fable.Unit -> Expression.none, []
430432
| Fable.Boolean -> Expression.name "bool", []
431433
| Fable.Char -> Expression.name "str", []

src/Fable.Transforms/Python/Fable2Python.Transforms.fs

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3022,8 +3022,11 @@ let transformFunction
30223022
let mutable isTailCallOptimized = false
30233023

30243024
let argTypes = args |> List.map (fun id -> id.Type)
3025-
let genTypeParams = getGenericTypeParams (argTypes @ [ body.Type ])
3026-
let newTypeParams = Set.difference genTypeParams ctx.ScopedTypeParams
3025+
// Only track actually-declared (repeated) generic params in ScopedTypeParams.
3026+
// With PEP 695, type params are lexically scoped, so only params that appear
3027+
// in the function's [] declaration should be tracked. Non-repeated params
3028+
// (erased to Any) must not enter scope or inner functions will reference them.
3029+
let newTypeParams = Set.difference repeatedGenerics ctx.ScopedTypeParams
30273030

30283031
let ctx =
30293032
{ ctx with
@@ -3739,13 +3742,35 @@ let transformModuleFunction
37393742
(info: Fable.MemberFunctionOrValue)
37403743
(membName: string)
37413744
(fableArgs: Fable.Ident list)
3742-
body
3745+
(body: Fable.Expr)
37433746
=
3747+
let argTypes = fableArgs |> List.map _.Type
3748+
3749+
// Compute which type params will be declared in this function's PEP 695 []
3750+
// BEFORE transforming the body, so inner functions know these are already in scope
3751+
// and won't re-declare them (which would cause "TypeVar already in use" errors).
3752+
let explicitGenerics = Annotation.getMemberGenParams info.GenericParameters
3753+
3754+
let signatureGenerics =
3755+
(getGenericTypeParams (argTypes @ [ body.Type ]), ctx.ScopedTypeParams)
3756+
||> Set.difference
3757+
3758+
let declaredTypeParams =
3759+
Set.empty
3760+
|> Set.union explicitGenerics
3761+
|> Set.union signatureGenerics
3762+
|> Set.difference
3763+
<| ctx.ScopedTypeParams
3764+
3765+
let ctxWithDeclaredParams =
3766+
{ ctx with ScopedTypeParams = Set.union ctx.ScopedTypeParams declaredTypeParams }
3767+
37443768
let args, body', returnType =
3745-
getMemberArgsAndBody com ctx (NonAttached membName) info.HasSpread fableArgs body
3769+
getMemberArgsAndBody com ctxWithDeclaredParams (NonAttached membName) info.HasSpread fableArgs body
3770+
3771+
let typeParams =
3772+
Annotation.makeFunctionTypeParamsWithConstraints com ctx info.GenericParameters declaredTypeParams
37463773

3747-
let argTypes = fableArgs |> List.map _.Type
3748-
let typeParams = calculateTypeParams com ctx info argTypes body.Type
37493774
let name = com.GetIdentifier(ctx, membName |> Naming.toPythonNaming)
37503775

37513776
let isAsync = isTaskType body.Type

0 commit comments

Comments
 (0)