Skip to content

Commit a125b78

Browse files
committed
Rotate release notes as part of the 0.63.0 release
1 parent d7d800c commit a125b78

File tree

2 files changed

+147
-147
lines changed

2 files changed

+147
-147
lines changed

.release-notes/0.63.0.md

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
## Fix use-after-free in IOCP ASIO system
2+
3+
We fixed a pair of use-after-free races in the Windows IOCP event system. A previous fix introduced a token mechanism to prevent IOCP callbacks from accessing freed events, but missed two windows where raw pointers could outlive the event they pointed to. One was between the callback and event destruction, the other between a queued message and event destruction.
4+
5+
This is the hard part that Pony protects you from. Concurrent access to mutable data across threads is genuinely difficult to get right, even when you have a mechanism designed specifically to handle it.
6+
7+
## Remove support for Alpine 3.20
8+
9+
Alpine 3.20 has reached end-of-life. We no longer test against it or build ponyc releases for it.
10+
11+
## Fix with tuple only processing first binding in build_with_dispose
12+
13+
When using a `with` block with a tuple pattern, only the first binding was processed for dispose-call generation and `_` validation. Later bindings were silently skipped, which meant dispose was never called on them and `_` in a later position was not rejected.
14+
15+
For example, the following code compiled without error even though `_` is not allowed in a `with` block:
16+
17+
```pony
18+
class D
19+
new create() => None
20+
fun dispose() => None
21+
22+
actor Main
23+
new create(env: Env) =>
24+
with (a, _) = (D.create(), D.create()) do
25+
None
26+
end
27+
```
28+
29+
This now correctly produces an error: `_ isn't allowed for a variable in a with block`.
30+
31+
Additionally, valid tuple patterns like `with (a, b) = (D.create(), D.create()) do ... end` now correctly generate dispose calls for all bindings, not just the first.
32+
33+
## Fix memory leak in Windows networking subsystem
34+
35+
Fixed a memory leak on Windows where an IOCP token's reference count was not decremented when a network send operation encountered backpressure. Over time, this could cause memory to grow unboundedly in programs with sustained network traffic.
36+
37+
## Remove docgen pass
38+
39+
We've removed ponyc's built-in documentation generation pass. The `--docs`, `-g`, and `--docs-public` command-line flags no longer exist, and `--pass docs` is no longer a valid compilation limit.
40+
41+
Use `pony-doc` instead. It shipped in 0.61.0 as the replacement and has been the recommended tool since then. If you were using `--docs-public`, `pony-doc` generates public-only documentation by default. If you were using `--docs` to include private types, use `pony-doc --include-private`.
42+
43+
## Fix spurious error when assigning to a field on an `as` cast in a try block
44+
45+
Assigning to a field on the result of an `as` expression inside a `try` block incorrectly produced an error about consumed identifiers:
46+
47+
```pony
48+
class Wumpus
49+
var hunger: USize = 0
50+
51+
actor Main
52+
new create(env: Env) =>
53+
let a: (Wumpus | None) = Wumpus
54+
try
55+
(a as Wumpus).hunger = 1
56+
end
57+
```
58+
59+
```
60+
can't reassign to a consumed identifier in a try expression if there is a
61+
partial call involved
62+
```
63+
64+
The workaround was to use a `match` expression instead. This has been fixed. The `as` form now compiles correctly, including when chaining method calls before the field assignment (e.g., `(a as Wumpus).some_method().hunger = 1`).
65+
66+
## Fix segfault when using Generator.map with PonyCheck shrinking
67+
68+
Using `Generator.map` to transform values from one type to another would segfault during shrinking when a property test failed. For example, this program would crash:
69+
70+
```pony
71+
let gen = recover val
72+
Generators.u32().map[String]({(n: U32): String^ => n.string()})
73+
end
74+
PonyCheck.for_all[String](gen, h)(
75+
{(sample: String, ph: PropertyHelper) =>
76+
ph.assert_true(sample.size() > 0)
77+
})?
78+
```
79+
80+
The underlying compiler bug affected any code where a lambda appeared inside an object literal inside a generic method and was then passed to another generic method. The lambda's `apply` method was silently omitted from the vtable, causing a segfault when called at runtime.
81+
82+
## Add --shuffle option to PonyTest
83+
84+
PonyTest now has a `--shuffle` option that randomizes the order tests are dispatched. This catches a class of bug that's invisible under fixed ordering: test B passes, but only because test A ran first and left behind some state. You won't find out until someone removes test A and something breaks in a way that's hard to trace.
85+
86+
Use `--shuffle` for a random seed or `--shuffle=SEED` with a specific U64 seed for reproducibility. When shuffle is active, the seed is printed before any test output:
87+
88+
```
89+
Test seed: 8675309
90+
```
91+
92+
Grab that seed from your CI log and pass it back to reproduce the exact ordering:
93+
94+
```
95+
./my-tests --shuffle=8675309
96+
```
97+
98+
Shuffle applies to all scheduling modes. For CI environments that run tests sequentially to avoid resource contention, `--sequential --shuffle` is the recommended combination: stable runs without flakiness, and each run uses a different seed so test coupling surfaces over time instead of hiding forever.
99+
100+
`--list --shuffle=SEED` shows the test names in the order that seed would produce, so you can preview orderings without running anything.
101+
102+
## Fix pony-lint blank-lines rule false positives on multi-line docstrings
103+
104+
The `style/blank-lines` rule incorrectly counted blank lines inside multi-line docstrings as blank lines between members. A method or field whose docstring contained blank lines (e.g., between paragraphs) would be flagged for having too many blank lines before the next member. The rule now correctly identifies where a docstring ends rather than using only its start line.
105+
106+
## Fix `FloatingPoint.frexp` returning unsigned exponent
107+
108+
`FloatingPoint.frexp` (and its implementations on `F32` and `F64`) returned the exponent as `U32` when C's `frexp` writes a signed `int`. Negative exponents were silently reinterpreted as large positive values.
109+
110+
The return type is now `(A, I32)` instead of `(A, U32)`. If you destructure the result and type the exponent, update it:
111+
112+
```pony
113+
// Before
114+
(let mantissa, let exp: U32) = my_float.frexp()
115+
116+
// After
117+
(let mantissa, let exp: I32) = my_float.frexp()
118+
```
119+
120+
## Fix asymmetric NaN handling in F32/F64 min and max
121+
122+
`F32.min` and `F64.min` (and `max`) gave different results depending on which argument was NaN. `F32.nan().min(5.0)` returned `5.0`, but `F32(5.0).min(F32.nan())` returned `NaN`. The result of a min/max operation shouldn't depend on argument order.
123+
124+
The root cause was the conditional implementation `if this < y then this else y end`. IEEE 754 comparisons involving NaN always return `false`, so the `else` branch fires whenever `this` is NaN but not when only `y` is NaN.
125+
126+
## Use LLVM intrinsics for NaN-propagating float min and max
127+
128+
Float `min` and `max` now use LLVM's `llvm.minimum` and `llvm.maximum` intrinsics instead of conditional comparisons. These implement IEEE 754-2019 semantics: if either operand is NaN, the result is NaN.
129+
130+
This is a breaking change. Code that relied on `min`/`max` to silently discard a NaN operand will now get NaN back. That said, the old behavior was order-dependent and unreliable, so anyone depending on it was already getting inconsistent results.
131+
132+
Before:
133+
134+
```pony
135+
// Old behavior: result depended on argument order
136+
F32.nan().min(F32(5.0)) // => 5.0
137+
F32(5.0).min(F32.nan()) // => NaN
138+
```
139+
140+
After:
141+
142+
```pony
143+
// New behavior: NaN propagates regardless of position
144+
F32.nan().min(F32(5.0)) // => NaN
145+
F32(5.0).min(F32.nan()) // => NaN
146+
```
147+

