In type position, parentheses now mirror value position: (T) (a single unnamed element, no trailing comma) is a GROUPING that resolves to the inner type; (T,) is a 1-tuple; (A, B) a 2-tuple; named (x: T) and spread (..Ts) stay tuples; (...) -> R stays a function type. This lets a closure/optional/function type be parenthesized for readability without silently becoming a 1-tuple: [1](Closure(i64,i64) -> i64) // array of closures (issue 0177) -> 7 ?(?i64) // genuine nested optional (issue 0165 intent) Parser: src/parser.zig returns the inner node for a single unnamed non-spread no-trailing-comma parenthesized type. formatTypeName (both generic.zig diagnostics + types.zig reflection) now render a 1-tuple as (T,) so the spelling is unambiguous and diagnostics are self-consistent. The 0165 coerce/stmt note reworded accordingly. specs.md §Type Syntax updated; basic/0036 wrap return -> (i64,); obsolete diagnostic 1195 removed (?(?i64) now compiles); regression examples/types/0201-types-parenthesized-type-grouping.sx added; 0414 .ir golden regenerated for the (T,) rendering. Resolves 0177; updates 0165/0170. Verified by 3 adversarial reviews; suite 792/0.
56 lines
2.9 KiB
Markdown
56 lines
2.9 KiB
Markdown
# 0177 — calling a closure stored in an array element directly (`fns[i](args)`) crashes / miscompiles
|
|
|
|
> **RESOLVED via parenthesized-type grouping.** The repro
|
|
> `[1](Closure(i64,i64) -> i64) = .[ add ]` was not an array of closures — under
|
|
> the old rule `(Closure(...) -> R)` was a 1-tuple, so it was an array of
|
|
> 1-tuples and `fns[0](...)` tried to call a tuple → LLVM "Called function must
|
|
> be a pointer!". Per the user's direction, parentheses in TYPE position are now
|
|
> a GROUPING (mirroring value position): `(T)` (no trailing comma) resolves to
|
|
> the inner type, `(T,)` is the 1-tuple. So `[1](Closure(...) -> R)` is now an
|
|
> array of closures and `fns[0](3,4)` returns `7`. (The canonical non-paren
|
|
> `[1]Closure(...) -> R = .[ add ]` already worked.) Implemented in
|
|
> `src/parser.zig` (single unnamed non-spread element, no trailing comma →
|
|
> return the inner type node). Regression:
|
|
> `examples/types/0201-types-parenthesized-type-grouping.sx`. specs.md §Type
|
|
> Syntax updated. Verified by 3 adversarial reviews; suite 792/0.
|
|
|
|
## Symptom
|
|
|
|
A closure (or `Closure(...)`-typed value) stored in an array, called DIRECTLY via
|
|
index (`fns[i](args)`), does not dispatch through the closure ABI: it emits a bare
|
|
`call_indirect` on the whole `{fn,env}` struct → LLVM "Called function must be a
|
|
pointer!" (verify fail) for some return/arg shapes, or returns garbage for others.
|
|
Pre-existing (reproduces on master); distinct from issue 0170 (which fixed the
|
|
unwrap-through-optional call `g!()`). Here the callee is a non-optional closure
|
|
reached via array index, called directly without unwrap.
|
|
|
|
## Reproduction
|
|
|
|
```sx
|
|
#import "modules/std.sx";
|
|
add :: (a: i64, b: i64) -> i64 { return a + b; }
|
|
main :: () {
|
|
fns : [1](Closure(i64, i64) -> i64) = .{ add };
|
|
print("{}\n", fns[0](3, 4)); // LLVM "Called function must be a pointer!" — expected 7
|
|
}
|
|
```
|
|
|
|
Expected: `7`. Observed: LLVM verification failure (or, for other shapes, garbage
|
|
return / f64-arg verify failure).
|
|
|
|
## Investigation prompt
|
|
|
|
`src/ir/lower/call.zig`: issue 0170 added closure-vs-fn-pointer dispatch to the
|
|
indirect-call catch-all `else` arm via `inferExprType(callee)` → `.closure` →
|
|
`call_closure`. A direct call whose callee is an ARRAY-INDEX expression
|
|
(`fns[0]`) of closure type apparently does not reach that dispatch — either it
|
|
takes an earlier call arm that still emits `call_indirect`, or
|
|
`inferExprType(index_expr)` does not return `.closure` so the `else` arm falls to
|
|
the fn-pointer path. Trace which arm `fns[0](args)` lowers through and ensure a
|
|
closure-typed callee — regardless of whether it's a bare ident, field access,
|
|
index, or call result — dispatches through `call_closure` (threading env + ctx
|
|
via the `[ctx, env, user_args]` ABI). Compare with the working `arr[i]!()`
|
|
(unwrap) path. Follow the no-silent-fallback rule. Verify: `fns[0](3,4)` → 7;
|
|
array-of-closure with captures; non-i64 returns (void/f64/struct); f64 args.
|
|
Add an `examples/closures/03xx-array-of-closures-call.sx` regression.
|