import { ArgumentCategory , ExpressionNode , isExpressionNode , NameNode , ParameterCategory , ParseNode , ParseNodeType , } from '../parser/parseNodes' ; import { KeywordType , OperatorType } from '../parser/tokenizerTypes' ; import { getFileInfo } from './analyzerNodeInfo' ; import { Declaration , DeclarationType } from './declaration' ; import * as ParseTreeUtils from './parseTreeUtils' ; import { ScopeType } from './scope' ; import { getScopeForNode } from './scopeUtils' ; import { Symbol , SymbolFlags } from './symbol' ; import { getTypedDictMembersForClass } from './typedDicts' ; import { EvaluatorFlags , TypeEvaluator } from './typeEvaluatorTypes' ; import { ClassType , ClassTypeFlags , combineTypes , FunctionParameter , FunctionType , FunctionTypeFlags , isAnyOrUnknown , isClass , isClassInstance , isFunction , isInstantiableClass , isModule , isNever , isNoneInstance , isNoneTypeClass , isOverloadedFunction , isSameWithoutLiteralValue , isTypeSame , isTypeVar , NeverType , NoneType , OverloadedFunctionType , Type , TypeBase , TypeCategory , TypeCondition , TypedDictEntry , TypeVarType , UnknownType , } from './types' ; import { addConditionToType , applySolvedTypeVars , ClassMember , computeMroLinearization , convertToInstance , convertToInstantiable , doForEachSubtype , getTypeCondition , getTypeVarScopeId , isLiteralType , isLiteralTypeOrUnion , isNoReturnType , isOpenEndedTupleClass , isTupleClass , lookUpClassMember , lookUpObjectMember , mapSubtypes , stripLiteralValue , transformPossibleRecursiveTypeAlias , } from './typeUtils' ; import { TypeVarMap } from './typeVarMap' ; export type TypeNarrowingCallback = ( type : Type ) => Type | undefined ; export function getTypeNarrowingCallback ( evaluator , reference , testExpression , isPositiveTest ) { if ( testExpression . nodeType === ParseNodeType . AssignmentExpression ) { return ( getTypeNarrowingCallback ( evaluator , reference , testExpression . rightExpression , isPositiveTest ) ?? getTypeNarrowingCallback ( evaluator , reference , testExpression . name , isPositiveTest ) ) ; } if ( testExpression . nodeType === ParseNodeType . BinaryOperation ) { const isOrIsNotOperator = testExpression . operator === OperatorType . Is || testExpression . operator === OperatorType . IsNot ; const equalsOrNotEqualsOperator = testExpression . operator === OperatorType . Equals || testExpression . operator === OperatorType . NotEquals ; if ( isOrIsNotOperator || equalsOrNotEqualsOperator ) { const adjIsPositiveTest = testExpression . operator === OperatorType . Is || testExpression . operator === OperatorType . Equals ? isPositiveTest : ! isPositiveTest ; if ( testExpression . rightExpression . nodeType === ParseNodeType . Constant && testExpression . rightExpression . constType === KeywordType . None ) { let leftExpression = testExpression . leftExpression ; if ( leftExpression . nodeType === ParseNodeType . AssignmentExpression ) { leftExpression = leftExpression . name ; } if ( ParseTreeUtils . isMatchingExpression ( reference , leftExpression ) ) { return ( type ) => { return narrowTypeForIsNone ( evaluator , type , adjIsPositiveTest ) ; } ; } if ( leftExpression . nodeType === ParseNodeType . Index && ParseTreeUtils . isMatchingExpression ( reference , leftExpression . baseExpression ) && leftExpression . items . length === 1 && ! leftExpression . trailingComma && leftExpression . items [ 0 ] . argumentCategory === ArgumentCategory . Simple && ! leftExpression . items [ 0 ] . name && leftExpression . items [ 0 ] . valueExpression . nodeType === ParseNodeType . Number && leftExpression . items [ 0 ] . valueExpression . isInteger && ! leftExpression . items [ 0 ] . valueExpression . isImaginary ) { const indexValue = leftExpression . items [ 0 ] . valueExpression . value ; return ( type ) => { return narrowTupleTypeForIsNone ( evaluator , type , adjIsPositiveTest , indexValue ) ; } ; } } if ( isOrIsNotOperator && testExpression . leftExpression . nodeType === ParseNodeType . Call ) { const callType = evaluator . getTypeOfExpression ( testExpression . leftExpression . leftExpression , undefined , EvaluatorFlags . DoNotSpecialize ) . type ; if ( isInstantiableClass ( callType ) && ClassType . isBuiltIn ( callType , 'type' ) && testExpression . leftExpression . arguments . length === 1 && testExpression . leftExpression . arguments [ 0 ] . argumentCategory === ArgumentCategory . Simple ) { const arg0Expr = testExpression . leftExpression . arguments [ 0 ] . valueExpression ; if ( ParseTreeUtils . isMatchingExpression ( reference , arg0Expr ) ) { const classType = evaluator . getTypeOfExpression ( testExpression . rightExpression ) . type ; if ( isInstantiableClass ( classType ) ) { return ( type ) => { return narrowTypeForTypeIs ( type , classType , adjIsPositiveTest ) ; } ; } } } } if ( isOrIsNotOperator ) { if ( ParseTreeUtils . isMatchingExpression ( reference , testExpression . leftExpression ) ) { const rightType = evaluator . getTypeOfExpression ( testExpression . rightExpression ) . type ; if ( isClassInstance ( rightType ) && ( ClassType . isEnumClass ( rightType ) || ClassType . isBuiltIn ( rightType , 'bool' ) ) && rightType . literalValue !== undefined ) { return ( type ) => { return narrowTypeForLiteralComparison ( evaluator , type , rightType , adjIsPositiveTest , true ) ; } ; } } } if ( equalsOrNotEqualsOperator ) { const adjIsPositiveTest = testExpression . operator === OperatorType . Equals ? isPositiveTest : ! isPositiveTest ; if ( ParseTreeUtils . isMatchingExpression ( reference , testExpression . leftExpression ) ) { const rightType = evaluator . getTypeOfExpression ( testExpression . rightExpression ) . type ; if ( isClassInstance ( rightType ) && rightType . literalValue !== undefined ) { return ( type ) => { return narrowTypeForLiteralComparison ( evaluator , type , rightType , adjIsPositiveTest , false ) ; } ; } } if ( ParseTreeUtils . isMatchingExpression ( reference , testExpression . rightExpression ) ) { const leftType = evaluator . getTypeOfExpression ( testExpression . leftExpression ) . type ; if ( isClassInstance ( leftType ) && leftType . literalValue !== undefined ) { return ( type ) => { return narrowTypeForLiteralComparison ( evaluator , type , leftType , adjIsPositiveTest , false ) ; } ; } } if ( testExpression . leftExpression . nodeType === ParseNodeType . MemberAccess && ParseTreeUtils . isMatchingExpression ( reference , testExpression . leftExpression . leftExpression ) ) { const rightType = evaluator . getTypeOfExpression ( testExpression . rightExpression ) . type ; const memberName = testExpression . leftExpression . memberName ; if ( isClassInstance ( rightType ) && rightType . literalValue !== undefined ) { return ( type ) => { return narrowTypeForDiscriminatedFieldComparison ( evaluator , type , memberName . value , rightType , adjIsPositiveTest ) ; } ; } } if ( testExpression . leftExpression . nodeType === ParseNodeType . Index && testExpression . leftExpression . items . length === 1 && ! testExpression . leftExpression . trailingComma && testExpression . leftExpression . items [ 0 ] . argumentCategory === ArgumentCategory . Simple && ParseTreeUtils . isMatchingExpression ( reference , testExpression . leftExpression . baseExpression ) ) { const indexType = evaluator . getTypeOfExpression ( testExpression . leftExpression . items [ 0 ] . valueExpression ) . type ; if ( isClassInstance ( indexType ) && isLiteralType ( indexType ) ) { if ( ClassType . isBuiltIn ( indexType , 'str' ) ) { const rightType = evaluator . getTypeOfExpression ( testExpression . rightExpression ) . type ; if ( isClassInstance ( rightType ) && rightType . literalValue !== undefined ) { return ( type ) => { return narrowTypeForDiscriminatedDictEntryComparison ( evaluator , type , indexType , rightType , adjIsPositiveTest ) ; } ; } } else if ( ClassType . isBuiltIn ( indexType , 'int' ) ) { const rightType = evaluator . getTypeOfExpression ( testExpression . rightExpression ) . type ; if ( isClassInstance ( rightType ) && rightType . literalValue !== undefined ) { return ( type ) => { return narrowTypeForDiscriminatedTupleComparison ( evaluator , type , indexType , rightType , adjIsPositiveTest ) ; } ; } } } } } if ( equalsOrNotEqualsOperator && testExpression . leftExpression . nodeType === ParseNodeType . Call && testExpression . leftExpression . arguments . length === 1 && testExpression . rightExpression . nodeType === ParseNodeType . Number && testExpression . rightExpression . isInteger ) { const arg0Expr = testExpression . leftExpression . arguments [ 0 ] . valueExpression ; if ( ParseTreeUtils . isMatchingExpression ( reference , arg0Expr ) ) { const callType = evaluator . getTypeOfExpression ( testExpression . leftExpression . leftExpression , undefined , EvaluatorFlags . DoNotSpecialize ) . type ; if ( isFunction ( callType ) && callType . details . fullName === 'builtins.len' ) { const tupleLength = testExpression . rightExpression . value ; return ( type ) => { return narrowTypeForTupleLength ( evaluator , type , tupleLength , adjIsPositiveTest ) ; } ; } } } } if ( testExpression . operator === OperatorType . In ) { if ( isPositiveTest && ParseTreeUtils . isMatchingExpression ( reference , testExpression . leftExpression ) ) { const rightType = evaluator . getTypeOfExpression ( testExpression . rightExpression ) . type ; return ( type ) => { return narrowTypeForContains ( evaluator , type , rightType ) ; } ; } } if ( testExpression . operator === OperatorType . In || testExpression . operator === OperatorType . NotIn ) { if ( ParseTreeUtils . isMatchingExpression ( reference , testExpression . rightExpression ) ) { const leftType = evaluator . getTypeOfExpression ( testExpression . leftExpression ) . type ; if ( isClassInstance ( leftType ) && ClassType . isBuiltIn ( leftType , 'str' ) && isLiteralType ( leftType ) ) { const adjIsPositiveTest = testExpression . operator === OperatorType . In ? isPositiveTest : ! isPositiveTest ; return ( type ) => { return narrowTypeForTypedDictKey ( evaluator , type , ClassType . cloneAsInstantiable ( leftType ) , adjIsPositiveTest ) ; } ; } } } } if ( testExpression . nodeType === ParseNodeType . Call ) { const callType = evaluator . getTypeOfExpression ( testExpression . leftExpression , undefined , EvaluatorFlags . DoNotSpecialize ) . type ; if ( isFunction ( callType ) && ( callType . details . builtInName === 'isinstance' || callType . details . builtInName === 'issubclass' ) && testExpression . arguments . length === 2 ) { const isInstanceCheck = callType . details . builtInName === 'isinstance' ; const arg0Expr = testExpression . arguments [ 0 ] . valueExpression ; const arg1Expr = testExpression . arguments [ 1 ] . valueExpression ; if ( ParseTreeUtils . isMatchingExpression ( reference , arg0Expr ) ) { const arg1Type = evaluator . getTypeOfExpression ( arg1Expr , undefined , EvaluatorFlags . EvaluateStringLiteralAsType | EvaluatorFlags . ParamSpecDisallowed | EvaluatorFlags . TypeVarTupleDisallowed ) . type ; const classTypeList = getIsInstanceClassTypes ( arg1Type ) ; if ( classTypeList ) { return ( type ) => { const narrowedType = narrowTypeForIsInstance ( evaluator , type , classTypeList , isInstanceCheck , isPositiveTest , false , testExpression ) ; if ( ! isNever ( narrowedType ) ) { return narrowedType ; } return narrowTypeForIsInstance ( evaluator , type , classTypeList , isInstanceCheck , isPositiveTest , true , testExpression ) ; } ; } } } if ( isFunction ( callType ) && callType . details . builtInName === 'callable' && testExpression . arguments . length === 1 ) { const arg0Expr = testExpression . arguments [ 0 ] . valueExpression ; if ( ParseTreeUtils . isMatchingExpression ( reference , arg0Expr ) ) { return ( type ) => { let narrowedType = narrowTypeForCallable ( evaluator , type , isPositiveTest , testExpression , false ) ; if ( isPositiveTest && isNever ( narrowedType ) ) { narrowedType = narrowTypeForCallable ( evaluator , type , isPositiveTest , testExpression , true ) ; } return narrowedType ; } ; } } if ( isInstantiableClass ( callType ) && ClassType . isBuiltIn ( callType , 'bool' ) && testExpression . arguments . length === 1 && ! testExpression . arguments [ 0 ] . name ) { if ( ParseTreeUtils . isMatchingExpression ( reference , testExpression . arguments [ 0 ] . valueExpression ) ) { return ( type ) => { return narrowTypeForTruthiness ( evaluator , type , isPositiveTest ) ; } ; } } if ( testExpression . arguments . length >= 1 ) { const arg0Expr = testExpression . arguments [ 0 ] . valueExpression ; if ( ParseTreeUtils . isMatchingExpression ( reference , arg0Expr ) ) { if ( isFunction ( callType ) && callType . details . declaredReturnType && isClassInstance ( callType . details . declaredReturnType ) && ClassType . isBuiltIn ( callType . details . declaredReturnType , 'TypeGuard' ) ) { const functionReturnType = evaluator . getTypeOfExpression ( testExpression ) . type ; if ( isClassInstance ( functionReturnType ) && ClassType . isBuiltIn ( functionReturnType , 'bool' ) ) { const typeForTest = isPositiveTest ? functionReturnType . positiveTypeGuardType : functionReturnType . negativeTypeGuardType ; if ( typeForTest ) { return ( type ) => { if ( ! isPositiveTest && isNoReturnType ( typeForTest ) ) { return NeverType . create ( ) ; } return typeForTest ; } ; } } } } } } if ( ParseTreeUtils . isMatchingExpression ( reference , testExpression ) ) { return ( type ) => { return narrowTypeForTruthiness ( evaluator , type , isPositiveTest ) ; } ; } if ( testExpression . nodeType === ParseNodeType . Name && reference . nodeType === ParseNodeType . Name && testExpression !== reference ) { const testExprDecl = getDeclsForLocalVar ( evaluator , testExpression , testExpression ) ; if ( testExprDecl && testExprDecl . length === 1 && testExprDecl [ 0 ] . type === DeclarationType . Variable ) { const referenceDecls = getDeclsForLocalVar ( evaluator , reference , testExpression ) ; if ( referenceDecls ) { let modifyingDecls = [ ] ; if ( referenceDecls . length > 1 ) { modifyingDecls = referenceDecls . filter ( ( decl ) => { return ( evaluator . isNodeReachable ( testExpression , decl . node ) && evaluator . isNodeReachable ( decl . node , testExprDecl [ 0 ] . node ) ) ; } ) ; } if ( modifyingDecls . length === 0 ) { const initNode = testExprDecl [ 0 ] . inferredTypeSource ; if ( initNode && initNode !== testExpression && isExpressionNode ( initNode ) ) { return getTypeNarrowingCallback ( evaluator , reference , initNode , isPositiveTest ) ; } } } } } if ( testExpression . nodeType === ParseNodeType . UnaryOperation ) { if ( testExpression . operator === OperatorType . Not ) { return getTypeNarrowingCallback ( evaluator , reference , testExpression . expression , ! isPositiveTest ) ; } } return undefined ; } function getDeclsForLocalVar ( evaluator , name , reachableFrom ) { const scope = getScopeForNode ( name ) ; if ( scope ?. type !== ScopeType . Function && scope ?. type !== ScopeType . Module ) { return undefined ; } const symbol = scope . lookUpSymbol ( name . value ) ; if ( ! symbol ) { return undefined ; } const decls = symbol . getDeclarations ( ) ; if ( decls . length === 0 || decls . some ( ( decl ) => decl . type !== DeclarationType . Variable && decl . type !== DeclarationType . Parameter ) ) { return undefined ; } let prevDeclScope ; if ( decls . some ( ( decl ) => { const nodeToConsider = decl . type === DeclarationType . Parameter ? decl . node . name ! : decl . node ; const declScopeNode = ParseTreeUtils . getExecutionScopeNode ( nodeToConsider ) ; if ( prevDeclScope && declScopeNode !== prevDeclScope ) { return true ; } prevDeclScope = declScopeNode ; return false ; } ) ) { return undefined ; } const reachableDecls = decls . filter ( ( decl ) => evaluator . isNodeReachable ( reachableFrom , decl . node ) ) ; return reachableDecls . length > 0 ? reachableDecls : undefined ; } function narrowTypeForTruthiness ( evaluator , type , isPositiveTest ) { return mapSubtypes ( type , ( subtype ) => { if ( isPositiveTest ) { if ( evaluator . canBeTruthy ( subtype ) ) { return evaluator . removeFalsinessFromType ( subtype ) ; } } else { if ( evaluator . canBeFalsy ( subtype ) ) { return evaluator . removeTruthinessFromType ( subtype ) ; } } return undefined ; } ) ; } function narrowTupleTypeForIsNone ( evaluator , type , isPositiveTest , indexValue ) { return evaluator . mapSubtypesExpandTypeVars ( type , undefined , ( subtype ) => { if ( ! isClassInstance ( subtype ) || ! isTupleClass ( subtype ) || isOpenEndedTupleClass ( subtype ) || ! subtype . tupleTypeArguments ) { return subtype ; } const tupleLength = subtype . tupleTypeArguments . length ; if ( indexValue < 0 || indexValue >= tupleLength ) { return subtype ; } const typeOfEntry = evaluator . makeTopLevelTypeVarsConcrete ( subtype . tupleTypeArguments [ indexValue ] ) ; if ( isPositiveTest ) { if ( ! evaluator . canAssignType ( typeOfEntry , NoneType . createInstance ( ) ) ) { return undefined ; } } else { if ( isNoneInstance ( typeOfEntry ) ) { return undefined ; } } return subtype ; } ) ; } function narrowTypeForIsNone ( evaluator , type , isPositiveTest ) { const expandedType = mapSubtypes ( type , ( subtype ) => { return transformPossibleRecursiveTypeAlias ( subtype ) ; } ) ; return evaluator . mapSubtypesExpandTypeVars ( expandedType , undefined , ( subtype , unexpandedSubtype ) => { if ( isAnyOrUnknown ( subtype ) ) { return subtype ; } const adjustedSubtype = isTypeVar ( unexpandedSubtype ) && unexpandedSubtype . details . constraints . length === 0 ? unexpandedSubtype : subtype ; if ( isClassInstance ( subtype ) && ClassType . isBuiltIn ( subtype , 'object' ) ) { return isPositiveTest ? NoneType . createInstance ( ) : adjustedSubtype ; } if ( isNoneInstance ( subtype ) === isPositiveTest ) { return adjustedSubtype ; } return undefined ; } ) ; } function getIsInstanceClassTypes ( argType ) { let foundNonClassType = false ; const classTypeList = [ ] ; const addClassTypesToList = ( types ) => { types . forEach ( ( subtype ) => { if ( isInstantiableClass ( subtype ) || ( isTypeVar ( subtype ) && TypeBase . isInstantiable ( subtype ) ) ) { classTypeList . push ( subtype ) ; } else if ( isNoneTypeClass ( subtype ) ) { classTypeList . push ( subtype ) ; } else if ( isFunction ( subtype ) && subtype . details . parameters . length === 2 && subtype . details . parameters [ 0 ] . category === ParameterCategory . VarArgList && subtype . details . parameters [ 1 ] . category === ParameterCategory . VarArgDictionary ) { classTypeList . push ( subtype ) ; } else { foundNonClassType = true ; } } ) ; } ; doForEachSubtype ( argType , ( subtype ) => { if ( isClass ( subtype ) && TypeBase . isInstance ( subtype ) && isTupleClass ( subtype ) ) { if ( subtype . tupleTypeArguments ) { addClassTypesToList ( isOpenEndedTupleClass ( subtype ) ? [ subtype . tupleTypeArguments [ 0 ] ] : subtype . tupleTypeArguments ) ; } } else { addClassTypesToList ( [ subtype ] ) ; } return undefined ; } ) ; return foundNonClassType ? undefined : classTypeList ; } function narrowTypeForIsInstance ( evaluator , type , classTypeList , isInstanceCheck , isPositiveTest , allowIntersections , errorNode ) { const expandedTypes = mapSubtypes ( type , ( subtype ) => { return transformPossibleRecursiveTypeAlias ( subtype ) ; } ) ; const filterClassType = ( varType , unexpandedType , constraints , negativeFallbackType ) : Type [ ] => { const filteredTypes = [ ] ; let foundSuperclass = false ; let isClassRelationshipIndeterminate = false ; for ( const filterType of classTypeList ) { const concreteFilterType = evaluator . makeTopLevelTypeVarsConcrete ( filterType ) ; if ( isInstantiableClass ( concreteFilterType ) ) { const filterIsSuperclass = ! isTypeVar ( filterType ) && ( ClassType . isDerivedFrom ( varType , concreteFilterType ) || ( isInstanceCheck && ClassType . isProtocolClass ( concreteFilterType ) && evaluator . canAssignType ( concreteFilterType , varType ) ) || ( ClassType . isBuiltIn ( concreteFilterType , 'dict' ) && ClassType . isTypedDictClass ( varType ) ) ) ; const filterIsSubclass = ClassType . isDerivedFrom ( concreteFilterType , varType ) || ( isInstanceCheck && ClassType . isProtocolClass ( varType ) && evaluator . canAssignType ( varType , concreteFilterType ) ) ; if ( filterIsSuperclass ) { foundSuperclass = true ; } if ( filterIsSubclass && filterIsSuperclass && ! ClassType . isSameGenericClass ( varType , concreteFilterType ) ) { isClassRelationshipIndeterminate = true ; } if ( isPositiveTest ) { if ( filterIsSuperclass ) { filteredTypes . push ( addConditionToType ( varType , constraints ) ) ; } else if ( filterIsSubclass ) { let specializedFilterType = filterType ; if ( isClass ( filterType ) ) { if ( ClassType . isSpecialBuiltIn ( filterType ) || filterType . details . typeParameters . length > 0 ) { const typeVarMap = new TypeVarMap ( getTypeVarScopeId ( filterType ) ) ; const unspecializedFilterType = ClassType . cloneForSpecialization ( filterType , undefined , false ) ; if ( evaluator . populateTypeVarMapBasedOnExpectedType ( unspecializedFilterType , ClassType . cloneAsInstance ( varType ) , typeVarMap , undefined ) ) { specializedFilterType = applySolvedTypeVars ( unspecializedFilterType , typeVarMap , true ) as ClassType ; } } } filteredTypes . push ( addConditionToType ( specializedFilterType , constraints ) ) ; } else if ( allowIntersections ) { const className = `<subclass of ${ varType . details . name } and ${ concreteFilterType . details . name }>` ; const fileInfo = getFileInfo ( errorNode ) ; let newClassType = ClassType . createInstantiable ( className , ParseTreeUtils . getClassFullName ( errorNode , fileInfo . moduleName , className ) , fileInfo . moduleName , fileInfo . filePath , ClassTypeFlags . None , ParseTreeUtils . getTypeSourceId ( errorNode ) , undefined , varType . details . effectiveMetaclass , varType . details . docString ) ; newClassType . details . baseClasses = [ ClassType . cloneAsInstantiable ( varType ) , concreteFilterType ] ; computeMroLinearization ( newClassType ) ; newClassType = addConditionToType ( newClassType , concreteFilterType . condition ) as ClassType ; if ( isTypeVar ( unexpandedType ) && ! unexpandedType . details . isParamSpec && unexpandedType . details . constraints . length === 0 ) { newClassType = addConditionToType ( newClassType , [ { typeVarName : TypeVarType . getNameWithScope ( unexpandedType ) , constraintIndex : 0 , isConstrainedTypeVar : false , } , ] ) as ClassType ; } filteredTypes . push ( isInstanceCheck ? ClassType . cloneAsInstance ( newClassType ) : newClassType ) ; } } } else if ( isTypeVar ( filterType ) && TypeBase . isInstantiable ( filterType ) ) { if ( isInstanceCheck && TypeBase . isInstance ( unexpandedType ) ) { if ( isTypeVar ( unexpandedType ) && isTypeSame ( convertToInstance ( filterType ) , unexpandedType ) ) { if ( isPositiveTest ) { filteredTypes . push ( unexpandedType ) ; } } else { if ( isPositiveTest ) { filteredTypes . push ( convertToInstance ( filterType ) ) ; } else { filteredTypes . push ( unexpandedType ) ; isClassRelationshipIndeterminate = true ; } } } else if ( ! isInstanceCheck && TypeBase . isInstantiable ( unexpandedType ) ) { if ( isTypeVar ( unexpandedType ) && isTypeSame ( filterType , unexpandedType ) ) { if ( isPositiveTest ) { filteredTypes . push ( unexpandedType ) ; } } else { if ( isPositiveTest ) { filteredTypes . push ( filterType ) ; } else { filteredTypes . push ( unexpandedType ) ; isClassRelationshipIndeterminate = true ; } } } } else if ( isFunction ( filterType ) ) { if ( isInstanceCheck ) { let isCallable = false ; if ( isClass ( varType ) ) { if ( TypeBase . isInstantiable ( unexpandedType ) ) { isCallable = true ; } else { isCallable = ! ! lookUpClassMember ( varType , '__call__' ) ; } } if ( isCallable ) { if ( isPositiveTest ) { filteredTypes . push ( unexpandedType ) ; } else { foundSuperclass = true ; } } } } } if ( ! isPositiveTest ) { if ( ! foundSuperclass || isClassRelationshipIndeterminate ) { filteredTypes . push ( negativeFallbackType ) ; } } if ( ! isInstanceCheck ) { return filteredTypes ; } return filteredTypes . map ( ( t ) => convertToInstance ( t ) ) ; } ; const filterFunctionType = ( varType , unexpandedType ) : Type [ ] => { const filteredTypes = [ ] ; if ( isPositiveTest ) { for ( const filterType of classTypeList ) { const concreteFilterType = evaluator . makeTopLevelTypeVarsConcrete ( filterType ) ; if ( evaluator . canAssignType ( varType , convertToInstance ( concreteFilterType ) ) ) { if ( isFunction ( filterType ) ) { filteredTypes . push ( unexpandedType ) ; } else { filteredTypes . push ( convertToInstance ( filterType ) ) ; } } } } else if ( ! classTypeList . some ( ( filterType ) => evaluator . canAssignType ( varType , convertToInstance ( evaluator . makeTopLevelTypeVarsConcrete ( filterType ) ) ) ) ) { filteredTypes . push ( unexpandedType ) ; } return filteredTypes ; } ; const anyOrUnknownSubstitutions = [ ] ; const anyOrUnknown = [ ] ; const filteredType = evaluator . mapSubtypesExpandTypeVars ( expandedTypes , undefined , ( subtype , unexpandedSubtype ) => { const negativeFallback = getTypeCondition ( subtype ) ? subtype : unexpandedSubtype ; const isSubtypeTypeObject = isClassInstance ( subtype ) && ClassType . isBuiltIn ( subtype , 'type' ) ; if ( isPositiveTest && isAnyOrUnknown ( subtype ) ) { if ( isInstanceCheck ) { anyOrUnknownSubstitutions . push ( combineTypes ( classTypeList . map ( ( classType ) => convertToInstance ( classType ) ) ) ) ; } else { anyOrUnknownSubstitutions . push ( combineTypes ( classTypeList ) ) ; } anyOrUnknown . push ( subtype ) ; return undefined ; } if ( isInstanceCheck ) { if ( isNoneInstance ( subtype ) ) { const containsNoneType = classTypeList . some ( ( t ) => isNoneTypeClass ( t ) ) ; if ( isPositiveTest ) { return containsNoneType ? subtype : undefined ; } else { return containsNoneType ? undefined : subtype ; } } if ( isModule ( subtype ) || ( isClassInstance ( subtype ) && ClassType . isBuiltIn ( subtype , 'ModuleType' ) ) ) { if ( isPositiveTest ) { const filteredTypes = classTypeList . filter ( ( classType ) => { const concreteClassType = evaluator . makeTopLevelTypeVarsConcrete ( classType ) ; return ( isInstantiableClass ( concreteClassType ) && ClassType . isProtocolClass ( concreteClassType ) ) ; } ) ; if ( filteredTypes . length > 0 ) { return convertToInstance ( combineTypes ( filteredTypes ) ) ; } } } if ( isClassInstance ( subtype ) && ! isSubtypeTypeObject ) { return combineTypes ( filterClassType ( ClassType . cloneAsInstantiable ( subtype ) , convertToInstance ( unexpandedSubtype ) , getTypeCondition ( subtype ) , negativeFallback ) ) ; } if ( ( isFunction ( subtype ) || isOverloadedFunction ( subtype ) ) && isInstanceCheck ) { return combineTypes ( filterFunctionType ( subtype , convertToInstance ( unexpandedSubtype ) ) ) ; } if ( isInstantiableClass ( subtype ) || isSubtypeTypeObject ) { const includesTypeType = classTypeList . some ( ( classType ) => isInstantiableClass ( classType ) && ClassType . isBuiltIn ( classType , 'type' ) ) ; if ( isPositiveTest ) { return includesTypeType ? negativeFallback : undefined ; } else { return includesTypeType ? undefined : negativeFallback ; } } } else { if ( isInstantiableClass ( subtype ) ) { return combineTypes ( filterClassType ( subtype , unexpandedSubtype , getTypeCondition ( subtype ) , negativeFallback ) ) ; } if ( isSubtypeTypeObject ) { const objectType = evaluator . getBuiltInObject ( errorNode , 'object' ) ; if ( objectType && isClassInstance ( objectType ) ) { return combineTypes ( filterClassType ( ClassType . cloneAsInstantiable ( objectType ) , convertToInstantiable ( unexpandedSubtype ) , getTypeCondition ( subtype ) , negativeFallback ) ) ; } } } return isPositiveTest ? undefined : negativeFallback ; } ) ; if ( isNever ( filteredType ) && anyOrUnknownSubstitutions . length > 0 ) { return combineTypes ( anyOrUnknownSubstitutions ) ; } if ( anyOrUnknown . length > 0 ) { return combineTypes ( [ filteredType , ... anyOrUnknown ] ) ; } return filteredType ; } function narrowTypeForTupleLength ( evaluator , referenceType , lengthValue , isPositiveTest ) { return mapSubtypes ( referenceType , ( subtype ) => { const concreteSubtype = evaluator . makeTopLevelTypeVarsConcrete ( subtype ) ; if ( ! isClassInstance ( concreteSubtype ) || ! isTupleClass ( concreteSubtype ) || isOpenEndedTupleClass ( concreteSubtype ) || ! concreteSubtype . tupleTypeArguments ) { return subtype ; } const tupleLengthMatches = concreteSubtype . tupleTypeArguments . length === lengthValue ; return tupleLengthMatches === isPositiveTest ? subtype : undefined ; } ) ; } function narrowTypeForContains ( evaluator , referenceType , containerType ) { if ( ! isClassInstance ( containerType ) || ! ClassType . isBuiltIn ( containerType ) ) { return referenceType ; } const builtInName = containerType . details . name ; if ( ! [ 'list' , 'set' , 'frozenset' , 'deque' , 'tuple' ] . some ( ( name ) => name === builtInName ) ) { return referenceType ; } if ( ! containerType . typeArguments || containerType . typeArguments . length !== 1 ) { return referenceType ; } let elementType = containerType . typeArguments [ 0 ] ; if ( isTupleClass ( containerType ) && ! isOpenEndedTupleClass ( containerType ) && containerType . tupleTypeArguments ) { elementType = combineTypes ( containerType . tupleTypeArguments ) ; } let canNarrow = true ; const elementTypeWithoutLiteral = stripLiteralValue ( elementType ) ; const narrowedType = mapSubtypes ( referenceType , ( referenceSubtype ) => { if ( isAnyOrUnknown ( referenceSubtype ) ) { canNarrow = false ; return referenceSubtype ; } if ( evaluator . canAssignType ( elementType , referenceSubtype ) ) { return referenceSubtype ; } if ( evaluator . canAssignType ( elementTypeWithoutLiteral , referenceSubtype ) ) { return mapSubtypes ( elementType , ( elementSubtype ) => { if ( isClassInstance ( elementSubtype ) && isSameWithoutLiteralValue ( referenceSubtype , elementSubtype ) ) { return elementSubtype ; } return undefined ; } ) ; } return undefined ; } ) ; return canNarrow ? narrowedType : referenceType ; } function narrowTypeForTypedDictKey ( evaluator , referenceType , literalKey , isPositiveTest ) { const narrowedType = mapSubtypes ( referenceType , ( subtype ) => { if ( isClassInstance ( subtype ) && ClassType . isTypedDictClass ( subtype ) ) { const entries = getTypedDictMembersForClass ( evaluator , subtype , true ) ; const tdEntry = entries . get ( literalKey . literalValue as string ) ; if ( isPositiveTest ) { if ( ! tdEntry ) { return ClassType . isFinal ( subtype ) ? undefined : subtype ; } if ( tdEntry . isRequired || tdEntry . isProvided ) { return subtype ; } const oldNarrowedEntriesMap = subtype . typedDictNarrowedEntries ; const newNarrowedEntriesMap = new Map < string , TypedDictEntry > ( ) ; if ( oldNarrowedEntriesMap ) { oldNarrowedEntriesMap . forEach ( ( value , key ) => { newNarrowedEntriesMap . set ( key , value ) ; } ) ; } newNarrowedEntriesMap . set ( literalKey . literalValue as string , { valueType : tdEntry . valueType , isRequired : false , isProvided : true , } ) ; return ClassType . cloneAsInstance ( ClassType . cloneForNarrowedTypedDictEntries ( ClassType . cloneAsInstantiable ( subtype ) , newNarrowedEntriesMap ) ) ; } else { return tdEntry !== undefined && ( tdEntry . isRequired || tdEntry . isProvided ) ? undefined : subtype ; } } return subtype ; } ) ; return narrowedType ; } function narrowTypeForDiscriminatedDictEntryComparison ( evaluator , referenceType , indexLiteralType , literalType , isPositiveTest ) { let canNarrow = true ; const narrowedType = mapSubtypes ( referenceType , ( subtype ) => { if ( isClassInstance ( subtype ) && ClassType . isTypedDictClass ( subtype ) ) { const symbolMap = getTypedDictMembersForClass ( evaluator , subtype ) ; const tdEntry = symbolMap . get ( indexLiteralType . literalValue as string ) ; if ( tdEntry && isLiteralTypeOrUnion ( tdEntry . valueType ) ) { if ( isPositiveTest ) { return evaluator . canAssignType ( tdEntry . valueType , literalType ) ? subtype : undefined ; } else { return evaluator . canAssignType ( literalType , tdEntry . valueType ) ? undefined : subtype ; } } } canNarrow = false ; return subtype ; } ) ; return canNarrow ? narrowedType : referenceType ; } function narrowTypeForDiscriminatedTupleComparison ( evaluator , referenceType , indexLiteralType , literalType , isPositiveTest ) { let canNarrow = true ; const narrowedType = mapSubtypes ( referenceType , ( subtype ) => { if ( isClassInstance ( subtype ) && ClassType . isTupleClass ( subtype ) && ! isOpenEndedTupleClass ( subtype ) ) { const indexValue = indexLiteralType . literalValue as number ; if ( subtype . tupleTypeArguments && indexValue >= 0 && indexValue < subtype . tupleTypeArguments . length ) { const tupleEntryType = subtype . tupleTypeArguments [ indexValue ] ; if ( tupleEntryType && isLiteralTypeOrUnion ( tupleEntryType ) ) { if ( isPositiveTest ) { return evaluator . canAssignType ( tupleEntryType , literalType ) ? subtype : undefined ; } else { return evaluator . canAssignType ( literalType , tupleEntryType ) ? undefined : subtype ; } } } } canNarrow = false ; return subtype ; } ) ; return canNarrow ? narrowedType : referenceType ; } function narrowTypeForDiscriminatedFieldComparison ( evaluator , referenceType , memberName , literalType , isPositiveTest ) { let canNarrow = true ; const narrowedType = mapSubtypes ( referenceType , ( subtype ) => { let memberInfo ; if ( isClassInstance ( subtype ) ) { memberInfo = lookUpObjectMember ( subtype , memberName ) ; } else if ( isInstantiableClass ( subtype ) ) { memberInfo = lookUpClassMember ( subtype , memberName ) ; } if ( memberInfo && memberInfo . isTypeDeclared ) { const memberType = evaluator . getTypeOfMember ( memberInfo ) ; if ( isLiteralTypeOrUnion ( memberType ) ) { if ( isPositiveTest ) { return evaluator . canAssignType ( memberType , literalType ) ? subtype : undefined ; } else { return evaluator . canAssignType ( literalType , memberType ) ? undefined : subtype ; } } } canNarrow = false ; return subtype ; } ) ; return canNarrow ? narrowedType : referenceType ; } function narrowTypeForTypeIs ( type , classType , isPositiveTest ) { return mapSubtypes ( type , ( subtype ) => { if ( isClassInstance ( subtype ) ) { const matches = ClassType . isDerivedFrom ( classType , ClassType . cloneAsInstantiable ( subtype ) ) ; if ( isPositiveTest ) { if ( matches ) { if ( ClassType . isSameGenericClass ( subtype , classType ) ) { return subtype ; } return ClassType . cloneAsInstance ( classType ) ; } return undefined ; } else { if ( matches && ClassType . isFinal ( subtype ) ) { return undefined ; } return subtype ; } } else if ( isNoneInstance ( subtype ) ) { return isPositiveTest ? undefined : subtype ; } return subtype ; } ) ; } function narrowTypeForLiteralComparison ( evaluator , referenceType , literalType , isPositiveTest , isIsOperator ) { return mapSubtypes ( referenceType , ( subtype ) => { subtype = evaluator . makeTopLevelTypeVarsConcrete ( subtype ) ; if ( isClassInstance ( subtype ) && ClassType . isSameGenericClass ( literalType , subtype ) ) { if ( subtype . literalValue !== undefined ) { const literalValueMatches = ClassType . isLiteralValueSame ( subtype , literalType ) ; if ( ( literalValueMatches && ! isPositiveTest ) || ( ! literalValueMatches && isPositiveTest ) ) { return undefined ; } return subtype ; } else if ( isPositiveTest ) { return literalType ; } else { const allLiteralTypes = enumerateLiteralsForType ( evaluator , subtype ) ; if ( allLiteralTypes && allLiteralTypes . length > 0 ) { return combineTypes ( allLiteralTypes . filter ( ( type ) => ! ClassType . isLiteralValueSame ( type , literalType ) ) ) ; } } } else if ( isPositiveTest ) { if ( isIsOperator || isNoneInstance ( subtype ) ) { return undefined ; } } return subtype ; } ) ; } export function enumerateLiteralsForType ( evaluator , type ) { if ( ClassType . isBuiltIn ( type , 'bool' ) ) { return [ ClassType . cloneWithLiteral ( type , true ) , ClassType . cloneWithLiteral ( type , false ) , ] ; } if ( ClassType . isEnumClass ( type ) ) { const enumList = [ ] ; const fields = type . details . fields ; fields . forEach ( ( symbol ) => { if ( ! symbol . isIgnoredForProtocolMatch ( ) ) { const symbolType = evaluator . getEffectiveTypeOfSymbol ( symbol ) ; if ( isClassInstance ( symbolType ) && ClassType . isSameGenericClass ( type , symbolType ) && symbolType . literalValue !== undefined ) { enumList . push ( symbolType ) ; } } } ) ; return enumList ; } return undefined ; } function narrowTypeForCallable ( evaluator , type , isPositiveTest , errorNode , allowIntersections ) { return evaluator . mapSubtypesExpandTypeVars ( type , undefined , ( subtype ) => { switch ( subtype . category ) { case TypeCategory . Function : case TypeCategory . OverloadedFunction : { return isPositiveTest ? subtype : undefined ; } case TypeCategory . None : case TypeCategory . Module : { return isPositiveTest ? undefined : subtype ; } case TypeCategory . Class : { if ( TypeBase . isInstantiable ( subtype ) ) { return isPositiveTest ? subtype : undefined ; } const callMemberType = lookUpClassMember ( subtype , '__call__' ) ; if ( ! callMemberType ) { if ( ! isPositiveTest ) { return subtype ; } if ( allowIntersections ) { const className = `<callable subtype of ${ subtype . details . name }>` ; const fileInfo = getFileInfo ( errorNode ) ; let newClassType = ClassType . createInstantiable ( className , ParseTreeUtils . getClassFullName ( errorNode , fileInfo . moduleName , className ) , fileInfo . moduleName , fileInfo . filePath , ClassTypeFlags . None , ParseTreeUtils . getTypeSourceId ( errorNode ) , undefined , subtype . details . effectiveMetaclass , subtype . details . docString ) ; newClassType . details . baseClasses = [ ClassType . cloneAsInstantiable ( subtype ) ] ; computeMroLinearization ( newClassType ) ; newClassType = addConditionToType ( newClassType , subtype . condition ) as ClassType ; const callMethod = FunctionType . createInstance ( '__call__' , '' , '' , FunctionTypeFlags . SynthesizedMethod ) ; const selfParam = { category : ParameterCategory . Simple , name : 'self' , type : ClassType . cloneAsInstance ( newClassType ) , hasDeclaredType : true , } ; FunctionType . addParameter ( callMethod , selfParam ) ; FunctionType . addDefaultParameters ( callMethod ) ; callMethod . details . declaredReturnType = UnknownType . create ( ) ; newClassType . details . fields . set ( '__call__' , Symbol . createWithType ( SymbolFlags . ClassMember , callMethod ) ) ; return ClassType . cloneAsInstance ( newClassType ) ; } return undefined ; } else { return isPositiveTest ? subtype : undefined ; } } default : { return subtype ; } } } ) ; }