1- // Reproduces a known defect: default parameter values are not applied when a
2- // method is invoked through dynamic (interface or any-typed) dispatch.
3- //
4- // Mechanism: a statically resolved call site fills omitted arguments with the
5- // declared defaults, because the compiler can see the declaration. A dynamic
6- // call site pushes only the arguments written at the call, and the arity
7- // wrapper on the callee fills the missing slots with `undefined` -- the
8- // parameter initializer (`b = 5` below) never runs. The method then computes
9- // 1 + undefined = NaN.
10- //
11- // The assertions that fail today are commented out (block marked REPRO) so
12- // the suite stays green. To reproduce: uncomment that block and run
13- // `gulp testlang` -- qzdp:iface and qzdp:any fail with the calls yielding
14- // NaN instead of 6. A fix for the defect makes the REPRO block pass; it can
15- // then be uncommented permanently and the qzdp:agree assertion below retired.
16- //
17- // 56ifacedispatch.ts documents the same constraint where it affects the
18- // dispatch corpus; this file is the minimal standalone repro.
1+ // Regression for #11562. Default initializers belong to the runtime callee,
2+ // not the statically known call signature. Both omitted and explicit undefined
3+ // arguments trigger defaults; other falsy values must survive unchanged.
194
205interface DfShape {
216 dfOpt ( a : number , b ?: number ) : number ;
@@ -35,23 +20,118 @@ function testDefaultParamDispatch() {
3520 const viaIface : DfShape = direct ;
3621 const viaAny : any = direct ;
3722
38- // Statically resolved call: the compiler applies the default. Passes .
23+ // Statically resolved calls still apply the default.
3924 assert ( direct . dfOpt ( 1 ) == 6 , "qzdp:direct" ) ;
4025
4126 // Passing the argument explicitly works on every path.
4227 assert ( direct . dfOpt ( 1 , 2 ) == 3 , "qzdp:direct2" ) ;
4328 assert ( viaIface . dfOpt ( 1 , 2 ) == 3 , "qzdp:iface2" ) ;
4429 assert ( viaAny . dfOpt ( 1 , 2 ) == 3 , "qzdp:any2" ) ;
4530
46- // REPRO -- uncomment these two lines to surface the defect:
47- // assert(viaIface.dfOpt(1) == 6, "qzdp:iface");
48- // assert(viaAny.dfOpt(1) == 6, "qzdp:any");
31+ // Regression for #11562: dynamic calls must apply the implementation's default.
32+ assert ( viaIface . dfOpt ( 1 ) == 6 , "qzdp:iface" ) ;
33+ assert ( viaAny . dfOpt ( 1 ) == 6 , "qzdp:any" ) ;
4934
50- // Kept active so any CHANGE in the current behavior is noticed: the two
51- // dynamic paths must at least agree with each other. Compared as strings
52- // so this holds both before a fix (NaN == NaN fails as numbers) and
53- // after one.
54- assert ( "" + viaIface . dfOpt ( 1 ) == "" + viaAny . dfOpt ( 1 ) , "qzdp:agree" ) ;
35+ assert ( direct . dfOpt ( 1 , undefined ) === 6 , "qzdp:direct-undefined" ) ;
36+ assert ( viaIface . dfOpt ( 1 , undefined ) === 6 , "qzdp:iface-undefined" ) ;
37+ assert ( viaAny . dfOpt ( 1 , undefined ) === 6 , "qzdp:any-undefined" ) ;
38+ assert ( viaIface . dfOpt ( 1 , 0 ) === 1 , "qzdp:zero" ) ;
39+ assert ( viaIface . dfOpt ( 1 , null ) === 1 , "qzdp:null" ) ;
40+ }
41+
42+ namespace DefaultDispatch {
43+ class Base implements DfShape {
44+ dfOpt ( a : number , b = 5 ) : number { return a + b ; }
45+ }
46+ class Derived extends Base {
47+ dfOpt ( a : number , b = 9 ) : number { return a + b ; }
48+ fromSuper ( ) : number { return super . dfOpt ( 1 ) ; }
49+ }
50+ class ParameterProperty {
51+ constructor ( public value = 13 ) { }
52+ }
53+ class DerivedConstructor extends ParameterProperty {
54+ constructor ( value = 17 ) { super ( value ) ; }
55+ }
56+ interface OptionalValues {
57+ value ( n ?: any ) : any ;
58+ pair ( a ?: number , b ?: number ) : number ;
59+ capture ( n ?: number ) : ( ) => number ;
60+ }
61+ class Values implements OptionalValues {
62+ value ( n : any = 11 ) : any { return n ; }
63+ pair ( a = 2 , b = 3 ) : number { return a * 10 + b ; }
64+ capture ( n = 7 ) : ( ) => number { return ( ) => ++ n ; }
65+ }
66+ function free ( n = 19 ) : number { return n ; }
67+ function bool ( n = true ) : boolean { return n ; }
68+ function ref ( n : number [ ] = null ) : number [ ] { return n ; }
69+ function missing ( n ?: number ) : number { return n ; }
70+ function immutableCapture ( n = 29 ) : ( ) => number { return ( ) => n ; }
71+
72+ // Native calls use TD_ID; simjs runs the TypeScript dummy body.
73+ //% shim=TD_ID
74+ function shimDefault ( n = 31 ) : number { return n ; }
75+ //% shim=TD_ID
76+ //% n.defl=37 explicitDefaults="n"
77+ function annotatedDefault ( n ?: number ) : number { return n ; }
78+
79+ export function run ( ) {
80+ const derived = new Derived ( ) ;
81+ const base : Base = derived ;
82+ const iface : DfShape = derived ;
83+ const dynamic : any = derived ;
84+ assert ( derived . dfOpt ( 1 ) === 10 , "qzdp:derived" ) ;
85+ assert ( base . dfOpt ( 1 ) === 10 , "qzdp:virtual-default" ) ;
86+ assert ( iface . dfOpt ( 1 ) === 10 , "qzdp:override-iface" ) ;
87+ assert ( dynamic . dfOpt ( 1 ) === 10 , "qzdp:override-any" ) ;
88+ assert ( base . dfOpt ( 1 , undefined ) === 10 , "qzdp:virtual-undefined" ) ;
89+ assert ( base . dfOpt ( 1 , 2 ) === 3 , "qzdp:virtual-explicit" ) ;
90+ assert ( derived . fromSuper ( ) === 6 , "qzdp:super" ) ;
91+
92+ const action : ( n ?: number ) => number = free ;
93+ const dynamicAction : any = free ;
94+ const arrow : ( n ?: number ) => number = ( n = 23 ) => n ;
95+ assert ( free ( ) === 19 && free ( undefined ) === 19 , "qzdp:free" ) ;
96+ assert ( action ( ) === 19 && action ( undefined ) === 19 , "qzdp:action" ) ;
97+ assert ( dynamicAction ( ) === 19 , "qzdp:any-action" ) ;
98+ assert ( arrow ( ) === 23 && arrow ( 0 ) === 0 , "qzdp:arrow" ) ;
99+ const literal : DfShape = { dfOpt : ( a : number , b = 41 ) => a + b } ;
100+ assert ( literal . dfOpt ( 1 ) === 42 , "qzdp:literal-action" ) ;
101+
102+ const values : OptionalValues = new Values ( ) ;
103+ const anyValues : any = values ;
104+ assert ( values . value ( ) === 11 , "qzdp:value-default" ) ;
105+ assert ( values . value ( undefined ) === 11 , "qzdp:value-undefined" ) ;
106+ assert ( values . value ( null ) === null , "qzdp:preserve-null" ) ;
107+ assert ( values . value ( 0 ) === 0 , "qzdp:preserve-zero" ) ;
108+ assert ( values . value ( false ) === false , "qzdp:preserve-false" ) ;
109+ assert ( values . value ( "" ) === "" , "qzdp:preserve-empty-string" ) ;
110+ const nan = values . value ( 0 / 0 ) ;
111+ assert ( nan !== nan , "qzdp:preserve-nan" ) ;
112+ assert ( values . pair ( ) === 23 , "qzdp:multiple" ) ;
113+ assert ( anyValues . pair ( ) === 23 , "qzdp:arity-padding" ) ;
114+ assert ( values . pair ( undefined , 8 ) === 28 , "qzdp:argument-hole" ) ;
115+ assert ( values . pair ( 4 ) === 43 , "qzdp:trailing" ) ;
116+ const counter = values . capture ( ) ;
117+ const explicitCounter = values . capture ( 10 ) ;
118+ assert ( counter ( ) === 8 && counter ( ) === 9 , "qzdp:boxed-param" ) ;
119+ assert ( explicitCounter ( ) === 11 , "qzdp:boxed-explicit" ) ;
120+ assert ( immutableCapture ( ) ( ) === 29 , "qzdp:immutable-capture" ) ;
121+
122+ assert ( new ParameterProperty ( ) . value === 13 , "qzdp:constructor-property" ) ;
123+ assert ( new ParameterProperty ( undefined ) . value === 13 , "qzdp:constructor-undefined" ) ;
124+ assert ( new DerivedConstructor ( ) . value === 17 , "qzdp:derived-constructor" ) ;
125+ assert ( new DerivedConstructor ( 0 ) . value === 0 , "qzdp:constructor-zero" ) ;
126+ assert ( bool ( ) && ! bool ( false ) , "qzdp:boolean" ) ;
127+ const array = [ 1 , 2 ] ;
128+ assert ( ref ( ) === null && ref ( array ) === array , "qzdp:reference" ) ;
129+ assert ( missing ( ) === undefined , "qzdp:no-default" ) ;
130+ assert ( shimDefault ( ) === 31 && shimDefault ( 0 ) === 0 , "qzdp:shim-default" ) ;
131+ assert ( shimDefault ( undefined ) === undefined , "qzdp:shim-undefined-unchanged" ) ;
132+ assert ( annotatedDefault ( ) === 37 , "qzdp:annotated-default" ) ;
133+ }
55134}
56135
57136testDefaultParamDispatch ( ) ;
137+ DefaultDispatch . run ( ) ;
0 commit comments