.release-notes/next-release.md

Lines changed: 0 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -1,147 +0,0 @@
1-
## Fix use-after-free in IOCP ASIO system
2-
3-
We fixed a pair of use-after-free races in the Windows IOCP event system. A previous fix introduced a token mechanism to prevent IOCP callbacks from accessing freed events, but missed two windows where raw pointers could outlive the event they pointed to. One was between the callback and event destruction, the other between a queued message and event destruction.
4-
5-
This is the hard part that Pony protects you from. Concurrent access to mutable data across threads is genuinely difficult to get right, even when you have a mechanism designed specifically to handle it.
6-
7-
## Remove support for Alpine 3.20
8-
9-
Alpine 3.20 has reached end-of-life. We no longer test against it or build ponyc releases for it.
10-
11-
## Fix with tuple only processing first binding in build_with_dispose
12-
13-
When using a `with` block with a tuple pattern, only the first binding was processed for dispose-call generation and `_` validation. Later bindings were silently skipped, which meant dispose was never called on them and `_` in a later position was not rejected.
14-
15-
For example, the following code compiled without error even though `_` is not allowed in a `with` block:
16-
17-
```pony
18-
class D
19-
new create() => None
20-
fun dispose() => None
21-
22-
actor Main
23-
new create(env: Env) =>
24-
with (a, _) = (D.create(), D.create()) do
25-
None
26-
end
27-
```
28-
29-
This now correctly produces an error: `_ isn't allowed for a variable in a with block`.
30-
31-
Additionally, valid tuple patterns like `with (a, b) = (D.create(), D.create()) do ... end` now correctly generate dispose calls for all bindings, not just the first.
32-
33-
## Fix memory leak in Windows networking subsystem
34-
35-
Fixed a memory leak on Windows where an IOCP token's reference count was not decremented when a network send operation encountered backpressure. Over time, this could cause memory to grow unboundedly in programs with sustained network traffic.
36-
37-
## Remove docgen pass
38-
39-
We've removed ponyc's built-in documentation generation pass. The `--docs`, `-g`, and `--docs-public` command-line flags no longer exist, and `--pass docs` is no longer a valid compilation limit.
40-
41-
Use `pony-doc` instead. It shipped in 0.61.0 as the replacement and has been the recommended tool since then. If you were using `--docs-public`, `pony-doc` generates public-only documentation by default. If you were using `--docs` to include private types, use `pony-doc --include-private`.
42-
43-
## Fix spurious error when assigning to a field on an `as` cast in a try block
44-
45-
Assigning to a field on the result of an `as` expression inside a `try` block incorrectly produced an error about consumed identifiers:
46-
47-
```pony
48-
class Wumpus
49-
var hunger: USize = 0
50-
51-
actor Main
52-
new create(env: Env) =>
53-
let a: (Wumpus | None) = Wumpus
54-
try
55-
(a as Wumpus).hunger = 1
56-
end
57-
```
58-
59-
```
60-
can't reassign to a consumed identifier in a try expression if there is a
61-
partial call involved
62-
```
63-
64-
The workaround was to use a `match` expression instead. This has been fixed. The `as` form now compiles correctly, including when chaining method calls before the field assignment (e.g., `(a as Wumpus).some_method().hunger = 1`).
65-
66-
## Fix segfault when using Generator.map with PonyCheck shrinking
67-
68-
Using `Generator.map` to transform values from one type to another would segfault during shrinking when a property test failed. For example, this program would crash:
69-
70-
```pony
71-
let gen = recover val
72-
Generators.u32().map[String]({(n: U32): String^ => n.string()})
73-
end
74-
PonyCheck.for_all[String](gen, h)(
75-
{(sample: String, ph: PropertyHelper) =>
76-
ph.assert_true(sample.size() > 0)
77-
})?
78-
```
79-
80-
The underlying compiler bug affected any code where a lambda appeared inside an object literal inside a generic method and was then passed to another generic method. The lambda's `apply` method was silently omitted from the vtable, causing a segfault when called at runtime.
81-
82-
## Add --shuffle option to PonyTest
83-
84-
PonyTest now has a `--shuffle` option that randomizes the order tests are dispatched. This catches a class of bug that's invisible under fixed ordering: test B passes, but only because test A ran first and left behind some state. You won't find out until someone removes test A and something breaks in a way that's hard to trace.
85-
86-
Use `--shuffle` for a random seed or `--shuffle=SEED` with a specific U64 seed for reproducibility. When shuffle is active, the seed is printed before any test output:
87-
88-
```
89-
Test seed: 8675309
90-
```
91-
92-
Grab that seed from your CI log and pass it back to reproduce the exact ordering:
93-
94-
```
95-
./my-tests --shuffle=8675309
96-
```
97-
98-
Shuffle applies to all scheduling modes. For CI environments that run tests sequentially to avoid resource contention, `--sequential --shuffle` is the recommended combination: stable runs without flakiness, and each run uses a different seed so test coupling surfaces over time instead of hiding forever.
99-
100-
`--list --shuffle=SEED` shows the test names in the order that seed would produce, so you can preview orderings without running anything.
101-
102-
## Fix pony-lint blank-lines rule false positives on multi-line docstrings
103-
104-
The `style/blank-lines` rule incorrectly counted blank lines inside multi-line docstrings as blank lines between members. A method or field whose docstring contained blank lines (e.g., between paragraphs) would be flagged for having too many blank lines before the next member. The rule now correctly identifies where a docstring ends rather than using only its start line.
105-
106-
## Fix `FloatingPoint.frexp` returning unsigned exponent
107-
108-
`FloatingPoint.frexp` (and its implementations on `F32` and `F64`) returned the exponent as `U32` when C's `frexp` writes a signed `int`. Negative exponents were silently reinterpreted as large positive values.
109-
110-
The return type is now `(A, I32)` instead of `(A, U32)`. If you destructure the result and type the exponent, update it:
111-
112-
```pony
113-
// Before
114-
(let mantissa, let exp: U32) = my_float.frexp()
115-
116-
// After
117-
(let mantissa, let exp: I32) = my_float.frexp()
118-
```
119-
120-
## Fix asymmetric NaN handling in F32/F64 min and max
121-
122-
`F32.min` and `F64.min` (and `max`) gave different results depending on which argument was NaN. `F32.nan().min(5.0)` returned `5.0`, but `F32(5.0).min(F32.nan())` returned `NaN`. The result of a min/max operation shouldn't depend on argument order.
123-
124-
The root cause was the conditional implementation `if this < y then this else y end`. IEEE 754 comparisons involving NaN always return `false`, so the `else` branch fires whenever `this` is NaN but not when only `y` is NaN.
125-
126-
## Use LLVM intrinsics for NaN-propagating float min and max
127-
128-
Float `min` and `max` now use LLVM's `llvm.minimum` and `llvm.maximum` intrinsics instead of conditional comparisons. These implement IEEE 754-2019 semantics: if either operand is NaN, the result is NaN.
129-
130-
This is a breaking change. Code that relied on `min`/`max` to silently discard a NaN operand will now get NaN back. That said, the old behavior was order-dependent and unreliable, so anyone depending on it was already getting inconsistent results.
131-
132-
Before:
133-
134-
```pony
135-
// Old behavior: result depended on argument order
136-
F32.nan().min(F32(5.0)) // => 5.0
137-
F32(5.0).min(F32.nan()) // => NaN
138-
```
139-
140-
After:
141-
142-
```pony
143-
// New behavior: NaN propagates regardless of position
144-
F32.nan().min(F32(5.0)) // => NaN
145-
F32(5.0).min(F32.nan()) // => NaN
146-
```
147-

0 commit comments

Comments
 (0)