Skip to content

Compiled: value-type default parameters on (virtual) instance & static methods are not applied #737

@nickna

Description

@nickna

Summary

Follow-up to #705. In compiled mode, a value-type default (x: number = N, b: boolean = ...) on a sync instance or static method does not fire — an omitted argument leaves the CLR zero value and an explicit/padded undefined throws/NaNs. #705 fixed this for the non-virtual members (free functions, constructors, private methods) by widening the defaulted param to an object slot so the entry prologue can detect undefined. The remaining members were deliberately left out because widening them is not override-safe.

Repro (compiled)

class C { add(a: number, b: number = 10): number { return a + b; } }
console.log(new C().add(5));          // interp: 15   compiled: 5    (default not applied)
console.log(new C().add(5, undefined)); // interp: 15   compiled: throws InvalidCastException

class S { static add(a: number, b: number = 10): number { return a + b; } }
console.log(S.add(5));                // interp: 15   compiled: 5

Reference-type defaults (name: string = "x") do fire — they need no slot change. Argument-present calls are correct. Generator methods (*m(b = 5)) also don't fire value-type or reference-type defaults (their state-machine emitter, GeneratorMoveNextEmitter, never runs the default prologue at all).

Why deferred from #705

Instance methods are virtual. Widening a defaulted param's slot from double/bool to object changes the CLR signature, so a derived override that adds a default to a value-type param the base declares as required silently lands in a new vtable slot — a base-typed call then dispatches to the base method (a real polymorphism regression). See the regression guard Override_DerivedAddsDefault_StillOverrides in SharpTS.Tests/CompilerTests/ClassMethodDefaultParameterTests.cs. ParameterTypeResolver.ResolveMethodParameters therefore does not widen (see its remarks).

Fix sketch

Two override-safe options:

  1. Hierarchy-consistent widening — decide each param's slot from the topmost base declaration of the method (so the whole override chain agrees), not per-method defaults.
  2. Bridge methods — keep the value-type signature for override matching and emit a separate widened impl that the bridge forwards to (boxing).

Separately, the generator/async-generator method emitters need the default-parameter prologue added (the async non-generator emitter already has it).

Discovered

While implementing #705.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions