import { assert , fail } from '../common/debug' ; import { CallNode , ExpressionNode , ParseNode , ParseNodeType } from '../parser/parseNodes' ; import { getImportInfo } from './analyzerNodeInfo' ; import { CodeFlowReferenceExpressionNode , createKeyForReference , createKeysForReferenceSubexpressions , FlowAssignment , FlowAssignmentAlias , FlowBranchLabel , FlowCall , FlowCondition , FlowExhaustedMatch , FlowFlags , FlowLabel , FlowNarrowForPattern , FlowNode , FlowPostContextManagerLabel , FlowPostFinally , FlowPreFinallyGate , FlowVariableAnnotation , FlowWildcardImport , } from './codeFlowTypes' ; import { DeclarationType } from './declaration' ; import { isMatchingExpression , isPartialMatchingExpression } from './parseTreeUtils' ; import { Symbol } from './symbol' ; import { CachedType , IncompleteSubtypeInfo , IncompleteType , isIncompleteType , SpeculativeTypeTracker , TypeCache , } from './typeCache' ; import { EvaluatorFlags , TypeEvaluator , TypeResult } from './typeEvaluatorTypes' ; import { getTypeNarrowingCallback } from './typeGuards' ; import { ClassType , combineTypes , FunctionType , isClassInstance , isFunction , isInstantiableClass , isModule , isNever , isOverloadedFunction , isTypeSame , isTypeVar , ModuleType , removeUnknownFromUnion , Type , TypeVarType , UnboundType , UnknownType , } from './types' ; import { ClassMemberLookupFlags , convertToInstance , doForEachSubtype , isNoReturnType , isTypeAliasPlaceholder , lookUpClassMember , mapSubtypes , } from './typeUtils' ; export interface FlowNodeTypeResult { type : Type | undefined ; usedOuterScopeAlias : boolean ; isIncomplete : boolean ; generationCount ? : number | undefined ; incompleteType ? : Type | undefined ; incompleteSubtypes ? : IncompleteSubtypeInfo [ ] | undefined ; recursiveVisitCount ? : number ; } export interface CodeFlowAnalyzer { getTypeFromCodeFlow : ( flowNode : FlowNode , reference : CodeFlowReferenceExpressionNode | undefined , targetSymbolId : number | undefined , initialType : Type | undefined , isInitialTypeIncomplete : boolean ) => FlowNodeTypeResult ; } export interface CodeFlowEngine { createCodeFlowAnalyzer : ( ) => CodeFlowAnalyzer ; isFlowNodeReachable : ( flowNode : FlowNode , sourceFlowNode ? : FlowNode ) => boolean ; narrowConstrainedTypeVar : ( flowNode : FlowNode , typeVar : TypeVarType ) => Type | undefined ; } const maxFlowNodeLoopVisitCount = 64 ; const maxCodeFlowInvocationsPerLoop = 16 * 1024 ; export function getCodeFlowEngine ( evaluator , speculativeTypeTracker ) { const isReachableRecursionMap = new Map < number , true > ( ) ; const callIsNoReturnCache = new Map < number , boolean > ( ) ; const isExceptionContextManagerCache = new Map < number , boolean > ( ) ; let codeFlowInvocations = 0 ; let flowIncompleteGeneration = 1 ; function createCodeFlowAnalyzer ( ) { const flowNodeTypeCacheSet = new Map < string , TypeCache > ( ) ; function getTypeFromCodeFlow ( flowNode , reference , targetSymbolId , initialType , isInitialTypeIncomplete ) { const referenceKey = reference !== undefined ? createKeyForReference ( reference ) : undefined ; let subexpressionReferenceKeys ; const referenceKeyWithSymbolId = referenceKey !== undefined && targetSymbolId !== undefined ? referenceKey + `.${ targetSymbolId . toString ( ) }` : '.' ; let flowNodeTypeCache = flowNodeTypeCacheSet . get ( referenceKeyWithSymbolId ) ; if ( ! flowNodeTypeCache ) { flowNodeTypeCache = new Map < number , CachedType | undefined > ( ) ; flowNodeTypeCacheSet . set ( referenceKeyWithSymbolId , flowNodeTypeCache ) ; } function setCacheEntry ( flowNode , type , usedOuterScopeAlias , isIncomplete ) { if ( ! isIncomplete ) { flowIncompleteGeneration ++ ; } else { const prevEntry = flowNodeTypeCache ! . get ( flowNode . id ) ; if ( prevEntry === undefined ) { flowIncompleteGeneration ++ ; } else if ( type && ( prevEntry as IncompleteType ) . isIncompleteType ) { const prevIncompleteType = prevEntry as IncompleteType ; if ( prevIncompleteType . type && ! isTypeSame ( prevIncompleteType . type , type ) ) { flowIncompleteGeneration ++ ; } } } const entry = isIncomplete ? { isIncompleteType : true , type , incompleteSubtypes : [ ] , generationCount : flowIncompleteGeneration , } : type ; flowNodeTypeCache ! . set ( flowNode . id , entry ) ; speculativeTypeTracker . trackEntry ( flowNodeTypeCache ! , flowNode . id ) ; return { type , usedOuterScopeAlias , isIncomplete , generationCount : flowIncompleteGeneration , incompleteSubtypes : isIncomplete ? [ ] : undefined , } ; } function setIncompleteSubtype ( flowNode , index , type , isIncomplete , isPending , usedOuterScopeAlias ) { const cachedEntry = flowNodeTypeCache ! . get ( flowNode . id ) ; if ( cachedEntry === undefined || ! isIncompleteType ( cachedEntry ) ) { fail ( 'setIncompleteSubtype can be called only on a valid incomplete cache entry' ) ; } const incompleteEntries = cachedEntry . incompleteSubtypes ; if ( index < incompleteEntries . length ) { const oldEntry = incompleteEntries [ index ] ; if ( oldEntry . isIncomplete !== isIncomplete || oldEntry . type === undefined || type === undefined || ! isTypeSame ( oldEntry . type , type ) ) { incompleteEntries [ index ] = { type , isIncomplete , isPending } ; flowIncompleteGeneration ++ ; } else if ( oldEntry . isPending !== isPending ) { incompleteEntries [ index ] = { type , isIncomplete , isPending } ; } } else { assert ( incompleteEntries . length === index ) ; incompleteEntries . push ( { type , isIncomplete , isPending } ) ; flowIncompleteGeneration ++ ; } return getCacheEntry ( flowNode , usedOuterScopeAlias ) ; } function incrementFlowNodeVisitCount ( flowNode ) { const cachedEntry = flowNodeTypeCache ! . get ( flowNode . id ) ; if ( cachedEntry === undefined || ! isIncompleteType ( cachedEntry ) ) { fail ( 'incrementFlowNodeVisitCount can be called only on a valid incomplete cache entry' ) ; } cachedEntry . recursiveVisitCount = ( cachedEntry . recursiveVisitCount ?? 0 ) + 1 ; return cachedEntry . recursiveVisitCount ; } function incrementFlowNodeConvergenceCount ( flowNode , reset = false ) { const cachedEntry = flowNodeTypeCache ! . get ( flowNode . id ) ; if ( cachedEntry === undefined || ! isIncompleteType ( cachedEntry ) ) { return 0 ; } if ( reset ) { cachedEntry . recursiveConvergenceCount = 0 ; } else { cachedEntry . recursiveConvergenceCount = ( cachedEntry . recursiveConvergenceCount ?? 0 ) + 1 ; } return cachedEntry . recursiveConvergenceCount ; } function getCacheEntry ( flowNode , usedOuterScopeAlias ) { if ( ! flowNodeTypeCache ! . has ( flowNode . id ) ) { return undefined ; } const cachedEntry = flowNodeTypeCache ! . get ( flowNode . id ) ; if ( cachedEntry === undefined ) { return { type : cachedEntry , usedOuterScopeAlias , isIncomplete : false , } ; } if ( ! isIncompleteType ( cachedEntry ) ) { return { type : cachedEntry , usedOuterScopeAlias , isIncomplete : false , } ; } let type = cachedEntry . type ; if ( cachedEntry . incompleteSubtypes . length > 0 ) { const typesToCombine = [ ] ; cachedEntry . incompleteSubtypes . forEach ( ( t ) => { if ( t . type ) { typesToCombine . push ( t . type ) ; } } ) ; type = typesToCombine . length > 0 ? combineTypes ( typesToCombine ) : undefined ; } return { type , usedOuterScopeAlias , isIncomplete : true , incompleteSubtypes : cachedEntry . incompleteSubtypes , generationCount : cachedEntry . generationCount , } ; } function evaluateAssignmentFlowNode ( flowNode ) { let nodeForCacheLookup = flowNode . node ; const parentNode = flowNode . node . parent ; if ( parentNode ) { if ( parentNode . nodeType === ParseNodeType . Function || parentNode . nodeType === ParseNodeType . Class ) { nodeForCacheLookup = parentNode ; } } return evaluator . evaluateTypeForSubnode ( nodeForCacheLookup , ( ) => { evaluator . evaluateTypesForStatement ( flowNode . node ) ; } ) ; } function getTypeFromFlowNode ( flowNode , reference , targetSymbolId , initialType , isInitialTypeIncomplete ) { let curFlowNode = flowNode ; let usedOuterScopeAlias = false ; const codeFlowInvocationsAtStart = codeFlowInvocations ; codeFlowInvocations ++ ; evaluator . checkForCancellation ( ) ; while ( true ) { const cachedEntry = getCacheEntry ( curFlowNode , usedOuterScopeAlias ) ; if ( cachedEntry ) { if ( ! cachedEntry . isIncomplete ) { return cachedEntry ; } if ( cachedEntry . generationCount === flowIncompleteGeneration ) { return { type : cachedEntry ?. type ? removeUnknownFromUnion ( cachedEntry . type ) : undefined , usedOuterScopeAlias , isIncomplete : true , } ; } } if ( curFlowNode . flags & FlowFlags . Unreachable ) { return setCacheEntry ( curFlowNode , undefined , usedOuterScopeAlias , false ) ; } if ( curFlowNode . flags & FlowFlags . VariableAnnotation ) { const varAnnotationNode = curFlowNode as FlowVariableAnnotation ; curFlowNode = varAnnotationNode . antecedent ; continue ; } if ( curFlowNode . flags & FlowFlags . Call ) { const callFlowNode = curFlowNode as FlowCall ; if ( isCallNoReturn ( callFlowNode . node ) ) { return setCacheEntry ( curFlowNode , undefined , usedOuterScopeAlias , false ) ; } if ( callFlowNode . typeGuardArgExpression && reference && isMatchingExpression ( reference , callFlowNode . typeGuardArgExpression ) ) { const callLeftExpr = callFlowNode . node . leftExpression ; const functionTypeResult = evaluator . getTypeOfExpression ( callLeftExpr , undefined , EvaluatorFlags . DoNotSpecialize ) ; const functionType = functionTypeResult . type ; if ( isFunction ( functionType ) && functionType . details . declaredReturnType && isClassInstance ( functionType . details . declaredReturnType ) && ClassType . isBuiltIn ( functionType . details . declaredReturnType , 'TypeGuard' ) ) { const functionReturnTypeResult = evaluator . getTypeOfExpression ( callFlowNode . node ) ; const functionReturnType = functionReturnTypeResult . type ; if ( isClassInstance ( functionReturnType ) && ClassType . isBuiltIn ( functionReturnType , 'bool' ) ) { if ( functionReturnType . positiveTypeGuardType && functionReturnType . negativeTypeGuardType && isNoReturnType ( functionReturnType . negativeTypeGuardType ) ) { return setCacheEntry ( curFlowNode , functionReturnType . positiveTypeGuardType , usedOuterScopeAlias , ! ! functionReturnTypeResult . isIncomplete ) ; } } } } curFlowNode = callFlowNode . antecedent ; continue ; } if ( curFlowNode . flags & FlowFlags . Assignment ) { const assignmentFlowNode = curFlowNode as FlowAssignment ; if ( reference ) { if ( targetSymbolId === assignmentFlowNode . targetSymbolId && isMatchingExpression ( reference , assignmentFlowNode . node ) ) { if ( curFlowNode . flags & FlowFlags . Unbind ) { return setCacheEntry ( curFlowNode , UnboundType . create ( ) , usedOuterScopeAlias , false ) ; } if ( cachedEntry && cachedEntry . type === undefined ) { return { type : undefined , usedOuterScopeAlias , isIncomplete : true } ; } setCacheEntry ( curFlowNode , reference ? undefined : initialType , usedOuterScopeAlias , true ) ; let flowTypeResult = evaluateAssignmentFlowNode ( assignmentFlowNode ) ; if ( flowTypeResult ) { if ( isTypeAliasPlaceholder ( flowTypeResult . type ) ) { flowTypeResult = undefined ; } else if ( reference . nodeType === ParseNodeType . MemberAccess && evaluator . isAsymmetricDescriptorAssignment ( assignmentFlowNode . node ) ) { flowTypeResult = undefined ; } } return setCacheEntry ( curFlowNode , flowTypeResult ?. type , usedOuterScopeAlias , ! ! flowTypeResult ?. isIncomplete ) ; } else if ( isPartialMatchingExpression ( reference , assignmentFlowNode . node ) ) { return { type : initialType , usedOuterScopeAlias , isIncomplete : isInitialTypeIncomplete , } ; } } curFlowNode = assignmentFlowNode . antecedent ; continue ; } if ( curFlowNode . flags & FlowFlags . AssignmentAlias ) { const aliasFlowNode = curFlowNode as FlowAssignmentAlias ; if ( targetSymbolId === aliasFlowNode . targetSymbolId ) { targetSymbolId = aliasFlowNode . aliasSymbolId ; usedOuterScopeAlias = true ; } curFlowNode = aliasFlowNode . antecedent ; continue ; } if ( curFlowNode . flags & FlowFlags . BranchLabel ) { const branchFlowNode = curFlowNode as FlowBranchLabel ; if ( curFlowNode . flags & FlowFlags . PostContextManager ) { const contextMgrNode = curFlowNode as FlowPostContextManagerLabel ; if ( ! contextMgrNode . expressions . some ( ( expr ) => isExceptionContextManager ( expr , contextMgrNode . isAsync ) ) ) { return setCacheEntry ( curFlowNode , undefined , usedOuterScopeAlias , false ) ; } } if ( reference && branchFlowNode . preBranchAntecedent && branchFlowNode . affectedExpressions ) { if ( ! subexpressionReferenceKeys ) { subexpressionReferenceKeys = createKeysForReferenceSubexpressions ( reference ) ; } if ( ! subexpressionReferenceKeys . some ( ( key ) => branchFlowNode . affectedExpressions ! . has ( key ) ) && isFlowNodeReachable ( curFlowNode , branchFlowNode . preBranchAntecedent ) ) { curFlowNode = branchFlowNode . preBranchAntecedent ; continue ; } } const labelNode = curFlowNode as FlowLabel ; const typesToCombine = [ ] ; let branchUsedOuterScopeAlias = usedOuterScopeAlias ; let sawIncomplete = false ; setCacheEntry ( curFlowNode , reference ? undefined : initialType , usedOuterScopeAlias , true ) ; labelNode . antecedents . forEach ( ( antecedent ) => { const flowTypeResult = getTypeFromFlowNode ( antecedent , reference , targetSymbolId , initialType , isInitialTypeIncomplete ) ; if ( flowTypeResult . isIncomplete ) { sawIncomplete = true ; } if ( flowTypeResult . usedOuterScopeAlias ) { branchUsedOuterScopeAlias = true ; } if ( flowTypeResult . type ) { typesToCombine . push ( flowTypeResult . type ) ; } } ) ; const effectiveType = ! ! reference || typesToCombine . length > 0 ? combineTypes ( typesToCombine ) : undefined ; if ( codeFlowInvocations - codeFlowInvocationsAtStart > maxCodeFlowInvocationsPerLoop ) { sawIncomplete = false ; } return setCacheEntry ( curFlowNode , effectiveType , branchUsedOuterScopeAlias , sawIncomplete ) ; } if ( curFlowNode . flags & FlowFlags . LoopLabel ) { const loopNode = curFlowNode as FlowLabel ; if ( reference ) { if ( ! subexpressionReferenceKeys ) { subexpressionReferenceKeys = createKeysForReferenceSubexpressions ( reference ) ; } if ( ! subexpressionReferenceKeys . some ( ( key ) => loopNode . affectedExpressions ! . has ( key ) ) ) { curFlowNode = loopNode . antecedents [ 0 ] ; continue ; } } let sawIncomplete = false ; let loopUsedOuterScopeAlias = usedOuterScopeAlias ; let cacheEntry = getCacheEntry ( curFlowNode , usedOuterScopeAlias ) ; let typeAtStart ; if ( cacheEntry === undefined ) { cacheEntry = setCacheEntry ( curFlowNode , reference ? undefined : initialType , usedOuterScopeAlias , true ) ; } else { typeAtStart = cacheEntry . type ; } const isRecursive = cacheEntry . incompleteSubtypes !== undefined && cacheEntry . incompleteSubtypes . some ( ( subtype ) => subtype . isPending ) ; const visitCount = incrementFlowNodeVisitCount ( curFlowNode ) ; loopNode . antecedents . forEach ( ( antecedent , index ) => { cacheEntry = getCacheEntry ( curFlowNode , usedOuterScopeAlias ) ! ; const subtypeEntry = cacheEntry . incompleteSubtypes !== undefined && index < cacheEntry . incompleteSubtypes . length ? cacheEntry . incompleteSubtypes [ index ] : undefined ; if ( subtypeEntry === undefined || ( ! subtypeEntry ?. isPending && subtypeEntry ?. isIncomplete ) ) { cacheEntry = setIncompleteSubtype ( curFlowNode , index , subtypeEntry ?. type ?? ( reference ? undefined : initialType ) , true , true , usedOuterScopeAlias ) ; try { const flowTypeResult = getTypeFromFlowNode ( antecedent , reference , targetSymbolId , initialType , isInitialTypeIncomplete ) ; if ( flowTypeResult . isIncomplete ) { sawIncomplete = true ; } if ( flowTypeResult . usedOuterScopeAlias ) { loopUsedOuterScopeAlias = true ; } cacheEntry = setIncompleteSubtype ( curFlowNode , index , flowTypeResult . type , flowTypeResult . isIncomplete , false , loopUsedOuterScopeAlias ) ; } catch ( e ) { setIncompleteSubtype ( curFlowNode , index , undefined , true , false , usedOuterScopeAlias ) ; throw e ; } } } ) ; if ( isRecursive ) { let isIncomplete = visitCount >= maxFlowNodeLoopVisitCount ? cacheEntry . incompleteSubtypes ! [ 0 ] . isIncomplete : reference !== undefined ; if ( codeFlowInvocations - codeFlowInvocationsAtStart > maxCodeFlowInvocationsPerLoop ) { isIncomplete = false ; } return { type : cacheEntry . type , usedOuterScopeAlias , isIncomplete , } ; } if ( sawIncomplete && typeAtStart && cacheEntry . type ) { if ( isTypeSame ( typeAtStart , cacheEntry . type ) ) { if ( incrementFlowNodeConvergenceCount ( flowNode ) > 2 ) { sawIncomplete = false ; } } else { incrementFlowNodeConvergenceCount ( flowNode , true ) ; } } if ( sawIncomplete ) { return { type : cacheEntry ?. type ? removeUnknownFromUnion ( cacheEntry . type ) : undefined , usedOuterScopeAlias : loopUsedOuterScopeAlias , isIncomplete : false , } ; } return setCacheEntry ( curFlowNode , cacheEntry ! . type , loopUsedOuterScopeAlias , false ) ; } if ( curFlowNode . flags & ( FlowFlags . TrueCondition | FlowFlags . FalseCondition ) ) { const conditionalFlowNode = curFlowNode as FlowCondition ; if ( reference ) { const typeNarrowingCallback = getTypeNarrowingCallback ( evaluator , reference , conditionalFlowNode . expression , ! ! ( conditionalFlowNode . flags & ( FlowFlags . TrueCondition | FlowFlags . TrueNeverCondition ) ) ) ; if ( typeNarrowingCallback ) { setCacheEntry ( curFlowNode , reference ? undefined : initialType , usedOuterScopeAlias , true ) ; const flowTypeResult = getTypeFromFlowNode ( conditionalFlowNode . antecedent , reference , targetSymbolId , initialType , isInitialTypeIncomplete ) ; let flowType = flowTypeResult . type ; if ( flowType ) { flowType = typeNarrowingCallback ( flowType ) ; } return setCacheEntry ( curFlowNode , flowType , flowTypeResult . usedOuterScopeAlias , flowTypeResult . isIncomplete ) ; } } curFlowNode = conditionalFlowNode . antecedent ; continue ; } if ( curFlowNode . flags & ( FlowFlags . TrueNeverCondition | FlowFlags . FalseNeverCondition ) ) { const conditionalFlowNode = curFlowNode as FlowCondition ; if ( conditionalFlowNode . reference ) { const symbolWithScope = evaluator . lookUpSymbolRecursive ( conditionalFlowNode . reference , conditionalFlowNode . reference . value , false ) ; if ( symbolWithScope && symbolWithScope . symbol . getTypedDeclarations ( ) . length > 0 ) { const typeNarrowingCallback = getTypeNarrowingCallback ( evaluator , conditionalFlowNode . reference , conditionalFlowNode . expression , ! ! ( conditionalFlowNode . flags & ( FlowFlags . TrueCondition | FlowFlags . TrueNeverCondition ) ) ) ; if ( typeNarrowingCallback ) { const refTypeInfo = evaluator . getTypeOfExpression ( conditionalFlowNode . reference ! ) ; const narrowedType = typeNarrowingCallback ( refTypeInfo . type ) || refTypeInfo . type ; if ( isNever ( narrowedType ) ) { return setCacheEntry ( curFlowNode , undefined , usedOuterScopeAlias , ! ! refTypeInfo . isIncomplete ) ; } } } } curFlowNode = conditionalFlowNode . antecedent ; continue ; } if ( curFlowNode . flags & FlowFlags . ExhaustedMatch ) { const exhaustedMatchFlowNode = curFlowNode as FlowExhaustedMatch ; const narrowedTypeResult = evaluator . evaluateTypeForSubnode ( exhaustedMatchFlowNode . node , ( ) => { evaluator . evaluateTypesForMatchNode ( exhaustedMatchFlowNode . node ) ; } ) ; if ( narrowedTypeResult && isNever ( narrowedTypeResult . type ) ) { return setCacheEntry ( curFlowNode , undefined , usedOuterScopeAlias , ! ! narrowedTypeResult . isIncomplete ) ; } curFlowNode = exhaustedMatchFlowNode . antecedent ; continue ; } if ( curFlowNode . flags & FlowFlags . NarrowForPattern ) { const patternFlowNode = curFlowNode as FlowNarrowForPattern ; if ( ! reference || isMatchingExpression ( reference , patternFlowNode . subjectExpression ) ) { const typeResult = evaluator . evaluateTypeForSubnode ( patternFlowNode . statement , ( ) => { if ( patternFlowNode . statement . nodeType === ParseNodeType . Case ) { evaluator . evaluateTypesForCaseNode ( patternFlowNode . statement ) ; } else { evaluator . evaluateTypesForMatchNode ( patternFlowNode . statement ) ; } } ) ; if ( typeResult ) { if ( ! reference ) { if ( isNever ( typeResult . type ) ) { return setCacheEntry ( curFlowNode , undefined , usedOuterScopeAlias , ! ! typeResult . isIncomplete ) ; } } else { return setCacheEntry ( curFlowNode , typeResult . type , usedOuterScopeAlias , ! ! typeResult . isIncomplete ) ; } } } curFlowNode = patternFlowNode . antecedent ; continue ; } if ( curFlowNode . flags & FlowFlags . PreFinallyGate ) { const preFinallyFlowNode = curFlowNode as FlowPreFinallyGate ; if ( preFinallyFlowNode . isGateClosed ) { return { type : undefined , usedOuterScopeAlias , isIncomplete : false } ; } curFlowNode = preFinallyFlowNode . antecedent ; continue ; } if ( curFlowNode . flags & FlowFlags . PostFinally ) { const postFinallyFlowNode = curFlowNode as FlowPostFinally ; const wasGateClosed = postFinallyFlowNode . preFinallyGate . isGateClosed ; try { postFinallyFlowNode . preFinallyGate . isGateClosed = true ; let flowTypeResult ; evaluator . useSpeculativeMode ( postFinallyFlowNode . finallyNode , ( ) => { flowTypeResult = getTypeFromFlowNode ( postFinallyFlowNode . antecedent , reference , targetSymbolId , initialType , isInitialTypeIncomplete ) ; } ) ; return flowTypeResult ! . isIncomplete ? flowTypeResult ! : setCacheEntry ( curFlowNode , flowTypeResult ! . type , flowTypeResult ! . usedOuterScopeAlias , false ) ; } finally { postFinallyFlowNode . preFinallyGate . isGateClosed = wasGateClosed ; } } if ( curFlowNode . flags & FlowFlags . Start ) { return setCacheEntry ( curFlowNode , initialType , usedOuterScopeAlias , isInitialTypeIncomplete ) ; } if ( curFlowNode . flags & FlowFlags . WildcardImport ) { const wildcardImportFlowNode = curFlowNode as FlowWildcardImport ; if ( reference && reference . nodeType === ParseNodeType . Name ) { const nameValue = reference . value ; if ( wildcardImportFlowNode . names . some ( ( name ) => name === nameValue ) ) { const type = getTypeFromWildcardImport ( wildcardImportFlowNode , nameValue ) ; return setCacheEntry ( curFlowNode , type , usedOuterScopeAlias , false ) ; } } curFlowNode = wildcardImportFlowNode . antecedent ; continue ; } fail ( 'Unexpected flow node flags' ) ; return setCacheEntry ( curFlowNode , undefined , usedOuterScopeAlias , false ) ; } } if ( ! flowNode ) { return { type : initialType , usedOuterScopeAlias : false , isIncomplete : isInitialTypeIncomplete , } ; } return getTypeFromFlowNode ( flowNode , reference , targetSymbolId , initialType , isInitialTypeIncomplete ) ; } return { getTypeFromCodeFlow , } ; } function isFlowNodeReachable ( flowNode , sourceFlowNode ? ) { const visitedFlowNodeMap = new Set < number > ( ) ; function isFlowNodeReachableRecursive ( flowNode , sourceFlowNode ) { let curFlowNode = flowNode ; while ( true ) { if ( visitedFlowNodeMap . has ( curFlowNode . id ) ) { return false ; } visitedFlowNodeMap . add ( curFlowNode . id ) ; if ( curFlowNode . flags & FlowFlags . Unreachable ) { return false ; } if ( curFlowNode === sourceFlowNode ) { return true ; } if ( curFlowNode . flags & ( FlowFlags . VariableAnnotation | FlowFlags . Assignment | FlowFlags . AssignmentAlias | FlowFlags . TrueCondition | FlowFlags . FalseCondition | FlowFlags . WildcardImport | FlowFlags . TrueNeverCondition | FlowFlags . FalseNeverCondition | FlowFlags . NarrowForPattern | FlowFlags . ExhaustedMatch ) ) { const typedFlowNode = curFlowNode as FlowVariableAnnotation | FlowAssignment | FlowAssignmentAlias | FlowCondition | FlowWildcardImport | FlowCondition | FlowExhaustedMatch ; curFlowNode = typedFlowNode . antecedent ; continue ; } if ( curFlowNode . flags & FlowFlags . Call ) { const callFlowNode = curFlowNode as FlowCall ; if ( isCallNoReturn ( callFlowNode . node ) ) { return false ; } curFlowNode = callFlowNode . antecedent ; continue ; } if ( curFlowNode . flags & ( FlowFlags . BranchLabel | FlowFlags . LoopLabel ) ) { if ( curFlowNode . flags & FlowFlags . PostContextManager ) { const contextMgrNode = curFlowNode as FlowPostContextManagerLabel ; if ( ! contextMgrNode . expressions . some ( ( expr ) => isExceptionContextManager ( expr , contextMgrNode . isAsync ) ) ) { return false ; } } const labelNode = curFlowNode as FlowLabel ; for ( const antecedent of labelNode . antecedents ) { if ( isFlowNodeReachableRecursive ( antecedent , sourceFlowNode ) ) { return true ; } } return false ; } if ( curFlowNode . flags & FlowFlags . Start ) { return sourceFlowNode ? false : true ; } if ( curFlowNode . flags & FlowFlags . PreFinallyGate ) { const preFinallyFlowNode = curFlowNode as FlowPreFinallyGate ; return ! preFinallyFlowNode . isGateClosed ; } if ( curFlowNode . flags & FlowFlags . PostFinally ) { const postFinallyFlowNode = curFlowNode as FlowPostFinally ; const wasGateClosed = postFinallyFlowNode . preFinallyGate . isGateClosed ; try { postFinallyFlowNode . preFinallyGate . isGateClosed = true ; return isFlowNodeReachableRecursive ( postFinallyFlowNode . antecedent , sourceFlowNode ) ; } finally { postFinallyFlowNode . preFinallyGate . isGateClosed = wasGateClosed ; } } fail ( 'Unexpected flow node flags' ) ; return false ; } } if ( isReachableRecursionMap . has ( flowNode . id ) ) { return true ; } isReachableRecursionMap . set ( flowNode . id , true ) ; try { return isFlowNodeReachableRecursive ( flowNode , sourceFlowNode ) ; } finally { isReachableRecursionMap . delete ( flowNode . id ) ; } } function narrowConstrainedTypeVar ( flowNode , typeVar ) { assert ( ! typeVar . details . isParamSpec ) ; assert ( ! typeVar . details . isVariadic ) ; assert ( ! typeVar . details . boundType ) ; assert ( typeVar . details . constraints . length > 0 ) ; const visitedFlowNodeMap = new Set < number > ( ) ; const startingConstraints = [ ] ; for ( const constraint of typeVar . details . constraints ) { if ( isClassInstance ( constraint ) ) { startingConstraints . push ( constraint ) ; } else { return undefined ; } } function narrowConstrainedTypeVarRecursive ( flowNode , typeVar ) { let curFlowNode = flowNode ; while ( true ) { if ( visitedFlowNodeMap . has ( curFlowNode . id ) ) { return startingConstraints ; } visitedFlowNodeMap . add ( curFlowNode . id ) ; if ( curFlowNode . flags & ( FlowFlags . Unreachable | FlowFlags . Start ) ) { return startingConstraints ; } if ( curFlowNode . flags & ( FlowFlags . VariableAnnotation | FlowFlags . Assignment | FlowFlags . AssignmentAlias | FlowFlags . WildcardImport | FlowFlags . TrueNeverCondition | FlowFlags . FalseNeverCondition | FlowFlags . NarrowForPattern | FlowFlags . ExhaustedMatch | FlowFlags . PostFinally | FlowFlags . PreFinallyGate | FlowFlags . Call ) ) { const typedFlowNode = curFlowNode as FlowVariableAnnotation | FlowAssignment | FlowAssignmentAlias | FlowWildcardImport | FlowExhaustedMatch | FlowNarrowForPattern | FlowPostFinally | FlowPreFinallyGate | FlowCall ; curFlowNode = typedFlowNode . antecedent ; continue ; } if ( curFlowNode . flags & ( FlowFlags . TrueCondition | FlowFlags . FalseCondition ) ) { const conditionFlowNode = curFlowNode as FlowCondition ; const testExpression = conditionFlowNode . expression ; const isPositiveTest = ( curFlowNode . flags & FlowFlags . TrueCondition ) !== 0 ; if ( testExpression . nodeType === ParseNodeType . Call && testExpression . leftExpression . nodeType === ParseNodeType . Name && testExpression . leftExpression . value === 'isinstance' && testExpression . arguments . length === 2 ) { const arg0Expr = testExpression . arguments [ 0 ] . valueExpression ; const arg0Type = evaluator . getTypeOfExpression ( arg0Expr ) . type ; if ( isCompatibleWithConstrainedTypeVar ( arg0Type , typeVar ) ) { const priorRemainingConstraints = narrowConstrainedTypeVarRecursive ( conditionFlowNode . antecedent , typeVar ) ; const arg1Expr = testExpression . arguments [ 1 ] . valueExpression ; const arg1Type = evaluator . getTypeOfExpression ( arg1Expr , undefined , EvaluatorFlags . EvaluateStringLiteralAsType | EvaluatorFlags . ParamSpecDisallowed | EvaluatorFlags . TypeVarTupleDisallowed ) . type ; if ( isInstantiableClass ( arg1Type ) ) { return priorRemainingConstraints . filter ( ( subtype ) => { if ( ClassType . isSameGenericClass ( subtype , arg1Type ) ) { return isPositiveTest ; } else { return ! isPositiveTest ; } } ) ; } } } curFlowNode = conditionFlowNode . antecedent ; continue ; } if ( curFlowNode . flags & ( FlowFlags . BranchLabel | FlowFlags . LoopLabel ) ) { const labelNode = curFlowNode as FlowLabel ; const newConstraints = [ ] ; for ( const antecedent of labelNode . antecedents ) { const constraintsToAdd = narrowConstrainedTypeVarRecursive ( antecedent , typeVar ) ; for ( const constraint of constraintsToAdd ) { if ( ! newConstraints . some ( ( t ) => isTypeSame ( t , constraint ) ) ) { newConstraints . push ( constraint ) ; } } } return newConstraints ; } fail ( 'Unexpected flow node flags' ) ; return startingConstraints ; } } const narrowedConstrainedType = narrowConstrainedTypeVarRecursive ( flowNode , typeVar ) ; return narrowedConstrainedType . length === 1 ? narrowedConstrainedType [ 0 ] : undefined ; } function isCompatibleWithConstrainedTypeVar ( type , typeVar ) { let isCompatible = true ; doForEachSubtype ( type , ( subtype ) => { if ( isTypeVar ( subtype ) ) { if ( ! isTypeSame ( subtype , typeVar ) ) { isCompatible = false ; } } else if ( subtype . condition ) { if ( ! subtype . condition . some ( ( condition ) => condition . isConstrainedTypeVar && condition . typeVarName === typeVar . nameWithScope ) ) { isCompatible = false ; } } else { isCompatible = false ; } } ) ; return isCompatible ; } function isCallNoReturn ( node ) { if ( callIsNoReturnCache . has ( node . id ) ) { return callIsNoReturnCache . get ( node . id ) ; } callIsNoReturnCache . set ( node . id , false ) ; let noReturnTypeCount = 0 ; let subtypeCount = 0 ; const callType = getDeclaredCallBaseType ( node . leftExpression ) ; if ( callType ) { doForEachSubtype ( callType , ( callSubtype ) => { subtypeCount ++ ; let functionType ; if ( isInstantiableClass ( callSubtype ) ) { let constructorMember = lookUpClassMember ( callSubtype , '__init__' , ClassMemberLookupFlags . SkipInstanceVariables | ClassMemberLookupFlags . SkipObjectBaseClass ) ; if ( constructorMember === undefined ) { constructorMember = lookUpClassMember ( callSubtype , '__new__' , ClassMemberLookupFlags . SkipInstanceVariables | ClassMemberLookupFlags . SkipObjectBaseClass ) ; } if ( constructorMember ) { const constructorType = evaluator . getTypeOfMember ( constructorMember ) ; if ( constructorType ) { if ( isFunction ( constructorType ) || isOverloadedFunction ( constructorType ) ) { const boundConstructorType = evaluator . bindFunctionToClassOrObject ( undefined , constructorType ) ; if ( boundConstructorType ) { callSubtype = boundConstructorType ; } } } } } else if ( isClassInstance ( callSubtype ) ) { const callMember = lookUpClassMember ( callSubtype , '__call__' , ClassMemberLookupFlags . SkipInstanceVariables ) ; if ( callMember ) { const callMemberType = evaluator . getTypeOfMember ( callMember ) ; if ( callMemberType ) { if ( isFunction ( callMemberType ) || isOverloadedFunction ( callMemberType ) ) { const boundCallType = evaluator . bindFunctionToClassOrObject ( undefined , callMemberType ) ; if ( boundCallType ) { callSubtype = boundCallType ; } } } } } if ( isFunction ( callSubtype ) ) { functionType = callSubtype ; } else if ( isOverloadedFunction ( callSubtype ) ) { const overloadedFunction = callSubtype ; functionType = overloadedFunction . overloads [ overloadedFunction . overloads . length - 1 ] ; } if ( functionType ) { const returnType = functionType . details . declaredReturnType ; if ( FunctionType . isAsync ( functionType ) ) { if ( returnType && isClassInstance ( returnType ) && ClassType . isBuiltIn ( returnType , 'Coroutine' ) && returnType . typeArguments && returnType . typeArguments . length >= 3 ) { if ( isNoReturnType ( returnType . typeArguments [ 2 ] ) ) { noReturnTypeCount ++ ; } } } else if ( returnType ) { if ( isNoReturnType ( returnType ) ) { noReturnTypeCount ++ ; } } else if ( functionType . details . declaration ) { if ( ! functionType . details . declaration . yieldStatements && ! FunctionType . isAbstractMethod ( functionType ) && ! FunctionType . isStubDefinition ( functionType ) && ! FunctionType . isPyTypedDefinition ( functionType ) ) { const functionStatements = functionType . details . declaration . node . suite . statements ; let foundRaiseNotImplemented = false ; for ( const statement of functionStatements ) { if ( statement . nodeType !== ParseNodeType . StatementList || statement . statements . length !== 1 ) { break ; } const simpleStatement = statement . statements [ 0 ] ; if ( simpleStatement . nodeType === ParseNodeType . StringList ) { continue ; } if ( simpleStatement . nodeType === ParseNodeType . Raise && simpleStatement . typeExpression ) { const isNotImplementedName = ( node ) => { return ( node ?. nodeType === ParseNodeType . Name && node . value === 'NotImplementedError' ) ; } ; if ( isNotImplementedName ( simpleStatement . typeExpression ) ) { foundRaiseNotImplemented = true ; } else if ( simpleStatement . typeExpression . nodeType === ParseNodeType . Call && isNotImplementedName ( simpleStatement . typeExpression . leftExpression ) ) { foundRaiseNotImplemented = true ; } } break ; } if ( ! foundRaiseNotImplemented && ! evaluator . isAfterNodeReachable ( functionType . details . declaration . node ) ) { noReturnTypeCount ++ ; } } } } } ) ; } const callIsNoReturn = subtypeCount > 0 && noReturnTypeCount === subtypeCount ; callIsNoReturnCache . set ( node . id , callIsNoReturn ) ; return callIsNoReturn ; } function isExceptionContextManager ( node , isAsync ) { if ( isExceptionContextManagerCache . has ( node . id ) ) { return isExceptionContextManagerCache . get ( node . id ) ; } isExceptionContextManagerCache . set ( node . id , false ) ; let cmSwallowsExceptions = false ; let cmType ; if ( node . nodeType === ParseNodeType . Call ) { const callType = getDeclaredCallBaseType ( node . leftExpression ) ; if ( callType && isInstantiableClass ( callType ) ) { cmType = convertToInstance ( callType ) ; } else if ( callType && isFunction ( callType ) ) { cmType = callType . details . declaredReturnType ; } } else if ( node . nodeType === ParseNodeType . Name ) { cmType = evaluator . getDeclaredTypeForExpression ( node ) ; } if ( cmType && isClassInstance ( cmType ) ) { const exitMethodName = isAsync ? '__aexit__' : '__exit__' ; const exitType = evaluator . getTypeFromObjectMember ( node , cmType , exitMethodName ) ?. type ; if ( exitType && isFunction ( exitType ) && exitType . details . declaredReturnType ) { const returnType = exitType . details . declaredReturnType ; cmSwallowsExceptions = isClassInstance ( returnType ) && ClassType . isBuiltIn ( returnType , 'bool' ) ; } } isExceptionContextManagerCache . set ( node . id , cmSwallowsExceptions ) ; return cmSwallowsExceptions ; } function getTypeFromWildcardImport ( flowNode , name ) { const importInfo = getImportInfo ( flowNode . node . module ) ; assert ( importInfo !== undefined && importInfo . isImportFound ) ; assert ( flowNode . node . isWildcardImport ) ; const symbolWithScope = evaluator . lookUpSymbolRecursive ( flowNode . node , name , false ) ; assert ( symbolWithScope !== undefined ) ; const decls = symbolWithScope ! . symbol . getDeclarations ( ) ; const wildcardDecl = decls . find ( ( decl ) => decl . node === flowNode . node ) ; if ( ! wildcardDecl ) { return UnknownType . create ( ) ; } return evaluator . getInferredTypeOfDeclaration ( wildcardDecl ) || UnknownType . create ( ) ; } function getDeclaredCallBaseType ( node ) { if ( node . nodeType === ParseNodeType . Name ) { const symbolWithScope = evaluator . lookUpSymbolRecursive ( node , node . value , false ) ; if ( ! symbolWithScope ) { return undefined ; } const symbol = symbolWithScope . symbol ; const type = evaluator . getDeclaredTypeOfSymbol ( symbol ) ; if ( type ) { return type ; } const declarations = symbol . getDeclarations ( ) ; if ( declarations . length === 0 ) { return undefined ; } const decl = declarations [ declarations . length - 1 ] ; if ( decl . type === DeclarationType . Parameter ) { return evaluator . evaluateTypeForSubnode ( decl . node . name ! , ( ) => { evaluator . evaluateTypeOfParameter ( decl . node ) ; } ) ?. type ; } if ( decl . type === DeclarationType . Alias || symbolWithScope . isBeyondExecutionScope ) { return evaluator . getInferredTypeOfDeclaration ( decl ) ; } return undefined ; } if ( node . nodeType === ParseNodeType . MemberAccess ) { const memberName = node . memberName . value ; let baseType = getDeclaredCallBaseType ( node . leftExpression ) ; if ( ! baseType ) { return undefined ; } baseType = evaluator . makeTopLevelTypeVarsConcrete ( baseType ) ; const declaredTypeOfSymbol = mapSubtypes ( baseType , ( subtype ) => { let symbol ; if ( isModule ( subtype ) ) { symbol = ModuleType . getField ( subtype , memberName ) ; } else if ( isInstantiableClass ( subtype ) ) { const classMemberInfo = lookUpClassMember ( subtype , memberName ) ; symbol = classMemberInfo ? classMemberInfo . symbol : undefined ; } else if ( isClassInstance ( subtype ) ) { const classMemberInfo = lookUpClassMember ( subtype , memberName ) ; symbol = classMemberInfo ? classMemberInfo . symbol : undefined ; } return symbol ? evaluator . getEffectiveTypeOfSymbol ( symbol ) : undefined ; } ) ; if ( ! isNever ( declaredTypeOfSymbol ) ) { return declaredTypeOfSymbol ; } } if ( node . nodeType === ParseNodeType . Call ) { const baseType = getDeclaredCallBaseType ( node . leftExpression ) ; if ( ! baseType ) { return undefined ; } if ( baseType && isInstantiableClass ( baseType ) ) { const inst = convertToInstance ( baseType ) ; return inst ; } if ( isFunction ( baseType ) ) { return baseType . details . declaredReturnType ; } } return undefined ; } return { createCodeFlowAnalyzer , isFlowNodeReachable , narrowConstrainedTypeVar , } ; }