(f *FlowState, flow *ast.FlowNode, antecedents *ast.FlowList)
| 1230 | } |
| 1231 | |
| 1232 | func (c *Checker) getTypeAtFlowBranchLabel(f *FlowState, flow *ast.FlowNode, antecedents *ast.FlowList) FlowType { |
| 1233 | antecedentStart := len(c.antecedentTypes) |
| 1234 | subtypeReduction := false |
| 1235 | seenIncomplete := false |
| 1236 | var bypassFlow *ast.FlowNode |
| 1237 | for list := antecedents; list != nil; list = list.Next { |
| 1238 | antecedent := list.Flow |
| 1239 | if bypassFlow == nil && antecedent.Flags&ast.FlowFlagsSwitchClause != 0 && antecedent.Node.AsFlowSwitchClauseData().IsEmpty() { |
| 1240 | // The antecedent is the bypass branch of a potentially exhaustive switch statement. |
| 1241 | bypassFlow = antecedent |
| 1242 | continue |
| 1243 | } |
| 1244 | flowType := c.getTypeAtFlowNode(f, antecedent) |
| 1245 | // If the type at a particular antecedent path is the declared type and the |
| 1246 | // reference is known to always be assigned (i.e. when declared and initial types |
| 1247 | // are the same), there is no reason to process more antecedents since the only |
| 1248 | // possible outcome is subtypes that will be removed in the final union type anyway. |
| 1249 | if flowType.t == f.declaredType && f.declaredType == f.initialType { |
| 1250 | c.antecedentTypes = c.antecedentTypes[:antecedentStart] |
| 1251 | return FlowType{t: flowType.t} |
| 1252 | } |
| 1253 | if !slices.Contains(c.antecedentTypes[antecedentStart:], flowType.t) { |
| 1254 | c.antecedentTypes = append(c.antecedentTypes, flowType.t) |
| 1255 | } |
| 1256 | // If an antecedent type is not a subset of the declared type, we need to perform |
| 1257 | // subtype reduction. This happens when a "foreign" type is injected into the control |
| 1258 | // flow using the instanceof operator or a user defined type predicate. |
| 1259 | if !c.isTypeSubsetOf(flowType.t, f.initialType) { |
| 1260 | subtypeReduction = true |
| 1261 | } |
| 1262 | if flowType.incomplete { |
| 1263 | seenIncomplete = true |
| 1264 | } |
| 1265 | } |
| 1266 | if bypassFlow != nil { |
| 1267 | flowType := c.getTypeAtFlowNode(f, bypassFlow) |
| 1268 | // If the bypass flow contributes a type we haven't seen yet and the switch statement |
| 1269 | // isn't exhaustive, process the bypass flow type. Since exhaustiveness checks increase |
| 1270 | // the risk of circularities, we only want to perform them when they make a difference. |
| 1271 | if flowType.t.flags&TypeFlagsNever == 0 && !slices.Contains(c.antecedentTypes[antecedentStart:], flowType.t) && !c.isExhaustiveSwitchStatement(bypassFlow.Node.AsFlowSwitchClauseData().SwitchStatement) { |
| 1272 | if flowType.t == f.declaredType && f.declaredType == f.initialType { |
| 1273 | c.antecedentTypes = c.antecedentTypes[:antecedentStart] |
| 1274 | return FlowType{t: flowType.t} |
| 1275 | } |
| 1276 | c.antecedentTypes = append(c.antecedentTypes, flowType.t) |
| 1277 | if !c.isTypeSubsetOf(flowType.t, f.initialType) { |
| 1278 | subtypeReduction = true |
| 1279 | } |
| 1280 | if flowType.incomplete { |
| 1281 | seenIncomplete = true |
| 1282 | } |
| 1283 | } |
| 1284 | } |
| 1285 | result := c.newFlowType(c.getUnionOrEvolvingArrayType(f, c.antecedentTypes[antecedentStart:], core.IfElse(subtypeReduction, UnionReductionSubtype, UnionReductionLiteral)), seenIncomplete) |
| 1286 | c.antecedentTypes = c.antecedentTypes[:antecedentStart] |
| 1287 | return result |
| 1288 | } |
| 1289 |
no test coverage detected