Summary
xs["0"] answers undefined where JavaScript answers the character, when xs is
declared string[] (or any array type) but holds a string at runtime. The
numeric form xs[0] is correct. The same read written directly on an any is correct.
So the wrong answer is selected by the annotation, not by the value.
Repro
type Bag = { items: string[] };
function mk(v: any): Bag { return { items: v }; }
function viaDeclared(b: Bag): string { return "" + b.items["0"] + "/" + b.items[0]; }
const s: any = "ss";
console.log(viaDeclared(mk(s))); // node: s/s perry: undefined/s
const direct: any = "ss";
console.log("" + direct["0"] + "/" + direct[0]); // node: s/s perry: s/s
test-files/test_gap_7891_string_receiver_numeric_string_key.ts.
Why
A declared array type is an erased annotation, not a runtime proof, so the value can be
anything. is_array_expr admits the receiver to expr/index_get.rs's array arm, and that
arm's static-string-key route is
js_array_get_index_or_string → array_get_property_by_key →
js_object_get_field_by_name — which has no string-receiver index arm and returns
undefined. The numeric route is fine: js_array_get_f64 classifies the receiver
(clean_arr_ptr / array_object_receiver) and handles a string correctly.
So the array arm's two key routes have different receiver-validation strength, and only
the numeric one is claim-safe.
Scope / severity
Pre-existing on main @ 564bd997b, reachable through a non-union declared receiver
(a plain b: Bag parameter, no union, no reassignment). Silent wrong answer, no crash.
Found while adding coverage for #7890, which is why #7890 deliberately refuses to
widen the set of receivers that reach this route: its claim is restricted to a
non-string, non-symbol key so a string key keeps exactly the path it has today.
Suggested fix
Either give array_get_property_by_key the same receiver classification the numeric
route has, or refuse the array arm's static-string-key route for any receiver whose
array type is a claim rather than a proof.
Summary
xs["0"]answersundefinedwhere JavaScript answers the character, whenxsisdeclared
string[](or any array type) but holds a string at runtime. Thenumeric form
xs[0]is correct. The same read written directly on ananyis correct.So the wrong answer is selected by the annotation, not by the value.
Repro
test-files/test_gap_7891_string_receiver_numeric_string_key.ts.Why
A declared array type is an erased annotation, not a runtime proof, so the value can be
anything.
is_array_expradmits the receiver toexpr/index_get.rs's array arm, and thatarm's static-string-key route is
js_array_get_index_or_string→array_get_property_by_key→js_object_get_field_by_name— which has no string-receiver index arm and returnsundefined. The numeric route is fine:js_array_get_f64classifies the receiver(
clean_arr_ptr/array_object_receiver) and handles a string correctly.So the array arm's two key routes have different receiver-validation strength, and only
the numeric one is claim-safe.
Scope / severity
Pre-existing on
main@564bd997b, reachable through a non-union declared receiver(a plain
b: Bagparameter, no union, no reassignment). Silent wrong answer, no crash.Found while adding coverage for #7890, which is why #7890 deliberately refuses to
widen the set of receivers that reach this route: its claim is restricted to a
non-string, non-symbol key so a string key keeps exactly the path it has today.
Suggested fix
Either give
array_get_property_by_keythe same receiver classification the numericroute has, or refuse the array arm's static-string-key route for any receiver whose
array type is a claim rather than a proof.