Skip to content

Fix #668 #646 #640: optional/default parameter handling (type-checker + compiled)#707

Merged
nickna merged 1 commit into
mainfrom
wrk/silly-hamilton-08d539
Jun 16, 2026
Merged

Fix #668 #646 #640: optional/default parameter handling (type-checker + compiled)#707
nickna merged 1 commit into
mainfrom
wrk/silly-hamilton-08d539

Conversation

@nickna

@nickna nickna commented Jun 16, 2026

Copy link
Copy Markdown
Owner

Completes #668, #646, and #640 — all optional/default-parameter handling, fixed in dependency order.

#668 — type checker rejected explicit undefined for optional/default params

An optional (x?: T) or default-valued (x: T = ...) parameter now accepts an explicit undefined argument at the call site (its call-site type is T | undefined); passing undefined to a default-valued parameter triggers the default. tsc allows this; we emitted TS2345.

  • New IsArgumentCompatible(paramType, argType, optional) helper in TypeChecker.Compatibility.cs — fast-path IsCompatible, else (when optional) retry against T | undefined.
  • Applied at the regular-call (TypeChecker.Calls.cs), private-method (TypeChecker.Properties.cs), and constructor (TypeChecker.Properties.New.cs) argument-check sites. optional = position >= MinArity (both ? and = default reduce MinArity).
  • Required params still reject undefined; rest-parameter elements are not widened (matching tsc).

#646 — compiled function expressions / arrow functions ignored defaults for omitted args

Two root causes:

  1. A value-type default slot (y: number = 3) emitted invalid ldarg; brfalse IL (StackUnexpected) and the default never fired. Arrows/function-expressions get no OverloadGenerator lower-arity forwarding, so the runtime entry prologue is their only default mechanism — and it needs an object slot to detect a missing/undefined argument. ParameterTypeResolver.WidenDefaultedParamsToObject now widens every defaulted arrow/function-expression parameter to object (a typed reference slot like string coerces the sentinel to the literal "undefined" before the prologue, so widening value-types alone is insufficient).
  2. AsyncArrowMoveNextEmitter never emitted the default prologue at all — it now does (placed after state dispatch so it runs only on initial entry), with arrow.Parameters threaded through its EmitMoveNext call sites.

#640 — compiled value-calls padded omitted optional args with CLR null

Calling a user function as a value (cross-module imports, callbacks, $TSFunction.Invoke) now pads omitted trailing optional arguments with the undefined sentinel instead of CLR null, so typeof / === undefined / === null answer correctly and object-slotted defaults fire.

  • A $PadUndefined marker attribute (mirroring the existing $CapturesArguments) tags user functions.
  • $TSFunction caches a _padUndefinedMask at construction: bit i set iff the wrapped method is marked and parameter i has an object slot. AdjustArgs pads the sentinel only at masked positions.
  • Typed default slots (string/double) and runtime built-ins keep null padding — built-ins null-check optional-arg absence, and a typed slot can't hold the sentinel (it would coerce before the prologue). This object-slot mask is what makes sentinel-vs-null correct and perf-neutral.
  • Marker applied to function declarations, sync arrows (static + display-class), inner functions, and async-arrow stubs.

Tests

New regression tests, both modes where applicable:

  • TypeCheckerTests/OptionalParamUndefinedArgTests.cs
  • CompilerTests/ArrowFunctionDefaultParameterTests.cs (incl. an IL-verification guard)
  • CompilerTests/ValueCallUndefinedPaddingTests.cs (incl. a built-in null-padding guard)

Verification: full suite 12797 passed / 0 failed; TypeScript conformance 31 / 0; Test262 default subset showed no bucket regressions before hitting the pre-existing 0x80131506 host crash (a documented .NET host/JIT flake, unrelated to these changes).

Follow-ups filed (out-of-scope gaps found along the way)

Reviewer notes

  • The --ref-asm (reference-assembly emit) path makes _types.Object != typeof(object); the widen helper takes the compilation's object type rather than hardcoding typeof(object).
  • An earlier cut padded $Undefined for all slots and regressed 24 tests (typed string-defaults coerced the sentinel) — the object-slot mask is the fix.

… + compiled)

#668 (type checker, both modes): an optional (`x?: T`) or default-valued
(`x: T = ...`) parameter now accepts an explicit `undefined` argument at the
call site (call-site type is `T | undefined`); a default-valued parameter fires
its default. Added `IsArgumentCompatible` in TypeChecker.Compatibility.cs and
applied it at the regular-call, private-method, and constructor argument-check
sites. Required parameters still reject `undefined`; rest-parameter elements are
not widened (matching tsc).

#646 (compiled): function expressions and arrow functions now honor parameter
defaults for omitted arguments. They get no OverloadGenerator forwarding, so
defaults are applied by the runtime entry prologue, which needs an `object`
slot to detect a missing/undefined argument -- ParameterTypeResolver now widens
every defaulted arrow/function-expression parameter to `object` (a value-type
slot emitted invalid `ldarg; brfalse` IL; a typed reference slot coerces the
sentinel before the prologue). Async arrows never emitted the default prologue
at all; AsyncArrowMoveNextEmitter now does, with `arrow.Parameters` threaded
through its EmitMoveNext call sites.

#640 (compiled): invoking a user function as a value (cross-module imports,
callbacks, $TSFunction.Invoke) now pads omitted trailing arguments with the
`undefined` sentinel instead of CLR null, so `typeof`/`=== undefined`/`=== null`
answer correctly. A `$PadUndefined` marker attribute (mirroring $CapturesArguments)
tags user functions; $TSFunction caches a `_padUndefinedMask` (object-typed
parameter positions of marked methods) and AdjustArgs pads the sentinel only at
those positions -- typed default slots and runtime built-ins keep null padding.
Marker applied to function declarations, sync arrows, inner functions, and
async-arrow stubs.

Regression tests in OptionalParamUndefinedArgTests, ArrowFunctionDefaultParameterTests,
and ValueCallUndefinedPaddingTests (both modes). Full suite green (12797);
TypeScript conformance green.

Follow-ups filed for related gaps found along the way: #696 (parser: optional
param in a private method), #698 (default value referencing an earlier param),
#703 (class methods as values still null-pad), #705 (value-type default cannot
represent an explicit/padded undefined -> NaN).
@nickna nickna merged commit b5bb563 into main Jun 16, 2026
3 checks passed
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