Skip to content

Call arity checks; C variadic (@varargs) functions callable through values and across modules - #604

Merged
ASDAlexander77 merged 3 commits into
mainfrom
cvariadic-func-type
Oct 18, 2026
Merged

ASDAlexander77 merged 3 commits into
mainfrom
cvariadic-func-type

Conversation

@ASDAlexander77

Copy link
Copy Markdown
Owner

Summary

Three commits:

  1. A call with more arguments than the callee has parameters is an error (TS2554, "Expected 2 arguments, but got 3."). It covers functions, methods, constructors, closures, ?. calls and @cpp vtable methods. A rest parameter and a C variadic (@varargs) callee take any number of arguments.
  2. A C variadic function called through a value of it is a variadic call. !ts.func gets an isCVarArg flag, printed only when set. It is set on the type of a @varargs function and kept by typeof and assignment. let p = printf; p("%f\n", 1.5) and a typeof printf parameter used to lower as fixed-arity calls, which printed 0.000000 on Win64; since the arity check they were rejected. Indirect calls and invokes of a flagged type now set var_callee_type. A C variadic function type and a plain one no longer cast to each other. Extra arguments get C's default promotions on every call, including by name: an f32 printed 0.000000 even through printf(...).
  3. A C variadic function works stored anywhere and across modules. New built-in type CVarArgs<(format: string) => s32> is the source spelling of a C variadic type. The printer writes it, so __decls round-trips an exported let cprintf = printf, parameter or field. Such a value is always one pointer, never widened to a hybrid one. !ts.hybrid_func carries the flag too, and mergeFuncTypes finds no common type for mixed flags.

CVarArgs is now a reserved built-in type name, like Shared.

Tests

  • call-arity/: too-many-arguments cases for each call kind, including a non-variadic function value and parameter.
  • c-varargs/value_call.ts (JIT): calls by name, through let and through a parameter, mixed int/double arguments, and promotions; expects 1.500000. It also prints correctly as an AOT exe.
  • c-varargs/to_plain.ts, from_plain.ts: the cast is rejected both ways.
  • 00funcs_c_varargs_storage.ts: class field, constructor-parameter field, object member, optional value and CVarArgs annotation; runs under the default, rc and none models.
  • import_c_varargs.ts / export_c_varargs.ts: static, -shared, and JIT -shared.

Windows Release ctest: 0 failures out of 4188.

🤖 Generated with Claude Code

ASDAlexander77 and others added 3 commits October 18, 2026 00:25
`new C().m(1, 2, 3)` for a two-parameter method compiled silently: the
third operand went into a slot the callee does not have. It is now
TS2554 ("Expected 2 arguments, but got 3."), checked in checkCallArity
for every call mlirGenCallFunction builds (functions, methods,
constructors, closures, `?.` calls, @cpp vtable methods).

A rest parameter takes any number of arguments, and so does a C
variadic function declared `@varargs declare function f(n: s32)`
(tsbindgen writes them so), whose extra arguments go to C's `...`. Its
function type does not say so, only the decorator, so isCVarArgCall
reads that. A @cpp method's hidden result slot is not counted in the
message.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`@varargs declare function printf(format: string)` put `func.varargs` on
the function only, so its type did not say it was variadic. A call by name
worked; `let p = printf; p("%f\n", 1.5)` or a `typeof printf` parameter
lowered as a fixed-arity indirect call (a double printed 0.000000 on
Win64), and since the arity check it was "Expected 1 arguments, but got 2."

!ts.func gets an isCVarArg parameter (printed only when set), set on the
type of a @VarArgs function in mlirGenFunctionPrototype. typeof and
assignment keep it; a `let` holding one stays a plain function, not a
hybrid one (it captures nothing). checkCallArity reads it from the callee
type, replacing isCVarArgCall's symbol lookup. An indirect call or invoke
of such a type gets var_callee_type, so LLVM passes the extra arguments the
C variadic way. A C variadic function type and a plain one do not cast to
each other.

The arguments to `...` get C's default promotions, on every call: an f32
went as a float and printed 0.000000 even when printf was called by name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The previous commit made `let p = printf` and a `typeof printf` parameter
variadic calls; a @VarArgs function held elsewhere or exported still was
not.

A C variadic type now has a spelling, `CVarArgs<(format: string) => s32>`,
which the printer writes for it, so __decls declares an exported
`let cprintf = printf` (and a parameter or field of that type) as one and
importers call it the C variadic way. Such a value is always one pointer,
as in C: CVarArgs<arrow> is a plain function, not a hybrid one, and an
exported global holding one is not widened, so library and importer store
it alike. An object-type field of that type prints as a value, not as a
method signature.

!ts.hybrid_func carries the flag as well (from the function it is made
from), and a call of a C variadic hybrid value is a variadic call.
mergeFuncTypes keeps it and finds no common type for a C variadic and a
TypeScript function.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ASDAlexander77
ASDAlexander77 merged commit b68979c into main Oct 18, 2026
2 checks passed
@ASDAlexander77
ASDAlexander77 deleted the cvariadic-func-type branch October 18, 2026 00:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant