Summary
A rest element in an array binding pattern must collect the remainder into a new array (ECMA-262 ArrayBindingPattern). For a string source it instead binds the trailing substring:
const [a, ...rest] = "hello";
console.log(a, rest, Array.isArray(rest));
// SharpTS: h ello false (rest is the string "ello")
// tsc/JS : h ["e","l","l","o"] true
Both interpreter and compiled mode are affected. Non-rest string destructuring (const [a, b] = "hi") is correct.
Cause
Array destructuring desugars the rest element to _dest0.slice(index) (Parser.Destructuring.cs). For an array source that yields an array; for a string source String.prototype.slice yields a string. String sources stay on the index/slice fast path (they are index-addressable), so the rest binds a substring.
Fix shape
Treat a string source as a materializing iterable in the __arrayDestructure normalization added in #685: type it as string[], materialize to a char array in both Interpreter.NormalizeArrayDestructureSource and the compiled ArrayDestructureSource, and drop string/StringLiteral from the index-addressable pass-through sets in TypeChecker.NormalizeArrayDestructureSourceType and ArrayDestructureHandler. Non-rest string destructuring is unaffected (element types and values are identical); the trade-off is a small char-array copy per string destructure (currently strings stay copy-free on the fast path).
Discovered
While implementing #685. The non-rest string cases are correct, so #685 deliberately scoped strings out as "correct"; this is the residual rest-element gap.
Summary
A rest element in an array binding pattern must collect the remainder into a new array (ECMA-262 ArrayBindingPattern). For a string source it instead binds the trailing substring:
Both interpreter and compiled mode are affected. Non-rest string destructuring (
const [a, b] = "hi") is correct.Cause
Array destructuring desugars the rest element to
_dest0.slice(index)(Parser.Destructuring.cs). For an array source that yields an array; for a string sourceString.prototype.sliceyields a string. String sources stay on the index/slicefast path (they are index-addressable), so the rest binds a substring.Fix shape
Treat a string source as a materializing iterable in the
__arrayDestructurenormalization added in #685: type it asstring[], materialize to a char array in bothInterpreter.NormalizeArrayDestructureSourceand the compiledArrayDestructureSource, and dropstring/StringLiteralfrom the index-addressable pass-through sets inTypeChecker.NormalizeArrayDestructureSourceTypeandArrayDestructureHandler. Non-rest string destructuring is unaffected (element types and values are identical); the trade-off is a small char-array copy per string destructure (currently strings stay copy-free on the fast path).Discovered
While implementing #685. The non-rest string cases are correct, so #685 deliberately scoped strings out as "correct"; this is the residual rest-element gap.