From f4e9dd27c90f19fe285c82324059b9ffd865a884 Mon Sep 17 00:00:00 2001 From: "Alexis H. Munsayac" Date: Fri, 25 Oct 2024 12:39:31 +0800 Subject: [PATCH] Simplify `derefSignal` --- packages/core/babel/core/deref-memo.ts | 8 +- packages/core/babel/core/deref-signal.ts | 448 +++++++++++------------ 2 files changed, 213 insertions(+), 243 deletions(-) diff --git a/packages/core/babel/core/deref-memo.ts b/packages/core/babel/core/deref-memo.ts index b9b2282..4095da0 100644 --- a/packages/core/babel/core/deref-memo.ts +++ b/packages/core/babel/core/deref-memo.ts @@ -24,7 +24,7 @@ function transformGetter( const key = propertyParent.node.key; assert( t.isExpression(key), - unexpectedType(propertyParent, key.type, 'Identifier'), + unexpectedType(propertyParent.get('key'), key.type, 'Identifier'), ); const objectParent = getProperParentPath( propertyParent, @@ -47,9 +47,9 @@ function transformReferencePath( if (!(trueCallee && CALL_CTF.has(trueCallee.name))) { return true; } - const rawArgs = parent.node.arguments[0]; - const arg = unwrapNode(rawArgs, t.isIdentifier); - assert(arg, unexpectedType(parent, rawArgs.type, 'Identifier')); + const rawArgs = parent.get('arguments')[0]; + const arg = unwrapNode(rawArgs.node, t.isIdentifier); + assert(arg, unexpectedType(rawArgs, rawArgs.type, 'Identifier')); if (arg !== ref.node) { return true; } diff --git a/packages/core/babel/core/deref-signal.ts b/packages/core/babel/core/deref-signal.ts index 476b634..9c8f5b1 100644 --- a/packages/core/babel/core/deref-signal.ts +++ b/packages/core/babel/core/deref-signal.ts @@ -4,269 +4,239 @@ import assert from './assert'; import { unexpectedType } from './errors'; import isAwaited from './is-awaited'; import isYielded from './is-yielded'; -// import isInTypeScript from './is-in-typescript'; -import unwrapNode from './unwrap-node'; import { addProtoGetter, addProtoProperty, addProtoSetter } from './proto'; +// import isInTypeScript from './is-in-typescript'; +import unwrapNode, { getProperParentPath, isPathValid } from './unwrap-node'; const REF_SIGNAL_CTF = '$refSignal'; const GET_CTF = '$get'; const SET_CTF = '$set'; -const CALL_CTF = new Set([REF_SIGNAL_CTF, GET_CTF, SET_CTF]); - const GETTER_CTF = '$getter'; const SETTER_CTF = '$setter'; const PROPERTY_CTF = '$property'; -const OBJECT_PROPERTY_CTF = new Set([GETTER_CTF, SETTER_CTF, PROPERTY_CTF]); +const CALL_CTF = new Set([ + REF_SIGNAL_CTF, + GET_CTF, + SET_CTF, + GETTER_CTF, + SETTER_CTF, + PROPERTY_CTF, +]); -interface DerefSignalState { - current?: babel.NodePath; - prev?: babel.NodePath; +function transformProperty( + parent: babel.NodePath, + ctf: string, + readIdentifier: t.Identifier, + writeIdentifier: t.Identifier, +): boolean { + const propertyParent = getProperParentPath(parent, t.isObjectProperty); + if (!propertyParent) { + return true; + } + const key = propertyParent.node.key; + assert( + t.isExpression(key), + unexpectedType(propertyParent.get('key'), key.type, 'Identifier'), + ); + const objectParent = getProperParentPath( + propertyParent, + t.isObjectExpression, + ); + if (!objectParent) { + return true; + } + switch (ctf) { + case SETTER_CTF: + addProtoSetter(objectParent, propertyParent, key, writeIdentifier); + break; + case GETTER_CTF: + addProtoGetter(objectParent, propertyParent, key, writeIdentifier); + break; + case PROPERTY_CTF: + addProtoProperty( + objectParent, + propertyParent, + key, + readIdentifier, + writeIdentifier, + ); + break; + } + return false; } -export default function derefSignal( - path: babel.NodePath, - signalIdentifier: t.Identifier, +function transformSignalRead( + ref: babel.NodePath, readIdentifier: t.Identifier, writeIdentifier: t.Identifier, +): boolean { + const parent = getProperParentPath(ref, t.isCallExpression); + if (parent) { + const trueCallee = unwrapNode(parent.node.callee, t.isIdentifier); + if (!(trueCallee && CALL_CTF.has(trueCallee.name))) { + return true; + } + const rawArgs = parent.get('arguments')[0]; + const arg = unwrapNode(rawArgs.node, t.isIdentifier); + assert(arg, unexpectedType(rawArgs, rawArgs.type, 'Identifier')); + if (arg !== ref.node) { + return true; + } + switch (trueCallee.name) { + case REF_SIGNAL_CTF: + parent.replaceWith( + t.arrayExpression([readIdentifier, writeIdentifier]), + ); + break; + case SET_CTF: + parent.replaceWith(writeIdentifier); + break; + case GET_CTF: + parent.replaceWith(readIdentifier); + break; + case PROPERTY_CTF: + case SETTER_CTF: + case GETTER_CTF: + return transformProperty( + parent, + trueCallee.name, + readIdentifier, + writeIdentifier, + ); + } + return false; + } + return true; +} + +function transformUpdateExpression( + ref: babel.NodePath, + writeIdentifier: t.Identifier, ): void { - path.scope.path.traverse( - { - CallExpression(p) { - if ( - p.scope !== path.scope && - p.scope.hasOwnBinding(signalIdentifier.name) - ) { - return; - } - const trueCallee = unwrapNode(p.node.callee, t.isIdentifier); - if ( - !trueCallee || - p.scope.hasBinding(trueCallee.name) || - !CALL_CTF.has(trueCallee.name) - ) { - return; - } - const rawArgs = p.node.arguments[0]; - const arg = unwrapNode(rawArgs, t.isIdentifier); - assert(arg, unexpectedType(p, rawArgs.type, 'Identifier')); - if (arg.name !== signalIdentifier.name) { - return; - } - if (trueCallee.name === REF_SIGNAL_CTF) { - p.replaceWith(t.arrayExpression([readIdentifier, writeIdentifier])); - } - if (trueCallee.name === GET_CTF) { - p.replaceWith(readIdentifier); - } - if (trueCallee.name === SET_CTF) { - p.replaceWith(writeIdentifier); - } - }, - ObjectExpression: { - enter(p) { - this.prev = this.current; - this.current = p; - }, - exit() { - this.current = this.prev; - }, - }, - ObjectProperty(p) { - if ( - p.scope !== path.scope && - p.scope.hasOwnBinding(signalIdentifier.name) - ) { - return; - } - const currentValue = p.node.value; - const currentKey = p.node.key; - if (p.node.shorthand && !p.node.computed) { - if ( - t.isIdentifier(currentKey) && - t.isIdentifier(currentValue) && - currentKey.name === signalIdentifier.name && - currentValue.name === signalIdentifier.name - ) { - p.replaceWith( - t.objectProperty( - currentKey, - t.callExpression(readIdentifier, []), - ), - ); - } - return; - } - const trueCallExpr = unwrapNode(currentValue, t.isCallExpression); - if (trueCallExpr) { - const trueCallee = unwrapNode(trueCallExpr.callee, t.isIdentifier); - if (!trueCallee || !OBJECT_PROPERTY_CTF.has(trueCallee.name)) { - return; - } - assert( - !t.isPrivateName(currentKey), - unexpectedType(p, 'PrivateName', 'Expression'), - ); - const arg = trueCallExpr.arguments[0]; - assert( - t.isIdentifier(arg), - unexpectedType(p, arg.type, 'Identifier'), - ); - if (arg.name !== signalIdentifier.name) { - return; - } - if (this.current) { - switch (trueCallee.name) { - case GETTER_CTF: - addProtoGetter(this.current, p, currentKey, readIdentifier); - break; - case SETTER_CTF: - addProtoSetter(this.current, p, currentKey, writeIdentifier); - break; - case PROPERTY_CTF: - addProtoProperty( - this.current, - p, - currentKey, - readIdentifier, - writeIdentifier, - ); - break; - default: - break; - } - } - } - }, - Expression(p) { - if ( - p.scope !== path.scope && - p.scope.hasOwnBinding(signalIdentifier.name) - ) { - return; - } - if ( - t.isIdentifier(p.node) && - // && !isInTypeScript(p) - p.node.name === signalIdentifier.name - ) { - p.replaceWith(t.callExpression(readIdentifier, [])); - } - }, - UpdateExpression(p) { - if ( - p.scope !== path.scope && - p.scope.hasOwnBinding(signalIdentifier.name) - ) { - return; - } - if ( - t.isIdentifier(p.node.argument) && - p.node.argument.name === signalIdentifier.name - ) { - const param = p.scope.generateUidIdentifier('current'); - if (p.node.prefix) { - p.replaceWith( + const param = ref.scope.generateUidIdentifier('current'); + if (ref.node.prefix) { + const tmp = ref.scope.generateUidIdentifier('tmp'); + ref.replaceWith( + t.callExpression( + t.arrowFunctionExpression( + [], + t.blockStatement([ + t.variableDeclaration('let', [t.variableDeclarator(tmp)]), + t.expressionStatement( t.callExpression(writeIdentifier, [ t.arrowFunctionExpression( [param], t.binaryExpression( - p.node.operator === '++' ? '+' : '-', - param, + ref.node.operator === '++' ? '+' : '-', + t.assignmentExpression('=', tmp, param), t.numericLiteral(1), ), ), ]), - ); - } else { - p.replaceWith( - t.callExpression( - t.arrowFunctionExpression( - [], - t.blockStatement([ - t.variableDeclaration('const', [ - t.variableDeclarator( - param, - t.callExpression(readIdentifier, []), - ), - ]), - t.expressionStatement( - t.callExpression(writeIdentifier, [ - t.arrowFunctionExpression( - [], - t.binaryExpression( - p.node.operator === '++' ? '+' : '-', - param, - t.numericLiteral(1), - ), - ), - ]), - ), - t.returnStatement(param), - ]), - ), - [], - ), - ); - } - } - }, - AssignmentExpression(p) { - if ( - p.scope !== path.scope && - p.scope.hasOwnBinding(signalIdentifier.name) - ) { - return; - } - const identifier = p.node.left; - let expression = p.node.right; - if (isAwaited(expression) || isYielded(expression)) { - const statement = p.getStatementParent(); - const functionParent = p.getFunctionParent(); - if (statement) { - const awaitedID = statement.scope.generateUidIdentifier('tmp'); - const declaration = t.variableDeclaration('const', [ - t.variableDeclarator(awaitedID, expression), - ]); + ), + t.returnStatement(tmp), + ]), + ), + [], + ), + ); + } else { + ref.replaceWith( + t.callExpression(writeIdentifier, [ + t.arrowFunctionExpression( + [param], + t.binaryExpression( + ref.node.operator === '++' ? '+' : '-', + param, + t.numericLiteral(1), + ), + ), + ]), + ); + } +} - if (functionParent) { - if (functionParent.isAncestor(statement)) { - statement.insertBefore(declaration); - } else { - functionParent.scope.push({ - id: awaitedID, - init: expression, - kind: 'const', - }); - } - } else { - statement.insertBefore(declaration); - } - expression = awaitedID; - } - } - if ( - t.isIdentifier(identifier) && - identifier.name === signalIdentifier.name - ) { - let arg: t.Expression; - if (p.node.operator === '=') { - arg = t.arrowFunctionExpression([], expression); - } else { - const param = p.scope.generateUidIdentifier('current'); - arg = t.arrowFunctionExpression( - [param], - t.assignmentExpression(p.node.operator, param, expression), - ); - } - p.replaceWith(t.callExpression(writeIdentifier, [arg])); - } - }, - }, - { - prev: undefined, - current: undefined, - }, +function transformAssignmentExpression( + ref: babel.NodePath, + writeIdentifier: t.Identifier, +): void { + assert( + t.isIdentifier(ref.node.left), + unexpectedType(ref.get('left'), ref.node.left.type, 'Identifier'), ); + let expression = ref.node.right; + if (isAwaited(expression) || isYielded(expression)) { + const statement = ref.getStatementParent(); + const functionParent = ref.getFunctionParent(); + if (statement) { + const awaitedID = statement.scope.generateUidIdentifier('tmp'); + const declaration = t.variableDeclaration('const', [ + t.variableDeclarator(awaitedID, expression), + ]); + + if (functionParent) { + if (functionParent.isAncestor(statement)) { + statement.insertBefore(declaration); + } else { + functionParent.scope.push({ + id: awaitedID, + init: expression, + kind: 'const', + }); + } + } else { + statement.insertBefore(declaration); + } + expression = awaitedID; + } + } + let arg: t.Expression; + if (ref.node.operator === '=') { + arg = t.arrowFunctionExpression([], expression); + } else { + const param = ref.scope.generateUidIdentifier('current'); + arg = t.arrowFunctionExpression( + [param], + t.assignmentExpression(ref.node.operator, param, expression), + ); + } + ref.replaceWith(t.callExpression(writeIdentifier, [arg])); +} + +function transformSignalWrite( + ref: babel.NodePath, + writeIdentifier: t.Identifier, +): void { + if (isPathValid(ref, t.isUpdateExpression)) { + transformUpdateExpression(ref, writeIdentifier); + return; + } + if (isPathValid(ref, t.isAssignmentExpression)) { + transformAssignmentExpression(ref, writeIdentifier); + return; + } +} + +export default function derefSignal( + path: babel.NodePath, + signalIdentifier: t.Identifier, + readIdentifier: t.Identifier, + writeIdentifier: t.Identifier, +): void { + const binding = path.scope.getBinding(signalIdentifier.name); + if (!binding) { + return; + } + // Transform all writes + for (const ref of binding.constantViolations) { + transformSignalWrite(ref, writeIdentifier); + } + // Transform all reads + for (const ref of binding.referencePaths) { + if (transformSignalRead(ref, readIdentifier, writeIdentifier)) { + ref.replaceWith(t.callExpression(readIdentifier, [])); + } + } }