@@ -638,9 +638,22 @@ local lookIntoChild = util.switch()
638638 tracer :lookIntoChild (action [2 ], topNode )
639639 return topNode , outNode
640640 end
641- if action .op .type == ' and' then
642- topNode = tracer :lookIntoChild (action [1 ], topNode , topNode :copy ())
643- topNode = tracer :lookIntoChild (action [2 ], topNode , topNode :copy ())
641+ if action .op .type == ' and' then
642+ local topNode1 , outNode1 = tracer :lookIntoChild (action [1 ], topNode , topNode :copy ())
643+ topNode = tracer :lookIntoChild (action [2 ], topNode1 , topNode1 :copy ())
644+ -- When the right side of `and` is a truthy literal (string, number,
645+ -- true, table, function), the `and` can only be false when the left
646+ -- side is false. Propagate the narrowed outNode so that patterns
647+ -- like `x == nil and "default" or x` correctly infer x as non-nil.
648+ local tp2 = action [2 ].type
649+ if tp2 == ' string'
650+ or tp2 == ' number'
651+ or tp2 == ' integer'
652+ or tp2 == ' table'
653+ or tp2 == ' function'
654+ or (tp2 == ' boolean' and action [2 ][1 ] == true ) then
655+ outNode = outNode1
656+ end
644657 elseif action .op .type == ' or' then
645658 outNode = outNode or topNode :copy ()
646659 local topNode1 , outNode1 = tracer :lookIntoChild (action [1 ], topNode , outNode )
@@ -844,12 +857,40 @@ function mt:calcNode(source)
844857 return
845858 end
846859 if self .assignMap [source ] then
860+ -- Guard against circular dependency: when this setlocal is already
861+ -- being compiled (e.g. if-handler's getNode triggers calcNode for
862+ -- a setlocal whose value is currently being compiled), skip
863+ -- lookIntoBlock to avoid propagating incomplete types and setting
864+ -- marks that would prevent later correct processing.
865+ if self ._compilingAssigns and self ._compilingAssigns [source ] then
866+ self .nodes [source ] = vm .compileNode (source )
867+ return
868+ end
869+ if not self ._compilingAssigns then
870+ self ._compilingAssigns = {}
871+ end
872+ self ._compilingAssigns [source ] = true
847873 local node = vm .compileNode (source )
874+ -- When the compiled node has no known type (only contains a 'variable'
875+ -- due to circular dependency), fall back to the variable's base
876+ -- declaration node. This prevents incomplete nodes from propagating
877+ -- through control flow analysis (e.g. if-blocks inside for-loops),
878+ -- which would otherwise cause the type to degrade to 'unknown'.
879+ if not node :hasKnownType ()
880+ and self .mode == ' local'
881+ and self .source .type == ' variable'
882+ and self .source .base then
883+ local baseNode = vm .compileNode (self .source .base )
884+ if baseNode :hasKnownType () then
885+ node = baseNode
886+ end
887+ end
848888 self .nodes [source ] = node
849889 local parentBlock = guide .getParentBlock (source )
850890 if parentBlock then
851891 self :lookIntoBlock (parentBlock , source .finish , node )
852892 end
893+ self ._compilingAssigns [source ] = nil
853894 return
854895 end
855896end
0 commit comments