fix-untrack-expose-unsafe
Dominic Gannaway 4 days ago
parent 3221d652fd
commit 89780e38e8

@ -239,9 +239,10 @@ export function internal_set(source, value) {
/**
* @param {Value} signal
* @param {number} status should be DIRTY or MAYBE_DIRTY
* @param {boolean} [unsafe] mark all reactions for unsafe mutations
* @returns {void}
*/
export function mark_reactions(signal, status) {
export function mark_reactions(signal, status, unsafe = false) {
var reactions = signal.reactions;
if (reactions === null) return;
@ -253,23 +254,25 @@ export function mark_reactions(signal, status) {
var flags = reaction.f;
// Skip any effects that are already dirty
if ((flags & DIRTY) !== 0) continue;
if ((flags & DIRTY) !== 0 && !unsafe) continue;
// In legacy mode, skip the current effect to prevent infinite loops
if (!runes && reaction === active_effect) continue;
// Inspect effects need to run immediately, so that the stack trace makes sense
if (DEV && (flags & INSPECT_EFFECT) !== 0) {
// Inspect effects need to run immediately, so that the stack trace makes sense.
// Skip doing this for the unsafe mutations as they will have already been added
// in the unsafe() wrapper
if (DEV && !unsafe && (flags & INSPECT_EFFECT) !== 0) {
inspect_effects.add(reaction);
continue;
}
set_signal_status(reaction, status);
// If the signal a) was previously clean or b) is an unowned derived, then mark it
if ((flags & (CLEAN | UNOWNED)) !== 0) {
// If the signal a) was previously clean or b) is an unowned derived then mark it
if ((flags & (CLEAN | UNOWNED)) !== 0 || unsafe) {
if ((flags & DERIVED) !== 0) {
mark_reactions(/** @type {Derived} */ (reaction), MAYBE_DIRTY);
mark_reactions(/** @type {Derived} */ (reaction), MAYBE_DIRTY, unsafe);
} else {
schedule_effect(/** @type {Effect} */ (reaction));
}

@ -552,7 +552,7 @@ export function update_effect(effect) {
// to ensure consistency of the graph
if (unsafe_sources !== null && (effect.f & CLEAN) !== 0) {
for (let i = 0; i < /** @type {Source[]} */ (unsafe_sources).length; i++) {
mark_reactions(unsafe_sources[i], DIRTY);
mark_reactions(unsafe_sources[i], DIRTY, true);
}
}

@ -781,4 +781,115 @@ describe('signals', () => {
assert.equal($.get(count), 0n);
};
});
test('unsafe() correctly ensures graph consistency', () => {
return () => {
const output: any[] = [];
const destroy = effect_root(() => {
const a = state(0);
const b = state(0);
const c = derived(() => {
$.unsafe(() => {
set(b, $.get(a) + 1);
});
return $.get(a);
});
render_effect(() => {
output.push('b' + $.get(b));
});
render_effect(() => {
output.push('b' + $.get(b), 'c' + $.get(c));
});
flushSync();
set(a, 1);
flushSync();
});
destroy();
assert.deepEqual(output, ['b0', 'b0', 'c0', 'b1', 'b1', 'c0', 'b2', 'c1', 'b2']);
};
});
test('unsafe() correctly ensures graph consistency #2', () => {
return () => {
const output: any[] = [];
const destroy = effect_root(() => {
const a = state(0);
const b = state(0);
const c = derived(() => {
$.unsafe(() => {
set(b, $.get(a) + 1);
});
return $.get(a);
});
let d = derived(() => $.get(b));
render_effect(() => {
output.push('d' + $.get(d));
});
render_effect(() => {
output.push('d' + $.get(d), 'c' + $.get(c));
});
flushSync();
set(a, 1);
flushSync();
});
destroy();
assert.deepEqual(output, ['d0', 'd0', 'c0', 'd1', 'd1', 'c0', 'd2', 'c1', 'd2']);
};
});
test('unsafe() correctly ensures graph consistency #3', () => {
return () => {
const output: any[] = [];
const destroy = effect_root(() => {
const a = state(0);
const b = state(0);
const c = derived(() => {
$.unsafe(() => {
set(b, $.get(a) + 1);
});
return $.get(a);
});
let d = state(true);
let e = derived(() => $.get(b));
render_effect(() => {
if ($.get(d)) {
return;
}
output.push('e' + $.get(e), 'c' + $.get(c));
});
flushSync();
set(d, false);
flushSync();
set(a, 1);
flushSync();
});
destroy();
assert.deepEqual(output, ['e0', 'c0', 'e1', 'c0', 'e2', 'c1']);
};
});
});

Loading…
Cancel
Save