Skip to content

Commit c7f21e1

Browse files
committed
maybe fix purity issues, cleanup code
1 parent 9d934f4 commit c7f21e1

File tree

1 file changed

+65
-30
lines changed
  • packages/svelte/src/compiler/phases

1 file changed

+65
-30
lines changed

packages/svelte/src/compiler/phases/scope.js

Lines changed: 65 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1024,6 +1024,23 @@ export class Scope {
10241024
evaluate(expression, values = new Set(), seen_bindings = [], from_fn_call = false) {
10251025
return new Evaluation(this, expression, values, seen_bindings, from_fn_call);
10261026
}
1027+
1028+
/**
1029+
* @param {Scope} child
1030+
*/
1031+
contains(child) {
1032+
let contains = false;
1033+
/** @type {Scope | null} */
1034+
let curr = child;
1035+
while (curr?.parent != null) {
1036+
curr = curr?.parent;
1037+
if (curr === this) {
1038+
contains = true;
1039+
break;
1040+
}
1041+
}
1042+
return contains;
1043+
}
10271044
}
10281045

10291046
/** @type {Record<BinaryOperator, (left: any, right: any) => any>} */
@@ -1646,7 +1663,12 @@ function get_type_of_ts_node(node, scope) {
16461663
* @returns {any[]}
16471664
*/
16481665
function intersect_types(types) {
1649-
if (types.includes(UNKNOWN)) return [UNKNOWN];
1666+
if (
1667+
types.includes(UNKNOWN) ||
1668+
types.filter((type) => !['symbol', 'string', 'number', 'bigint'].includes(typeof type))
1669+
.length > 1
1670+
)
1671+
return [UNKNOWN];
16501672
/** @type {any[]} */
16511673
let res = [];
16521674
if (
@@ -1666,14 +1688,9 @@ function get_type_of_ts_node(node, scope) {
16661688
} else {
16671689
res.push(...types.filter((type) => typeof type === 'string'));
16681690
}
1669-
if (
1670-
types.filter((type) => !['symbol', 'string', 'number', 'bigint'].includes(typeof type))
1671-
.length > 1
1672-
) {
1673-
res.push(UNKNOWN);
1674-
} else {
1691+
if (types.some((type) => !['symbol', 'string', 'number', 'bigint'].includes(typeof type))) {
16751692
types.push(
1676-
...types.filter((type) => !['symbol', 'string', 'number', 'bigint'].includes(typeof type))
1693+
types.find((type) => !['symbol', 'string', 'number', 'bigint'].includes(typeof type))
16771694
);
16781695
}
16791696
return res;
@@ -1751,6 +1768,19 @@ const known_globals = [
17511768

17521769
let fn_cache = new Map();
17531770

1771+
/**
1772+
* @param {Expression} callee
1773+
* @param {Scope} scope
1774+
*/
1775+
function is_global_class(callee, scope) {
1776+
let keypath = get_global_keypath(callee, scope);
1777+
if (keypath === null) return false;
1778+
if (keypath.match(/^(globalThis\.)+/)) {
1779+
keypath = keypath.replace(/^(globalThis\.)+/, '');
1780+
}
1781+
return global_classes.includes(keypath);
1782+
}
1783+
17541784
/**
17551785
* Analyzes and partially evaluates the provided function.
17561786
* @param {FunctionExpression | ArrowFunctionExpression | FunctionDeclaration} fn
@@ -1806,21 +1836,31 @@ function evaluate_function(fn, binding, stack = new Set(), [...seen_bindings] =
18061836
};
18071837
const uses_implicit_return =
18081838
fn.type === 'ArrowFunctionExpression' && fn.body.type !== 'BlockStatement';
1839+
function needs_check(purity = true, throwability = true) {
1840+
if (!throwability) {
1841+
return analysis.pure;
1842+
}
1843+
if (!purity) {
1844+
return analysis.never_throws;
1845+
}
1846+
return analysis.pure || analysis.never_throws;
1847+
}
18091848
/**
18101849
* @param {CallExpression | NewExpression} node
18111850
* @param {import('zimmerframe').Context<AST.SvelteNode, typeof state>} context
18121851
*/
18131852
function handle_call_expression(node, context) {
18141853
const { callee: call, arguments: args } = node;
1815-
const callee = context.visit(call, {
1816-
...context.state,
1817-
current_call: (node.type === 'CallExpression' ? CALL_EXPRESSION : NEW_EXPRESSION) | 0
1818-
});
1854+
const callee = /** @type {Expression} */ (
1855+
context.visit(call, {
1856+
...context.state,
1857+
current_call: (node.type === 'CallExpression' ? CALL_EXPRESSION : NEW_EXPRESSION) | 0
1858+
})
1859+
);
18191860
for (let arg of args) {
18201861
context.visit(arg);
18211862
}
1822-
if (analysis.pure || analysis.never_throws) {
1823-
// don't check unless we think the function is pure or error-free
1863+
if (needs_check()) {
18241864
if (callee.type === 'Identifier') {
18251865
const binding = context.state.scope.get(callee.name);
18261866
if (
@@ -1851,21 +1891,24 @@ function evaluate_function(fn, binding, stack = new Set(), [...seen_bindings] =
18511891
);
18521892
analysis.pure &&= child_analysis.pure;
18531893
analysis.never_throws &&= child_analysis.never_throws;
1894+
} else if (node.type === 'NewExpression' && !is_global_class(callee, context.state.scope)) {
1895+
analysis.pure = false;
1896+
analysis.never_throws = false;
18541897
}
18551898
}
18561899
}
18571900
walk(/** @type {AST.SvelteNode} */ (fn), state, {
18581901
MemberExpression(node, context) {
18591902
const keypath = get_global_keypath(node, context.state.scope);
18601903
const evaluated = context.state.scope.evaluate(node, new Set(), seen_bindings, true);
1861-
if (keypath === null && !evaluated.is_known) {
1904+
if (!(keypath !== null && Object.hasOwn(globals, keypath)) && !evaluated.is_known) {
18621905
analysis.pure = false;
18631906
analysis.never_throws = false;
18641907
}
18651908
context.next();
18661909
},
18671910
Identifier(node, context) {
1868-
if (is_reference(node, /** @type {Node} */ (context.path.at(-1)))) {
1911+
if (is_reference(node, /** @type {Node} */ (context.path.at(-1))) && needs_check()) {
18691912
const binding = context.state.scope.get(node.name);
18701913
if (binding !== fn_binding) {
18711914
if (binding === null) {
@@ -1878,21 +1921,10 @@ function evaluate_function(fn, binding, stack = new Set(), [...seen_bindings] =
18781921
binding.scope !== fn_scope &&
18791922
binding.updated &&
18801923
context.state.current_call === 0 &&
1881-
!seen_bindings.includes(binding)
1924+
!seen_bindings.includes(binding) &&
1925+
needs_check(true, false)
18821926
) {
1883-
let has_fn_scope = false;
1884-
/** @type {null | Scope} */
1885-
let curr = binding.scope;
1886-
while (curr !== null) {
1887-
curr = curr?.parent ?? null;
1888-
if (fn_scope === curr) {
1889-
has_fn_scope = true;
1890-
break;
1891-
}
1892-
}
1893-
if (!has_fn_scope) {
1894-
analysis.pure = false;
1895-
}
1927+
analysis.pure &&= fn_scope.contains(binding.scope);
18961928
seen_bindings.push(binding);
18971929
}
18981930
if (binding.kind === 'derived') {
@@ -1904,6 +1936,9 @@ function evaluate_function(fn, binding, stack = new Set(), [...seen_bindings] =
19041936
},
19051937
CallExpression: handle_call_expression,
19061938
NewExpression: handle_call_expression,
1939+
TaggedTemplateExpression(node, context) {
1940+
return handle_call_expression(b.call(node.tag, node.quasi), context);
1941+
},
19071942
ThrowStatement(node, context) {
19081943
if (
19091944
fn.type !== 'FunctionDeclaration' ||

0 commit comments

Comments
 (0)