From 18f47ef74c6f7d99f40d86ffa612666441bdef25 Mon Sep 17 00:00:00 2001 From: simontreanor Date: Sat, 17 Oct 2026 23:42:44 +0100 Subject: [PATCH 1/2] ci: run the tests on Python 3.14 as well as 3.12 The test job ran only on 3.12, the oldest supported Python, while local runs use 3.14, so a behaviour that differs between the two ends of the range was caught by neither alone: 3.12's asyncio.run takes only a coroutine, where 3.14's takes any awaitable (#177). The matrix keeps 3.12 on all three systems and adds 3.14 on Linux. CONTRIBUTING says so. --- .github/workflows/ci.yml | 9 ++++++++- CONTRIBUTING.md | 4 +++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ab7bdc8..b2e4bd3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,6 +26,13 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] + python: ['3.12'] + # The newest Python too, so a behaviour that differs between the ends of + # the supported range fails here (3.12's `asyncio.run` takes only a + # coroutine, where 3.14's takes any awaitable). + include: + - os: ubuntu-latest + python: '3.14' runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v6 @@ -34,7 +41,7 @@ jobs: # which would quietly hollow out this job. - uses: actions/setup-python@v6 with: - python-version: '3.12' + python-version: ${{ matrix.python }} - uses: dtolnay/rust-toolchain@stable with: toolchain: '1.97.0' diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5e44727..3cce882 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,7 +11,9 @@ You need two things. - **Rust.** The repository pins its toolchain in `rust-toolchain.toml`, and `rustup` selects it automatically inside the repo. The crate has no dependencies, so the first build is quick. - **Python 3.12 or newer** on your `PATH` as `python` or `python3`. The end-to-end tests run the - emitted Python, and emitted code uses `match` statements and PEP 701 f-strings. + emitted Python, and emitted code uses `match` statements and PEP 701 f-strings. CI runs them on + 3.12 on all three systems and on 3.14 on Linux, so a difference between the two ends of the + supported range fails there even when your local run passes. Then: From d97d003ef7765d8d5d47c0a908e59d3ca2ac2ddd Mon Sep 17 00:00:00 2001 From: simontreanor Date: Sat, 17 Oct 2026 23:50:15 +0100 Subject: [PATCH 2/2] examples: hello takes the cube root of 64, which is exact on every platform hello.pyfun printed cbrt 27.0 and its comments promised 3.0, but glibc's cbrt is not correctly rounded, so Linux and most cloud notebooks print 3.0000000000000004. 64.0 gives 4.0 on Linux and Windows alike, and the example now prints the same on both. DESIGN notes that, as in Python, a float maths function's last digit follows the platform's C library. --- DESIGN.md | 4 +++- examples/hello.pyfun | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/DESIGN.md b/DESIGN.md index f047c48..15381bd 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -504,7 +504,9 @@ something to call. The MVP prelude is `print : 'a -> unit` and the unit-polymorp `truncate` lower to `math.floor`/`ceil`/`trunc` with `import math` — the extern dotted-target path — while staying *unqualified* Pyfun names), plus the **unit-aware roots `sqrt : float<'u^2> -> float<'u>`** (√area = length) **and `cbrt : float<'u^3> -> float<'u>`** (∛volume = length; see §8.2; lower to -`math.sqrt`/`math.cbrt` like the conversions), plus a `unit` type whose one value is written `()` (both lower to +`math.sqrt`/`math.cbrt` like the conversions; as in Python, the result is the platform C library's, and glibc's +`cbrt` is not correctly rounded, so `cbrt 27.0` prints `3.0000000000000004` on Linux and `3.0` on Windows: the +last digit of a float maths function can differ between systems), plus a `unit` type whose one value is written `()` (both lower to Python `None` — the natural result of an effectful call). It also seeds the **standard combinators** `id : 'a -> 'a`, `const : 'a -> 'b -> 'a`, `ignore : 'a -> unit`, and `flip : (a -> b -> c) -> b -> a -> c` (fully type-polymorphic; `id`/`const`/`ignore` are pure, while diff --git a/examples/hello.pyfun b/examples/hello.pyfun index 0586445..2ef0e14 100644 --- a/examples/hello.pyfun +++ b/examples/hello.pyfun @@ -269,7 +269,7 @@ let floor = area 4 5 # int # (`sqrt 2.0`, `cbrt 2.0` are rejected dimensionally). These two are the # only tractable unit-carrying power ops — `**` stays dimensionless. let side = sqrt 16.0 # float — 4.0, unit halved (m^2 -> m) -let edge = cbrt 27.0 # float — 3.0, unit thirded (m^3 -> m) +let edge = cbrt 64.0 # float — 4.0, unit thirded (m^3 -> m) # Float literals support scientific notation (`1e6`, `2.5e-3`) — the exponent sign # is part of the literal. Physics constants read naturally, units and all: @@ -457,7 +457,7 @@ print planck # 6.626e-34 (scientific notation) print rounded # 4 (round, float -> int) print truncated # 10 (truncate, unit-preserving) print side # 4.0 (unit-aware sqrt: float -> float) -print edge # 3.0 (unit-aware cbrt: float -> float) +print edge # 4.0 (unit-aware cbrt: float -> float) print parsedF # 2.71828 (String.toFloat) print cube # 27.0 (exponentiation) print tower # 512.0 (right-associative `**`)