diff --git a/CHANGELOG.md b/CHANGELOG.md
index ef5ba2307fd2..717bce08fede 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,7 @@
## Unreleased
* Exclude global variables from `$capture_state` ([#4463](https://github.com/sveltejs/svelte/issues/4463))
+* Fix bitmask overflow for slots ([#4481](https://github.com/sveltejs/svelte/issues/4481))
## 3.19.1
diff --git a/src/runtime/internal/utils.ts b/src/runtime/internal/utils.ts
index 4ba7e18c153a..acb88c766945 100644
--- a/src/runtime/internal/utils.ts
+++ b/src/runtime/internal/utils.ts
@@ -83,7 +83,11 @@ export function get_slot_changes(definition, $$scope, dirty, fn) {
if (definition[2] && fn) {
const lets = definition[2](fn(dirty));
- if (typeof $$scope.dirty === 'object') {
+ if ($$scope.dirty === undefined) {
+ return lets;
+ }
+
+ if (typeof lets === 'object') {
const merged = [];
const len = Math.max($$scope.dirty.length, lets.length);
for (let i = 0; i < len; i += 1) {
diff --git a/test/runtime/samples/bitmask-overflow-slot-3/Echo.svelte b/test/runtime/samples/bitmask-overflow-slot-3/Echo.svelte
new file mode 100644
index 000000000000..d3ecf142c924
--- /dev/null
+++ b/test/runtime/samples/bitmask-overflow-slot-3/Echo.svelte
@@ -0,0 +1,9 @@
+
+
+
_0_1_2_3_4_5_6_7_8_9_10_11_12_13_14_15_16_17_18_19_20_21_22_23_24_25_26_27_28_29_30_31_32_33_34_35_36_37_38_39_40
+0
+ + `, + + async test({ assert, component, target, window }) { + // change from inside + const button = target.querySelector('button'); + await button.dispatchEvent(new window.MouseEvent('click')); + + assert.htmlEqual(target.innerHTML, ` +_0_1_2_3_4_5_6_7_8_9_10_11_12_13_14_15_16_17_18_19_20_21_22_23_24_25_26_27_28_29_30_31_32_33_34_35_36_37_38_39_40
+1
+ + `); + + // change from outside + component._0 = 'a'; + component._40 = 'b'; + + assert.htmlEqual(target.innerHTML, ` +a_1_2_3_4_5_6_7_8_9_10_11_12_13_14_15_16_17_18_19_20_21_22_23_24_25_26_27_28_29_30_31_32_33_34_35_36_37_38_39b
+1
+ + `); + } +}; \ No newline at end of file diff --git a/test/runtime/samples/bitmask-overflow-slot-3/main.svelte b/test/runtime/samples/bitmask-overflow-slot-3/main.svelte new file mode 100644 index 000000000000..ae798e4aeeb5 --- /dev/null +++ b/test/runtime/samples/bitmask-overflow-slot-3/main.svelte @@ -0,0 +1,11 @@ + + +{_0}{_1}{_2}{_3}{_4}{_5}{_6}{_7}{_8}{_9}{_10}{_11}{_12}{_13}{_14}{_15}{_16}{_17}{_18}{_19}{_20}{_21}{_22}{_23}{_24}{_25}{_26}{_27}{_28}{_29}{_30}{_31}{_32}{_33}{_34}{_35}{_36}{_37}{_38}{_39}{_40}
+{dummy}
+{_0}{_1}{_2}{_3}{_4}{_5}{_6}{_7}{_8}{_9}{_10}{_11}{_12}{_13}{_14}{_15}{_16}{_17}{_18}{_19}{_20}{_21}{_22}{_23}{_24}{_25}{_26}{_27}{_28}{_29}{_30}{_31}{_32}{_33}{_34}{_35}{_36}{_37}{_38}{_39}{_40}
+_0_1_2_3_4_5_6_7_8_9_10_11_12_13_14_15_16_17_18_19_20_21_22_23_24_25_26_27_28_29_30_31_32_33_34_35_36_37_38_39_40
+0
+0
+ + `, + + async test({ assert, component, target, window }) { + // change from inside + const button = target.querySelector('button'); + await button.dispatchEvent(new window.MouseEvent('click')); + + assert.htmlEqual(target.innerHTML, ` +_0_1_2_3_4_5_6_7_8_9_10_11_12_13_14_15_16_17_18_19_20_21_22_23_24_25_26_27_28_29_30_31_32_33_34_35_36_37_38_39_40
+0
+1
+ + `); + + // change from outside + component._0 = 'a'; + + assert.htmlEqual(target.innerHTML, ` +_0_1_2_3_4_5_6_7_8_9_10_11_12_13_14_15_16_17_18_19_20_21_22_23_24_25_26_27_28_29_30_31_32_33_34_35_36_37_38_39_40
+a
+1
+ + `); + + // change from outside through props + component._40 = 'b'; + + assert.htmlEqual(target.innerHTML, ` +_0_1_2_3_4_5_6_7_8_9_10_11_12_13_14_15_16_17_18_19_20_21_22_23_24_25_26_27_28_29_30_31_32_33_34_35_36_37_38_39b
+a
+1
+ + `); + } +}; \ No newline at end of file diff --git a/test/runtime/samples/bitmask-overflow-slot-4/main.svelte b/test/runtime/samples/bitmask-overflow-slot-4/main.svelte new file mode 100644 index 000000000000..7e02487a305d --- /dev/null +++ b/test/runtime/samples/bitmask-overflow-slot-4/main.svelte @@ -0,0 +1,12 @@ + + +{_0}
+{dummy}
+{_0}{_1}{_2}{_3}{_4}{_5}{_6}{_7}{_8}{_9}{_10}{_11}{_12}{_13}{_14}{_15}{_16}{_17}{_18}{_19}{_20}{_21}{_22}{_23}{_24}{_25}{_26}{_27}{_28}{_29}{_30}{_31}{_32}{_33}{_34}{_35}{_36}{_37}{_38}{_39}{_40}
+{b}
+_0_1_2_3_4_5_6_7_8_9_10_11_12_13_14_15_16_17_18_19_20_21_22_23_24_25_26_27_28_29_30_31_32_33_34_35_36_37_38_39_40
+b
+-0-1-2-3-4-5-6-7-8-9-10-11-12-13-14-15-16-17-18-19-20-21-22-23-24-25-26-27-28-29-30-31-32-33-34-35-36-37-38-39-40
+0
+0
+ + `, + + async test({ assert, component, target, window }) { + // change from inside + const button = target.querySelector('button'); + await button.dispatchEvent(new window.MouseEvent('click')); + + assert.htmlEqual(target.innerHTML, ` +_0_1_2_3_4_5_6_7_8_9_10_11_12_13_14_15_16_17_18_19_20_21_22_23_24_25_26_27_28_29_30_31_32_33_34_35_36_37_38_39_40
+b
+-0-1-2-3-4-5-6-7-8-9-10-11-12-13-14-15-16-17-18-19-20-21-22-23-24-25-26-27-28-29-30-31-32-33-34-35-36-37-38-39-40
+0
+1
+ + `); + + // change from outside + component.a = 'AA'; + + assert.htmlEqual(target.innerHTML, ` +_0_1_2_3_4_5_6_7_8_9_10_11_12_13_14_15_16_17_18_19_20_21_22_23_24_25_26_27_28_29_30_31_32_33_34_35_36_37_38_39_40
+b
+-0-1-2-3-4-5-6-7-8-9-10-11-12-13-14-15-16-17-18-19-20-21-22-23-24-25-26-27-28-29-30-31-32-33-34-35-36-37-38-39-40
+AA
+1
+ + `); + + // change from outside through props + component.b = 'BB'; + + assert.htmlEqual(target.innerHTML, ` +_0_1_2_3_4_5_6_7_8_9_10_11_12_13_14_15_16_17_18_19_20_21_22_23_24_25_26_27_28_29_30_31_32_33_34_35_36_37_38_39_40
+BB
+-0-1-2-3-4-5-6-7-8-9-10-11-12-13-14-15-16-17-18-19-20-21-22-23-24-25-26-27-28-29-30-31-32-33-34-35-36-37-38-39-40
+AA
+1
+ + `); + } +}; \ No newline at end of file diff --git a/test/runtime/samples/bitmask-overflow-slot-5/main.svelte b/test/runtime/samples/bitmask-overflow-slot-5/main.svelte new file mode 100644 index 000000000000..b17d6f7baef8 --- /dev/null +++ b/test/runtime/samples/bitmask-overflow-slot-5/main.svelte @@ -0,0 +1,13 @@ + + +{_0}{_1}{_2}{_3}{_4}{_5}{_6}{_7}{_8}{_9}{_10}{_11}{_12}{_13}{_14}{_15}{_16}{_17}{_18}{_19}{_20}{_21}{_22}{_23}{_24}{_25}{_26}{_27}{_28}{_29}{_30}{_31}{_32}{_33}{_34}{_35}{_36}{_37}{_38}{_39}{_40}
+{a}
+{dummy}
+