ffi: drop legacy name: ..T variadic syntax

Parser hard-rejects the legacy `name: ..T` form with a one-line
migration message pointing at the new `..name: []T` shape. The
leading-`..` form is the one the lowering paths
(`resolveParamType` / `packVariadicCallArgs`) treat as canonical
post-issue-0049; leaving both forms accepted invited the same
class of cross-module emit crashes any time a `..T`-form decl in
stdlib crossed an import boundary.

`specs.md` updated alongside: the Variadic Functions section now
documents `..name: []T` as the surface form, with notes on
homogeneous vs `[]Any` boxing and the `..` spread at call sites.
Inline references to `args: ..Any` in §7 and §8 refreshed.
This commit is contained in:
agra
2026-05-27 21:32:45 +03:00
parent 5b3d86440b
commit 952dc0e161
2 changed files with 24 additions and 13 deletions

View File

@@ -951,14 +951,22 @@ sum :: (a: $T, b: T) -> T {
- Multiple type parameters are supported: `(a: $T, b: $U) -> T`
### Variadic Functions
Functions can accept a variable number of arguments using `..Type` syntax:
Functions can accept a variable number of arguments using `..name: []Type` syntax:
```sx
print :: (fmt: string, args: ..Any) { ... }
print :: (fmt: string, ..args: []Any) { ... }
path_join :: (..parts: []string) -> string { ... }
```
- `..Any` means zero or more arguments, each boxed into `Any` (type tag + payload)
- The variadic parameter must be the last parameter
- At call sites, variadic arguments are automatically boxed: `print("x={}, y={}\n", x, y)`
- Inside the function body, `args` is accessed as a slice-like sequence
- The leading `..` marks the parameter as variadic; the declared type is the
slice the body sees (so `..parts: []string` makes `parts` a `[]string` inside).
- The variadic parameter must be the last positional parameter.
- For homogeneous element types (`[]s32`, `[]string`, ...), the call site packs the
trailing args into a stack-allocated `[N x T]` and passes a slice over it.
- For `[]Any`, each trailing arg is boxed into `Any` (type tag + payload) before
packing; `args[i]` reads back the boxed value.
- A `..` spread at the call site unpacks an existing slice/array into the variadic
tail: `sum(..arr)`.
- The heterogeneous comptime-pack form `..$args: []Type` binds per-position
comptime types — see "Variadic heterogeneous type packs" below.
### Type Inference
- `::` bindings infer type from the right-hand side
@@ -1663,7 +1671,7 @@ Built-in functions are declared in `std.sx` with the `#builtin` suffix, which te
### I/O
- `out(str: string) -> void` — write a string to standard output
- `print(fmt: string, args: ..Any)` — formatted print. Parses `{}` placeholders in the format string and substitutes arguments. When all argument types are statically known, the compiler specializes the call at compile time (no `Any` boxing).
- `print(fmt: string, ..args: []Any)` — formatted print. Parses `{}` placeholders in the format string and substitutes arguments. When all argument types are statically known, the compiler specializes the call at compile time (no `Any` boxing).
### Math
- `sqrt(x: $T) -> T` — square root (maps to LLVM intrinsic)
@@ -1741,7 +1749,7 @@ response :: format("HTTP/1.1 200 OK\r\nContent-Length: {}\r\n\r\n{}", body.len,
// response is a static string constant — no runtime allocation
```
This works for any function, not just `format`. The mechanism is general: the VM compiles the function body (including `#insert` directives, variadic `..Any` args, and calls to other functions) and executes it entirely at compile time. If the VM encounters something it cannot evaluate (e.g., foreign function calls, unsupported operations), it silently falls through to runtime codegen.
This works for any function, not just `format`. The mechanism is general: the VM compiles the function body (including `#insert` directives, variadic `..args: []Any` args, and calls to other functions) and executes it entirely at compile time. If the VM encounters something it cannot evaluate (e.g., foreign function calls, unsupported operations), it silently falls through to runtime codegen.
### Build Configuration

View File

@@ -1631,12 +1631,15 @@ pub const Parser = struct {
continue;
}
self.advance(); // consume ':'
// Old syntax keeps the variadic marker after the colon
// (`args: ..T`). Accept it even if a leading `..` already
// appeared — both forms set the same flag.
// The legacy `name: ..T` form is rejected — write `..name: []T`.
// Both forms used to live; the leading-`..` form is the one the
// language settled on (the AST shape downstream assumes the
// declared type IS the slice). Leaving the post-colon form
// accepted lets users land code that bypasses the new-form
// lowering paths (resolveParamType / packVariadicCallArgs) and
// hit cross-module emit crashes (issue-0049). Hard error here.
if (self.current.tag == .dot_dot) {
is_variadic = true;
self.advance();
return self.fail("legacy variadic syntax `name: ..T` is no longer supported — use `..name: []T` instead");
}
const param_type = try self.parseTypeExpr();
var is_comptime_param = false;