pull/15589/merge
ComputerGuy 4 months ago committed by GitHub
commit a16db88db1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': minor
---
feat: allow `$state` in return statements

@ -65,6 +65,19 @@ let { done, text } = todos[0];
todos[0].done = !todos[0].done;
```
You can also use `$state` in return statements to proxy their argument:
```js
function createCounter() {
return $state({
count: 0,
increment() {
this.count++;
}
});
}
```
### Classes
You can also use `$state` in class fields (whether public or private):

@ -219,6 +219,12 @@ Reactive `$state(...)` proxies and the values they proxy have different identiti
To resolve this, ensure you're comparing values where both values were created with `$state(...)`, or neither were. Note that `$state.raw(...)` will _not_ create a state proxy.
### state_return_not_proxyable
```
The argument passed to a `$state` call in a return statement must be a plain object or array. Otherwise, the `$state` call will have no effect
```
### transition_slide_display
```

@ -185,6 +185,10 @@ To fix it, either create callback props to communicate changes, or mark `person`
To resolve this, ensure you're comparing values where both values were created with `$state(...)`, or neither were. Note that `$state.raw(...)` will _not_ create a state proxy.
## state_return_not_proxyable
> The argument passed to a `$state` call in a return statement must be a plain object or array. Otherwise, the `$state` call will have no effect
## transition_slide_display
> The `slide` transition does not work correctly for elements with `display: %value%`

@ -112,6 +112,20 @@ export function CallExpression(node, context) {
}
case '$state':
if (
(!(parent.type === 'VariableDeclarator' || parent.type === 'ReturnStatement') ||
get_parent(context.path, -3).type === 'ConstTag') &&
!(parent.type === 'PropertyDefinition' && !parent.static && !parent.computed) &&
!(parent.type === 'ArrowFunctionExpression' && parent.body === node)
) {
e.state_invalid_placement(node, rune);
}
if (node.arguments.length > 1) {
e.rune_invalid_arguments_length(node, rune, 'zero or one arguments');
}
break;
case '$state.raw':
case '$derived':
case '$derived.by':

@ -4,12 +4,14 @@ import { dev, is_ignored } from '../../../../state.js';
import * as b from '#compiler/builders';
import { get_rune } from '../../../scope.js';
import { transform_inspect_rune } from '../../utils.js';
import { should_proxy } from '../utils.js';
/**
* @param {CallExpression} node
* @param {Context} context
*/
export function CallExpression(node, context) {
const parent = context.path.at(-1);
switch (get_rune(node, context.state.scope)) {
case '$host':
return b.id('$$props.$$host');
@ -33,6 +35,18 @@ export function CallExpression(node, context) {
case '$inspect':
case '$inspect().with':
return transform_inspect_rune(node, context);
case '$state':
if (
parent?.type === 'ReturnStatement' ||
(parent?.type === 'ArrowFunctionExpression' && parent.body === node)
) {
if (node.arguments[0]) {
return b.call(
'$.return_proxy',
/** @type {Expression} */ (context.visit(node.arguments[0] ?? b.void0))
);
}
}
}
if (

@ -1,4 +1,4 @@
/** @import { CallExpression, Expression } from 'estree' */
/** @import { ArrowFunctionExpression, CallExpression, Expression } from 'estree' */
/** @import { Context } from '../types.js' */
import { is_ignored } from '../../../../state.js';
import * as b from '#compiler/builders';
@ -37,5 +37,17 @@ export function CallExpression(node, context) {
return transform_inspect_rune(node, context);
}
if (
rune === '$state' &&
(context.path.at(-1)?.type === 'ReturnStatement' ||
(context.path.at(-1)?.type === 'ArrowFunctionExpression' &&
/** @type {ArrowFunctionExpression} */ (context.path.at(-1)).body === node))
) {
if (node.arguments[0]) {
return context.visit(node.arguments[0]);
}
return b.void0;
}
context.next();
}

@ -143,7 +143,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, return_proxy } from './proxy.js';
export { create_custom_element } from './dom/elements/custom-element.js';
export {
child,

@ -12,9 +12,25 @@ import { state as source, set } from './reactivity/sources.js';
import { STATE_SYMBOL } from '#client/constants';
import { UNINITIALIZED } from '../../constants.js';
import * as e from './errors.js';
import * as w from './warnings.js';
import { get_stack } from './dev/tracing.js';
import { tracing_mode_flag } from '../flags/index.js';
/**
* @param {unknown} value
* @returns {boolean}
*/
function should_proxy(value) {
if (typeof value !== 'object' || value === null || STATE_SYMBOL in value) {
return false;
}
const prototype = get_prototype_of(value);
if (prototype !== object_prototype && prototype !== array_prototype) {
return false;
}
return true;
}
/**
* @template T
* @param {T} value
@ -22,13 +38,7 @@ import { tracing_mode_flag } from '../flags/index.js';
*/
export function proxy(value) {
// if non-proxyable, or is already a proxy, return `value`
if (typeof value !== 'object' || value === null || STATE_SYMBOL in value) {
return value;
}
const prototype = get_prototype_of(value);
if (prototype !== object_prototype && prototype !== array_prototype) {
if (!should_proxy(value)) {
return value;
}
@ -282,6 +292,21 @@ export function proxy(value) {
});
}
/**
* @template T
* @param {T} value
* @returns {T | void}
*/
export function return_proxy(value) {
if (should_proxy(value)) {
return proxy(value);
} else if (DEV && !(typeof value === 'object' && value !== null && STATE_SYMBOL in value)) {
// if the argument passed was already a proxy, we don't warn
w.state_return_not_proxyable();
}
return value;
}
/**
* @param {Source<number>} signal
* @param {1 | -1} [d]

@ -170,6 +170,17 @@ export function state_proxy_equality_mismatch(operator) {
}
}
/**
* The argument passed to a `$state` call in a return statement must be a plain object or array. Otherwise, the `$state` call will have no effect
*/
export function state_return_not_proxyable() {
if (DEV) {
console.warn(`%c[svelte] state_return_not_proxyable\n%cThe argument passed to a \`$state\` call in a return statement must be a plain object or array. Otherwise, the \`$state\` call will have no effect\nhttps://svelte.dev/e/state_return_not_proxyable`, bold, normal);
} else {
console.warn(`https://svelte.dev/e/state_return_not_proxyable`);
}
}
/**
* The `slide` transition does not work correctly for elements with `display: %value%`
* @param {string} value

@ -0,0 +1,14 @@
/* index.svelte.js generated by Svelte VERSION */
import * as $ from 'svelte/internal/client';
export default function proxy(object) {
return $.return_proxy(object);
}
export function createCounter() {
let count = $.state(0);
$.update(count);
}
export const proxy_in_arrow = (object) => $.return_proxy(object);

@ -0,0 +1,14 @@
/* index.svelte.js generated by Svelte VERSION */
import * as $ from 'svelte/internal/server';
export default function proxy(object) {
return object;
}
export function createCounter() {
let count = 0;
count++;
}
export const proxy_in_arrow = (object) => object;

@ -0,0 +1,8 @@
export default function proxy(object) {
return $state(object);
}
export function createCounter() {
let count = $state(0);
count++;
}
export const proxy_in_arrow = (object) => $state(object);
Loading…
Cancel
Save