Fix #668 #646 #640: optional/default parameter handling (type-checker + compiled)#707
Merged
Conversation
… + 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).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Completes #668, #646, and #640 — all optional/default-parameter handling, fixed in dependency order.
#668 — type checker rejected explicit
undefinedfor optional/default paramsAn optional (
x?: T) or default-valued (x: T = ...) parameter now accepts an explicitundefinedargument at the call site (its call-site type isT | undefined); passingundefinedto a default-valued parameter triggers the default.tscallows this; we emittedTS2345.IsArgumentCompatible(paramType, argType, optional)helper inTypeChecker.Compatibility.cs— fast-pathIsCompatible, else (when optional) retry againstT | undefined.TypeChecker.Calls.cs), private-method (TypeChecker.Properties.cs), and constructor (TypeChecker.Properties.New.cs) argument-check sites.optional = position >= MinArity(both?and= defaultreduceMinArity).undefined; rest-parameter elements are not widened (matchingtsc).#646 — compiled function expressions / arrow functions ignored defaults for omitted args
Two root causes:
y: number = 3) emitted invalidldarg; brfalseIL (StackUnexpected) and the default never fired. Arrows/function-expressions get noOverloadGeneratorlower-arity forwarding, so the runtime entry prologue is their only default mechanism — and it needs anobjectslot to detect a missing/undefined argument.ParameterTypeResolver.WidenDefaultedParamsToObjectnow widens every defaulted arrow/function-expression parameter toobject(a typed reference slot likestringcoerces the sentinel to the literal"undefined"before the prologue, so widening value-types alone is insufficient).AsyncArrowMoveNextEmitternever emitted the default prologue at all — it now does (placed after state dispatch so it runs only on initial entry), witharrow.Parametersthreaded through itsEmitMoveNextcall sites.#640 — compiled value-calls padded omitted optional args with CLR
nullCalling a user function as a value (cross-module imports, callbacks,
$TSFunction.Invoke) now pads omitted trailing optional arguments with theundefinedsentinel instead of CLRnull, sotypeof/=== undefined/=== nullanswer correctly and object-slotted defaults fire.$PadUndefinedmarker attribute (mirroring the existing$CapturesArguments) tags user functions.$TSFunctioncaches a_padUndefinedMaskat construction: bitiset iff the wrapped method is marked and parameterihas anobjectslot.AdjustArgspads the sentinel only at masked positions.nullpadding — 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.Tests
New regression tests, both modes where applicable:
TypeCheckerTests/OptionalParamUndefinedArgTests.csCompilerTests/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
0x80131506host crash (a documented .NET host/JIT flake, unrelated to these changes).Follow-ups filed (out-of-scope gaps found along the way)
x?: T) in a private method (#name) fails to parse #696 — parser can't parse an optional param in a private method (#m(b?: T))(a, b = a * 2)) — "Undefined variable" #698 — a default value can't reference an earlier parameter (both modes)undefinedsentinel) #703 — class methods invoked as values still null-pad (scattered method-define sites)undefined(yields NaN) #705 — value-type default + explicit/paddedundefined→ NaN in func-decl/method/ctor and the--ref-asmvalue-call (needs a broad value-type→object widening; deferred for perf/blast-radius)Reviewer notes
--ref-asm(reference-assembly emit) path makes_types.Object != typeof(object); the widen helper takes the compilation's object type rather than hardcodingtypeof(object).$Undefinedfor all slots and regressed 24 tests (typed string-defaults coerced the sentinel) — the object-slot mask is the fix.