From 758d97d735331076c42725f645c83e268bf14f7c Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Fri, 1 May 2026 16:04:51 +0200 Subject: [PATCH] feat(stdlib): expose Python type references and dict/list constructors in Builtins Closes #290. Adds the missing pieces in `Fable.Python.Builtins` that downstream interop libraries (Thoth.Json.Python, Fable.TypedJson) currently work around with private `[]` declarations: - Type value references (`pyInt`, `pyFloat`, `pyBool`, `pyStr`, `pyBytes`, `pyList`, `pyDict`, `pyTuple`, `pySet`) for use as the second argument to `Fable.Core.PyInterop.pyInstanceof`. The `py` prefix avoids colliding with F#/.NET built-in types. - `pyNone` value for Python's `None` singleton, useful as a JSON-null sentinel and at interop sites that need an explicit None. - `bool`, `dict`, and `list` constructors on `Builtins.IExports`, completing the set alongside the existing `int`/`float`/`str`/`bytes` constructors. `dict` and `list` are typed to return `Dictionary` and `ResizeArray<'T>` respectively to stay idiomatic per BINDINGS_GUIDE.md. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/stdlib/Builtins.fs | 58 ++++++++++++++++++++++++++++++++++++++++++ test/TestBuiltins.fs | 55 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 113 insertions(+) diff --git a/src/stdlib/Builtins.fs b/src/stdlib/Builtins.fs index f7ee0eb..dbe193a 100644 --- a/src/stdlib/Builtins.fs +++ b/src/stdlib/Builtins.fs @@ -200,11 +200,20 @@ type IExports = abstract int: obj -> int /// Object to float abstract float: obj -> float + /// Object to bool + abstract bool: obj -> bool /// Convert to bytes abstract bytes: byte[] -> byte[] /// Convert string to bytes with encoding abstract bytes: string * encoding: string -> byte[] + /// Create a new empty dictionary. + abstract dict: unit -> Dictionary + /// Create a dictionary from an iterable of key/value pairs. + abstract dict: IEnumerable -> Dictionary + /// Create a list from an iterable. + abstract list: IEnumerable<'T> -> ResizeArray<'T> + /// Return the largest item in an iterable or the largest of two or more arguments. abstract max: 'T * 'T -> 'T /// Return the largest item in an iterable or the largest of two or more arguments. @@ -349,6 +358,55 @@ type SystemError() = [] let __name__: string = nativeOnly +// ============================================================================ +// Python type references +// +// These expose Python's built-in types as values, primarily for use with +// `Fable.Core.PyInterop.pyInstanceof`. F# names are prefixed with `py` to +// avoid colliding with F#/.NET built-in types. +// ============================================================================ + +/// Reference to Python's `int` type. +[] +let pyInt: obj = nativeOnly + +/// Reference to Python's `float` type. +[] +let pyFloat: obj = nativeOnly + +/// Reference to Python's `bool` type. +[] +let pyBool: obj = nativeOnly + +/// Reference to Python's `str` type. +[] +let pyStr: obj = nativeOnly + +/// Reference to Python's `bytes` type. +[] +let pyBytes: obj = nativeOnly + +/// Reference to Python's `list` type. +[] +let pyList: obj = nativeOnly + +/// Reference to Python's `dict` type. +[] +let pyDict: obj = nativeOnly + +/// Reference to Python's `tuple` type. +[] +let pyTuple: obj = nativeOnly + +/// Reference to Python's `set` type. +[] +let pySet: obj = nativeOnly + +/// Python's `None` singleton, typed as `obj` for use as a sentinel +/// (e.g. JSON null) and in interop sites that need an explicit None value. +[] +let pyNone: obj = nativeOnly + /// Python print function. Takes a single argument, so can be used with e.g string interpolation. let print obj = builtins.print obj diff --git a/test/TestBuiltins.fs b/test/TestBuiltins.fs index 5aee957..5af0fc8 100644 --- a/test/TestBuiltins.fs +++ b/test/TestBuiltins.fs @@ -1,5 +1,6 @@ module Fable.Python.Tests.Builtins +open Fable.Core.PyInterop open Fable.Python.Testing open Fable.Python.Builtins open Fable.Python.Os @@ -65,3 +66,57 @@ let ``test any works`` () = builtins.any [ false; false; true ] |> equal true builtins.any [ false; false; false ] |> equal false builtins.any [] |> equal false + +[] +let ``test bool works`` () = + builtins.bool 1 |> equal true + builtins.bool 0 |> equal false + builtins.bool "" |> equal false + builtins.bool "x" |> equal true + +[] +let ``test dict empty works`` () = + let d = builtins.dict () + builtins.len d |> equal 0 + +[] +let ``test dict from pairs works`` () = + let d = builtins.dict [ "a", 1; "b", 2; "c", 3 ] + builtins.len d |> equal 3 + d.["a"] |> equal 1 + d.["b"] |> equal 2 + d.["c"] |> equal 3 + +[] +let ``test list works`` () = + let xs = builtins.list (seq { 1..3 }) + builtins.len xs |> equal 3 + xs.[0] |> equal 1 + xs.[2] |> equal 3 + +[] +let ``test pyInstanceof with type references`` () = + // Use emitPyExpr to construct genuinely Python-native values, since F#'s + // `int`/`float`/`bool` compile to Fable wrapper classes, not Python primitives. + let pyIntVal: obj = emitPyExpr () "42" + let pyFloatVal: obj = emitPyExpr () "3.14" + let pyBoolVal: obj = emitPyExpr () "True" + let pyStrVal: obj = emitPyExpr () "'hello'" + + pyInstanceof pyIntVal pyInt |> equal true + pyInstanceof pyFloatVal pyFloat |> equal true + pyInstanceof pyBoolVal pyBool |> equal true + pyInstanceof pyStrVal pyStr |> equal true + pyInstanceof (builtins.dict ()) pyDict |> equal true + pyInstanceof (builtins.list (seq { 1..3 })) pyList |> equal true + + // Cross-checks + pyInstanceof pyStrVal pyInt |> equal false + pyInstanceof (builtins.dict ()) pyList |> equal false + +[] +let ``test pyNone is None`` () = + // bool(None) is False + builtins.bool pyNone |> equal false + // None has type NoneType, so isinstance(None, type(None)) holds + builtins.isinstance (pyNone, builtins.``type`` pyNone) |> equal true