diff --git a/Compilation/CallHandlers/ArrayDestructureHandler.cs b/Compilation/CallHandlers/ArrayDestructureHandler.cs
new file mode 100644
index 00000000..4150b657
--- /dev/null
+++ b/Compilation/CallHandlers/ArrayDestructureHandler.cs
@@ -0,0 +1,57 @@
+using System.Reflection.Emit;
+using SharpTS.Compilation.Emitters;
+using SharpTS.Parsing;
+using SharpTS.Runtime.BuiltIns;
+using SharpTS.TypeSystem;
+
+namespace SharpTS.Compilation.CallHandlers;
+
+///
+/// Handles the internal __arrayDestructure helper that normalizes an array binding-pattern
+/// source through the iterator protocol (#685). Index-addressable sources (arrays, tuples, strings)
+/// are emitted as-is — the desugared positional index access reads them directly and the type checker
+/// assigns them a matching pass-through type, so no runtime work is needed (preserves the fast path
+/// and tuple positional element types). Every other source is routed through the emitted
+/// ArrayDestructureSource runtime helper, which materializes non-indexable iterables
+/// (generators, Set, Map, [Symbol.iterator] objects) into an array and passes everything else
+/// through unchanged.
+///
+public class ArrayDestructureHandler : ICallHandler
+{
+ public int Priority => 16; // Internal helper, right after ObjectRest (15)
+
+ public bool TryHandle(IEmitterContext emitter, Expr.Call call)
+ {
+ if (call.Callee is not Expr.Variable v || v.Name.Lexeme != BuiltInNames.ArrayDestructure)
+ return false;
+ if (call.Arguments.Count != 1)
+ return false;
+
+ var ctx = emitter.Context;
+ var il = ctx.IL;
+ var arg = call.Arguments[0];
+
+ // Emit the source value (boxed to object).
+ emitter.EmitExpression(arg);
+ emitter.EmitBoxIfNeeded(arg);
+
+ // Fast path: a statically index-addressable source needs no normalization. The type
+ // checker passes these through with their precise type (see
+ // TypeChecker.NormalizeArrayDestructureSourceType), so the runtime value is already an
+ // array/tuple/string the index access reads directly — and tuple positional element types
+ // stay intact.
+ if (IsIndexAddressable(ctx.TypeMap?.Get(arg)))
+ return true;
+
+ // Otherwise normalize at runtime:
+ // ArrayDestructureSource(value, Symbol.iterator, runtimeType)
+ il.Emit(OpCodes.Ldsfld, ctx.Runtime!.SymbolIterator);
+ il.Emit(OpCodes.Ldtoken, ctx.Runtime!.RuntimeType);
+ il.Emit(OpCodes.Call, ctx.Types.GetMethod(ctx.Types.Type, "GetTypeFromHandle"));
+ il.Emit(OpCodes.Call, ctx.Runtime!.ArrayDestructureSource);
+ return true;
+ }
+
+ private static bool IsIndexAddressable(TypeInfo? type) =>
+ type is TypeInfo.Array or TypeInfo.Tuple or TypeInfo.String or TypeInfo.StringLiteral;
+}
diff --git a/Compilation/CallHandlers/CallHandlerRegistry.cs b/Compilation/CallHandlers/CallHandlerRegistry.cs
index e6314ffb..d20d4667 100644
--- a/Compilation/CallHandlers/CallHandlerRegistry.cs
+++ b/Compilation/CallHandlers/CallHandlerRegistry.cs
@@ -21,6 +21,7 @@ public CallHandlerRegistry()
[
new SuperConstructorHandler(), // Priority 10 - super() calls
new ObjectRestHandler(), // Priority 15 - Internal helpers first
+ new ArrayDestructureHandler(), // Priority 16 - Internal helper (#685)
new ConsoleMethodHandler(), // Priority 20 - Console methods
new StaticTypeHandler(), // Priority 30 - Math, JSON, Object, Array, etc.
new GlobalThisChainHandler(), // Priority 32 - globalThis.X.Y()
diff --git a/Compilation/EmittedRuntime.cs b/Compilation/EmittedRuntime.cs
index 121ba0d6..830e4bf7 100644
--- a/Compilation/EmittedRuntime.cs
+++ b/Compilation/EmittedRuntime.cs
@@ -765,6 +765,7 @@ public class EmittedRuntime
public MethodBuilder InvokeTaggedTemplateWithThis { get; set; } = null!;
public MethodBuilder StringRaw { get; set; } = null!;
public MethodBuilder ObjectRest { get; set; } = null!;
+ public MethodBuilder ArrayDestructureSource { get; set; } = null!; // #685: normalize array binding-pattern source via the iterator protocol
// JSON methods
public MethodBuilder JsonParse { get; set; } = null!;
diff --git a/Compilation/RuntimeEmitter.Objects.Iteration.cs b/Compilation/RuntimeEmitter.Objects.Iteration.cs
index 2ecb5d46..e2b93a82 100644
--- a/Compilation/RuntimeEmitter.Objects.Iteration.cs
+++ b/Compilation/RuntimeEmitter.Objects.Iteration.cs
@@ -7,6 +7,62 @@ namespace SharpTS.Compilation;
public partial class RuntimeEmitter
{
+ ///
+ /// Emits ArrayDestructureSource: normalizes an array binding-pattern source through the
+ /// iterator protocol (#685). Index-addressable sources — strings and any
+ /// (arrays, $Array, typed lists) — pass through
+ /// unchanged so the desugared positional index access reads them directly and stays consistent
+ /// with the matching pass-through type the type checker assigned. Any other iterable (Set, Map,
+ /// generators, [Symbol.iterator] objects, IEnumerable<object>) is materialized
+ /// via IterateToList into a List<object> so positional access yields the
+ /// iterated elements. Non-iterable sources pass through, preserving the existing lenient behavior.
+ /// Signature: object ArrayDestructureSource(object value, $TSSymbol iteratorSymbol, Type runtimeType)
+ ///
+ private void EmitArrayDestructureSource(TypeBuilder typeBuilder, EmittedRuntime runtime)
+ {
+ var method = typeBuilder.DefineMethod(
+ "ArrayDestructureSource",
+ MethodAttributes.Public | MethodAttributes.Static,
+ _types.Object,
+ [_types.Object, runtime.TSSymbolType, _types.Type]
+ );
+ runtime.ArrayDestructureSource = method;
+
+ var il = method.GetILGenerator();
+ var ilistType = typeof(System.Collections.IList);
+
+ var passThroughLabel = il.DefineLabel();
+
+ // string → pass through (the source stays typed as string; index access reads chars).
+ il.Emit(OpCodes.Ldarg_0);
+ il.Emit(OpCodes.Isinst, _types.String);
+ il.Emit(OpCodes.Brtrue, passThroughLabel);
+
+ // IList (List