From 5b5e9efb92ba9c2934e2d8e77c301c55962c87f7 Mon Sep 17 00:00:00 2001 From: ComputerGuy <63362464+Ocean-OS@users.noreply.github.com> Date: Sun, 26 Oct 2025 15:09:37 -0700 Subject: [PATCH] don't alias constant expressions --- .../3-transform/client/visitors/ClassBody.js | 57 +++++++++++++++-- .../3-transform/server/visitors/ClassBody.js | 60 ++++++++++++++--- packages/svelte/src/compiler/phases/scope.js | 64 +++++++++++++++++++ 3 files changed, 166 insertions(+), 15 deletions(-) diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/ClassBody.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/ClassBody.js index d4b24469bd..3585660cba 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/ClassBody.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/ClassBody.js @@ -5,6 +5,7 @@ import * as b from '#compiler/builders'; import { dev } from '../../../../state.js'; import { get_parent } from '../../../../utils/ast.js'; import { get_name } from '../../../nodes.js'; +import { is_constant } from '../../../scope.js'; /** * @param {ClassBody} node @@ -61,6 +62,20 @@ export function ClassBody(node, context) { ); continue; } + if (is_constant(computed_key, context.state.scope)) { + body.push( + b.prop_def(field.key, null), + b.method('get', computed_key, [], [b.return(b.call('$.get', member))], true), + b.method( + 'set', + computed_key, + [b.id('value')], + [b.stmt(b.call('$.set', member, b.id('value'), should_proxy && b.true))], + true + ) + ); + continue; + } const key = context.state.scope.generate('key'); computed_field_declarations.push(b.let(key)); @@ -163,6 +178,42 @@ export function ClassBody(node, context) { b.literal(`${declaration.id?.name ?? '[class]'}[computed key]`) ); } + const computed_key = /** @type {Expression} */ (context.visit(field.computed_key)); + const evaluation = context.state.scope.evaluate(computed_key); + if (evaluation.is_known) { + body.push( + b.prop_def(field.key, call), + b.method( + 'get', + b.literal(evaluation.value), + [], + [b.return(b.call('$.get', member))], + true + ), + b.method( + 'set', + b.literal(evaluation.value), + [b.id('value')], + [b.stmt(b.call('$.set', member, b.id('value'), should_proxy && b.true))], + true + ) + ); + continue; + } + if (is_constant(computed_key, context.state.scope)) { + body.push( + b.prop_def(field.key, call), + b.method('get', computed_key, [], [b.return(b.call('$.get', member))], true), + b.method( + 'set', + computed_key, + [b.id('value')], + [b.stmt(b.call('$.set', member, b.id('value'), should_proxy && b.true))], + true + ) + ); + continue; + } const key = context.state.scope.generate('key'); computed_field_declarations.push(b.let(key)); @@ -170,11 +221,7 @@ export function ClassBody(node, context) { b.prop_def(field.key, call), b.method( 'get', - b.assignment( - '=', - b.id(key), - /** @type {Expression} */ (context.visit(field.computed_key)) - ), + b.assignment('=', b.id(key), computed_key), [], [b.return(b.call('$.get', member))], true diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/ClassBody.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/ClassBody.js index d3c04af9ca..44926f1a67 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/ClassBody.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/ClassBody.js @@ -2,6 +2,7 @@ /** @import { Context } from '../types.js' */ import * as b from '#compiler/builders'; import { get_name } from '../../../nodes.js'; +import { is_constant } from '../../../scope.js'; /** * @param {ClassBody} node @@ -34,17 +35,43 @@ export function ClassBody(node, context) { const member = b.member(b.this, field.key); if (typeof name !== 'string' && field.computed_key) { + const computed_key = /** @type {Expression} */ (context.visit(field.computed_key)); + const evaluation = context.state.scope.evaluate(computed_key); + if (evaluation.is_known) { + body.push( + b.prop_def(field.key, null), + b.method('get', b.literal(evaluation.value), [], [b.return(b.call(member))], true), + b.method( + 'set', + b.literal(evaluation.value), + [b.id('$$value')], + [b.return(b.call(member, b.id('$$value')))], + true + ) + ); + continue; + } + if (is_constant(computed_key, context.state.scope)) { + body.push( + b.prop_def(field.key, null), + b.method('get', computed_key, [], [b.return(b.call(member))], true), + b.method( + 'set', + computed_key, + [b.id('$$value')], + [b.return(b.call(member, b.id('$$value')))], + true + ) + ); + continue; + } const key = context.state.scope.generate('key'); computed_field_declarations.push(b.let(key)); body.push( b.prop_def(field.key, null), b.method( 'get', - b.assignment( - '=', - b.id(key), - /** @type {Expression} */ (context.visit(field.computed_key)) - ), + b.assignment('=', b.id(key), computed_key), [], [b.return(b.call(member))], true @@ -133,6 +160,23 @@ export function ClassBody(node, context) { ); continue; } + if (is_constant(computed_key, context.state.scope)) { + body.push( + b.prop_def( + field.key, + /** @type {CallExpression} */ (context.visit(field.value, child_state)) + ), + b.method('get', computed_key, [], [b.return(b.call(member))], true), + b.method( + 'set', + computed_key, + [b.id('$$value')], + [b.return(b.call(member, b.id('$$value')))], + true + ) + ); + continue; + } const key = context.state.scope.generate('key'); computed_field_declarations.push(b.let(key)); body.push( @@ -142,11 +186,7 @@ export function ClassBody(node, context) { ), b.method( 'get', - b.assignment( - '=', - b.id(key), - /** @type {Expression} */ (context.visit(field.computed_key)) - ), + b.assignment('=', b.id(key), computed_key), [], [b.return(b.call(member))], true diff --git a/packages/svelte/src/compiler/phases/scope.js b/packages/svelte/src/compiler/phases/scope.js index 7dbdf47967..c319a7999d 100644 --- a/packages/svelte/src/compiler/phases/scope.js +++ b/packages/svelte/src/compiler/phases/scope.js @@ -1358,6 +1358,70 @@ export function get_rune(node, scope) { return keypath; } +/** + * @param {Expression} expression + * @param {Scope} scope + */ +export function is_constant(expression, scope) { + const evaluation = scope.evaluate(expression); + if (evaluation.is_known) { + return true; + } + let constant = true; + walk(/** @type {Node} */ (expression), null, { + Identifier(node, { path, stop }) { + if (is_reference(node, /** @type {Node} */ (path.at(-1)))) { + const binding = scope.get(node.name); + if (!binding || binding.reassigned) { + constant = false; + stop(); + return; + } + } + }, + ArrowFunctionExpression(_, { stop }) { + constant = false; + stop(); + }, + FunctionExpression(_, { stop }) { + constant = false; + stop(); + }, + ClassExpression(_, { stop }) { + constant = false; + stop(); + }, + MemberExpression(_, { stop }) { + constant = false; + stop(); + }, + CallExpression(node, { stop }) { + if (scope.evaluate(node).is_known) { + return; + } + constant = false; + stop(); + }, + UpdateExpression(_, { stop }) { + constant = false; + stop(); + }, + AssignmentExpression(_, { stop }) { + constant = false; + stop(); + }, + UnaryExpression(node, { next, stop }) { + if (node.operator === 'delete') { + constant = false; + stop(); + return; + } + next(); + } + }); + return constant; +} + /** * Returns the name of the rune if the given expression is a `CallExpression` using a rune. * @param {Expression | Super} node