11import * as ts from "typescript" ;
22import * as lua from "../../../LuaAST" ;
3- import { cast } from "../../../utils" ;
3+ import { cast , assertNever } from "../../../utils" ;
44import { TransformationContext } from "../../context" ;
55import { createImmediatelyInvokedFunctionExpression } from "../../utils/lua-ast" ;
66import { isArrayType , isExpressionWithEvaluationEffect } from "../../utils/typescript" ;
@@ -46,9 +46,12 @@ type CompoundAssignmentToken =
4646 | ts . SyntaxKind . AsteriskAsteriskToken
4747 | ts . SyntaxKind . LessThanLessThanToken
4848 | ts . SyntaxKind . GreaterThanGreaterThanToken
49- | ts . SyntaxKind . GreaterThanGreaterThanGreaterThanToken ;
49+ | ts . SyntaxKind . GreaterThanGreaterThanGreaterThanToken
50+ | ts . SyntaxKind . BarBarToken
51+ | ts . SyntaxKind . AmpersandAmpersandToken
52+ | ts . SyntaxKind . QuestionQuestionToken ;
5053
51- const compoundToAssignmentTokens : Partial < Record < ts . CompoundAssignmentOperator , CompoundAssignmentToken > > = {
54+ const compoundToAssignmentTokens : Record < ts . CompoundAssignmentOperator , CompoundAssignmentToken > = {
5255 [ ts . SyntaxKind . BarEqualsToken ] : ts . SyntaxKind . BarToken ,
5356 [ ts . SyntaxKind . PlusEqualsToken ] : ts . SyntaxKind . PlusToken ,
5457 [ ts . SyntaxKind . CaretEqualsToken ] : ts . SyntaxKind . CaretToken ,
@@ -61,14 +64,16 @@ const compoundToAssignmentTokens: Partial<Record<ts.CompoundAssignmentOperator,
6164 [ ts . SyntaxKind . LessThanLessThanEqualsToken ] : ts . SyntaxKind . LessThanLessThanToken ,
6265 [ ts . SyntaxKind . GreaterThanGreaterThanEqualsToken ] : ts . SyntaxKind . GreaterThanGreaterThanToken ,
6366 [ ts . SyntaxKind . GreaterThanGreaterThanGreaterThanEqualsToken ] : ts . SyntaxKind . GreaterThanGreaterThanGreaterThanToken ,
67+ [ ts . SyntaxKind . BarBarEqualsToken ] : ts . SyntaxKind . BarBarToken ,
68+ [ ts . SyntaxKind . AmpersandAmpersandEqualsToken ] : ts . SyntaxKind . AmpersandAmpersandToken ,
69+ [ ts . SyntaxKind . QuestionQuestionEqualsToken ] : ts . SyntaxKind . QuestionQuestionToken ,
6470} ;
6571
6672export const isCompoundAssignmentToken = ( token : ts . BinaryOperator ) : token is ts . CompoundAssignmentOperator =>
6773 token in compoundToAssignmentTokens ;
6874
69- export const unwrapCompoundAssignmentToken = (
70- token : ts . CompoundAssignmentOperator
71- ) : CompoundAssignmentToken | undefined => compoundToAssignmentTokens [ token ] ;
75+ export const unwrapCompoundAssignmentToken = ( token : ts . CompoundAssignmentOperator ) : CompoundAssignmentToken =>
76+ compoundToAssignmentTokens [ token ] ;
7277
7378export function transformCompoundAssignmentExpression (
7479 context : TransformationContext ,
@@ -139,6 +144,15 @@ export function transformCompoundAssignmentExpression(
139144 const operatorExpression = transformBinaryOperation ( context , left , right , operator , expression ) ;
140145 const tmpDeclaration = lua . createVariableDeclarationStatement ( tmpIdentifier , operatorExpression ) ;
141146 const assignStatements = transformAssignment ( context , lhs , tmpIdentifier ) ;
147+
148+ if ( isSetterSkippingCompoundAssignmentOperator ( operator ) ) {
149+ return createImmediatelyInvokedFunctionExpression (
150+ [ tmpDeclaration , ...transformSetterSkippingCompoundAssignment ( context , tmpIdentifier , operator , rhs ) ] ,
151+ tmpIdentifier ,
152+ expression
153+ ) ;
154+ }
155+
142156 return createImmediatelyInvokedFunctionExpression (
143157 [ tmpDeclaration , ...assignStatements ] ,
144158 tmpIdentifier ,
@@ -175,13 +189,74 @@ export function transformCompoundAssignmentStatement(
175189 [ context . transformExpression ( objExpression ) , context . transformExpression ( indexExpression ) ]
176190 ) ;
177191 const accessExpression = lua . createTableIndexExpression ( obj , index ) ;
192+
193+ if ( isSetterSkippingCompoundAssignmentOperator ( operator ) ) {
194+ return [
195+ objAndIndexDeclaration ,
196+ ...transformSetterSkippingCompoundAssignment ( context , accessExpression , operator , rhs , node ) ,
197+ ] ;
198+ }
199+
178200 const operatorExpression = transformBinaryOperation ( context , accessExpression , right , operator , node ) ;
179201 const assignStatement = lua . createAssignmentStatement ( accessExpression , operatorExpression ) ;
180202 return [ objAndIndexDeclaration , assignStatement ] ;
181203 } else {
204+ if ( isSetterSkippingCompoundAssignmentOperator ( operator ) ) {
205+ const luaLhs = context . transformExpression ( lhs ) as lua . AssignmentLeftHandSideExpression ;
206+ return transformSetterSkippingCompoundAssignment ( context , luaLhs , operator , rhs , node ) ;
207+ }
208+
182209 // Simple statements
183210 // ${left} = ${left} ${replacementOperator} ${right}
184211 const operatorExpression = transformBinaryOperation ( context , left , right , operator , node ) ;
185212 return transformAssignment ( context , lhs , operatorExpression ) ;
186213 }
187214}
215+
216+ /* These setter-skipping operators will not execute the setter if result does not change.
217+ * x.y ||= z does NOT call the x.y setter if x.y is already true.
218+ * x.y &&= z does NOT call the x.y setter if x.y is already false.
219+ * x.y ??= z does NOT call the x.y setter if x.y is already not nullish.
220+ */
221+ type SetterSkippingCompoundAssignmentOperator = ts . LogicalOperator | ts . SyntaxKind . QuestionQuestionToken ;
222+
223+ function isSetterSkippingCompoundAssignmentOperator (
224+ operator : ts . BinaryOperator
225+ ) : operator is SetterSkippingCompoundAssignmentOperator {
226+ return (
227+ operator === ts . SyntaxKind . AmpersandAmpersandToken ||
228+ operator === ts . SyntaxKind . BarBarToken ||
229+ operator === ts . SyntaxKind . QuestionQuestionToken
230+ ) ;
231+ }
232+
233+ function transformSetterSkippingCompoundAssignment (
234+ context : TransformationContext ,
235+ lhs : lua . AssignmentLeftHandSideExpression ,
236+ operator : SetterSkippingCompoundAssignmentOperator ,
237+ rhs : ts . Expression ,
238+ node ?: ts . Node
239+ ) : lua . Statement [ ] {
240+ // These assignments have the form 'if x then y = z', figure out what condition x is first.
241+ let condition : lua . Expression ;
242+
243+ if ( operator === ts . SyntaxKind . AmpersandAmpersandToken ) {
244+ condition = lhs ;
245+ } else if ( operator === ts . SyntaxKind . BarBarToken ) {
246+ condition = lua . createUnaryExpression ( lhs , lua . SyntaxKind . NotOperator ) ;
247+ } else if ( operator === ts . SyntaxKind . QuestionQuestionToken ) {
248+ condition = lua . createBinaryExpression ( lhs , lua . createNilLiteral ( ) , lua . SyntaxKind . EqualityOperator ) ;
249+ } else {
250+ assertNever ( operator ) ;
251+ }
252+
253+ // if condition then lhs = rhs end
254+ return [
255+ lua . createIfStatement (
256+ condition ,
257+ lua . createBlock ( [ lua . createAssignmentStatement ( lhs , context . transformExpression ( rhs ) ) ] ) ,
258+ undefined ,
259+ node
260+ ) ,
261+ ] ;
262+ }
0 commit comments