From d4394c55d3ffae1b2d4a59692adab37f2c264ebc Mon Sep 17 00:00:00 2001 From: ComputerGuy <63362464+Ocean-OS@users.noreply.github.com> Date: Thu, 10 Apr 2025 18:32:02 -0700 Subject: [PATCH] try adding support for individual property invalidation, might revert later --- .../2-analyze/visitors/CallExpression.js | 16 ++++++-- .../client/visitors/CallExpression.js | 41 ++++++++++++------- .../svelte/src/internal/client/constants.js | 1 + packages/svelte/src/internal/client/index.js | 2 +- packages/svelte/src/internal/client/proxy.js | 27 +++++++++++- .../src/internal/client/reactivity/sources.js | 5 ++- 6 files changed, 70 insertions(+), 22 deletions(-) diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js index 1d7af8b4f5..80b6a5cdde 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js @@ -1,9 +1,9 @@ -/** @import { ArrowFunctionExpression, CallExpression, Expression, FunctionDeclaration, FunctionExpression, Identifier, VariableDeclarator } from 'estree' */ +/** @import { ArrowFunctionExpression, CallExpression, Expression, FunctionDeclaration, FunctionExpression, Identifier, MemberExpression, VariableDeclarator } from 'estree' */ /** @import { AST } from '#compiler' */ /** @import { Context } from '../types' */ import { get_rune } from '../../scope.js'; import * as e from '../../../errors.js'; -import { get_parent, unwrap_optional } from '../../../utils/ast.js'; +import { get_parent, object, unwrap_optional } from '../../../utils/ast.js'; import { is_pure, is_safe_identifier } from './shared/utils.js'; import { dev, locate_node, source } from '../../../state.js'; import * as b from '../../../utils/builders.js'; @@ -121,10 +121,18 @@ export function CallExpression(node, context) { } if (arg.type === 'MemberExpression') { if (arg.object.type !== 'ThisExpression') { - e.state_invalidate_nonreactive_argument(node); + const obj = object((arg = /** @type {MemberExpression} */ (context.visit(arg)))); + if (obj?.type === 'Identifier') { + // there isn't really a good way to tell because of stuff like `notproxied = proxied` + break; + } else if (obj?.type !== 'ThisExpression') { + e.state_invalidate_nonreactive_argument(node); + } + } else if (arg.computed) { + e.state_invalidate_invalid_this_property(node); } const class_body = context.path.findLast((parent) => parent.type === 'ClassBody'); - if (arg.computed || !class_body) { + if (!class_body) { e.state_invalidate_invalid_this_property(node); } const possible_this_bindings = context.path.filter((parent, index) => { diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/CallExpression.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/CallExpression.js index 3983479fe7..e29ec86948 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/CallExpression.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/CallExpression.js @@ -1,10 +1,11 @@ -/** @import { CallExpression, Expression } from 'estree' */ +/** @import { CallExpression, Expression, Identifier } from 'estree' */ /** @import { Context } from '../types' */ import { dev, is_ignored } from '../../../../state.js'; import * as b from '../../../../utils/builders.js'; import { get_rune } from '../../../scope.js'; import { transform_inspect_rune } from '../../utils.js'; import * as e from '../../../../errors.js'; +import { object } from '../../../../utils/ast.js'; /** * @param {CallExpression} node @@ -29,20 +30,32 @@ export function CallExpression(node, context) { if (node.arguments[0].type === 'Identifier') { return b.call('$.invalidate', node.arguments[0]); } else if (node.arguments[0].type === 'MemberExpression') { - const { property } = node.arguments[0]; - let field; - switch (property.type) { - case 'Identifier': - field = context.state.public_state.get(property.name); - break; - case 'PrivateIdentifier': - field = context.state.private_state.get(property.name); - break; + const { object: obj, property } = node.arguments[0]; + const root = object(node.arguments[0]); + if (obj.type === 'ThisExpression') { + let field; + switch (property.type) { + case 'Identifier': + field = context.state.public_state.get(property.name); + break; + case 'PrivateIdentifier': + field = context.state.private_state.get(property.name); + break; + } + if (!field || (field.kind !== 'state' && field.kind !== 'raw_state')) { + e.state_invalidate_nonreactive_argument(node); + } + return b.call('$.invalidate', b.member(b.this, field.id)); } - if (!field || (field.kind !== 'state' && field.kind !== 'raw_state')) { - e.state_invalidate_nonreactive_argument(node); - } - return b.call('$.invalidate', b.member(b.this, field.id)); + /** @type {Expression[]} */ + const source_args = /** @type {Expression[]} */ ([ + context.visit(obj), + node.arguments[0].computed + ? context.visit(property) + : b.literal(/** @type {Identifier} */ (property).name) + ]); + const arg = b.call('$.lookup_source', ...source_args); + return b.call('$.invalidate', arg); } case '$effect.root': diff --git a/packages/svelte/src/internal/client/constants.js b/packages/svelte/src/internal/client/constants.js index 7e5196c606..6e3f62984e 100644 --- a/packages/svelte/src/internal/client/constants.js +++ b/packages/svelte/src/internal/client/constants.js @@ -25,3 +25,4 @@ export const EFFECT_IS_UPDATING = 1 << 21; export const STATE_SYMBOL = Symbol('$state'); export const LEGACY_PROPS = Symbol('legacy props'); export const LOADING_ATTR_SYMBOL = Symbol(''); +export const PROXY_SOURCES = Symbol('proxy sources'); diff --git a/packages/svelte/src/internal/client/index.js b/packages/svelte/src/internal/client/index.js index 88091ed848..b409dcf5a4 100644 --- a/packages/svelte/src/internal/client/index.js +++ b/packages/svelte/src/internal/client/index.js @@ -148,7 +148,7 @@ export { } from './runtime.js'; export { validate_binding, validate_each_keys } from './validate.js'; export { raf } from './timing.js'; -export { proxy } from './proxy.js'; +export { proxy, lookup_source } from './proxy.js'; export { create_custom_element } from './dom/elements/custom-element.js'; export { child, diff --git a/packages/svelte/src/internal/client/proxy.js b/packages/svelte/src/internal/client/proxy.js index 5e0aa3dbc3..32cf1eb664 100644 --- a/packages/svelte/src/internal/client/proxy.js +++ b/packages/svelte/src/internal/client/proxy.js @@ -9,7 +9,7 @@ import { object_prototype } from '../shared/utils.js'; import { state as source, set } from './reactivity/sources.js'; -import { STATE_SYMBOL } from './constants.js'; +import { STATE_SYMBOL, PROXY_SOURCES } from './constants.js'; import { UNINITIALIZED } from '../../constants.js'; import * as e from './errors.js'; import { get_stack } from './dev/tracing.js'; @@ -124,6 +124,10 @@ export function proxy(value) { return value; } + if (prop === PROXY_SOURCES) { + return sources; + } + var s = sources.get(prop); var exists = prop in target; @@ -165,7 +169,7 @@ export function proxy(value) { }, has(target, prop) { - if (prop === STATE_SYMBOL) { + if (prop === STATE_SYMBOL || prop === PROXY_SOURCES) { return true; } @@ -317,3 +321,22 @@ export function get_proxied_value(value) { export function is(a, b) { return Object.is(get_proxied_value(a), get_proxied_value(b)); } + +/** + * @param {Record} object + * @param {string | symbol} property + * @returns {Source | null} + */ +export function lookup_source(object, property) { + if (typeof object !== 'object' || object === null) return null; + if (STATE_SYMBOL in object) { + if (property in object) { + /** @type {Map} */ + const sources = object[PROXY_SOURCES]; + if (sources.has(property)) { + return /** @type {Source} */ (sources.get(property)); + } + } + } + return null; +} diff --git a/packages/svelte/src/internal/client/reactivity/sources.js b/packages/svelte/src/internal/client/reactivity/sources.js index 7724edad68..044b805429 100644 --- a/packages/svelte/src/internal/client/reactivity/sources.js +++ b/packages/svelte/src/internal/client/reactivity/sources.js @@ -221,9 +221,12 @@ export function internal_set(source, value) { } /** - * @param {Source} source + * @param {Source | null} source */ export function invalidate(source) { + if (source === null) { + return; + } if ( active_reaction !== null && !untracking &&