@@ -476,6 +476,8 @@ export class Compiler extends DiagnosticEmitter {
476476 hasCustomFunctionExports : bool = false ;
477477 /** Whether the module would use the exported runtime to lift/lower. */
478478 desiresExportRuntime : bool = false ;
479+ /** Unique suffix for synthesized scoped locals used by compound assignments. */
480+ private compoundAssignmentTempId : i32 = 0 ;
479481
480482 /** Compiles a {@link Program} to a {@link Module} using the specified options. */
481483 static compile ( program : Program ) : Module {
@@ -4473,7 +4475,121 @@ export class Compiler extends DiagnosticEmitter {
44734475 expr = this . makeAnd ( leftExpr , rightExpr , commonType ) ;
44744476 break ;
44754477 }
4476- case Token . Bar_Equals : compound = true ;
4478+ case Token . Bar_Equals : {
4479+ compound = true ;
4480+ let assignmentLeft = left ;
4481+ let setupExprs : ExpressionRef [ ] | null = null ;
4482+ if ( this . needsCompoundAssignmentSideEffectCache ( left ) ) {
4483+ let flow = this . currentFlow ;
4484+ if ( left . kind == NodeKind . PropertyAccess ) {
4485+ let access = < PropertyAccessExpression > left ;
4486+ let receiverExpression = access . expression ;
4487+ if ( this . expressionHasSideEffects ( receiverExpression ) ) {
4488+ let receiverExpr = this . compileExpression ( receiverExpression , Type . auto ) ;
4489+ let receiverType = this . currentType ;
4490+ let receiverTemp = flow . getTempLocal ( receiverType ) ;
4491+ let receiverName = this . makeCompoundAssignmentTempName ( flow ) ;
4492+ flow . addScopedAlias ( receiverName , receiverType , receiverTemp . index ) ;
4493+ flow . setLocalFlag ( receiverTemp . index , LocalFlags . Initialized ) ;
4494+ setupExprs = [ module . local_set ( receiverTemp . index , receiverExpr , receiverType . isManaged ) ] ;
4495+ assignmentLeft = Node . createPropertyAccessExpression (
4496+ Node . createIdentifierExpression ( receiverName , receiverExpression . range ) ,
4497+ access . property ,
4498+ access . range
4499+ ) ;
4500+ }
4501+ } else {
4502+ let access = < ElementAccessExpression > left ;
4503+ let receiverExpression = access . expression ;
4504+ let elementExpression = access . elementExpression ;
4505+ let receiverName : string | null = null ;
4506+ let elementName : string | null = null ;
4507+
4508+ if ( this . expressionHasSideEffects ( receiverExpression ) ) {
4509+ let receiverExpr = this . compileExpression ( receiverExpression , Type . auto ) ;
4510+ let receiverType = this . currentType ;
4511+ let receiverTemp = flow . getTempLocal ( receiverType ) ;
4512+ receiverName = this . makeCompoundAssignmentTempName ( flow ) ;
4513+ flow . addScopedAlias ( receiverName , receiverType , receiverTemp . index ) ;
4514+ flow . setLocalFlag ( receiverTemp . index , LocalFlags . Initialized ) ;
4515+ if ( ! setupExprs ) setupExprs = [ ] ;
4516+ setupExprs . push ( module . local_set ( receiverTemp . index , receiverExpr , receiverType . isManaged ) ) ;
4517+ }
4518+
4519+ if ( this . expressionHasSideEffects ( elementExpression ) ) {
4520+ let elementExpr = this . compileExpression ( elementExpression , Type . auto ) ;
4521+ let elementType = this . currentType ;
4522+ let elementTemp = flow . getTempLocal ( elementType ) ;
4523+ elementName = this . makeCompoundAssignmentTempName ( flow ) ;
4524+ flow . addScopedAlias ( elementName , elementType , elementTemp . index ) ;
4525+ flow . setLocalFlag ( elementTemp . index , LocalFlags . Initialized ) ;
4526+ if ( ! setupExprs ) setupExprs = [ ] ;
4527+ setupExprs . push ( module . local_set ( elementTemp . index , elementExpr , elementType . isManaged ) ) ;
4528+ }
4529+
4530+ assignmentLeft = Node . createElementAccessExpression (
4531+ receiverName
4532+ ? Node . createIdentifierExpression ( receiverName , receiverExpression . range )
4533+ : receiverExpression ,
4534+ elementName
4535+ ? Node . createIdentifierExpression ( elementName , elementExpression . range )
4536+ : elementExpression ,
4537+ access . range
4538+ ) ;
4539+ }
4540+ }
4541+
4542+ leftExpr = this . compileExpression ( assignmentLeft , contextualType . intType ) ;
4543+ leftType = this . currentType ;
4544+ if ( setupExprs ) {
4545+ let wrappedExprs = setupExprs ;
4546+ wrappedExprs . push ( leftExpr ) ;
4547+ leftExpr = module . block ( null , wrappedExprs , leftType . toRef ( ) ) ;
4548+ }
4549+
4550+ // check operator overload
4551+ let classReference = leftType . getClassOrWrapper ( this . program ) ;
4552+ if ( classReference ) {
4553+ let overload = classReference . lookupOverload ( OperatorKind . BitwiseOr ) ;
4554+ if ( overload ) {
4555+ expr = this . compileBinaryOverload ( overload , assignmentLeft , leftExpr , leftType , right , expression ) ;
4556+ break ;
4557+ }
4558+ }
4559+
4560+ if ( ! leftType . isIntegerValue ) {
4561+ this . error (
4562+ DiagnosticCode . The_0_operator_cannot_be_applied_to_type_1 ,
4563+ expression . range , "|" , leftType . toString ( )
4564+ ) ;
4565+ return module . unreachable ( ) ;
4566+ }
4567+ rightExpr = this . compileExpression ( right , leftType , Constraints . ConvImplicit ) ;
4568+ rightType = commonType = this . currentType ;
4569+ expr = this . makeOr ( leftExpr , rightExpr , commonType ) ;
4570+
4571+ let resolver = this . resolver ;
4572+ let target = resolver . lookupExpression ( assignmentLeft , this . currentFlow ) ;
4573+ if ( ! target ) return module . unreachable ( ) ;
4574+ let targetType = resolver . getTypeOfElement ( target ) ;
4575+ if ( ! targetType ) targetType = Type . void ;
4576+ if ( ! this . currentType . isStrictlyAssignableTo ( targetType ) ) {
4577+ this . error (
4578+ DiagnosticCode . Type_0_is_not_assignable_to_type_1 ,
4579+ expression . range , this . currentType . toString ( ) , targetType . toString ( )
4580+ ) ;
4581+ return module . unreachable ( ) ;
4582+ }
4583+ return this . makeAssignment (
4584+ target ,
4585+ expr ,
4586+ this . currentType ,
4587+ right ,
4588+ resolver . currentThisExpression ,
4589+ resolver . currentElementExpression ,
4590+ contextualType != Type . void
4591+ ) ;
4592+ }
44774593 case Token . Bar : {
44784594 leftExpr = this . compileExpression ( left , contextualType . intType ) ;
44794595 leftType = this . currentType ;
@@ -4790,6 +4906,95 @@ export class Compiler extends DiagnosticEmitter {
47904906 ) ;
47914907 }
47924908
4909+ private makeCompoundAssignmentTempName ( flow : Flow ) : string {
4910+ let name : string ;
4911+ do {
4912+ let id = this . compoundAssignmentTempId ++ ;
4913+ name = "__as_or_assign_tmp" + id . toString ( ) ;
4914+ } while ( flow . lookup ( name ) ) ;
4915+ return name ;
4916+ }
4917+
4918+ private needsCompoundAssignmentSideEffectCache ( target : Expression ) : bool {
4919+ if ( target . kind == NodeKind . PropertyAccess ) {
4920+ return this . expressionHasSideEffects ( ( < PropertyAccessExpression > target ) . expression ) ;
4921+ }
4922+ if ( target . kind == NodeKind . ElementAccess ) {
4923+ let access = < ElementAccessExpression > target ;
4924+ return this . expressionHasSideEffects ( access . expression )
4925+ || this . expressionHasSideEffects ( access . elementExpression ) ;
4926+ }
4927+ return false ;
4928+ }
4929+
4930+ private expressionHasSideEffects ( expression : Expression ) : bool {
4931+ while ( expression . kind == NodeKind . Parenthesized ) {
4932+ expression = ( < ParenthesizedExpression > expression ) . expression ;
4933+ }
4934+ switch ( expression . kind ) {
4935+ case NodeKind . Call :
4936+ case NodeKind . New :
4937+ case NodeKind . UnaryPostfix :
4938+ case NodeKind . Class :
4939+ case NodeKind . Function :
4940+ case NodeKind . Compiled :
4941+ return true ;
4942+ case NodeKind . UnaryPrefix : {
4943+ let unary = < UnaryPrefixExpression > expression ;
4944+ if ( unary . operator == Token . Plus_Plus || unary . operator == Token . Minus_Minus ) {
4945+ return true ;
4946+ }
4947+ return this . expressionHasSideEffects ( unary . operand ) ;
4948+ }
4949+ case NodeKind . Binary : {
4950+ let binary = < BinaryExpression > expression ;
4951+ switch ( binary . operator ) {
4952+ case Token . Equals :
4953+ case Token . Plus_Equals :
4954+ case Token . Minus_Equals :
4955+ case Token . Asterisk_Equals :
4956+ case Token . Asterisk_Asterisk_Equals :
4957+ case Token . Slash_Equals :
4958+ case Token . Percent_Equals :
4959+ case Token . LessThan_LessThan_Equals :
4960+ case Token . GreaterThan_GreaterThan_Equals :
4961+ case Token . GreaterThan_GreaterThan_GreaterThan_Equals :
4962+ case Token . Ampersand_Equals :
4963+ case Token . Bar_Equals :
4964+ case Token . Caret_Equals :
4965+ return true ;
4966+ default :
4967+ return this . expressionHasSideEffects ( binary . left )
4968+ || this . expressionHasSideEffects ( binary . right ) ;
4969+ }
4970+ }
4971+ case NodeKind . Assertion :
4972+ return this . expressionHasSideEffects ( ( < AssertionExpression > expression ) . expression ) ;
4973+ case NodeKind . Comma : {
4974+ let expressions = ( < CommaExpression > expression ) . expressions ;
4975+ for ( let i = 0 , k = expressions . length ; i < k ; ++ i ) {
4976+ if ( this . expressionHasSideEffects ( unchecked ( expressions [ i ] ) ) ) return true ;
4977+ }
4978+ return false ;
4979+ }
4980+ case NodeKind . ElementAccess : {
4981+ let access = < ElementAccessExpression > expression ;
4982+ return this . expressionHasSideEffects ( access . expression )
4983+ || this . expressionHasSideEffects ( access . elementExpression ) ;
4984+ }
4985+ case NodeKind . PropertyAccess :
4986+ return this . expressionHasSideEffects ( ( < PropertyAccessExpression > expression ) . expression ) ;
4987+ case NodeKind . Ternary : {
4988+ let ternary = < TernaryExpression > expression ;
4989+ return this . expressionHasSideEffects ( ternary . condition )
4990+ || this . expressionHasSideEffects ( ternary . ifThen )
4991+ || this . expressionHasSideEffects ( ternary . ifElse ) ;
4992+ }
4993+ default :
4994+ return false ;
4995+ }
4996+ }
4997+
47934998 makeLt ( leftExpr : ExpressionRef , rightExpr : ExpressionRef , type : Type ) : ExpressionRef {
47944999 // Cares about garbage bits and signedness
47955000 let module = this . module ;
0 commit comments