|
1 | 1 | use emmylua_parser::{ |
2 | | - BinaryOperator, LuaAssignStat, LuaAstNode, LuaCallExprStat, LuaChunk, LuaDocOpType, LuaExpr, |
3 | | - LuaForStat, LuaIndexKey, LuaIndexMemberExpr, LuaSyntaxId, LuaTableExpr, LuaUnaryExpr, |
4 | | - LuaVarExpr, UnaryOperator, |
| 2 | + LuaAssignStat, LuaAstNode, LuaCallExprStat, LuaChunk, LuaDocOpType, LuaExpr, LuaForStat, |
| 3 | + LuaIndexKey, LuaIndexMemberExpr, LuaSyntaxId, LuaTableExpr, LuaUnaryExpr, LuaVarExpr, |
| 4 | + UnaryOperator, |
5 | 5 | }; |
6 | 6 | use hashbrown::HashSet; |
7 | 7 | use std::{rc::Rc, sync::Arc}; |
@@ -100,6 +100,14 @@ enum Continuation { |
100 | 100 | expr_type: LuaType, |
101 | 101 | reuse_antecedent_narrowing: bool, |
102 | 102 | }, |
| 103 | + // Resume RHS replay once the assigned ref's pre-assignment type is known. |
| 104 | + // This resolves self-reads before earlier RHS dependencies can repeatedly |
| 105 | + // walk the same assignment chain. |
| 106 | + AssignmentSelfAntecedent { |
| 107 | + walk: QueryWalk, |
| 108 | + replay: FlowExprReplay, |
| 109 | + replay_query: FlowReplayQuery, |
| 110 | + }, |
103 | 111 | // Resume an assignment result after proving the branch can reach the |
104 | 112 | // assignment. An impossible branch must not contribute this result to a merge. |
105 | 113 | AssignmentReachability { |
@@ -218,9 +226,39 @@ impl FlowReplayQuery { |
218 | 226 | Ok(()) |
219 | 227 | } |
220 | 228 |
|
221 | | - fn resolve_dependencies(&mut self, var_ref_id: &VarRefId, typ: LuaType) { |
| 229 | + fn has_unskippable_dependency(&self, var_ref_id: &VarRefId, flow_id: FlowId) -> bool { |
| 230 | + // A resolved index can skip its prefix and key dependencies. Only hoist |
| 231 | + // a self-read when every earlier success path still reaches it. |
| 232 | + let mut furthest_success_jump = self.next_dependency_idx; |
| 233 | + for (idx, query) in self |
| 234 | + .dependency_queries |
| 235 | + .iter() |
| 236 | + .enumerate() |
| 237 | + .skip(self.next_dependency_idx) |
| 238 | + { |
| 239 | + if idx >= furthest_success_jump |
| 240 | + && query.resolved_type.is_none() |
| 241 | + && query.var_ref_id == *var_ref_id |
| 242 | + && query.flow_id == flow_id |
| 243 | + { |
| 244 | + return true; |
| 245 | + } |
| 246 | + if let Some(next_idx) = query.next_dependency_idx_on_success { |
| 247 | + furthest_success_jump = furthest_success_jump.max(next_idx); |
| 248 | + } |
| 249 | + } |
| 250 | + |
| 251 | + false |
| 252 | + } |
| 253 | + |
| 254 | + fn resolve_dependencies_at_flow( |
| 255 | + &mut self, |
| 256 | + var_ref_id: &VarRefId, |
| 257 | + flow_id: FlowId, |
| 258 | + typ: LuaType, |
| 259 | + ) { |
222 | 260 | for query in &mut self.dependency_queries { |
223 | | - if query.var_ref_id == *var_ref_id { |
| 261 | + if query.var_ref_id == *var_ref_id && query.flow_id == flow_id { |
224 | 262 | query.resolved_type = Some(typ.clone()); |
225 | 263 | } |
226 | 264 | } |
@@ -496,6 +534,16 @@ impl<'a> FlowTypeEngine<'a> { |
496 | 534 | reuse_antecedent_narrowing, |
497 | 535 | query_result, |
498 | 536 | ), |
| 537 | + Some(Continuation::AssignmentSelfAntecedent { |
| 538 | + walk, |
| 539 | + replay, |
| 540 | + replay_query, |
| 541 | + }) => self.resume_assignment_self_antecedent( |
| 542 | + walk, |
| 543 | + replay, |
| 544 | + replay_query, |
| 545 | + query_result, |
| 546 | + ), |
499 | 547 | Some(Continuation::AssignmentReachability { walk, result_type }) => { |
500 | 548 | match query_result { |
501 | 549 | Ok(FlowQueryResult::Unreachable) => { |
@@ -692,6 +740,30 @@ impl<'a> FlowTypeEngine<'a> { |
692 | 740 | Ok(self.finish_walk(walk, result_type)) |
693 | 741 | } |
694 | 742 |
|
| 743 | + fn resume_assignment_self_antecedent( |
| 744 | + &mut self, |
| 745 | + walk: QueryWalk, |
| 746 | + replay: FlowExprReplay, |
| 747 | + mut replay_query: FlowReplayQuery, |
| 748 | + antecedent_result: FlowResult, |
| 749 | + ) -> Result<SchedulerStep, InferFailReason> { |
| 750 | + let antecedent_type = match antecedent_result { |
| 751 | + Ok(FlowQueryResult::Type(antecedent_type)) => antecedent_type, |
| 752 | + Ok(FlowQueryResult::Unreachable) => { |
| 753 | + return Ok(self.finish_unreachable_branch(walk)); |
| 754 | + } |
| 755 | + Err(err) => return self.fail_query(&walk.query, err), |
| 756 | + }; |
| 757 | + |
| 758 | + let antecedent_flow_id = replay_query.flow_id; |
| 759 | + replay_query.resolve_dependencies_at_flow( |
| 760 | + &walk.query.var_ref_id, |
| 761 | + antecedent_flow_id, |
| 762 | + antecedent_type, |
| 763 | + ); |
| 764 | + self.start_expr_replay(walk, replay, replay_query) |
| 765 | + } |
| 766 | + |
695 | 767 | fn start_expr_replay( |
696 | 768 | &mut self, |
697 | 769 | walk: QueryWalk, |
@@ -1106,29 +1178,31 @@ impl<'a> FlowTypeEngine<'a> { |
1106 | 1178 | let expr_idx = i.min(last_expr_idx); |
1107 | 1179 | let result_slot = i.saturating_sub(last_expr_idx); |
1108 | 1180 | let expr = assignment_info.exprs[expr_idx].clone(); |
1109 | | - let mut replay_query = FlowReplayQuery::new( |
| 1181 | + let replay_query = FlowReplayQuery::new( |
1110 | 1182 | self.db, |
1111 | 1183 | Some(self.tree), |
1112 | 1184 | self.cache, |
1113 | 1185 | antecedent_flow_id, |
1114 | | - expr.clone(), |
| 1186 | + expr, |
1115 | 1187 | true, |
1116 | 1188 | ); |
1117 | | - // A plain self-dependent RHS would replay this assignment while |
1118 | | - // trying to type itself. Treat that self read as unknown; `and`/`or` |
1119 | | - // assignments still need the antecedent value for their semantics. |
1120 | | - if explicit_var_type.is_none() && !contains_short_circuit_binary_expr(&expr) { |
1121 | | - replay_query.resolve_dependencies(&var_ref_id, LuaType::Unknown); |
| 1189 | + let replay = FlowExprReplay::Assignment { |
| 1190 | + antecedent_flow_id, |
| 1191 | + explicit_var_type, |
| 1192 | + result_slot, |
| 1193 | + }; |
| 1194 | + if replay_query.has_unskippable_dependency(&var_ref_id, antecedent_flow_id) { |
| 1195 | + let query = FlowQuery::new(self.cache, &var_ref_id, antecedent_flow_id); |
| 1196 | + return Ok(SchedulerStep::StartQuery { |
| 1197 | + query, |
| 1198 | + continuation: Some(Continuation::AssignmentSelfAntecedent { |
| 1199 | + walk, |
| 1200 | + replay, |
| 1201 | + replay_query, |
| 1202 | + }), |
| 1203 | + }); |
1122 | 1204 | } |
1123 | | - return self.start_expr_replay( |
1124 | | - walk, |
1125 | | - FlowExprReplay::Assignment { |
1126 | | - antecedent_flow_id, |
1127 | | - explicit_var_type, |
1128 | | - result_slot, |
1129 | | - }, |
1130 | | - replay_query, |
1131 | | - ); |
| 1205 | + return self.start_expr_replay(walk, replay, replay_query); |
1132 | 1206 | } |
1133 | 1207 |
|
1134 | 1208 | self.finish_assignment_expr_type(walk, antecedent_flow_id, explicit_var_type, LuaType::Nil) |
@@ -1971,17 +2045,6 @@ fn integer_enum_assignment_declared_type( |
1971 | 2045 | .then_some(declared_type) |
1972 | 2046 | } |
1973 | 2047 |
|
1974 | | -fn contains_short_circuit_binary_expr(expr: &LuaExpr) -> bool { |
1975 | | - expr.descendants::<LuaExpr>().any(|expr| { |
1976 | | - let LuaExpr::BinaryExpr(binary_expr) = expr else { |
1977 | | - return false; |
1978 | | - }; |
1979 | | - binary_expr.get_op_token().is_some_and(|token| { |
1980 | | - matches!(token.get_op(), BinaryOperator::OpAnd | BinaryOperator::OpOr) |
1981 | | - }) |
1982 | | - }) |
1983 | | -} |
1984 | | - |
1985 | 2048 | fn is_partial_assignment_expr_compatible( |
1986 | 2049 | db: &DbIndex, |
1987 | 2050 | source_type: &LuaType, |
|
0 commit comments