fix: cache call expressions in render tag arguments (#12418)

* fix: cache call expressions in render tag arguments

Ensure that they're called at most once per change, not once per access within the snippet
fixes #12187

* leverage state

* types

* fix
pull/12408/head
Simon H 2 months ago committed by GitHub
parent d967780c4f
commit 70cec4e40e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: cache call expressions in render tag arguments

@ -600,7 +600,8 @@ function special(parser) {
end: parser.index, end: parser.index,
expression: expression, expression: expression,
metadata: { metadata: {
dynamic: false dynamic: false,
args_with_call_expression: new Set()
} }
}); });
} }

@ -8,7 +8,8 @@ import {
extract_paths, extract_paths,
is_event_attribute, is_event_attribute,
is_text_attribute, is_text_attribute,
object object,
unwrap_optional
} from '../../utils/ast.js'; } from '../../utils/ast.js';
import * as b from '../../utils/builders.js'; import * as b from '../../utils/builders.js';
import { MathMLElements, ReservedKeywords, Runes, SVGElements } from '../constants.js'; import { MathMLElements, ReservedKeywords, Runes, SVGElements } from '../constants.js';
@ -441,6 +442,7 @@ export function analyze_component(root, source, options) {
has_props_rune: false, has_props_rune: false,
component_slots: new Set(), component_slots: new Set(),
expression: null, expression: null,
render_tag: null,
private_derived_state: [], private_derived_state: [],
function_depth: scope.function_depth function_depth: scope.function_depth
}; };
@ -512,6 +514,7 @@ export function analyze_component(root, source, options) {
reactive_statements: analysis.reactive_statements, reactive_statements: analysis.reactive_statements,
component_slots: new Set(), component_slots: new Set(),
expression: null, expression: null,
render_tag: null,
private_derived_state: [], private_derived_state: [],
function_depth: scope.function_depth function_depth: scope.function_depth
}; };
@ -1289,7 +1292,7 @@ const common_visitors = {
} }
}, },
CallExpression(node, context) { CallExpression(node, context) {
const { expression } = context.state; const { expression, render_tag } = context.state;
if ( if (
(expression?.type === 'ExpressionTag' || expression?.type === 'SpreadAttribute') && (expression?.type === 'ExpressionTag' || expression?.type === 'SpreadAttribute') &&
!is_known_safe_call(node, context) !is_known_safe_call(node, context)
@ -1297,6 +1300,18 @@ const common_visitors = {
expression.metadata.contains_call_expression = true; expression.metadata.contains_call_expression = true;
} }
if (render_tag) {
// Find out which of the render tag arguments contains this call expression
const arg_idx = unwrap_optional(render_tag.expression).arguments.findIndex(
(arg) => arg === node || context.path.includes(arg)
);
// -1 if this is the call expression of the render tag itself
if (arg_idx !== -1) {
render_tag.metadata.args_with_call_expression.add(arg_idx);
}
}
const callee = node.callee; const callee = node.callee;
const rune = get_rune(node, context.state.scope); const rune = get_rune(node, context.state.scope);
@ -1523,6 +1538,9 @@ const common_visitors = {
); );
node.metadata.dynamic = binding !== null && binding.kind !== 'normal'; node.metadata.dynamic = binding !== null && binding.kind !== 'normal';
},
RenderTag(node, context) {
context.next({ ...context.state, render_tag: node });
} }
}; };

@ -3,6 +3,7 @@ import type { ComponentAnalysis, ReactiveStatement } from '../types.js';
import type { import type {
ClassDirective, ClassDirective,
ExpressionTag, ExpressionTag,
RenderTag,
SpreadAttribute, SpreadAttribute,
SvelteNode, SvelteNode,
ValidatedCompileOptions ValidatedCompileOptions
@ -20,6 +21,8 @@ export interface AnalysisState {
component_slots: Set<string>; component_slots: Set<string>;
/** The current {expression}, if any */ /** The current {expression}, if any */
expression: ExpressionTag | ClassDirective | SpreadAttribute | null; expression: ExpressionTag | ClassDirective | SpreadAttribute | null;
/** The current {@render ...} tag, if any */
render_tag: null | RenderTag;
private_derived_state: string[]; private_derived_state: string[];
function_depth: number; function_depth: number;
} }

@ -154,6 +154,7 @@ export interface RenderTag extends BaseNode {
expression: SimpleCallExpression | (ChainExpression & { expression: SimpleCallExpression }); expression: SimpleCallExpression | (ChainExpression & { expression: SimpleCallExpression });
metadata: { metadata: {
dynamic: boolean; dynamic: boolean;
args_with_call_expression: Set<number>;
}; };
} }

@ -0,0 +1,10 @@
import { test } from '../../test';
export default test({
test({ assert, logs }) {
assert.deepEqual(logs, ['invoked']);
},
test_ssr({ assert, logs }) {
assert.deepEqual(logs, ['invoked']);
}
});

@ -0,0 +1,13 @@
<script lang="ts">
let el = $state({ foo: 'foo', bar: 'bar' });
function fn(el) {
console.log('invoked')
return el;
}
</script>
{#snippet foo(a)}
{a.foo} {a.bar}
{/snippet}
{@render foo(fn(el))}

@ -1548,6 +1548,7 @@ declare module 'svelte/compiler' {
expression: SimpleCallExpression | (ChainExpression & { expression: SimpleCallExpression }); expression: SimpleCallExpression | (ChainExpression & { expression: SimpleCallExpression });
metadata: { metadata: {
dynamic: boolean; dynamic: boolean;
args_with_call_expression: Set<number>;
}; };
} }

Loading…
Cancel
Save