55 * LICENSE file in the root directory of this source tree.
66 */
77
8+ import prettyFormat from 'pretty-format' ;
89import { CompilerError } from '../CompilerError' ;
910import {
1011 areEqualPaths ,
@@ -17,25 +18,29 @@ import {
1718 isObjectMethodType ,
1819 isRefValueType ,
1920 isUseRefType ,
21+ LoadLocal ,
2022 makeInstructionId ,
2123 Place ,
2224 PrunedReactiveScopeBlock ,
2325 ReactiveFunction ,
2426 ReactiveInstruction ,
27+ ReactiveOptionalCallValue ,
2528 ReactiveScope ,
2629 ReactiveScopeBlock ,
2730 ReactiveScopeDependency ,
2831 ReactiveTerminalStatement ,
2932 ReactiveValue ,
3033 ScopeId ,
3134} from '../HIR/HIR' ;
35+ import { printIdentifier , printPlace } from '../HIR/PrintHIR' ;
3236import { eachInstructionValueOperand , eachPatternOperand } from '../HIR/visitors' ;
3337import { empty , Stack } from '../Utils/Stack' ;
3438import { assertExhaustive , Iterable_some } from '../Utils/utils' ;
3539import {
3640 ReactiveScopeDependencyTree ,
3741 ReactiveScopePropertyDependency ,
3842} from './DeriveMinimalDependencies' ;
43+ import { printDependency , printReactiveFunction } from './PrintReactiveFunction' ;
3944import { ReactiveFunctionVisitor , visitReactiveFunction } from './visitors' ;
4045
4146/*
@@ -302,6 +307,7 @@ class Context {
302307 #properties: Map < Identifier , ReactiveScopePropertyDependency > = new Map ( ) ;
303308 #temporaries: Map < Identifier , Place > = new Map ( ) ;
304309 #inConditionalWithinScope: boolean = false ;
310+ inOptional : boolean = false ;
305311 /*
306312 * Reactive dependencies used unconditionally in the current conditional.
307313 * Composed of dependencies:
@@ -465,6 +471,7 @@ class Context {
465471 #getProperty(
466472 object : Place ,
467473 property : string ,
474+ optional : boolean ,
468475 ) : ReactiveScopePropertyDependency {
469476 const resolvedObject = this . resolveTemporary ( object ) ;
470477 const resolvedDependency = this . #properties. get ( resolvedObject . identifier ) ;
@@ -485,13 +492,18 @@ class Context {
485492 } ;
486493 }
487494
488- objectDependency . path . push ( { property, optional : false } ) ;
495+ objectDependency . path . push ( { property, optional} ) ;
489496
490497 return objectDependency ;
491498 }
492499
493- declareProperty ( lvalue : Place , object : Place , property : string ) : void {
494- const nextDependency = this . #getProperty( object , property ) ;
500+ declareProperty (
501+ lvalue : Place ,
502+ object : Place ,
503+ property : string ,
504+ optional : boolean ,
505+ ) : void {
506+ const nextDependency = this . #getProperty( object , property , optional ) ;
495507 this . #properties. set ( lvalue . identifier , nextDependency ) ;
496508 }
497509
@@ -571,8 +583,8 @@ class Context {
571583 this . visitDependency ( dependency ) ;
572584 }
573585
574- visitProperty ( object : Place , property : string ) : void {
575- const nextDependency = this . #getProperty( object , property ) ;
586+ visitProperty ( object : Place , property : string , optional : boolean ) : void {
587+ const nextDependency = this . #getProperty( object , property , optional ) ;
576588 this . visitDependency ( nextDependency ) ;
577589 }
578590
@@ -744,51 +756,102 @@ class PropagationVisitor extends ReactiveFunctionVisitor<Context> {
744756 } ) ;
745757 }
746758
759+ visitOptionalExpression (
760+ context : Context ,
761+ id : InstructionId ,
762+ value : ReactiveOptionalCallValue ,
763+ lvalue : Place | null ,
764+ ) : void {
765+ const wasWithinOptional = context . inOptional ;
766+ const isOptional = wasWithinOptional || value . optional ;
767+ const inner = value . value ;
768+ /*
769+ * OptionalExpression value is a SequenceExpression where the instructions
770+ * represent the code prior to the `?` and the final value represents the
771+ * conditional code that follows.
772+ */
773+ CompilerError . invariant ( inner . kind === 'SequenceExpression' , {
774+ reason : 'Expected OptionalExpression value to be a SequenceExpression' ,
775+ description : `Found a \`${ value . kind } \`` ,
776+ loc : value . loc ,
777+ } ) ;
778+ // Instructions are the unconditionally executed portion before the `?`
779+ context . inOptional = isOptional ;
780+ for ( const instr of inner . instructions ) {
781+ this . visitInstruction ( instr , context ) ;
782+ }
783+ context . inOptional = wasWithinOptional ;
784+ const innerValue = inner . value ;
785+ if (
786+ innerValue . kind !== 'SequenceExpression' ||
787+ innerValue . value . kind !== 'LoadLocal'
788+ ) {
789+ CompilerError . invariant ( false , {
790+ reason :
791+ 'Expected OptionalExpression inner value to be a SequenceExpression' ,
792+ description : `Found a \`${ innerValue . kind } \`` ,
793+ loc : innerValue . loc ,
794+ } ) ;
795+ }
796+ if (
797+ isOptional &&
798+ innerValue . instructions . length === 1 &&
799+ innerValue . instructions [ 0 ] . value . kind === 'PropertyLoad' &&
800+ innerValue . instructions [ 0 ] . lvalue !== null &&
801+ innerValue . instructions [ 0 ] . lvalue . identifier . id ===
802+ innerValue . value . place . identifier . id
803+ ) {
804+ const propertyLoad = innerValue . instructions [ 0 ] . value ;
805+ if ( lvalue !== null && ! context . isUsedOutsideDeclaringScope ( lvalue ) ) {
806+ context . declareProperty (
807+ lvalue ,
808+ propertyLoad . object ,
809+ propertyLoad . property ,
810+ isOptional ,
811+ ) ;
812+ } else {
813+ context . visitProperty (
814+ propertyLoad . object ,
815+ propertyLoad . property ,
816+ isOptional ,
817+ ) ;
818+ }
819+ if ( isOptional && ! wasWithinOptional && lvalue !== null ) {
820+ context . visitOperand ( lvalue ) ;
821+ }
822+ return ;
823+ }
824+ context . enterConditional ( ( ) => {
825+ this . visitReactiveValue ( context , id , innerValue , lvalue ) ;
826+ } ) ;
827+ }
828+
747829 visitReactiveValue (
748830 context : Context ,
749831 id : InstructionId ,
750832 value : ReactiveValue ,
833+ lvalue : Place | null ,
751834 ) : void {
752835 switch ( value . kind ) {
753836 case 'OptionalExpression' : {
754- const inner = value . value ;
755- /*
756- * OptionalExpression value is a SequenceExpression where the instructions
757- * represent the code prior to the `?` and the final value represents the
758- * conditional code that follows.
759- */
760- CompilerError . invariant ( inner . kind === 'SequenceExpression' , {
761- reason :
762- 'Expected OptionalExpression value to be a SequenceExpression' ,
763- description : `Found a \`${ value . kind } \`` ,
764- loc : value . loc ,
765- suggestions : null ,
766- } ) ;
767- // Instructions are the unconditionally executed portion before the `?`
768- for ( const instr of inner . instructions ) {
769- this . visitInstruction ( instr , context ) ;
770- }
771- // The final value is the conditional portion following the `?`
772- context . enterConditional ( ( ) => {
773- this . visitReactiveValue ( context , id , inner . value ) ;
774- } ) ;
837+ this . visitOptionalExpression ( context , id , value , lvalue ) ;
775838 break ;
776839 }
777840 case 'LogicalExpression' : {
778- this . visitReactiveValue ( context , id , value . left ) ;
841+ this . visitReactiveValue ( context , id , value . left , null ) ;
779842 context . enterConditional ( ( ) => {
780- this . visitReactiveValue ( context , id , value . right ) ;
843+ this . visitReactiveValue ( context , id , value . right , lvalue ) ;
781844 } ) ;
782845 break ;
783846 }
784847 case 'ConditionalExpression' : {
785- this . visitReactiveValue ( context , id , value . test ) ;
848+ this . visitReactiveValue ( context , id , value . test , null ) ;
786849
787850 const consequentDeps = context . enterConditional ( ( ) => {
788- this . visitReactiveValue ( context , id , value . consequent ) ;
851+ this . visitReactiveValue ( context , id , value . consequent , null ) ;
789852 } ) ;
790853 const alternateDeps = context . enterConditional ( ( ) => {
791- this . visitReactiveValue ( context , id , value . alternate ) ;
854+ this . visitReactiveValue ( context , id , value . alternate , null ) ;
792855 } ) ;
793856 context . promoteDepsFromExhaustiveConditionals ( [
794857 consequentDeps ,
@@ -797,10 +860,34 @@ class PropagationVisitor extends ReactiveFunctionVisitor<Context> {
797860 break ;
798861 }
799862 case 'SequenceExpression' : {
863+ /**
864+ * Sequence
865+ * <lvalue> = Sequence <-- we're here
866+ * <lvalue> = ...
867+ * LoadLocal <lvalue>
868+ */
869+ if (
870+ lvalue !== null &&
871+ value . instructions . length === 1 &&
872+ value . value . kind === 'LoadLocal' &&
873+ value . value . place . identifier . id === lvalue . identifier . id &&
874+ value . instructions [ 0 ] . lvalue !== null &&
875+ value . instructions [ 0 ] . lvalue . identifier . id ===
876+ value . value . place . identifier . id
877+ ) {
878+ this . visitInstructionValue (
879+ context ,
880+ value . instructions [ 0 ] . id ,
881+ value . instructions [ 0 ] . value ,
882+ lvalue ,
883+ ) ;
884+ break ;
885+ }
886+
800887 for ( const instr of value . instructions ) {
801888 this . visitInstruction ( instr , context ) ;
802889 }
803- this . visitInstructionValue ( context , id , value . value , null ) ;
890+ this . visitInstructionValue ( context , id , value . value , lvalue ) ;
804891 break ;
805892 }
806893 case 'FunctionExpression' : {
@@ -851,9 +938,9 @@ class PropagationVisitor extends ReactiveFunctionVisitor<Context> {
851938 }
852939 } else if ( value . kind === 'PropertyLoad' ) {
853940 if ( lvalue !== null && ! context . isUsedOutsideDeclaringScope ( lvalue ) ) {
854- context . declareProperty ( lvalue , value . object , value . property ) ;
941+ context . declareProperty ( lvalue , value . object , value . property , false ) ;
855942 } else {
856- context . visitProperty ( value . object , value . property ) ;
943+ context . visitProperty ( value . object , value . property , false ) ;
857944 }
858945 } else if ( value . kind === 'StoreLocal' ) {
859946 context . visitOperand ( value . value ) ;
@@ -896,7 +983,7 @@ class PropagationVisitor extends ReactiveFunctionVisitor<Context> {
896983 } ) ;
897984 }
898985 } else {
899- this . visitReactiveValue ( context , id , value ) ;
986+ this . visitReactiveValue ( context , id , value , lvalue ) ;
900987 }
901988 }
902989
@@ -947,25 +1034,30 @@ class PropagationVisitor extends ReactiveFunctionVisitor<Context> {
9471034 break ;
9481035 }
9491036 case 'for' : {
950- this . visitReactiveValue ( context , terminal . id , terminal . init ) ;
951- this . visitReactiveValue ( context , terminal . id , terminal . test ) ;
1037+ this . visitReactiveValue ( context , terminal . id , terminal . init , null ) ;
1038+ this . visitReactiveValue ( context , terminal . id , terminal . test , null ) ;
9521039 context . enterConditional ( ( ) => {
9531040 this . visitBlock ( terminal . loop , context ) ;
9541041 if ( terminal . update !== null ) {
955- this . visitReactiveValue ( context , terminal . id , terminal . update ) ;
1042+ this . visitReactiveValue (
1043+ context ,
1044+ terminal . id ,
1045+ terminal . update ,
1046+ null ,
1047+ ) ;
9561048 }
9571049 } ) ;
9581050 break ;
9591051 }
9601052 case 'for-of' : {
961- this . visitReactiveValue ( context , terminal . id , terminal . init ) ;
1053+ this . visitReactiveValue ( context , terminal . id , terminal . init , null ) ;
9621054 context . enterConditional ( ( ) => {
9631055 this . visitBlock ( terminal . loop , context ) ;
9641056 } ) ;
9651057 break ;
9661058 }
9671059 case 'for-in' : {
968- this . visitReactiveValue ( context , terminal . id , terminal . init ) ;
1060+ this . visitReactiveValue ( context , terminal . id , terminal . init , null ) ;
9691061 context . enterConditional ( ( ) => {
9701062 this . visitBlock ( terminal . loop , context ) ;
9711063 } ) ;
@@ -974,12 +1066,12 @@ class PropagationVisitor extends ReactiveFunctionVisitor<Context> {
9741066 case 'do-while' : {
9751067 this . visitBlock ( terminal . loop , context ) ;
9761068 context . enterConditional ( ( ) => {
977- this . visitReactiveValue ( context , terminal . id , terminal . test ) ;
1069+ this . visitReactiveValue ( context , terminal . id , terminal . test , null ) ;
9781070 } ) ;
9791071 break ;
9801072 }
9811073 case 'while' : {
982- this . visitReactiveValue ( context , terminal . id , terminal . test ) ;
1074+ this . visitReactiveValue ( context , terminal . id , terminal . test , null ) ;
9831075 context . enterConditional ( ( ) => {
9841076 this . visitBlock ( terminal . loop , context ) ;
9851077 } ) ;
0 commit comments