diff --git a/pyrightconfig.ci.json b/pyrightconfig.ci.json index ca44e111b..08b7c4fa0 100644 --- a/pyrightconfig.ci.json +++ b/pyrightconfig.ci.json @@ -5,7 +5,6 @@ "**/node_modules/**", "temp/tests/Python/test_applicative.py", "temp/tests/Python/test_hash_set.py", - "temp/tests/Python/test_mailbox_processor.py", "temp/tests/Python/test_nested_and_recursive_pattern.py", "temp/tests/Python/fable_modules/thoth_json_python/encode.py" ] diff --git a/src/Fable.Cli/CHANGELOG.md b/src/Fable.Cli/CHANGELOG.md index d2da0ede4..56b34bb2a 100644 --- a/src/Fable.Cli/CHANGELOG.md +++ b/src/Fable.Cli/CHANGELOG.md @@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * [Beam] Add Erlang/BEAM target (`--lang beam`). Compiles F# to `.erl` source files. 2086 tests passing. (by @dbrattli) +### Fixed + +* [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) + ## 5.0.0-alpha.24 - 2026-02-13 ### Fixed diff --git a/src/Fable.Compiler/CHANGELOG.md b/src/Fable.Compiler/CHANGELOG.md index 639c04bd5..475e29699 100644 --- a/src/Fable.Compiler/CHANGELOG.md +++ b/src/Fable.Compiler/CHANGELOG.md @@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * [Beam] Add Erlang/BEAM compilation support: Fable2Beam transform, Beam Replacements, ErlangPrinter, and 31 runtime `.erl` modules. 2086 tests passing. (by @dbrattli) +### Fixed + +* [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) + ## 5.0.0-alpha.23 - 2026-02-13 ### Fixed diff --git a/src/Fable.Transforms/Python/Fable2Python.Annotation.fs b/src/Fable.Transforms/Python/Fable2Python.Annotation.fs index d1baf0d60..0df69428e 100644 --- a/src/Fable.Transforms/Python/Fable2Python.Annotation.fs +++ b/src/Fable.Transforms/Python/Fable2Python.Annotation.fs @@ -424,8 +424,10 @@ let rec typeAnnotation com.AddTypeVar(ctx, name), [] | Some _ -> stdlibModuleTypeHint com ctx "typing" "Any" [] repeatedGenerics | None -> - let name = Helpers.clean name - com.AddTypeVar(ctx, name), [] + // No repeatedGenerics info means we're outside a function type param scope + // (e.g., class fields, variable annotations). With PEP 695, type vars are + // lexically scoped, so emit Any instead of an undefined type var reference. + stdlibModuleTypeHint com ctx "typing" "Any" [] repeatedGenerics | Fable.Unit -> Expression.none, [] | Fable.Boolean -> Expression.name "bool", [] | Fable.Char -> Expression.name "str", [] diff --git a/src/Fable.Transforms/Python/Fable2Python.Transforms.fs b/src/Fable.Transforms/Python/Fable2Python.Transforms.fs index 46e242afa..097022d85 100644 --- a/src/Fable.Transforms/Python/Fable2Python.Transforms.fs +++ b/src/Fable.Transforms/Python/Fable2Python.Transforms.fs @@ -3022,8 +3022,11 @@ let transformFunction let mutable isTailCallOptimized = false let argTypes = args |> List.map (fun id -> id.Type) - let genTypeParams = getGenericTypeParams (argTypes @ [ body.Type ]) - let newTypeParams = Set.difference genTypeParams ctx.ScopedTypeParams + // Only track actually-declared (repeated) generic params in ScopedTypeParams. + // With PEP 695, type params are lexically scoped, so only params that appear + // in the function's [] declaration should be tracked. Non-repeated params + // (erased to Any) must not enter scope or inner functions will reference them. + let newTypeParams = Set.difference repeatedGenerics ctx.ScopedTypeParams let ctx = { ctx with @@ -3739,13 +3742,35 @@ let transformModuleFunction (info: Fable.MemberFunctionOrValue) (membName: string) (fableArgs: Fable.Ident list) - body + (body: Fable.Expr) = + let argTypes = fableArgs |> List.map _.Type + + // Compute which type params will be declared in this function's PEP 695 [] + // BEFORE transforming the body, so inner functions know these are already in scope + // and won't re-declare them (which would cause "TypeVar already in use" errors). + let explicitGenerics = Annotation.getMemberGenParams info.GenericParameters + + let signatureGenerics = + (getGenericTypeParams (argTypes @ [ body.Type ]), ctx.ScopedTypeParams) + ||> Set.difference + + let declaredTypeParams = + Set.empty + |> Set.union explicitGenerics + |> Set.union signatureGenerics + |> Set.difference + <| ctx.ScopedTypeParams + + let ctxWithDeclaredParams = + { ctx with ScopedTypeParams = Set.union ctx.ScopedTypeParams declaredTypeParams } + let args, body', returnType = - getMemberArgsAndBody com ctx (NonAttached membName) info.HasSpread fableArgs body + getMemberArgsAndBody com ctxWithDeclaredParams (NonAttached membName) info.HasSpread fableArgs body + + let typeParams = + Annotation.makeFunctionTypeParamsWithConstraints com ctx info.GenericParameters declaredTypeParams - let argTypes = fableArgs |> List.map _.Type - let typeParams = calculateTypeParams com ctx info argTypes body.Type let name = com.GetIdentifier(ctx, membName |> Naming.toPythonNaming) let isAsync = isTaskType body.Type